ryanheise / audio_service

Flutter plugin to play audio in the background while the screen is off.
793 stars 472 forks source link

audio_service bug with iOS 16.2 - audio in background freeze if you load a new file and play #993

Open csacchetti opened 1 year ago

csacchetti commented 1 year ago

Documented behaviour

play() → Future Start or resume playback.

Actual behaviour

When the app is in the background with iOS 16.2 (or 16.0 but I have not tested this version) the audio, if I load a new audio file and play it the audio freezes. With iOS 14 the audio is played correctly and I think also in iOS15.

Runtime error

No error is indicated in the console but the bug is there

Minimal reproduction project

https://github.com/csacchetti/bug_audio_service_ios_16.git

Reproduction steps

First of all, download the example from GitHub. The code is that of the main of the example in audio_service gitHub. I have only added the final part where I put the explanations to reproduce the bug and I added in the construct of AudioPlayerHandler() the function _myListenVoiceState();

To check this, go to the end of the song and go back 10 seconds by clicking on the back button and then start play. If you are not in the background the song once it reaches the end will resume normally, whereas if you start play and go into the background the song if you are on iOS 14 will behave correctly and restart, whereas if you are on iOS 16.2 it will freeze.

Output of flutter doctor


    it-IT)
    • Flutter version 3.3.10 on channel stable at
      /Users/carlosacchetti/Developer/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 135454af32 (6 weeks ago), 2022-12-15 07:36:55 -0800
    • Engine revision 3316dd8728
    • Dart version 2.18.6
    • DevTools version 2.15.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    • Android SDK at /Users/carlosacchetti/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0
    • Java binary at: /Applications/Android
      Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14C18
    • CocoaPods version 1.11.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2020.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)

[✓] IntelliJ IDEA Community Edition (version 2021.1.1)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.74.3)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.56.0

[✓] Connected device (4 available)
    • iPhone di CS (mobile)    • 00008101-0015458A3488001E                • ios
      • iOS 14.7.1 18G82
    • iPhone di Carlo (mobile) • 8197a7889687e1b6883fab3f8b749fd17b32538b • ios
      • iOS 16.2 20C65
    • macOS (desktop)          • macos                                    •
      darwin-x64     • macOS 12.6.2 21G320 darwin-x64
    • Chrome (web)             • chrome                                   •
      web-javascript • Google Chrome 109.0.5414.87

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!```
### Devices exhibiting the bug
The problem is with iOS 16.2
csacchetti commented 1 year ago

https://user-images.githubusercontent.com/46923349/214356242-e5a9f737-f0f9-4ef0-9708-0347678e0336.mov

In this video I show a strange thing that happens with iOS 16 and I hope it can help solve the problem. The video is without sound but what is important is what happens to the background. Before you start the app you see that there is a wallpaper. Then I launch the app I start the song with 10 seconds to go and put the app in the background. If you look at the wallpaper it has changed. Then you wait until the song comes to the end and at that time when it tries to load the new file the background changes again. This suggests that there is a conflict with the new wallpaper management in iOS 16 Just a suspicion, but this behaviour is strange

csacchetti commented 1 year ago

I had the opportunity to test the problem on an iPad with iPadOS 16.1.1 and the bug is present. So it is likely that the bug started with iOS 16.0

ryanheise commented 1 year ago

I am copying @rjgpereira 's comment from the closed issue here:

@csacchetti I had a similar issue. I'm mimicking the playlist behaviour but not using the ConcatenatingAudioSource, just switching tracks manually calling player.setAudioSource() with the proper index, and listening to the "completed" playbackState in order to skip to the next index. Since I was using the player's 'LoopMode.off', after the 'completed' state was emitted, an 'idle' state was emitted as well. I think broadcasting this 'idle' state in iOS 16 might close the audio session. I don't know if this is exactly what's happening, and I can't explain why it didn't occur in the previous iOS versions. But this fixed it for me.

Try to broadcast AudioProcessingState.ready when you get a ProcessingState.idle, so that the OS keep your session alive while you switch tracks:

processingState: { ProcessingState.idle: Platform.isIOS ? AudioProcessingState.ready : AudioProcessingState.idle, ProcessingState.loading: AudioProcessingState.loading, ProcessingState.buffering: AudioProcessingState.buffering, ProcessingState.ready: AudioProcessingState.ready, ProcessingState.completed: AudioProcessingState.completed, }[_player.processingState]!,

Zeeshan-H commented 1 year ago

I am unable to change the mediaitem using just_audio and audio_service package please help!

/// An [AudioHandler] for playing a single item.
    class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {
            final _item = MediaItem(
                id: songUri.toString(),
                album: "Science Friday",
                title: songTitle.toString(),
                artist: "Science Friday and WNYC Studios",
                duration: Duration(milliseconds: songDuration!),
                artUri: Uri.tryParse(
                    "https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg"));

            final _player = AudioPlayer();

            /// Initialise our audio handler.
            AudioPlayerHandler() {
              // So that our clients (the Flutter UI and the system notification) know
              // what state to display, here we set up our audio handler to broadcast all
              // playback state changes as they happen via playbackState...

              _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
              // ... and also the current media item via mediaItem.
              mediaItem.add(_item);

              // Load the player.
              _player.setAudioSource(AudioSource.uri(Uri.parse(_item.id)));
            }

            // In this simple example, we handle only 4 actions: play, pause, seek and
            // stop. Any button press from the Flutter UI, notification, lock screen or
            // headset will be routed through to these 4 methods so that you can handle
            // your audio playback logic in one place.

            @override
            Future<void> play() async {
              print('Playing here');
              _player.play();
            }

            @override
            Future<void> pause() async {
              print('Pausing here');
              _player.pause();
            }

            @override
            Future<void> seek(Duration position) => _player.seek(position);

            @override
            Future<void> stop() => _player.stop();

            /// Transform a just_audio event into an audio_service state.
            ///
            /// This method is used from the constructor. Every event received from the
            /// just_audio player will be transformed into an audio_service state so that
            /// it can be broadcast to audio_service clients.
            PlaybackState _transformEvent(PlaybackEvent event) {
              return PlaybackState(
                controls: [
                  MediaControl.rewind,
                  if (_player.playing) MediaControl.pause else MediaControl.play,
                  MediaControl.stop,
                  MediaControl.fastForward,
                ],
                systemActions: const {
                  MediaAction.seek,
                  MediaAction.seekForward,
                  MediaAction.seekBackward,
                },
                androidCompactActionIndices: const [0, 1, 3],
                processingState: const {
                  ProcessingState.idle: AudioProcessingState.idle,
                  ProcessingState.loading: AudioProcessingState.loading,
                  ProcessingState.buffering: AudioProcessingState.buffering,
                  ProcessingState.ready: AudioProcessingState.ready,
                  ProcessingState.completed: AudioProcessingState.completed,
                }[_player.processingState]!,
                playing: _player.playing,
                updatePosition: _player.position,
                bufferedPosition: _player.bufferedPosition,
                speed: _player.speed,
                queueIndex: event.currentIndex,
              );
            }
    }

How do i updatemediaItem the right way. I tried to override this method and the audio changed but now when i pause and play again it starts from the beginning and does not resume it.

mklepaczko commented 1 year ago

For info the same problem happens in flutter web when running on IOS 16.3 and same fix solves this problem

wengxianxun commented 12 months ago

can,t work for me, what happend?

snipd-mikel commented 11 months ago

I have also encountered this issue. I have tried the workaround provided by @csacchetti and no luck. What ended up working for me, but it's a little bit of a hack, is to have a second player available and play a silence file in loop mode so iOS doesn't put the app to sleep (or whatever is going on). So, this is more or less what I'm doing now:

playSilence();
await player.setAudioSource(...);
pauseSilence();