Federico
Federico

Reputation: 49

Audio stop when phone sleep

in my Flutter app I can play a list of radio stations, but if while the audio is playing, I put the app in the background and lock the screen, sometimes immediately or after 30 seconds, the audio is interrupted and the control center is set with a "Not playing" label.

main.dart

import 'package:audio_service/audio_service.dart';
import 'package:radioplayer/const.dart';
import 'package:radioplayer/services/audio_handler.dart';

Future<void> main() async {
  audioHandler = await AudioService.init(
    builder: () => AudioPlayerHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'com.moeasy.radioplayer.channel.audio',
      androidNotificationChannelName: 'Radio Format',
      androidNotificationOngoing: true,
      preloadArtwork: true,
    ),
  );
}

audio_handler.dart

import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:just_audio/just_audio.dart';
import 'package:radioplayer/api/stations_api.dart';
import 'package:radioplayer/const.dart';

int? currentStationIndexOnLockScreen;

class AudioPlayerHandler extends BaseAudioHandler {
  AudioPlayerHandler() {
    _init();
  }

  Future<void> _init() async {
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration.music());

    playbackState.add(playbackState.value.copyWith(
      processingState: AudioProcessingState.idle,
    ));

    await Future.delayed(const Duration(seconds: 2));
    audioPlayer.playbackEventStream.listen(_transformEvent);
  }

  _addMediaItem(MediaItem item) {
    mediaItem.add(item);
  }

  @override
  Future<void> playMediaItem(MediaItem mediaItem) async {
    playbackState.add(playbackState.value.copyWith(
      playing: true,
      controls: [
        MediaControl.skipToPrevious,
        MediaControl.pause,
        MediaControl.stop,
        MediaControl.skipToNext
      ],
      processingState: AudioProcessingState.ready,
    ));
    _addMediaItem(mediaItem);
    await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.id)));
  }

  @override
  Future<void> play() async {
    playbackState.add(playbackState.value.copyWith(
      playing: true,
      controls: [
        MediaControl.skipToPrevious,
        MediaControl.pause,
        MediaControl.stop,
        MediaControl.skipToNext
      ],
      processingState: AudioProcessingState.ready,
    ));
    audioPlayer.play();
  }

  @override
  Future<void> pause() async {
    playbackState.add(playbackState.value.copyWith(
      playing: false,
      controls: [
        MediaControl.skipToPrevious,
        MediaControl.play,
        MediaControl.stop,
        MediaControl.skipToNext
      ],
      processingState: AudioProcessingState.ready,
    ));
    audioPlayer.pause();
  }

  @override
  Future<void> stop() async {
    audioPlayer.stop();
    playbackState.add(playbackState.value.copyWith(
      processingState: AudioProcessingState.idle,
    ));
  }

  @override
  Future<void> skipToPrevious() async {
    if (currentStationIndexOnLockScreen != 0) {
      currentStationIndexOnLockScreen = currentStationIndexOnLockScreen! - 1;
    } else {
      currentStationIndexOnLockScreen = allStations.length - 1;
    }
    MediaItem mediaItem = MediaItem(
      id: allStations[currentStationIndexOnLockScreen!].id,
      title: allStations[currentStationIndexOnLockScreen!].title,
      artist: allStations[currentStationIndexOnLockScreen!].artist,
      artUri: allStations[currentStationIndexOnLockScreen!].artUri,
    );
    _addMediaItem(mediaItem);
    audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.id)));
  }

  @override
  Future<void> skipToNext() async {
    if (currentStationIndexOnLockScreen! < allStations.length - 1) {
      currentStationIndexOnLockScreen = currentStationIndexOnLockScreen! + 1;
    } else {
      currentStationIndexOnLockScreen = 0;
    }
    MediaItem mediaItem = MediaItem(
      id: allStations[currentStationIndexOnLockScreen!].id,
      title: allStations[currentStationIndexOnLockScreen!].title,
      artist: allStations[currentStationIndexOnLockScreen!].artist,
      artUri: allStations[currentStationIndexOnLockScreen!].artUri,
    );
    _addMediaItem(mediaItem);
    audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.id)));
  }

  _transformEvent(PlaybackEvent event) {
    final playing = audioPlayer.playing;
    playbackState.add(playbackState.value.copyWith(
      controls: [
        MediaControl.skipToPrevious,
        if (playing) MediaControl.pause else MediaControl.play,
        MediaControl.stop,
        MediaControl.skipToNext,
      ],
      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,
      }[audioPlayer.processingState]!,
      playing: playing,
      updatePosition: audioPlayer.position,
      bufferedPosition: audioPlayer.bufferedPosition,
      speed: audioPlayer.speed,
      queueIndex: event.currentIndex,
    ));
  }
}

Anyone know how to help me please?

UPDATE:

I've edited the _init() method in audio_handler.dart with this:

Future<void> _init() async {
    final session = await AudioSession.instance;
    await session.configure(const AudioSessionConfiguration(
      avAudioSessionCategory: AVAudioSessionCategory.playback,
      avAudioSessionCategoryOptions:
          AVAudioSessionCategoryOptions.defaultToSpeaker,
      avAudioSessionMode: AVAudioSessionMode.defaultMode,
      avAudioSessionRouteSharingPolicy:
          AVAudioSessionRouteSharingPolicy.defaultPolicy,
      avAudioSessionSetActiveOptions:
          AVAudioSessionSetActiveOptions.notifyOthersOnDeactivation,
      androidAudioAttributes: AndroidAudioAttributes(
        contentType: AndroidAudioContentType.music,
        flags: AndroidAudioFlags.audibilityEnforced,
        usage: AndroidAudioUsage.media,
      ),
      androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
      androidWillPauseWhenDucked: true,
    ));

    playbackState.add(playbackState.value.copyWith(
      processingState: AudioProcessingState.idle,
    ));

    await Future.delayed(const Duration(seconds: 2));
    audioPlayer.playbackEventStream.listen(_transformEvent);
  }

Now work perfectly in background, with bluetooth device and airplay but now I see this error despite the app working great. I've tried changing the configuration to session but that's the only way it works fine. Does anyone know if this error / incompatibility below could affect the time of publishing on the appstore?

This is the error:

[as_client]     AVAudioSession_iOS.mm:2194  Error: category option 'defaultToSpeaker' is only applicable with category 'playAndRecord'
[as_client]     AVAudioSession_iOS.mm:2370  Failed to set category, error: -50
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(-50, Impossibile completare l'operazione. (Errore OSStatus -50)., null, null)
#0      StandardMethodCodec.decodeEnvelope
package:flutter/…/services/message_codecs.dart:653
#1      MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:296
<asynchronous suspension>
#2      AudioSession.configure
package:audio_session/src/core.dart:201
<asynchronous suspension>
#3      AudioPlayerHandler._init
package:radioplayer/services/audio_handler.dart:16
<asynchronous suspension>

Upvotes: 3

Views: 547

Answers (0)

Related Questions