From 4322b36518a1e12b46e8979be6fc1a570bd83273 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sat, 19 Dec 2020 13:14:35 +1100 Subject: [PATCH] Prevent ExoPlayer skipping beginning audio. --- .../com/ryanheise/just_audio/AudioPlayer.java | 20 +++++- just_audio/lib/just_audio.dart | 68 +++++++++++++------ 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index c1ec44d..7e7fe40 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -75,6 +75,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud private IcyInfo icyInfo; private IcyHeaders icyHeaders; private int errorCount; + private AudioAttributes pendingAudioAttributes; private SimpleExoPlayer player; private Integer audioSessionId; @@ -211,6 +212,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud response.put("duration", getDuration() == C.TIME_UNSET ? null : (1000 * getDuration())); prepareResult.success(response); prepareResult = null; + if (pendingAudioAttributes != null) { + player.setAudioAttributes(pendingAudioAttributes); + pendingAudioAttributes = null; + } } else { transition(ProcessingState.ready); } @@ -500,7 +505,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud private void load(final MediaSource mediaSource, final long initialPosition, final Integer initialIndex, final Result result) { this.initialPos = initialPosition; - this.initialIndex = currentIndex = initialIndex; + this.initialIndex = initialIndex; + currentIndex = initialIndex != null ? initialIndex : 0; switch (processingState) { case none: break; @@ -535,7 +541,14 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud builder.setFlags(flags); builder.setUsage(usage); //builder.setAllowedCapturePolicy((Integer)json.get("allowedCapturePolicy")); - player.setAudioAttributes(builder.build()); + AudioAttributes audioAttributes = builder.build(); + if (processingState == ProcessingState.loading) { + // audio attributes should be set either before or after loading to + // avoid an ExoPlayer glitch. + pendingAudioAttributes = audioAttributes; + } else { + player.setAudioAttributes(audioAttributes); + } } private void broadcastPlaybackEvent() { @@ -652,7 +665,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Aud } public void setSpeed(final float speed) { - player.setPlaybackParameters(new PlaybackParameters(speed)); + if (player.getPlaybackParameters().speed != speed) + player.setPlaybackParameters(new PlaybackParameters(speed)); broadcastPlaybackEvent(); } diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index ad80bb3..6b3db94 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -61,6 +61,7 @@ class AudioPlayer { AudioSource _audioSource; Map _audioSources = {}; bool _disposed = false; + _InitialSeekValues _initialSeekValues; PlaybackEvent _playbackEvent; final _playbackEventSubject = BehaviorSubject(sync: true); @@ -84,6 +85,7 @@ class AudioPlayer { BehaviorSubject _positionSubject; bool _automaticallyWaitsToMinimizeStalling = true; bool _playInterrupted = false; + AndroidAudioAttributes _androidAudioAttributes; /// Creates an [AudioPlayer]. The player will automatically pause/duck and /// resume/unduck when audio interruptions occur (e.g. a phone call) or when @@ -466,7 +468,7 @@ class AudioPlayer { Future setUrl( String url, { Map headers, - Duration initialPosition = Duration.zero, + Duration initialPosition, bool preload = true, }) => setAudioSource(AudioSource.uri(Uri.parse(url), headers: headers), @@ -483,7 +485,7 @@ class AudioPlayer { /// See [setAudioSource] for a detailed explanation of the options. Future setFilePath( String filePath, { - Duration initialPosition = Duration.zero, + Duration initialPosition, bool preload = true, }) => setAudioSource(AudioSource.uri(Uri.file(filePath)), @@ -530,14 +532,16 @@ class AudioPlayer { Future setAudioSource( AudioSource source, { bool preload = true, - int initialIndex = 0, - Duration initialPosition = Duration.zero, + int initialIndex, + Duration initialPosition, }) async { if (_disposed) return null; // Idea: always keep the idle player around and make it possible // to switch between idle and active players without disposing either // one. _audioSource = null; + _initialSeekValues = + _InitialSeekValues(position: initialPosition, index: initialIndex); _playbackEventSubject.add(_playbackEvent = PlaybackEvent( currentIndex: initialIndex, updatePosition: initialPosition)); _broadcastSequence(); @@ -596,8 +600,6 @@ class AudioPlayer { Future _load(AudioPlayerPlatform platform, AudioSource source, {Duration initialPosition, int initialIndex}) async { - initialIndex ??= 0; - initialPosition ??= Duration.zero; try { if (!kIsWeb && source._requiresHeaders) { if (_proxy == null) { @@ -790,6 +792,7 @@ class AudioPlayer { /// an audio source has been loaded. Future seek(final Duration position, {int index}) async { if (_disposed) return; + _initialSeekValues = null; switch (processingState) { case ProcessingState.loading: return; @@ -824,11 +827,17 @@ class AudioPlayer { AndroidAudioAttributes audioAttributes) async { if (_disposed) return; if (audioAttributes == null) return; - await (await _platform).setAndroidAudioAttributes( - SetAndroidAudioAttributesRequest( - contentType: audioAttributes.contentType.index, - flags: audioAttributes.flags.value, - usage: audioAttributes.usage.value)); + if (audioAttributes == _androidAudioAttributes) return; + _androidAudioAttributes = audioAttributes; + await _internalSetAndroidAudioAttributes(await _platform, audioAttributes); + } + + Future _internalSetAndroidAudioAttributes(AudioPlayerPlatform platform, + AndroidAudioAttributes audioAttributes) async { + await platform.setAndroidAudioAttributes(SetAndroidAudioAttributesRequest( + contentType: audioAttributes.contentType.index, + flags: audioAttributes.flags.value, + usage: audioAttributes.usage.value)); } /// Release all resources associated with this player. You must invoke this @@ -922,13 +931,16 @@ class AudioPlayer { if (active) { final automaticallyWaitsToMinimizeStalling = this.automaticallyWaitsToMinimizeStalling; - final setAndroidAudioAttributesRequest = - _idlePlatform.setAndroidAudioAttributesRequest; - if (setAndroidAudioAttributesRequest != null) { - // Only set if there was an unfulfilled pending request. - await platform - .setAndroidAudioAttributes(setAndroidAudioAttributesRequest); - _idlePlatform.setAndroidAudioAttributesRequest = null; + // To avoid a glitch in ExoPlayer, ensure that any requested audio + // attributes are set before loading the audio source. + final audioSession = await AudioSession.instance; + if (_androidAudioAttributes == null) { + _androidAudioAttributes = + audioSession.configuration?.androidAudioAttributes; + } + if (_androidAudioAttributes != null) { + await _internalSetAndroidAudioAttributes( + platform, _androidAudioAttributes); } if (!automaticallyWaitsToMinimizeStalling) { // Only set if different from default. @@ -947,8 +959,17 @@ class AudioPlayer { } if (audioSource != null) { try { + Duration initialPosition; + int initialIndex; + if (_initialSeekValues != null) { + initialPosition = _initialSeekValues.position; + initialIndex = _initialSeekValues.index; + } else { + initialPosition = position; + initialIndex = currentIndex; + } final duration = await _load(platform, _audioSource, - initialPosition: position, initialIndex: currentIndex); + initialPosition: initialPosition, initialIndex: initialIndex); // Wait for loading state to pass. await processingStateStream .firstWhere((state) => state != ProcessingState.loading); @@ -2054,3 +2075,12 @@ class _IdleAudioPlayer extends AudioPlayerPlatform { return ConcatenatingMoveResponse(); } } + +/// Holds the initial requested position and index for a newly loaded audio +/// source. +class _InitialSeekValues { + final Duration position; + final int index; + + _InitialSeekValues({@required this.position, @required this.index}); +}