Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running code while streaming in background #419

Closed
helloagain-dev opened this issue Jan 30, 2019 · 17 comments
Closed

Running code while streaming in background #419

helloagain-dev opened this issue Jan 30, 2019 · 17 comments

Comments

@helloagain-dev
Copy link

I want to stream an icecast radio stream which works fine.
However, I also want to update the title/artist and cover regularly.
I can fetch this data from a REST API.

How can I run code that fetches this data every 10 seconds (or so) and updates the Player regularly?
Can I put a long-running task that does the fetch into TrackPlayer.registerPlaybackService?

@curiousdustin
Copy link
Contributor

Maybe not exactly what you want, but I'm using this to periodically check the state and progress of the player.

https://github.com/ocetnik/react-native-background-timer

@helloagain-dev
Copy link
Author

That seems to be exactly what I want.
I can start this when I the user hits play and it will continue running every few seconds as long as the stream is active? In the callback I can access TrackPlayer?
Do you have some sample code where you run this together with react-native-track-player?
All the other solutions I found, only run once every 15 minutes or so and are used to do background downloads.

@curiousdustin
Copy link
Contributor

NOTE: On android if you try to access the player after it has been destroyed, this causes some errors. I haven't worked out all the kinks on my implementation yet, but in general this has been working for me so far.

async function setupPlayer() {
  await TrackPlayer.setupPlayer();

  let playerOptions = { ...options, ...trackPlayerOptions };
  await TrackPlayer.updateOptions(playerOptions);

  startMonitoring();
}

const MONITOR_INTERVAL = 5000;

function startMonitoring() {
  BackgroundTimer.runBackgroundTimer(() => {
    monitorProgress();
  }, MONITOR_INTERVAL);
}

async function monitorProgress() {
  try {
    let playerState = await TrackPlayer.getState();
... do stuff ...
  } catch (error) {
    console.log(error);
  }
}

@curiousdustin
Copy link
Contributor

I am still struggling with this actually. If the app is backgrounded for a bit. Android decides to kill the player because it is idle. At this point any code that attempts to run TrackPlayer methods will cause the app to crash with the following error. This includes the setupPlayer() method.

    Process: fm.pinna.app, PID: 19846
    java.lang.IllegalStateException: Not allowed to start service Intent { cmp=fm.pinna.app/com.guichaguri.trackplayer.service.MusicService }: app is in background uid UidRecord{80589ef u0a341 SVC  bg:+1m0s18ms idle procs:1 seq(0,0,0)}
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1506)
        at android.app.ContextImpl.startService(ContextImpl.java:1462)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)
        at com.guichaguri.trackplayer.module.MusicModule.waitForConnection(MusicModule.java:100)
        at com.guichaguri.trackplayer.module.MusicModule.setupPlayer(MusicModule.java:150)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
        at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:160)
        at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29)
        at android.os.Looper.loop(Looper.java:164)
        at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:192)
        at java.lang.Thread.run(Thread.java:764)

@curiousdustin
Copy link
Contributor

I should clarify, this is if the player has been setup, but not played anything yet, then the app is backgrounded.

@curiousdustin
Copy link
Contributor

I will try getting around this by only running my background code while the player is in its playing state.

@Guichaguri
Copy link
Collaborator

Yes, the playback service will run as long as the player runs, it's the perfect section to add player-related code that should also run in background. I'm pretty sure react-native-background-timer will create more overhead in this case.


@curiousdustin the issue happens because the app is trying to start a regular service while it's in background. Since Android 8, the app needs to start a foreground service instead of starting a regular one and then foregrounding it.

The problem is that a foreground service requires a notification to be up, and the service only shows a notification after it starts playing.

I can think about two fixes:

  • Reject any type of call when the player is not in foreground
  • Start the foreground service as Android expect, but then it's your job to add a track and start playing in less than a second.

The first workaround seems like the proper behavior, as the app should never play anything without an user action in the first place.
The latter one sounds like a racing condition, which is really unstable and will probably end up in spaghetti code.

What do you think?

@curiousdustin
Copy link
Contributor

In my case, I determined that I only need to run my background timer while the player is in the playing state. So I start and stop that timer/interval as needed. For now this seems to have avoided all the crashing cases I was running into.

However, now you made me curious.

When you say the "playback service" is the perfect place to add more code, are you saying there is a way to put more functionality into the background task by adding something to the code below?

Is there a way I could create a timer as part of this?

TrackPlayer.registerPlaybackService(() =>
    require('./track-player/TrackPlayerPlaybackService.js')
  );

Right now my TrackPlayerPlaybackService.js file just contains a bunch of calls to TrackPlayer.addEventListener() for all the events. Could I somehow start and stop a timer in there as well? How do you tell when the service is destroyed?

@Guichaguri
Copy link
Collaborator

You're right, you can run any type of code there. One interesting use-case is for recording analytics data.

There is no event for destroying the service as of yet. Although in you can achieve your goal by registering an interval in playback-track-changed and then unregistering it in playback-queue-ended.

let interval = null;

TrackPlayer.addEventListener('playback-track-changed', () => {
    if (interval == null) interval = setInterval(...);
});

TrackPlayer.addEventListener('playback-queue-ended', () => {
    if (interval != null) {
        clearInterval(interval);
        interval = null;
    }
});

I haven't tested something like this, but it should work.

@Guichaguri
Copy link
Collaborator

React Native shares the same Javascript context for both the service and the app itself. When the app is closed and the service is destroyed, it should also destroy the context, stopping the timer.

@curiousdustin
Copy link
Contributor

Perfect, I will give this a shot and see if I can do it this way instead of using a separate library.

@helloagain-dev
Copy link
Author

I did try @Guichaguri's proposal, but the setInterval function is not called anymore as soon as I put the App to the background (via Home button).

@helloagain-dev
Copy link
Author

react-native-background-timer seems to work great.
But I now struggle with updating the Metadata of the notification without stopping the player and queuing the same stream again which would lead to a small pause. Is there any way with the current API to update the artist, title and artwork while the song is playing?

@moonstruck
Copy link

@helloagain-dev Possibly updating metadata on the fly is not available yet. There's two open pull request in this regard. #384 and #384

@ishigamii
Copy link
Contributor

ishigamii commented Mar 5, 2019

Hi, I am having the same crash on Android very occasionnaly using the version 1.1.2 :

Fatal Exception: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.xxx/com.guichaguri.trackplayer.service.MusicService }: app is in background uid UidRecord{39a05c9 u0a454 CEM  bg:+1m5s685ms idle procs:1 seq(0,0,0)}
       at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1538)
       at android.app.ContextImpl.startService(ContextImpl.java:1484)
       at android.content.ContextWrapper.startService(ContextWrapper.java:663)
       at android.content.ContextWrapper.startService(ContextWrapper.java:663)
       at com.guichaguri.trackplayer.module.MusicModule.waitForConnection(MusicModule.java:100)
       at com.guichaguri.trackplayer.module.MusicModule.getPosition(MusicModule.java:405)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
       at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:160)
       at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)
       at android.os.Handler.handleCallback(Handler.java:789)
       at android.os.Handler.dispatchMessage(Handler.java:98)
       at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29)
       at android.os.Looper.loop(Looper.java:164)
       at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:192)
       at java.lang.Thread.run(Thread.java:764)

@xgustavoh
Copy link

@helloagain-dev, Look #552

@curiousdustin
Copy link
Contributor

Closing this as the original question has been answered.

Also metadata update is part of v2.
See #634

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants