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

Fix #1377: app process not stopping #1447

Merged
merged 28 commits into from
Aug 2, 2022

Conversation

mpivchev
Copy link
Contributor

@mpivchev mpivchev commented Mar 29, 2022

This PR is aimed to fix the bug where the app process would not die even when the app + service were killed.
The cause of this bug is not concrete, and a lot of the issues here are very strange, but I do have some theories:

  • We are creating and controlling an Android service in a RN module. This is outside of its intended container - an Android Activity. Activities have a special lifecycle tied to the Android OS, and a botched lifecycle might be one of the reasons why the service and process are misbehaving.
  • The main component of this fix was to introduce the Activity lifecycles to our RN module, and using them to control our service.

The good news is it did cause the process to die properly, but it introduced other bugs related to notifications (services are heavily tied to Android notifications). More info in this separate PR.


Here is how the current implementation of this PR:

  1. There is an Android interface that you can bind to an activity that can send lifecycle events:
interface ActivityLifecycleCallbacksAdapter: Application.ActivityLifecycleCallbacks {
    override fun onActivityCreated(activity: Activity, bundle: Bundle?) = Unit

    override fun onActivityStarted(activity: Activity) = Unit

    override fun onActivityResumed(activity: Activity) = Unit

    override fun onActivityPaused(activity: Activity) = Unit

    override fun onActivityStopped(activity: Activity) = Unit

    override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) = Unit

    override fun onActivityDestroyed(activity: Activity) = Unit
}
  1. I bind it through our module by getting the currentActivity. The currentActivity will always be a ReactActivity, and this way we don’t need to require people to add code to said activity, since we just grab it internally in our module :
 override fun initialize() {
        currentActivity?.application?.registerActivityLifecycleCallbacks(this)
    }
  1. The first service bind happens during setupPlayer of the module, since we can’t listen to the first activity onStart (it happens before the RN module is initialized)
Intent(context, MusicService::class.java).also { intent ->
    context.startService(intent)
    // This bind only happens the first time. Subsequent binds happen in onActivityStarted
    context.bindService(intent, this, Context.BIND_AUTO_CREATE)
}
  1. We use the listener events to control the service.
  • As mentioned, we bind for the first time in step 3. UI starts to update.
  • When app goes to recents, we unbind in onActivityStopped . UI stops updating but the service keeps running and waits for a rebind.
  • Subsequent rebinds happen in onActivityStarted when the app UI is recovered from recent apps. UI starts to update again thanks to the rebind.
  • When the app is removed from recents, the app has already been unbound in onActivityStopped. We call onActivityDestroyed and the service will be destroyed if stopWithApp is true. Otherwise it stays running and clicking on the notification will bring back the app and call onActivityStarted . UI gets updated from the running service bind as if it was never killed.
 override fun onActivityStarted(activity: Activity) {
        // Service MUST be rebound during activity onStart, if not already bound
        if (isServiceBound) return

        Intent(context, MusicService::class.java).also { intent ->
            context.bindService(intent, this, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onActivityStopped(activity: Activity) {
        // Service MUST be unbound during activity onStop
        unbindService()
    }

    override fun onActivityDestroyed(activity: Activity) {
        // Service MUST be destroyed during activity onDestroy
        destroyServiceIfAllowed()
    }

@jenshandersson
Copy link
Contributor

@mpivchev is this PR ready to be tested? Not sure why you added HockeyApp code (RNHockeyAppUsageTracker) to the lib?

@mpivchev
Copy link
Contributor Author

@mpivchev is this PR ready to be tested? Not sure why you added HockeyApp code (RNHockeyAppUsageTracker) to the lib?

Hi, it's not fully ready, hence why it's still a draft. Use at your own risk. That file is used for experimenting and will be removed in final release.

…-stopping

# Conflicts:
#	android/build.gradle
#	android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt
#	example/android/build.gradle
@jenshandersson
Copy link
Contributor

I know it's still WIP, but I'm getting the below crash:
It happens quite instantly when putting app into bg, I assume we have 2 players active simultaneously(?)
I also don't get the locked screen controls

04-01 08:58:12.301 12544 12544 E AndroidRuntime: java.lang.IllegalStateException: Another SimpleCache instance uses the folder: /data/user/0/com.x.beta.debug/cache/TrackPlayer
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.google.android.exoplayer2.upstream.cache.SimpleCache.<init>(SimpleCache.java:249)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.google.android.exoplayer2.upstream.cache.SimpleCache.<init>(SimpleCache.java:229)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.google.android.exoplayer2.upstream.cache.SimpleCache.<init>(SimpleCache.java:194)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.doublesymmetry.kotlinaudio.players.BaseAudioPlayer.<init>(BaseAudioPlayer.kt:111)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.doublesymmetry.kotlinaudio.players.QueuedAudioPlayer.<init>(QueuedAudioPlayer.kt:10)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.doublesymmetry.trackplayer.service.MusicService$setupPlayer$1.invokeSuspend(MusicService.kt:77)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:938)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:99)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at android.os.Looper.loopOnce(Looper.java:201)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:288)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:7839)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
04-01 08:58:12.301 12544 12544 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

Base automatically changed from feature/kotlinaudio to main April 25, 2022 07:13
@@ -49,14 +49,15 @@ repositories {
}

dependencies {
implementation 'com.github.DoubleSymmetry:KotlinAudio:v0.1.31'
implementation "com.github.doublesymmetry:kotlin-audio:0.1.35"
// implementation "com.github.DoubleSymmetry:KotlinAudio:v0.1.33"
Copy link
Contributor

@dcvz dcvz Apr 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: We can delete this right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be reverted as this is pointing the RNTP dependency at the locally built KotlinAudio project.

Copy link
Contributor

@dcvz dcvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments/questions

@@ -1,9 +1,10 @@
package com.example;

import android.os.Bundle;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: Leftover import?

)
is NotificationState.CANCELLED -> stopForeground(true)
is NotificationState.POSTED -> {
if (!isForegroundService) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: What does this isForegroundService flag do? Makes sure we only start once? Then we should use a different name like hasStartedForegroundService or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both names work, since only when we call startForeground, does the service become foreground.

}

override fun onHeadlessJsTaskFinish(taskId: Int) {
// Overridden to prevent the service from being terminated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so we don't use this anymore to prevent the service from terminating? 🌟

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use START_STICKY to prevent the service from terminating instead of this. The former is the proper way of keeping a service alive in Android.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this works with JS?

callback.resolve(null)
}

@ReactMethod
fun play(callback: Promise) {
if (verifyServiceBoundOrReject(callback)) return
// if (verifyServiceBoundOrReject(callback)) return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: why did we remove all of these checks? users might still try to perform an action before they call setupPlayer and it should throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explained above.

Comment on lines 371 to 378
if (verifyServiceBoundOrReject(callback)) return

musicService.removeUpcomingTracks()
callback.resolve(null)
}

@ReactMethod
fun skip(index: Int, callback: Promise) {
if (verifyServiceBoundOrReject(callback)) return

musicService.skip(index)
callback.resolve(null)
}

@ReactMethod
fun skipToNext(callback: Promise) {
if (verifyServiceBoundOrReject(callback)) return
musicService.skipToNext()
callback.resolve(null)
}

@ReactMethod
fun skipToPrevious(callback: Promise) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of these functions are called from the notification buttons, which happens when the service is unbound. We should not have these checks here, or the notification buttons would not work if the app is in recents (and unbound).

Comment on lines +60 to +64
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startTask(getTaskConfig(intent))
return START_STICKY
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We overwrite the JSTaskService here, and returning START_STICKY, which causes the service to try and recover itself if it dies for any reason.

destroyIfAllowed()
override fun onActivityStopped(activity: Activity) {
// Service MUST be unbound during activity onStop
unbindService()
Copy link
Contributor Author

@mpivchev mpivchev Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unbinding seems to kill the service automatically. Usually this is prevented by calling ContextCompat.startForegroundService to manually start the service (which we already do below), but it seems like it still doesn't work.

@jenshandersson
Copy link
Contributor

@mpivchev Do you think this PR solves all crashes we're seeing on Samsung devices?
Would love some help troubleshooting this as we're seeing so many crashes and ANR related to audio player. Thanks!

image

# Conflicts:
#	android/build.gradle
#	android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt
#	android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt
#	example/App.tsx
#	example/android/app/src/main/java/com/example/MainActivity.java
@mpivchev
Copy link
Contributor Author

mpivchev commented Jul 26, 2022

We are changing how the audio service handles its lifecycle. Previously we had the stopWithApp bool that would try and stop the service when you remove the app from recents, but due to how Android handles foreground services the OS never stops the app process, as it's waiting for the foreground service to come back. We are embracing this and basically copying what other audio apps (Spotify, Soundcloud, Google Podcast etc.) are doing:

  1. The service would never try to stop itself and would continue to exist even if the app is stopped.
  2. Our media player notification would always stay present, and the OS + phone manufacturers would choose how to show it and when/how to hide it. This is out of our control.
  3. Android also controls when it would stop the app process. This is, once again, out of our control. This was the main issue this PR was trying to fix, but after much research it turns out we can't really force Android to destroy it manually.
  4. stopWithApp turns into stoppingAppPausesPlayback where we can choose whether to pause the audio when the app stops or continue playing it. This would have no effect of the life of the service anymore, and would simply pause or resume the audio. We observed that podcast apps continue playing even if u stop the app, while audio apps pause the music when you stop them.

@afkcodes cc

@mpivchev mpivchev marked this pull request as ready for review July 26, 2022 09:34
@mpivchev mpivchev requested review from jspizziri and dcvz July 26, 2022 09:34
@afkcodes
Copy link

afkcodes commented Jul 26, 2022

This is cool @mpivchev no words for the effort you have put in and the amount of research, just a few question & observation that i have.

  1. i have phone with Android 12 MIUI the moment i close the music app (Apple Music) the notification dissapears is this something to do with android or the OS. Same case with Spotify as well.
  2. Now that we are changing how the service would behave will it be possible to swipe and remove the notification ?

@nicomontanari
Copy link
Contributor

nicomontanari commented Jul 26, 2022

Hi @mpivchev, we tried your changes because we also have the same problem as you. We encountered a problem when trying to play audio and the app crashes with the below error.

W/MessageQueue: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread java.lang.IllegalStateException: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread at android.os.MessageQueue.enqueueMessage(MessageQueue.java:561) at android.os.Handler.enqueueMessage(Handler.java:754) at android.os.Handler.sendMessageAtTime(Handler.java:703) at android.os.Handler.sendMessageDelayed(Handler.java:673) at android.os.Handler.sendMessage(Handler.java:611) at android.os.Message.sendToTarget(Message.java:474) at com.google.android.exoplayer2.util.SystemHandlerWrapper$SystemMessage.sendToTarget(SystemHandlerWrapper.java:153) at com.google.android.exoplayer2.ExoPlayerImplInternal.setPlayWhenReady(ExoPlayerImplInternal.java:311) at com.google.android.exoplayer2.ExoPlayerImpl.updatePlayWhenReady(ExoPlayerImpl.java:2550) at com.google.android.exoplayer2.ExoPlayerImpl.setPlayWhenReady(ExoPlayerImpl.java:733) at com.google.android.exoplayer2.BasePlayer.play(BasePlayer.java:104) at com.doublesymmetry.kotlinaudio.players.BaseAudioPlayer.play(BaseAudioPlayer.kt:159) at com.doublesymmetry.trackplayer.service.MusicService.play(MusicService.kt:222) at com.doublesymmetry.trackplayer.module.MusicModule$play$1.invokeSuspend(MusicModule.kt:387) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:224) at android.app.ActivityThread.main(ActivityThread.java:7562) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

@afkcodes
Copy link

@nicomontanari This PR is still a WIP so crashes are expected, @mpivchev is working hard to get this through, lets hope for the best.

@mpivchev
Copy link
Contributor Author

This is cool @mpivchev no words for the effort you have put in and the amount of research, just a few question & observation that i have.

  1. i have phone with Android 12 MIUI the moment i close the music app (Apple Music) the notification dissapears is this something to do with android or the OS. Same case with Spotify as well.
  2. Now that we are changing how the service would behave will it be possible to swipe and remove the notification ?
  1. It's the manufacturer. We use a special notification type for media, that all other audio related apps use too. The manufacturer decides how their Android variant would handle those notifications. I am testing on two devices and on a OnePlus GM1910 the notification disappears, while on a Nokia G20 it stays.
  2. No, since a foreground (persistent) audio service requires that a notification is always present. It notifies the user that there is something on the background on their phone that is always running.

@mpivchev
Copy link
Contributor Author

Hi @mpivchev, we tried your changes because we also have the same problem as you. We encountered a problem when trying to play audio and the app crashes with the below error.

W/MessageQueue: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread java.lang.IllegalStateException: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread at android.os.MessageQueue.enqueueMessage(MessageQueue.java:561) at android.os.Handler.enqueueMessage(Handler.java:754) at android.os.Handler.sendMessageAtTime(Handler.java:703) at android.os.Handler.sendMessageDelayed(Handler.java:673) at android.os.Handler.sendMessage(Handler.java:611) at android.os.Message.sendToTarget(Message.java:474) at com.google.android.exoplayer2.util.SystemHandlerWrapper$SystemMessage.sendToTarget(SystemHandlerWrapper.java:153) at com.google.android.exoplayer2.ExoPlayerImplInternal.setPlayWhenReady(ExoPlayerImplInternal.java:311) at com.google.android.exoplayer2.ExoPlayerImpl.updatePlayWhenReady(ExoPlayerImpl.java:2550) at com.google.android.exoplayer2.ExoPlayerImpl.setPlayWhenReady(ExoPlayerImpl.java:733) at com.google.android.exoplayer2.BasePlayer.play(BasePlayer.java:104) at com.doublesymmetry.kotlinaudio.players.BaseAudioPlayer.play(BaseAudioPlayer.kt:159) at com.doublesymmetry.trackplayer.service.MusicService.play(MusicService.kt:222) at com.doublesymmetry.trackplayer.module.MusicModule$play$1.invokeSuspend(MusicModule.kt:387) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:224) at android.app.ActivityThread.main(ActivityThread.java:7562) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

Tested on 2 devices and experienced no crashes. Does this crash also happen on other branches/commits?

@mpivchev
Copy link
Contributor Author

@nicomontanari This PR is still a WIP so crashes are expected, @mpivchev is working hard to get this through, lets hope for the best.

It's not a WIP anymore since it's ready for review :)

@afkcodes
Copy link

Awesome looking forward.

@filippobusi
Copy link

Hi @mpivchev, we tried your changes because we also have the same problem as you. We encountered a problem when trying to play audio and the app crashes with the below error.
W/MessageQueue: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread java.lang.IllegalStateException: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread at android.os.MessageQueue.enqueueMessage(MessageQueue.java:561) at android.os.Handler.enqueueMessage(Handler.java:754) at android.os.Handler.sendMessageAtTime(Handler.java:703) at android.os.Handler.sendMessageDelayed(Handler.java:673) at android.os.Handler.sendMessage(Handler.java:611) at android.os.Message.sendToTarget(Message.java:474) at com.google.android.exoplayer2.util.SystemHandlerWrapper$SystemMessage.sendToTarget(SystemHandlerWrapper.java:153) at com.google.android.exoplayer2.ExoPlayerImplInternal.setPlayWhenReady(ExoPlayerImplInternal.java:311) at com.google.android.exoplayer2.ExoPlayerImpl.updatePlayWhenReady(ExoPlayerImpl.java:2550) at com.google.android.exoplayer2.ExoPlayerImpl.setPlayWhenReady(ExoPlayerImpl.java:733) at com.google.android.exoplayer2.BasePlayer.play(BasePlayer.java:104) at com.doublesymmetry.kotlinaudio.players.BaseAudioPlayer.play(BaseAudioPlayer.kt:159) at com.doublesymmetry.trackplayer.service.MusicService.play(MusicService.kt:222) at com.doublesymmetry.trackplayer.module.MusicModule$play$1.invokeSuspend(MusicModule.kt:387) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:224) at android.app.ActivityThread.main(ActivityThread.java:7562) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

Tested on 2 devices and experienced no crashes. Does this crash also happen on other branches/commits?

Hi @mpivchev I have the same issue, I think it is a problem with Exoplayer, but with the stable version it never happens (v2.2.0-rc4)
The behavior is an ANR, not a crash. Now I'm investigating TrackPlayer.setupPlayer because if I start the app with this method the ANR warning appears after a few seconds without doing anything else, if I remove it the ANR doesn't appear but obviously Trackplayer doesn't work

@mpivchev
Copy link
Contributor Author

@filippobusi What android version and device did this happen on?

@filippobusi
Copy link

@filippobusi What android version and device did this happen on?

Android 10, Xiaomi Mi 8
Android 11, Xiaomi Mi A2 (rooted)

@mpivchev
Copy link
Contributor Author

@filippobusi can you try testing on a non-xaomi device and/or a simulator?

dcvz
dcvz previously approved these changes Jul 26, 2022
Copy link
Contributor

@dcvz dcvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 Let's wait for a bit more testing before merging this in

player.stop()
player.destroy()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we destroy the player and it gets recreated by setupplayer again?

Copy link
Contributor Author

@mpivchev mpivchev Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should only be called in onDestroy of the service, but I see we also call it in reset react method. Do we need reset at this point? It's a very ambiguous function too, since we don’t really know what exactly we want to reset, but we are calling random functions in both iOS and Android in it.

in case of Android, it will break the service since we are indeed destroying the player with no way of recreating it. It's supposed to never get destroyed until the service dies:

  @ReactMethod
    fun reset(callback: Promise) = scope.launch {
        if (verifyServiceBoundOrReject(callback)) return@launch

        musicService.stopPlayer()
        callback.resolve(null)
    }

}

override fun onHeadlessJsTaskFinish(taskId: Int) {
// Overridden to prevent the service from being terminated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this works with JS?

@@ -9,7 +9,7 @@ export const SetupService = async (): Promise<boolean> => {
} catch {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
stopWithApp: false,
stoppingAppPausesPlayback: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to update the typescript sources as well to remove the old key and add this one.

Copy link
Contributor

@dcvz dcvz Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like someone has done that here: #1625

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should provide some sort of alias that maps stopWithApp to stoppingAppPausesPlayback to prevent a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are introducing other breaking changes, like removing the destroy method. We originally agreed to not introduce breaking changes in 2.X, but in this case I think it's inevitable, unless we want to deprecate everything and just confuse people on why destroy is not doing anything.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a tough one.. we considered it but its quite misleading -- although it would kind of prevent a breaking change. We also removed some methods that no longer make sense like destroy. We could also add TS stubs for them that don't actually do anything to keep compatibility but at that point its very much like "silently failing" vs actually keeping compatibility.

I'm up for either or.. but not sure what's the right thing to do here. Either define a breaking change or silently stub out most of the missing parts. If any app depends on current behaviour though it will still break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok to define breaking changes for this PR, some things in this rewrite became bigger than we thought and at this point having no breaking changes is impossible IMO. Of course we should still strive to have as few as possible breaking changes until 3.0, but sometimes exceptions must be made.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the best as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with introducing breaking changes, but if we do so I'd vote that we bump the major version, and make the next release a 3.0.0-rc.0 or something.

@@ -51,14 +51,15 @@ repositories {
}

dependencies {
implementation 'com.github.DoubleSymmetry:KotlinAudio:v0.1.34'
implementation "com.github.DoubleSymmetry:KotlinAudio:v0.1.34"
// implementation "com.github.doublesymmetry:kotlin-audio:0.1.36"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should remove this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok if it stays, since we almost always need to replace remote with local KA.

@@ -51,14 +51,15 @@ repositories {
}

dependencies {
implementation 'com.github.DoubleSymmetry:KotlinAudio:v0.1.34'
implementation "com.github.DoubleSymmetry:KotlinAudio:v0.1.34"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so which version of KA do we want to use?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is using remote 0.1.34

@filippobusi
Copy link

@mpivchev I've tested on Nexus 5X Android 8.1.0 and on emulator with Android 12 but nothing changes, the ANR is still here.
I tried to create a new blank project to exclude any error from my previous code but got the same result...
With blank project not appear this warning message 'W/MessageQueue: Handler (android.os.Handler) {d886407} sending message to a Handler on a dead thread' but the ANR appear anyway

@filippobusi
Copy link

filippobusi commented Jul 27, 2022

Hi @mpivchev, ANR it seems caused by foreground service. I think the command startForeground() is not launched within 10s. But if I use this code inside setupPlayer in MusicModule.kt the ANR not appear.

 @ReactMethod
    fun setupPlayer(data: ReadableMap?, promise: Promise) {
      ...
        Intent(context, MusicService::class.java).also { intent ->
            context.startService(intent)//<---- add this
            context.bindService(intent, this, Context.BIND_AUTO_CREATE)
            //ContextCompat.startForegroundService(context, intent) <--- remove this line
        }
    }

@mpivchev
Copy link
Contributor Author

mpivchev commented Jul 28, 2022

Hi @mpivchev, ANR it seems caused by foreground service. I think the command startForeground() is not launched within 10s. But if I use this code inside setupPlayer in MusicModule.kt the ANR not appear.

 @ReactMethod
    fun setupPlayer(data: ReadableMap?, promise: Promise) {
      ...
        Intent(context, MusicService::class.java).also { intent ->
            context.startService(intent)//<---- add this
            context.bindService(intent, this, Context.BIND_AUTO_CREATE)
            //ContextCompat.startForegroundService(context, intent) <--- remove this line
        }
    }
            context.startService(intent)//<---- add this

Thanks for the hint. We don't need to start a foreground service in the module, since the service itself tries to start itself as foreground internally. Trying to do it twice is a bad idea. I implemented your change so please update if you still get more ANRs.

@mpivchev
Copy link
Contributor Author

mpivchev commented Aug 2, 2022

We realized we can no longer keep updating the project without breaking changes, so this and future PRs will now be part of version 3.0 of RNTP.

@mpivchev mpivchev requested a review from dcvz August 2, 2022 08:25
Copy link
Contributor

@dcvz dcvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

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

Successfully merging this pull request may close these issues.

None yet

7 participants