@javascriptcommon/react-native-track-player 4.1.6 → 4.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +2 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +43 -33
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/AutoConnectionDetector.kt +97 -0
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ import com.doublesymmetry.trackplayer.model.Track
|
|
|
15
15
|
import com.doublesymmetry.trackplayer.module.MusicEvents.Companion.EVENT_INTENT
|
|
16
16
|
import com.doublesymmetry.trackplayer.service.MusicService
|
|
17
17
|
import com.doublesymmetry.trackplayer.utils.AppForegroundTracker
|
|
18
|
+
import com.doublesymmetry.trackplayer.utils.AutoConnectionDetector
|
|
18
19
|
import com.doublesymmetry.trackplayer.utils.RejectionException
|
|
19
20
|
import com.doublesymmetry.trackplayer.NativeTrackPlayerSpec
|
|
20
21
|
import com.facebook.react.bridge.*
|
|
@@ -64,6 +65,7 @@ class MusicModule(reactContext: ReactApplicationContext) : NativeTrackPlayerSpec
|
|
|
64
65
|
|
|
65
66
|
override fun initialize() {
|
|
66
67
|
AppForegroundTracker.start()
|
|
68
|
+
AutoConnectionDetector(context).registerCarConnectionReceiver()
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
|
@@ -951,30 +951,33 @@ class MusicService : HeadlessJsMediaService() {
|
|
|
951
951
|
|
|
952
952
|
@SuppressLint("VisibleForTests")
|
|
953
953
|
private fun selfWake(clientPackageName: String): Boolean {
|
|
954
|
-
|
|
955
|
-
if (
|
|
956
|
-
// HACK: validate reactActivity is present; if not, send wake intent
|
|
957
|
-
(reactActivity == null || reactActivity.isDestroyed)
|
|
958
|
-
&& Settings.canDrawOverlays(this)
|
|
959
|
-
) {
|
|
960
|
-
val currentTime = System.currentTimeMillis()
|
|
961
|
-
if (currentTime - lastWake < 100000) {
|
|
962
|
-
return false
|
|
963
|
-
}
|
|
964
|
-
lastWake = currentTime
|
|
965
|
-
val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
966
|
-
activityIntent!!.data = "trackplayer://service-bound".toUri()
|
|
967
|
-
activityIntent.action = Intent.ACTION_VIEW
|
|
968
|
-
activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
969
|
-
var activityOptions = ActivityOptions.makeBasic()
|
|
970
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
971
|
-
activityOptions = activityOptions.setPendingIntentBackgroundActivityStartMode(
|
|
972
|
-
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
|
|
973
|
-
}
|
|
974
|
-
this.startActivity(activityIntent, activityOptions.toBundle())
|
|
975
|
-
return true
|
|
976
|
-
}
|
|
954
|
+
// FORK PATCH: AVOID STARTING APP IN FOREGROUND, PREFER STARTING HEADLESS
|
|
977
955
|
return false
|
|
956
|
+
|
|
957
|
+
// val reactActivity = reactContext?.currentActivity
|
|
958
|
+
// if (
|
|
959
|
+
// // HACK: validate reactActivity is present; if not, send wake intent
|
|
960
|
+
// (reactActivity == null || reactActivity.isDestroyed)
|
|
961
|
+
// && Settings.canDrawOverlays(this)
|
|
962
|
+
// ) {
|
|
963
|
+
// val currentTime = System.currentTimeMillis()
|
|
964
|
+
// if (currentTime - lastWake < 100000) {
|
|
965
|
+
// return false
|
|
966
|
+
// }
|
|
967
|
+
// lastWake = currentTime
|
|
968
|
+
// val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
969
|
+
// activityIntent!!.data = "trackplayer://service-bound".toUri()
|
|
970
|
+
// activityIntent.action = Intent.ACTION_VIEW
|
|
971
|
+
// activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
972
|
+
// var activityOptions = ActivityOptions.makeBasic()
|
|
973
|
+
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
974
|
+
// activityOptions = activityOptions.setPendingIntentBackgroundActivityStartMode(
|
|
975
|
+
// ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
|
|
976
|
+
// }
|
|
977
|
+
// this.startActivity(activityIntent, activityOptions.toBundle())
|
|
978
|
+
// return true
|
|
979
|
+
// }
|
|
980
|
+
// return false
|
|
978
981
|
}
|
|
979
982
|
|
|
980
983
|
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
|
|
@@ -1001,10 +1004,15 @@ class MusicService : HeadlessJsMediaService() {
|
|
|
1001
1004
|
override fun onDestroy() {
|
|
1002
1005
|
Timber.tag("APM").d("RNTP service is destroyed.")
|
|
1003
1006
|
if (::player.isInitialized) {
|
|
1004
|
-
|
|
1007
|
+
// moved down ->
|
|
1008
|
+
// mediaSession.release()
|
|
1005
1009
|
player.destroy()
|
|
1006
1010
|
}
|
|
1007
1011
|
|
|
1012
|
+
// FORK PATCH
|
|
1013
|
+
// -> Attempt to fix https://github.com/doublesymmetry/react-native-track-player/issues/2485
|
|
1014
|
+
mediaSession.release()
|
|
1015
|
+
|
|
1008
1016
|
instance = null
|
|
1009
1017
|
progressUpdateJob?.cancel()
|
|
1010
1018
|
super.onDestroy()
|
|
@@ -1082,15 +1090,17 @@ class MusicService : HeadlessJsMediaService() {
|
|
|
1082
1090
|
private val rootItem = buildMediaItem(title = "root", mediaId = AA_ROOT_KEY, isPlayable = false)
|
|
1083
1091
|
private val forYouItem = buildMediaItem(title = "For You", mediaId = AA_FOR_YOU_KEY, isPlayable = false)
|
|
1084
1092
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1093
|
+
// FORK PATCH: onDisconnected it's called only a long time after Android Auto disconnection, replaced with AutoConnectionDetector
|
|
1094
|
+
// override fun onDisconnected(
|
|
1095
|
+
// session: MediaSession,
|
|
1096
|
+
// controller: MediaSession.ControllerInfo
|
|
1097
|
+
// ) {
|
|
1098
|
+
//
|
|
1099
|
+
// emit(MusicEvents.CONNECTOR_DISCONNECTED, Bundle().apply {
|
|
1100
|
+
// putString("package", controller.packageName)
|
|
1101
|
+
// })
|
|
1102
|
+
// super.onDisconnected(session, controller)
|
|
1103
|
+
// }
|
|
1094
1104
|
// Configure commands available to the controller in onConnect()
|
|
1095
1105
|
@OptIn(UnstableApi::class)
|
|
1096
1106
|
override fun onConnect(
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
package com.doublesymmetry.trackplayer.utils
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import androidx.car.app.connection.CarConnection
|
|
6
|
+
import androidx.lifecycle.LifecycleOwner
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.bridge.ReactContext
|
|
9
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import com.doublesymmetry.trackplayer.module.MusicEvents
|
|
11
|
+
|
|
12
|
+
class AutoConnectionDetector(
|
|
13
|
+
val context: ReactContext,
|
|
14
|
+
onConnectionChange: ((Boolean) -> Unit)? = null
|
|
15
|
+
) {
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
const val TAG = "AutoConnectionDetector"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var isCarConnected = false
|
|
22
|
+
private var carConnection: CarConnection? = null
|
|
23
|
+
private val onConnectionChangeCallback = onConnectionChange
|
|
24
|
+
private var isObserving = false
|
|
25
|
+
|
|
26
|
+
fun registerCarConnectionReceiver() {
|
|
27
|
+
if (carConnection == null) {
|
|
28
|
+
carConnection = CarConnection(context)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
val mainHandler = Handler(Looper.getMainLooper())
|
|
32
|
+
mainHandler.post {
|
|
33
|
+
val lifecycleOwner = context.currentActivity as? LifecycleOwner
|
|
34
|
+
if (lifecycleOwner != null && !isObserving) {
|
|
35
|
+
try {
|
|
36
|
+
carConnection?.type?.observe(lifecycleOwner, ::onConnectionStateUpdated)
|
|
37
|
+
isObserving = true
|
|
38
|
+
} catch (_: Throwable) {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun onConnectionStateUpdated(connectionState: Int) {
|
|
44
|
+
when (connectionState) {
|
|
45
|
+
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> {
|
|
46
|
+
if (isCarConnected) {
|
|
47
|
+
notifyCarDisconnected()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
CarConnection.CONNECTION_TYPE_NATIVE -> {
|
|
51
|
+
notifyCarConnected(connectionState)
|
|
52
|
+
}
|
|
53
|
+
CarConnection.CONNECTION_TYPE_PROJECTION -> {
|
|
54
|
+
notifyCarConnected(connectionState)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun emitConnectorEvent(eventName: String, connectionState: Int?) {
|
|
60
|
+
val params = Arguments.createMap()
|
|
61
|
+
params.putString("package", "com.google.android.projection.gearhead") // Android Auto package name
|
|
62
|
+
params.putBoolean(
|
|
63
|
+
"isAutomotiveController",
|
|
64
|
+
connectionState == CarConnection.CONNECTION_TYPE_NATIVE // Android Automotive
|
|
65
|
+
)
|
|
66
|
+
params.putBoolean(
|
|
67
|
+
"isAutoCompanionController",
|
|
68
|
+
connectionState == CarConnection.CONNECTION_TYPE_PROJECTION // Android Auto
|
|
69
|
+
)
|
|
70
|
+
params.putBoolean("isMediaNotificationController", false)
|
|
71
|
+
|
|
72
|
+
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
73
|
+
?.emit(eventName, params)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private fun notifyCarConnected(connectionState: Int) {
|
|
77
|
+
if (isCarConnected) return
|
|
78
|
+
isCarConnected = true
|
|
79
|
+
|
|
80
|
+
// emitConnectionUpdate(params) // old
|
|
81
|
+
// emitConnectorEvent(MusicEvents.CONNECTOR_CONNECTED, connectionState) // new but not necessary (MusicService event works)
|
|
82
|
+
|
|
83
|
+
onConnectionChangeCallback?.invoke(true)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private fun notifyCarDisconnected() {
|
|
87
|
+
if (!isCarConnected) return
|
|
88
|
+
isCarConnected = false
|
|
89
|
+
|
|
90
|
+
// emitConnectionUpdate(params) // old
|
|
91
|
+
|
|
92
|
+
// MusicService event doesn't work -> emit manually
|
|
93
|
+
emitConnectorEvent(MusicEvents.CONNECTOR_DISCONNECTED, CarConnection.CONNECTION_TYPE_NOT_CONNECTED)
|
|
94
|
+
|
|
95
|
+
onConnectionChangeCallback?.invoke(false)
|
|
96
|
+
}
|
|
97
|
+
}
|