Skip to content

Commit 7a0f582

Browse files
authored
Merge pull request #39 from GetStream/feat/audio-attibutes-focus-manager
feat: audio attributes configuration on focus manager
2 parents 1f700ae + 1d862b8 commit 7a0f582

File tree

11 files changed

+102
-17
lines changed

11 files changed

+102
-17
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
# Changelog
33

4+
[1.0.11] - 2025-08-13
5+
* [Android] Added option to configure Android audio attributes in AudioFocusManager
6+
47
[1.0.10] - 2025-08-07
58
* [Linux/Windows] added missing cloneTrack method
69

android/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public class MethodCallHandlerImpl implements MethodCallHandler, StateProvider {
137137

138138
public AudioProcessingFactoryProvider audioProcessingFactoryProvider;
139139

140+
private ConstraintsMap initializedAndroidAudioConfiguration;
141+
140142
MethodCallHandlerImpl(Context context, BinaryMessenger messenger, TextureRegistry textureRegistry) {
141143
this.context = context;
142144
this.textures = textureRegistry;
@@ -175,6 +177,8 @@ private void initialize(boolean bypassVoiceProcessing, int networkIgnoreMask, bo
175177
return;
176178
}
177179

180+
this.initializedAndroidAudioConfiguration = androidAudioConfiguration;
181+
178182
PeerConnectionFactory.initialize(
179183
InitializationOptions.builder(context)
180184
.setEnableInternalTracer(true)
@@ -387,7 +391,28 @@ public void onMethodCall(MethodCall call, @NonNull Result notSafeResult) {
387391
break;
388392
}
389393

390-
audioFocusManager = new AudioFocusManager(context, source);
394+
Integer usage = null, content = null;
395+
396+
// Prefer override values if provided, else fallback to persisted config
397+
String overrideUsageStr = call.argument("androidAudioAttributesUsageType");
398+
String overrideContentStr = call.argument("androidAudioAttributesContentType");
399+
400+
if (overrideUsageStr != null) {
401+
usage = AudioUtils.getAudioAttributesUsageTypeForString(overrideUsageStr);
402+
} else if (initializedAndroidAudioConfiguration != null) {
403+
usage = AudioUtils.getAudioAttributesUsageTypeForString(
404+
initializedAndroidAudioConfiguration.getString("androidAudioAttributesUsageType"));
405+
}
406+
407+
if (overrideContentStr != null) {
408+
content = AudioUtils.getAudioAttributesContentTypeFromString(overrideContentStr);
409+
} else if (initializedAndroidAudioConfiguration != null) {
410+
content = AudioUtils.getAudioAttributesContentTypeFromString(
411+
initializedAndroidAudioConfiguration.getString("androidAudioAttributesContentType"));
412+
}
413+
414+
audioFocusManager = new AudioFocusManager(context, source, usage, content);
415+
391416
audioFocusManager.setAudioFocusChangeListener(new AudioFocusManager.AudioFocusChangeListener() {
392417
@Override
393418
public void onInterruptionStart() {

android/src/main/java/io/getstream/webrtc/flutter/audio/AudioFocusManager.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,27 @@ public enum InterruptionSource {
3333
private InterruptionSource interruptionSource;
3434
private Context context;
3535

36+
private Integer focusUsageType; // AudioAttributes.USAGE_*
37+
private Integer focusContentType; // AudioAttributes.CONTENT_TYPE_*
38+
3639
public interface AudioFocusChangeListener {
3740
void onInterruptionStart();
3841
void onInterruptionEnd();
3942
}
4043

4144
public AudioFocusManager(Context context) {
42-
this(context, InterruptionSource.AUDIO_FOCUS_AND_TELEPHONY);
45+
this(context, InterruptionSource.AUDIO_FOCUS_AND_TELEPHONY, null, null);
4346
}
4447

4548
public AudioFocusManager(Context context, InterruptionSource interruptionSource) {
49+
this(context, interruptionSource, null, null);
50+
}
51+
52+
public AudioFocusManager(Context context, InterruptionSource interruptionSource, Integer usageType, Integer contentType) {
4653
this.context = context;
4754
this.interruptionSource = interruptionSource;
55+
this.focusUsageType = usageType;
56+
this.focusContentType = contentType;
4857

4958
if (interruptionSource == InterruptionSource.AUDIO_FOCUS_ONLY ||
5059
interruptionSource == InterruptionSource.AUDIO_FOCUS_AND_TELEPHONY) {
@@ -117,8 +126,8 @@ private void requestAudioFocusInternal() {
117126

118127
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
119128
AudioAttributes audioAttributes = new AudioAttributes.Builder()
120-
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
121-
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
129+
.setUsage(focusUsageType != null ? focusUsageType : AudioAttributes.USAGE_VOICE_COMMUNICATION)
130+
.setContentType(focusContentType != null ? focusContentType : AudioAttributes.CONTENT_TYPE_SPEECH)
122131
.build();
123132

124133
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
@@ -128,12 +137,47 @@ private void requestAudioFocusInternal() {
128137

129138
audioManager.requestAudioFocus(audioFocusRequest);
130139
} else {
140+
int streamType = inferPreOStreamType(focusUsageType, focusContentType);
131141
audioManager.requestAudioFocus(onAudioFocusChangeListener,
132-
AudioManager.STREAM_VOICE_CALL,
142+
streamType,
133143
AudioManager.AUDIOFOCUS_GAIN);
134144
}
135145
}
136146

147+
private int inferPreOStreamType(Integer usageType, Integer contentType) {
148+
if (usageType != null) {
149+
if (usageType == AudioAttributes.USAGE_MEDIA
150+
|| usageType == AudioAttributes.USAGE_GAME
151+
|| usageType == AudioAttributes.USAGE_ASSISTANT) {
152+
return AudioManager.STREAM_MUSIC;
153+
}
154+
if (usageType == AudioAttributes.USAGE_VOICE_COMMUNICATION
155+
|| usageType == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
156+
return AudioManager.STREAM_VOICE_CALL;
157+
}
158+
if (usageType == AudioAttributes.USAGE_NOTIFICATION
159+
|| usageType == AudioAttributes.USAGE_NOTIFICATION_RINGTONE
160+
|| usageType == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
161+
return AudioManager.STREAM_NOTIFICATION;
162+
}
163+
if (usageType == AudioAttributes.USAGE_ALARM) {
164+
return AudioManager.STREAM_ALARM;
165+
}
166+
}
167+
168+
if (contentType != null) {
169+
if (contentType == AudioAttributes.CONTENT_TYPE_MUSIC
170+
|| contentType == AudioAttributes.CONTENT_TYPE_MOVIE) {
171+
return AudioManager.STREAM_MUSIC;
172+
}
173+
if (contentType == AudioAttributes.CONTENT_TYPE_SPEECH) {
174+
return AudioManager.STREAM_VOICE_CALL;
175+
}
176+
}
177+
178+
return AudioManager.STREAM_VOICE_CALL;
179+
}
180+
137181
private void registerTelephonyListener() {
138182
if (telephonyManager == null) {
139183
Log.w(TAG, "TelephonyManager is null, cannot register telephony listener");

ios/stream_webrtc_flutter.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
Pod::Spec.new do |s|
55
s.name = 'stream_webrtc_flutter'
6-
s.version = '1.0.10'
6+
s.version = '1.0.11'
77
s.summary = 'Flutter WebRTC plugin for iOS.'
88
s.description = <<-DESC
99
A new flutter plugin project.

lib/src/android_interruption_source.dart

Lines changed: 0 additions & 5 deletions
This file was deleted.

lib/src/native/android/audio_configuration.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ extension AndroidAudioFocusModeEnumEx on String {
2525
AndroidAudioFocusMode.values.firstWhere((d) => d.name == toLowerCase());
2626
}
2727

28+
enum AndroidInterruptionSource {
29+
audioFocusOnly,
30+
telephonyOnly,
31+
audioFocusAndTelephony,
32+
}
33+
2834
enum AndroidAudioStreamType {
2935
accessibility,
3036
alarm,

lib/src/native/factory_impl.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import 'dart:io';
33

44
import 'package:webrtc_interface/webrtc_interface.dart';
55

6-
import '../android_interruption_source.dart';
76
import '../desktop_capturer.dart';
7+
import 'android/audio_configuration.dart';
88
import 'desktop_capturer_impl.dart';
99
import 'frame_cryptor_impl.dart';
1010
import 'media_recorder_impl.dart';
@@ -32,6 +32,8 @@ class RTCFactoryNative extends RTCFactory {
3232
void Function()? onInterruptionEnd, {
3333
AndroidInterruptionSource androidInterruptionSource =
3434
AndroidInterruptionSource.audioFocusAndTelephony,
35+
AndroidAudioAttributesUsageType? androidAudioAttributesUsageType,
36+
AndroidAudioAttributesContentType? androidAudioAttributesContentType,
3537
}) async {
3638
if (!Platform.isAndroid && !Platform.isIOS) {
3739
throw UnimplementedError(
@@ -43,6 +45,12 @@ class RTCFactoryNative extends RTCFactory {
4345
<String, dynamic>{
4446
if (Platform.isAndroid)
4547
'androidInterruptionSource': androidInterruptionSource.name,
48+
if (Platform.isAndroid && androidAudioAttributesUsageType != null)
49+
'androidAudioAttributesUsageType':
50+
androidAudioAttributesUsageType.name,
51+
if (Platform.isAndroid && androidAudioAttributesContentType != null)
52+
'androidAudioAttributesContentType':
53+
androidAudioAttributesContentType.name,
4654
},
4755
);
4856

@@ -136,12 +144,16 @@ Future<void> handleCallInterruptionCallbacks(
136144
void Function()? onInterruptionEnd, {
137145
AndroidInterruptionSource androidInterruptionSource =
138146
AndroidInterruptionSource.audioFocusAndTelephony,
147+
AndroidAudioAttributesUsageType? androidAudioAttributesUsageType,
148+
AndroidAudioAttributesContentType? androidAudioAttributesContentType,
139149
}) {
140150
return (RTCFactoryNative.instance as RTCFactoryNative)
141151
.handleCallInterruptionCallbacks(
142152
onInterruptionStart,
143153
onInterruptionEnd,
144154
androidInterruptionSource: androidInterruptionSource,
155+
androidAudioAttributesUsageType: androidAudioAttributesUsageType,
156+
androidAudioAttributesContentType: androidAudioAttributesContentType,
145157
);
146158
}
147159

lib/src/web/factory_impl.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import '../android_interruption_source.dart';
21
import '../desktop_capturer.dart';
32

43
export 'package:dart_webrtc/dart_webrtc.dart'
@@ -16,7 +15,9 @@ Future<void> setVideoEffects(
1615
Future<void> handleCallInterruptionCallbacks(
1716
void Function()? onInterruptionStart,
1817
void Function()? onInterruptionEnd, {
19-
AndroidInterruptionSource? androidInterruptionSource,
18+
Object? androidInterruptionSource,
19+
Object? androidAudioAttributesUsageType,
20+
Object? androidAudioAttributesContentType,
2021
}) {
2122
throw UnimplementedError(
2223
'handleCallInterruptionCallbacks() is not supported on web');

lib/stream_webrtc_flutter.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ library flutter_webrtc;
33
export 'package:webrtc_interface/webrtc_interface.dart'
44
hide MediaDevices, MediaRecorder, Navigator;
55

6-
export 'src/android_interruption_source.dart';
76
export 'src/helper.dart';
87
export 'src/desktop_capturer.dart';
98
export 'src/media_devices.dart';

macos/stream_webrtc_flutter.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
Pod::Spec.new do |s|
55
s.name = 'stream_webrtc_flutter'
6-
s.version = '1.0.10'
6+
s.version = '1.0.11'
77
s.summary = 'Flutter WebRTC plugin for macOS.'
88
s.description = <<-DESC
99
A new flutter plugin project.

0 commit comments

Comments
 (0)