ryanheise / audio_service

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

android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): #781

Closed vipCodeError closed 3 years ago

vipCodeError commented 3 years ago

App Crashes with this error after playing. player controller responding UI to Notification not notification to UI. sdk: ">=2.7.0 <3.0.0"

E/AndroidRuntime( 7834): android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{74c2cac u0 /com.dooboolab.TauEngine.FlautoBackgroundAudioService} E/AndroidRuntime( 7834): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2068) E/AndroidRuntime( 7834): at android.os.Handler.dispatchMessage(Handler.java:107) E/AndroidRuntime( 7834): at android.os.Looper.loop(Looper.java:237) E/AndroidRuntime( 7834): at android.app.ActivityThread.main(ActivityThread.java:7807) E/AndroidRuntime( 7834): at java.lang.reflect.Method.invoke(Native Method) E/AndroidRuntime( 7834): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) E/AndroidRuntime( 7834): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1047)

`class AudioPlayerTask extends BackgroundAudioTask {

final _mediaLibrary = MediaLibrary(); AudioPlayer _player = new AudioPlayer(); AudioProcessingState _skipState; StreamSubscription _eventSubscription;

List get queue => _mediaLibrary.items; int get index => _player.currentIndex; MediaItem mediaItem;

@override Future onStart(Map<String, dynamic> params) async { // We configure the audio session for speech since we're playing a podcast. // You can also put this in your app's initialisation if your app doesn't // switch between two types of audio as this example does. // queue.clear(); // queue.add(mediaItems);

final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration.speech());

// Broadcast media item changes.
// _player.currentIndexStream.listen((index) {
//    if (index != null) AudioServiceBackground.setMediaItem(queue[index]);
// });

// Propagate all events from the audio player to AudioService clients.
_eventSubscription = _player.playbackEventStream.listen((event) {
  _broadcastState();
});

// Special processing for state transitions.
_player.processingStateStream.listen((state) {
  switch (state) {
    case ProcessingState.completed:
    // In this example, the service stops when reaching the end.
      onStop();
      break;
    case ProcessingState.ready:
    // If we just came from skipping between tracks, clear the skip
    // state now that we're ready to play.
      _skipState = null;
      break;
    default:
      break;
  }
});

final songIndex = _player.playbackEvent.currentIndex;
_player.durationStream.listen((duration) {
  print("duration :: " +  duration.toString());
  final modifiedMediaItem = mediaItem.copyWith(duration: duration);
  AudioServiceBackground.setMediaItem(modifiedMediaItem);
});

}

@override Future onSkipToQueueItem(String mediaId) async { // Then default implementations of onSkipToNext and onSkipToPrevious will // delegate to this method. final newIndex = queue.indexWhere((item) => item.id == mediaId); if (newIndex == -1) return; // During a skip, the player may enter the buffering state. We could just // propagate that state directly to AudioService clients but AudioService // has some more specific states we could use for skipping to next and // previous. This variable holds the preferred state to send instead of // buffering during a skip, and it is cleared as soon as the player exits // buffering (see the listener in onStart). _skipState = newIndex > index ? AudioProcessingState.skippingToNext : AudioProcessingState.skippingToPrevious; // This jumps to the beginning of the queue item at newIndex. _player.seek(Duration.zero, index: newIndex); // Demonstrate custom events. AudioServiceBackground.sendCustomEvent('skip to $newIndex'); }

@override Future onPlay() => _player.play();

@override Future onPause() => _player.pause();

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

@override Future onFastForward() => _seekRelative(fastForwardInterval);

@override Future onRewind() => _seekRelative(-rewindInterval);

@override Future onSeekForward(bool begin) async => _seekContinuously(begin, 1);

@override Future onSeekBackward(bool begin) async => _seekContinuously(begin, -1);

@override Future onStop() async { await _player.dispose(); _eventSubscription.cancel(); // It is important to wait for this state to be broadcast before we shut // down the task. If we don't, the background task will be destroyed before // the message gets sent to the UI. await _broadcastState(); // Shut down this task await super.onStop(); }

/// Jumps away from the current position by [offset]. Future _seekRelative(Duration offset) async { var newPosition = _player.position + offset; // Make sure we don't jump out of bounds. if (newPosition < Duration.zero) newPosition = Duration.zero; if (newPosition > mediaItem.duration) newPosition = mediaItem.duration; // Perform the jump via a seek. await _player.seek(newPosition); }

/// Begins or stops a continuous seek in [direction]. After it begins it will /// continue seeking forward or backward by 10 seconds within the audio, at /// intervals of 1 second in app time. void _seekContinuously(bool begin, int direction) { if (begin) { // _seeker = Seeker(_player, Duration(seconds: 10 * direction), // Duration(seconds: 1), mediaItem) // ..start(); } }

/// Broadcasts the current state to all clients. Future _broadcastState() async { await AudioServiceBackground.setState( controls: [ MediaControl.skipToPrevious, if (_player.playing) MediaControl.pause else MediaControl.play, MediaControl.stop, MediaControl.skipToNext, ], systemActions: [ MediaAction.seekTo, MediaAction.seekForward, MediaAction.seekBackward, ], androidCompactActions: [0, 1, 3], processingState: _getProcessingState(), playing: _player.playing, position: _player.position, bufferedPosition: _player.bufferedPosition, speed: _player.speed, ); }

/// Maps just_audio's processing state into into audio_service's playing /// state. If we are in the middle of a skip, we use [_skipState] instead. AudioProcessingState _getProcessingState() { if (_skipState != null) return _skipState; switch (_player.processingState) { case ProcessingState.idle: return AudioProcessingState.stopped; case ProcessingState.loading: return AudioProcessingState.connecting; case ProcessingState.buffering: return AudioProcessingState.buffering; case ProcessingState.ready: return AudioProcessingState.ready; case ProcessingState.completed: return AudioProcessingState.completed; default: throw Exception("Invalid state: ${_player.processingState}"); } }

Future onUpdateMediaItem(MediaItem mediaItemUpdate) async { AudioServiceBackground.setMediaItem(mediaItemUpdate); print("queue_item" + mediaItemUpdate.id); mediaItem = mediaItemUpdate; await _player.setAudioSource(AudioSource.uri(Uri.parse(mediaItemUpdate.id))); } }

class MediaLibrary { String audioUrl;

final _items = [ ];

List get items => _items; set setMediaItem (MediaItem mediaItem) => items.add(mediaItem); }

class MediaState { final MediaItem mediaItem; final Duration position;

MediaState(this.mediaItem, this.position); }`

github-actions[bot] commented 3 years ago

This issue was automatically closed because it did not follow the issue template.

muhammadanas759 commented 3 years ago

same error coming with me

ryanheise commented 3 years ago

@muhammadanas759 please open a bug report if you believe it to be a bug.

vipCodeError commented 3 years ago

@muhammadanas759 this error mostly comes at internal confliction with other library dependency you have added on pubspec. only way to know make another project add your audio service code then all previous project dependency to pubspec. then try removing one by one after running audio service if error happen.

sallypeters commented 3 years ago

@vipCodeError Yes - The issue potentially is with flutter_sound_lite. I have raised an issue here : https://github.com/Canardoux/flutter_sound/issues/780#issue-1026182568

I'm not sure why just including this plugin and not even using any of its classes/methods would force audio_service to crash.

Flutter_Sound_Lite has its own ability to control playback from the front screen with ShadeUI but this hasnt been enabled.

Using tau_sound: ^9.0.0-alpha-2 with API 24 resolves the build warning of :

WARNING: [Processor] Library '/.gradle/caches/modules-2/files-2.1/com.github.canardoux/flutter_sound_core/8.4.1/df1da2efb4085098ab4cd8d45dc76626f0f6da3a/flutter_sound_core-8.4.1.aar' contains references to both AndroidX and old support library. This seems like the library is partially migrated. Jetifier will try to rewrite the library anyway.
 Example of androidX reference: 'androidx/arch/core/util/Function'
 Example of support library reference: 'android/support/v4/media/session/MediaSessionCompat$Callback'

But still causes issue with audio_service or maybe audio_service is doing something that interferes with other background service plugins.

ryanheise commented 3 years ago

flutter_sound_lite declares its own service in its manifest and so it is the very type of plugin that the audio_service README says cannot be used with audio_service. And since you cannot have two audio services in your app, you need to choose which plugin you want to use for that responsibility, it just won't work to have both.

I know that this isn't helped by the fact that flutter_sound_lite gives you the whole kitchen sink, while you just want to use, say, the recorder, so you cannot avoid also getting the things you don't want such as the service. This would be easier if the recorder were split into its own single responsibility plugin, but then you must also contend with the tricky license situation. (L)GPL is incompatible with the Apple and Google app stores. The MPL distribution is better, although at the same time I would want to do my due diligence to ensure that all of the open source contributors have agreed to the new license (IANAL, but I bring this up because changing the license of an open source project does require the consent of all contributors, not just the original author. Otherwise you'd have those parts of the plugin still licensed under (L)GPL and you'll have that issue of struggling to legally distribute your app on the Apple and Google platforms. As an example, people can't use ffmpeg in their mobile apps despite it having an awesome range of features. People have requested ffmpeg to change its license from (L)GPL but the answer was that this is practically impossible because it was written by hundreds of contributors and good luck tracking them all down and convincing them all that you have a better idea for a license.)

If you don't care about abiding by licenses (not advised!), then a technical solution for you at least is to fork flutter_sound_lite and remove the conflicting parts. Otherwise, there are other audio recorder plugins out there and if they don't do what you want, you could submit bug reports and feature requests to help make those projects better.

sallypeters commented 3 years ago

flutter_sound_lite declares its own service in its manifest and so it is the very type of plugin that the audio_service README says cannot be used with audio_service. And since you cannot have two audio services in your app, you need to choose which plugin you want to use for that responsibility, it just won't work to have both.

@ryanheise Thanks - I overlooked this and explains the issue we are all seeing.

if you don't care about abiding by licenses (not advised!), then a technical solution for you at least is to fork flutter_sound_lite and remove the conflicting parts. Otherwise, there are other audio recorder plugins out there and if they don't do what you want, you could submit bug reports and feature requests to help make those projects better.

There are a few (but old) recorder plugins that so far provide basic recording functionality although current issues with these is size of output file and recording quality. FSL 18 months ago was similar (Large recording file and poor quality) but has since improved. I did write a Xamarin plugin 5 years back that did exactly what I wanted so might have to revisit this code and just create a basic recording plugin for my own needs.

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs, or use StackOverflow if you need help with audio_service.