From 64d55345f8b4b18eb9974e313019683c04fad389 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Fri, 14 Aug 2020 14:38:55 +1000 Subject: [PATCH] Add setAndroidAudioAttributes and androidAudioSessionIdStream. --- .../com/ryanheise/just_audio/AudioPlayer.java | 29 ++++++- darwin/Classes/AudioPlayer.m | 2 + lib/just_audio.dart | 79 +++++++++++++++++++ lib/just_audio_web.dart | 2 + 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index ff17203..b0a844d 100644 --- a/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -9,6 +9,8 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.icy.IcyHeaders; @@ -46,7 +48,7 @@ import java.util.List; import java.util.Map; import java.util.Random; -public class AudioPlayer implements MethodCallHandler, Player.EventListener, MetadataOutput { +public class AudioPlayer implements MethodCallHandler, Player.EventListener, AudioListener, MetadataOutput { static final String TAG = "AudioPlayer"; @@ -76,6 +78,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met private int errorCount; private SimpleExoPlayer player; + private Integer audioSessionId; private MediaSource mediaSource; private Integer currentIndex; private Map loopingChildren = new HashMap<>(); @@ -136,6 +139,15 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met handler.post(bufferWatcher); } + @Override + public void onAudioSessionId(int audioSessionId) { + if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { + this.audioSessionId = null; + } else { + this.audioSessionId = audioSessionId; + } + } + @Override public void onMetadata(Metadata metadata) { for (int i = 0; i < metadata.length(); i++) { @@ -347,6 +359,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met case "concatenating.clear": concatenating(args.get(0)).clear(handler, () -> result.success(null)); break; + case "setAndroidAudioAttributes": + setAudioAttributes((Map)args.get(0)); + result.success(null); + break; default: result.notImplemented(); break; @@ -532,9 +548,19 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met player = new SimpleExoPlayer.Builder(context).build(); player.addMetadataOutput(this); player.addListener(this); + player.addAudioListener(this); } } + private void setAudioAttributes(Map json) { + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + builder.setContentType((Integer)json.get("contentType")); + builder.setFlags((Integer)json.get("flags")); + builder.setUsage((Integer)json.get("usage")); + builder.setAllowedCapturePolicy((Integer)json.get("allowedCapturePolicy")); + player.setAudioAttributes(builder.build()); + } + private void broadcastPlaybackEvent() { final Map event = new HashMap(); event.put("processingState", processingState.ordinal()); @@ -544,6 +570,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met event.put("icyMetadata", collectIcyMetadata()); event.put("duration", duration = getDuration()); event.put("currentIndex", currentIndex); + event.put("androidAudioSessionId", audioSessionId); if (eventSink != null) { eventSink.success(event); diff --git a/darwin/Classes/AudioPlayer.m b/darwin/Classes/AudioPlayer.m index 1b24793..9c5baa1 100644 --- a/darwin/Classes/AudioPlayer.m +++ b/darwin/Classes/AudioPlayer.m @@ -138,6 +138,8 @@ } else if ([@"concatenating.clear" isEqualToString:call.method]) { [self concatenatingClear:(NSString*)args[0]]; result(nil); + } else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) { + result(nil); } else { result(FlutterMethodNotImplemented); } diff --git a/lib/just_audio.dart b/lib/just_audio.dart index 1331fe9..e33b446 100644 --- a/lib/just_audio.dart +++ b/lib/just_audio.dart @@ -82,6 +82,7 @@ class AudioPlayer { final _sequenceStateSubject = BehaviorSubject(); final _loopModeSubject = BehaviorSubject(); final _shuffleModeEnabledSubject = BehaviorSubject(); + final _androidAudioSessionIdSubject = BehaviorSubject(); BehaviorSubject _positionSubject; bool _automaticallyWaitsToMinimizeStalling = true; @@ -97,6 +98,7 @@ class AudioPlayer { duration: null, icyMetadata: null, currentIndex: null, + androidAudioSessionId: null, ); _playbackEventSubject.add(_playbackEvent); _eventChannelStream = EventChannel('com.ryanheise.just_audio.events.$_id') @@ -121,6 +123,7 @@ class AudioPlayer { ? null : IcyMetadata.fromJson(data['icyMetadata']), currentIndex: data['currentIndex'], + androidAudioSessionId: data['androidAudioSessionId'], ); //print("created event object with state: ${_playbackEvent.state}"); return _playbackEvent; @@ -146,6 +149,10 @@ class AudioPlayer { .map((event) => event.currentIndex) .distinct() .handleError((err, stack) {/* noop */})); + _androidAudioSessionIdSubject.addStream(playbackEventStream + .map((event) => event.androidAudioSessionId) + .distinct() + .handleError((err, stack) {/* noop */})); _sequenceStateSubject.addStream( Rx.combineLatest2, int, SequenceState>( sequenceStream, @@ -274,6 +281,13 @@ class AudioPlayer { Stream get shuffleModeEnabledStream => _shuffleModeEnabledSubject.stream; + /// The current Android AudioSession ID or `null` if not set. + int get androidAudioSessionId => _playbackEvent.androidAudioSessionId; + + /// Broadcasts the current Android AudioSession ID or `null` if not set. + Stream get androidAudioSessionIdStream => + _androidAudioSessionIdSubject.stream; + /// Whether the player should automatically delay playback in order to /// minimize stalling. (iOS 10.0 or later only) bool get automaticallyWaitsToMinimizeStalling => @@ -591,6 +605,14 @@ class AudioPlayer { } } + /// Set the Android audio attributes for this player. Has no effect on other + /// platforms. This will cause a new Android AudioSession ID to be generated. + Future setAndroidAudioAttributes( + AndroidAudioAttributes audioAttributes) async { + await _invokeMethod( + 'setAndroidAudioAttributes', [audioAttributes.toJson()]); + } + /// Release all resources associated with this player. You must invoke this /// after you are done with the player. Future dispose() async { @@ -670,6 +692,9 @@ class PlaybackEvent { /// The index of the currently playing item. final int currentIndex; + /// The current Android AudioSession ID. + final int androidAudioSessionId; + PlaybackEvent({ @required this.processingState, @required this.updateTime, @@ -678,6 +703,7 @@ class PlaybackEvent { @required this.duration, @required this.icyMetadata, @required this.currentIndex, + @required this.androidAudioSessionId, }); PlaybackEvent copyWith({ @@ -689,6 +715,7 @@ class PlaybackEvent { Duration duration, IcyMetadata icyMetadata, UriAudioSource currentIndex, + int androidAudioSessionId, }) => PlaybackEvent( processingState: processingState ?? this.processingState, @@ -698,6 +725,8 @@ class PlaybackEvent { duration: duration ?? this.duration, icyMetadata: icyMetadata ?? this.icyMetadata, currentIndex: currentIndex ?? this.currentIndex, + androidAudioSessionId: + androidAudioSessionId ?? this.androidAudioSessionId, ); @override @@ -853,6 +882,56 @@ enum IosCategory { multiRoute, } +/// The Android AudioAttributes to use with a player. +class AndroidAudioAttributes { + static const FLAG_AUDIBILITY_ENFORCED = 0x1 << 0; + final AndroidAudioContentType contentType; + final int flags; + final AndroidAudioUsage usage; + final AndroidAudioAllowedCapturePolicy allowedCapturePolicy; + + AndroidAudioAttributes({ + this.contentType = AndroidAudioContentType.unknown, + this.flags = 0, + this.usage = AndroidAudioUsage.unknown, + this.allowedCapturePolicy = AndroidAudioAllowedCapturePolicy.all, + }); + + Map toJson() => { + 'contentType': contentType.index, + 'flags': flags, + 'usage': usage.index, + // The Android constant values for this enum are 1-indexed + 'allowedCapturePolicy': allowedCapturePolicy.index + 1, + }; +} + +/// The content type options for [AndroidAudioAttributes]. +enum AndroidAudioContentType { unknown, speech, music, movie, sonification } + +/// The usage options for [AndroidAudioAttributes]. +enum AndroidAudioUsage { + unknown, + media, + voiceCommunication, + voiceCommunicationSignalling, + alarm, + notification, + notificationRingtone, + notificationCommunicationRequest, + notificationCommunicationInstant, + notificationCommunicationDelayed, + notificationEvent, + assistanceAccessibility, + assistanceNavigationGuidance, + assistanceSonification, + unused_1, + assistant, +} + +/// The allowed capture policy options for [AndroidAudioAttributes]. +enum AndroidAudioAllowedCapturePolicy { all, system, none } + /// A local proxy HTTP server for making remote GET requests with headers. /// /// TODO: Recursively attach headers to items in playlists like m3u8. diff --git a/lib/just_audio_web.dart b/lib/just_audio_web.dart index 1df5735..4c6ea99 100644 --- a/lib/just_audio_web.dart +++ b/lib/just_audio_web.dart @@ -96,6 +96,8 @@ abstract class JustAudioPlayer { return await concatenatingMove(args[0], args[1], args[2]); case "concatenating.clear": return await concatenatingClear(args[0]); + case "setAndroidAudioAttributes": + return null; default: throw PlatformException(code: 'Unimplemented'); }