Flutter- How to know AudioService is stopped? - flutter

I am working with audio_service flutter package. I want to pop a player page if Audio Service stops. How to get the Audio Service stopped event? I didn't find any events to check if service is stopped

(Answer update: Since v0.18, the service is effectively always running while the app is running, so there is no longer a need to check. The following answer is for v0.17 and earlier.)
AudioService.running will emit true when the service is running and false when it is not.
To listen to when it changes from true to false, you could try this:
// Cast runningStream from dynamic to the correct type.
final runningStream =
AudioService.runningStream as ValueStream<bool>;
// Listen to stream pairwise and observe when it becomes false
runningStream.pairwise().listen((pair) {
final wasRunning = pair.first;
final isRunning = pair.last;
if (wasRunning && !isRunning) {
// take action
}
});
If you instead want to listen to the stopped playback state, you need to ensure that your background audio task actually emits that state change in onStop:
#override
Future<void> onStop() async {
await _player.dispose();
// the "await" is important
await AudioServiceBackground.setState(
processingState: AudioProcessingState.stopped);
// Shut down this task
await super.onStop();
}
This way, you can listen for this state in the UI:
AudioService.playbackStateStream.listen((state) {
if (state.processingState == AudioProcessingState.stopped)) {
// take action
}
});

Related

Cancel execution of method after it starts in Flutter

Consider this method:
Future<void> methodWithAlotOfSteps()async{
callMethodA();
await callMethodB();
...
return ...;
}
which makes some computation. Suppose I want the user to be able to stop this process at any point in time (when he taps cancel button for example).
How can I stop the execution of the above method no matter what line in the method the "program counter" has reached when the user presses cancel.
I am looking for something like methodWithAlotOfSteps.cancel();.
I tried using CancelableCompleter, but even though the Future is cancelled and onCancel method of the completer is called, but the function continues execution.
I know I can set a boolean flag and check it after each "step" ("line", "call to a method"),such as :
Future<void> methodWithAlotOfSteps()async{
if(!completer.isCancelled)
callMethodA();
if(!completer.isCancelled)
await callMethodB();
...
return ...;
}
but is there a better way of doing this?
As #Jamesdlin suggested, the apparent way is to check the cancelable completer's state after each async gap:
class MyService{
CancelableCompleter completer = CancelableCompleter();
Future<void> doSomething() async {
doSomeSyncWork(); // Sync work
doAnotherSyncWork(); // Sync work
await doSomeAsyncWork(); // Async work, this will return control to event loop and make it possible to, for example, press a button to cancel the future.
// here we know we have lost control for a while, so we must check if we have been cancelled
if (completer.isCompleted) {
return;
}
doSomeMoreSyncWork();
await doSomeMoreAsyncWork();
// here we know we have lost control for a while, so we must check if we have been cancelled
if (completer.isCompleted) {
return;
}
...
completer.complete();
}
}

Flutter stopwatchtimer doesn't respond to changing time

I use this package https://pub.dev/packages/stop_watch_timer in my app to keep track of the music that is playing. However if I want to change the song by changing the time on the stopwatch it says that I have to reset the timer first which I have already done. If I press the button for the second time it works. This is the code:
final StopWatchTimer _stopWatchTimer = StopWatchTimer(
mode: StopWatchMode.countUp,
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
);
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
_stopWatchTimer.setPresetSecondTime(new_time); // this is where I set new time
}
I don't know how to get around this. I have already created an issue on the creators GitHub but no response. So there's somebody who can help me here
As you mentioned in the github issue, it looks like the root cause of your issue is that the reset action takes place asynchronously, and so hasn't gone through yet by the time you try to set the time.
One way to get around this is to define your own async function which resets the stopwatch, then waits for the action to complete before returning:
Future<void> _resetTimer() {
final completer = Completer<void>();
// Create a listener that will trigger the completer when
// it detects a reset event.
void listener(StopWatchExecute event) {
if (event == StopWatchExecute.reset) {
completer.complete();
}
}
// Add the listener to the timer's execution stream, saving
// the sub for cancellation
final sub = _stopWatchTimer.execute.listen(listener);
// Send the 'reset' action
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
// Cancel the sub after the future is fulfilled.
return completer.future.whenComplete(sub.cancel);
}
Usage:
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_resetTimer().then(() {
_stopWatchTimer.setPresetSecondTime(new_time);
});
}
Or (with async/await):
void change_timer_value(int song_index) async {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
await _resetTimer();
_stopWatchTimer.setPresetSecondTime(new_time);
}

Listening to Song End in just_audio

I wanna ask is there any stream to listen when the song get ended to do some stuff at that point in just_audio flutter package or is there any method to do that ?
Looking at the documentation, AudioPlayer instances have a field called playerState or playerStateStream (If you want to listen for events). playerStateStream can be listened to using the stream which has a field called processingState. This field contains all the information you require (Here is the list). If the processing state is completed, then the player has finished playing.
Example:
_player.playerStateStream.listen((playerState) {
if (playerState.processingState == ProcessingState.completed) {
// Some Stuff
}
});
To enhance the Afridi's answer: one can subscribe to another stream, playbackEventStream, to get updates for individual audio sources in a playlist. This is an example how to update the song name:
player.playbackEventStream.listen((e) => updateCurrentSong());
...
void updateCurrentSong() {
setState(() {
final int index = player.currentIndex ?? 0;
currentSong = "${player.audioSource?.sequence[index].tag}";
});
}

Flutter Background Processes Using Android Alarm Manager and Isolates

I'm trying to get a timer (down to the hundredths of seconds) to work in Flutter even when the app is closed. I initially tried to use isolates as I thought they would work yet after testing with a Pixel 4 running Android 11 I found that it was still not firing correctly when the app was closed. After some googleing I came across Android Alarm Manager and I have everything set up again yet it doesn't appear that the periodic function is firing correctly.
Heres the BLoC map for triggering the counter:
Stream<TimerState> _mapTimerStartedToState(TimerStarted start) async* {
AndroidAlarmManager.initialize();
port.listen((_) async => await _incrementCounter());
startCounter();
print(_counter);
yield TimerRunInProgress(start.duration);
}
Here's the startCounter() function:
void startCounter() async {
prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey(countKey)) {
await prefs.setInt(countKey, 0);
}
IsolateNameServer.registerPortWithName(
port.sendPort,
isolateName,
);
await AndroidAlarmManager.periodic(
Duration(milliseconds: 100),
// Ensure we have a unique alarm ID.
Random().nextInt(pow(2, 31)),
callback,
exact: true,
wakeup: true,
);
}
And then here's my callback:
static Future<void> callback() async {
print('Alarm fired!');
// Get the previous cached count and increment it.
final prefs = await
SharedPreferences.getInstance();
int currentCount = prefs.getInt(countKey);
await prefs.setInt(countKey, currentCount + 1);
// This will be null if we're running in the background.
print(currentCount);
uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
uiSendPort?.send(null);
}
Am I on the right path here? Can AndroidAlarmManager do what I'm trying to do? I'm not exactly sure why the isolate approach didn't work on its own either, the only explanation I got was that I needed to use AndroidAlarmManager. Now, the events aren't firing at the 100 ms rate as I told them to and are instead firing 1 to several minutes apart.
Android restricts the frequencies for alarms. You cannot schedule alarms as frequently as 100 milliseconds with AlarmManager.
Please refer the note in red background on : https://developer.android.com/reference/android/app/AlarmManager
Note: Beginning with API 19 (Build.VERSION_CODES.KITKAT) alarm
delivery is inexact: the OS will shift alarms in order to minimize
wakeups and battery use. There are new APIs to support applications
which need strict delivery guarantees; see setWindow(int, long, long,
android.app.PendingIntent) and setExact(int, long,
android.app.PendingIntent). Applications whose targetSdkVersion is
earlier than API 19 will continue to see the previous behavior in
which all alarms are delivered exactly when requested.

Flutter: How to check if app is running on background from firebase messaging plugins onBackgroundMessage

I'm using firebase_messaging plugin to register a callback handler with onBackgroundMessage for my data-only payload of firebase messaging.
If the app is in foreground or in background, the normal way of operation is using sockets to get the data from network and show notification from the app.
But when the app is in killed state, I would like to show the notification by fetching the data from network.
But these operations conflicts when the app is in background as onBackgroundMessage is getting called in background also.
If I'm not wrong, the handler is running on a separate isolate and it has no access to the main contents.
So how can I differentiate the killed and background state of the app from this isolated function?
You can use IsolateNameServer to register a ReceiverPort from the foreground when it is running and remove it when the foreground is not running. Then on the background isolate check if it exists and if so redirect the FCM message through the port to the foreground for handling on foreground.
Something along the lines of this:
const FOREGROUND_ISOLATE_PORT_NAME = 'foreground_port';
class NotificationManager {
ReceivePort? _foregroundReceivePort;
StreamSubscription<RemoteMessage>? _fcmMessageSubscription;
init() async {
FirebaseMessaging.onBackgroundMessage(_fcmMessageHandlerBackground);
_fcmMessageSubscription = FirebaseMessaging.onMessage.listen(_fcmMessageHandlerForeground);
_foregroundReceivePort = ReceivePort();
IsolateNameServer.registerPortWithName(
_foregroundReceivePort!.sendPort,
FOREGROUND_ISOLATE_PORT_NAME,
);
_foregroundReceivePort!.listen((message) {
if (message is RemoteMessage) {
log('got fcm message for handling in foreground');
_fcmMessageHandlerForeground(message);
}
});
}
shutdown() async {
_fcmMessageSubscription?.cancel();
IsolateNameServer.removePortNameMapping(FOREGROUND_ISOLATE_PORT_NAME);
_foregroundReceivePort!.close();
_foregroundReceivePort = null;
}
}
With these two top level functions:
Future<void> _fcmMessageHandlerForeground(RemoteMessage message) async {
// ... handle message in foreground ...
}
Future<void> _fcmMessageHandlerBackground(RemoteMessage message) async {
final foreground = IsolateNameServer.lookupPortByName(FOREGROUND_ISOLATE_PORT_NAME);
if (foreground != null) {
log("redirecting FCM message to foreground");
foreground.send(message);
} else {
// ... handle message in background ...
}
}