feat: sync catalog, server, and MusicFree updates
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
diff --git a/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt b/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt
|
||||
index 9d6d869..8f149f8 100644
|
||||
--- a/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt
|
||||
+++ b/node_modules/react-native-track-player/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt
|
||||
@@ -4,17 +4,23 @@ import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
+import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.support.v4.media.RatingCompat
|
||||
+import android.support.v4.media.session.MediaSessionCompat
|
||||
+import android.support.v4.media.session.PlaybackStateCompat
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_LOW
|
||||
+import androidx.core.app.NotificationManagerCompat
|
||||
+import androidx.media.session.MediaButtonReceiver
|
||||
import com.doublesymmetry.kotlinaudio.models.*
|
||||
import com.doublesymmetry.kotlinaudio.models.NotificationButton.*
|
||||
+import com.doublesymmetry.kotlinaudio.notification.NotificationManager as KotlinAudioNotificationManager
|
||||
import com.doublesymmetry.kotlinaudio.players.QueuedAudioPlayer
|
||||
import com.doublesymmetry.trackplayer.R as TrackPlayerR
|
||||
import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toMilliseconds
|
||||
@@ -94,11 +100,52 @@ class MusicService : HeadlessJsTaskService() {
|
||||
private var compactCapabilities: List<Capability> = emptyList()
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
+ if (handleNotificationAction(intent)) {
|
||||
+ return START_STICKY
|
||||
+ }
|
||||
startTask(getTaskConfig(intent))
|
||||
startAndStopEmptyNotificationToAvoidANR()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
+ private fun handleNotificationAction(intent: Intent?): Boolean {
|
||||
+ val action = intent?.action ?: return false
|
||||
+ if (!this::player.isInitialized) {
|
||||
+ Timber.w("Ignoring notification action before player initialization: %s", action)
|
||||
+ return true
|
||||
+ }
|
||||
+ when (action) {
|
||||
+ ACTION_NOTIFICATION_PLAY -> play()
|
||||
+ ACTION_NOTIFICATION_PAUSE -> pause()
|
||||
+ ACTION_NOTIFICATION_NEXT -> skipToNext()
|
||||
+ ACTION_NOTIFICATION_PREVIOUS -> {
|
||||
+ dispatchPlaybackButtonEvent(MusicEvents.BUTTON_SKIP_PREVIOUS)
|
||||
+ }
|
||||
+ else -> return false
|
||||
+ }
|
||||
+ Timber.d("Handled explicit notification action: %s", action)
|
||||
+ return true
|
||||
+ }
|
||||
+
|
||||
+ private fun dispatchPlaybackButtonEvent(event: String, data: Bundle? = null) {
|
||||
+ val reactInstanceManager = reactNativeHost.reactInstanceManager
|
||||
+ if (reactInstanceManager.currentReactContext != null) {
|
||||
+ emit(event, data)
|
||||
+ return
|
||||
+ }
|
||||
+ startTask(getTaskConfig(null))
|
||||
+ scope.launch {
|
||||
+ repeat(40) {
|
||||
+ if (reactInstanceManager.currentReactContext != null) {
|
||||
+ emit(event, data)
|
||||
+ return@launch
|
||||
+ }
|
||||
+ delay(100)
|
||||
+ }
|
||||
+ Timber.w("Timed out waiting for React context before dispatching event: %s", event)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Workaround for the "Context.startForegroundService() did not then call Service.startForeground()"
|
||||
* within 5s" ANR and crash by creating an empty notification and stopping it right after. For more
|
||||
@@ -236,6 +283,11 @@ class MusicService : HeadlessJsTaskService() {
|
||||
val notificationConfig = NotificationConfig(buttonsList, accentColor, smallIcon, pendingIntent)
|
||||
|
||||
player.notificationManager.createNotification(notificationConfig)
|
||||
+ scope.launch {
|
||||
+ delay(500)
|
||||
+ player.notificationManager.createNotification(notificationConfig)
|
||||
+ player.notificationManager.invalidate()
|
||||
+ }
|
||||
|
||||
// setup progress update events if configured
|
||||
progressUpdateJob?.cancel()
|
||||
@@ -279,6 +331,138 @@ class MusicService : HeadlessJsTaskService() {
|
||||
}
|
||||
}
|
||||
|
||||
+ private fun hasNotificationCapability(capability: Capability): Boolean {
|
||||
+ return notificationCapabilities.contains(capability) || capabilities.contains(capability)
|
||||
+ }
|
||||
+
|
||||
+ private fun getMediaSessionCompat(): MediaSessionCompat? {
|
||||
+ return try {
|
||||
+ val field = KotlinAudioNotificationManager::class.java.getDeclaredField("mediaSession")
|
||||
+ field.isAccessible = true
|
||||
+ field.get(player.notificationManager) as? MediaSessionCompat
|
||||
+ } catch (error: Exception) {
|
||||
+ Timber.w(error, "Failed to access media session from notification manager")
|
||||
+ null
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private fun buildTransportControlNotification(original: Notification): Notification {
|
||||
+ val builder = NotificationCompat.Builder(this, getNotificationChannelId(original))
|
||||
+ .setContentTitle(original.extras?.getCharSequence(Notification.EXTRA_TITLE))
|
||||
+ .setContentText(original.extras?.getCharSequence(Notification.EXTRA_TEXT))
|
||||
+ .setSubText(original.extras?.getCharSequence(Notification.EXTRA_SUB_TEXT))
|
||||
+ .setContentIntent(original.contentIntent)
|
||||
+ .setDeleteIntent(original.deleteIntent)
|
||||
+ .setCategory(original.category ?: Notification.CATEGORY_TRANSPORT)
|
||||
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
+ .setOnlyAlertOnce(true)
|
||||
+ .setOngoing(player.playWhenReady)
|
||||
+ .setShowWhen(original.`when` > 0)
|
||||
+ .setWhen(original.`when`)
|
||||
+ .setSmallIcon(ExoPlayerR.drawable.exo_notification_small_icon)
|
||||
+ val mediaSession = getMediaSessionCompat()
|
||||
+ val compactActionIndexes = mutableListOf<Int>()
|
||||
+
|
||||
+ getNotificationLargeIcon(original)?.let { builder.setLargeIcon(it) }
|
||||
+ if (original.color != 0) {
|
||||
+ builder.color = original.color
|
||||
+ }
|
||||
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
+ builder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
|
||||
+ }
|
||||
+
|
||||
+ if (hasNotificationCapability(Capability.SKIP_TO_PREVIOUS)) {
|
||||
+ val previousIntent = createServiceActionPendingIntent(ACTION_NOTIFICATION_PREVIOUS, 1)
|
||||
+ if (previousIntent != null) {
|
||||
+ builder.addAction(
|
||||
+ ExoPlayerR.drawable.exo_notification_previous,
|
||||
+ "Previous",
|
||||
+ previousIntent
|
||||
+ )
|
||||
+ compactActionIndexes += compactActionIndexes.size
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (hasNotificationCapability(Capability.PLAY) || hasNotificationCapability(Capability.PAUSE)) {
|
||||
+ val playPauseIntent = if (player.isPlaying) {
|
||||
+ createServiceActionPendingIntent(ACTION_NOTIFICATION_PAUSE, 2)
|
||||
+ } else {
|
||||
+ createServiceActionPendingIntent(ACTION_NOTIFICATION_PLAY, 2)
|
||||
+ }
|
||||
+ if (playPauseIntent != null) {
|
||||
+ builder.addAction(
|
||||
+ if (player.isPlaying) ExoPlayerR.drawable.exo_notification_pause
|
||||
+ else ExoPlayerR.drawable.exo_notification_play,
|
||||
+ if (player.isPlaying) "Pause" else "Play",
|
||||
+ playPauseIntent
|
||||
+ )
|
||||
+ compactActionIndexes += compactActionIndexes.size
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (hasNotificationCapability(Capability.SKIP_TO_NEXT)) {
|
||||
+ val nextIntent = createServiceActionPendingIntent(ACTION_NOTIFICATION_NEXT, 3)
|
||||
+ if (nextIntent != null) {
|
||||
+ builder.addAction(
|
||||
+ ExoPlayerR.drawable.exo_notification_next,
|
||||
+ "Next",
|
||||
+ nextIntent
|
||||
+ )
|
||||
+ compactActionIndexes += compactActionIndexes.size
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()
|
||||
+ mediaSession?.sessionToken?.let { mediaStyle.setMediaSession(it) }
|
||||
+ if (compactActionIndexes.isNotEmpty()) {
|
||||
+ mediaStyle.setShowActionsInCompactView(*compactActionIndexes.toIntArray())
|
||||
+ }
|
||||
+
|
||||
+ builder.setStyle(mediaStyle)
|
||||
+ return builder.build()
|
||||
+ }
|
||||
+
|
||||
+ private fun createServiceActionPendingIntent(action: String, requestCode: Int): PendingIntent? {
|
||||
+ val intent = Intent(this, MusicService::class.java).apply {
|
||||
+ this.action = action
|
||||
+ `package` = packageName
|
||||
+ }
|
||||
+ val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
+ } else {
|
||||
+ PendingIntent.FLAG_UPDATE_CURRENT
|
||||
+ }
|
||||
+ return PendingIntent.getService(this, requestCode, intent, flags)
|
||||
+ }
|
||||
+
|
||||
+ private fun getNotificationChannelId(original: Notification): String {
|
||||
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
+ original.channelId ?: getString(TrackPlayerR.string.rntp_temporary_channel_id)
|
||||
+ } else {
|
||||
+ getString(TrackPlayerR.string.rntp_temporary_channel_id)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private fun getNotificationLargeIcon(original: Notification): Bitmap? {
|
||||
+ return when {
|
||||
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
||||
+ original.getLargeIcon()?.loadDrawable(this)?.let { drawable ->
|
||||
+ val width = drawable.intrinsicWidth.takeIf { it > 0 } ?: 1
|
||||
+ val height = drawable.intrinsicHeight.takeIf { it > 0 } ?: 1
|
||||
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { bitmap ->
|
||||
+ val canvas = android.graphics.Canvas(bitmap)
|
||||
+ drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
+ drawable.draw(canvas)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ else -> {
|
||||
+ @Suppress("DEPRECATION")
|
||||
+ original.largeIcon
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
private fun isCompact(capability: Capability): Boolean {
|
||||
return compactCapabilities.contains(capability)
|
||||
}
|
||||
@@ -573,7 +757,8 @@ class MusicService : HeadlessJsTaskService() {
|
||||
is NotificationState.POSTED -> {
|
||||
Timber.d("notification posted with id=%s, ongoing=%s", it.notificationId, it.ongoing)
|
||||
notificationId = it.notificationId;
|
||||
- notification = it.notification;
|
||||
+ notification = buildTransportControlNotification(it.notification)
|
||||
+ NotificationManagerCompat.from(this@MusicService).notify(notificationId!!, notification!!)
|
||||
if (it.ongoing) {
|
||||
if (player.playWhenReady) {
|
||||
startForegroundIfNecessary()
|
||||
@@ -850,5 +1035,10 @@ class MusicService : HeadlessJsTaskService() {
|
||||
|
||||
const val DEFAULT_JUMP_INTERVAL = 15.0
|
||||
const val DEFAULT_STOP_FOREGROUND_GRACE_PERIOD = 5
|
||||
+
|
||||
+ private const val ACTION_NOTIFICATION_PLAY = "com.doublesymmetry.trackplayer.notification.PLAY"
|
||||
+ private const val ACTION_NOTIFICATION_PAUSE = "com.doublesymmetry.trackplayer.notification.PAUSE"
|
||||
+ private const val ACTION_NOTIFICATION_NEXT = "com.doublesymmetry.trackplayer.notification.NEXT"
|
||||
+ private const val ACTION_NOTIFICATION_PREVIOUS = "com.doublesymmetry.trackplayer.notification.PREVIOUS"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user