@javascriptcommon/react-native-track-player 1.2.9 → 1.2.24

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.
Files changed (131) hide show
  1. package/android/build.gradle +61 -4
  2. package/android/src/main/AndroidManifest.xml +2 -4
  3. package/android/src/main/ic_home-playstore.png +0 -0
  4. package/android/src/main/ic_repeat-playstore.png +0 -0
  5. package/android/src/main/ic_repeat_50-playstore.png +0 -0
  6. package/android/src/main/ic_shuffle-playstore.png +0 -0
  7. package/android/src/main/ic_shuffle_50-playstore.png +0 -0
  8. package/android/src/main/ic_shuffle_sm-playstore.png +0 -0
  9. package/android/src/main/ic_stop-playstore.png +0 -0
  10. package/android/src/main/ic_test-playstore.png +0 -0
  11. package/android/src/main/java/com/guichaguri/trackplayer/{service/HeadlessJsMediaService.java → HeadlessJsMediaService.java} +83 -32
  12. package/android/src/main/java/com/guichaguri/trackplayer/TrackPlayer.kt +25 -0
  13. package/android/src/main/java/com/guichaguri/trackplayer/extensions/AudioPlayerStateExt.kt +19 -0
  14. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/event/EventHolder.kt +30 -0
  15. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/event/NotificationEventHolder.kt +20 -0
  16. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/event/PlayerEventHolder.kt +111 -0
  17. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/AAMediaSessionCallback.kt +10 -0
  18. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/AudioContentType.kt +10 -0
  19. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/AudioItem.kt +66 -0
  20. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/AudioItemTransitionReason.kt +33 -0
  21. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/AudioPlayerState.kt +30 -0
  22. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/BufferConfig.kt +8 -0
  23. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/CacheConfig.kt +17 -0
  24. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/Capability.kt +19 -0
  25. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/FocusChangeData.kt +3 -0
  26. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/MediaSessionCallback.kt +17 -0
  27. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/NotificationConfig.kt +43 -0
  28. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/NotificationMetadata.kt +8 -0
  29. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/NotificationState.kt +8 -0
  30. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PlayWhenReadyChangeData.kt +5 -0
  31. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PlaybackEndedReason.kt +5 -0
  32. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PlaybackError.kt +6 -0
  33. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PlaybackMetadata.kt +200 -0
  34. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PlayerConfig.kt +33 -0
  35. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PlayerOptions.kt +9 -0
  36. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/PositionChangedReason.kt +39 -0
  37. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/models/QueuedPlayerOptions.kt +49 -0
  38. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/notification/NotificationManager.kt +678 -0
  39. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/players/AudioPlayer.kt +10 -0
  40. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/players/BaseAudioPlayer.kt +864 -0
  41. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/players/QueuedAudioPlayer.kt +269 -0
  42. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/players/components/MediaSourceExt.kt +35 -0
  43. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/players/components/PlayerCache.kt +26 -0
  44. package/android/src/main/java/com/guichaguri/trackplayer/kotlinaudio/utils/Utils.kt +12 -0
  45. package/android/src/main/java/com/guichaguri/trackplayer/model/MetadataAdapter.kt +224 -0
  46. package/android/src/main/java/com/guichaguri/trackplayer/model/State.kt +13 -0
  47. package/android/src/main/java/com/guichaguri/trackplayer/model/Track.kt +120 -0
  48. package/android/src/main/java/com/guichaguri/trackplayer/model/TrackAudioItem.kt +19 -0
  49. package/android/src/main/java/com/guichaguri/trackplayer/model/TrackType.kt +11 -0
  50. package/android/src/main/java/com/guichaguri/trackplayer/module/AutoConnectionDetector.kt +151 -0
  51. package/android/src/main/java/com/guichaguri/trackplayer/module/MusicEvents.kt +66 -0
  52. package/android/src/main/java/com/guichaguri/trackplayer/module/MusicModule.kt +1192 -0
  53. package/android/src/main/java/com/guichaguri/trackplayer/service/BundleUtils.kt +117 -0
  54. package/android/src/main/java/com/guichaguri/trackplayer/service/MusicBinder.kt +31 -0
  55. package/android/src/main/java/com/guichaguri/trackplayer/service/MusicManager.kt +347 -0
  56. package/android/src/main/java/com/guichaguri/trackplayer/service/MusicService.kt +1268 -0
  57. package/android/src/main/java/com/guichaguri/trackplayer/service/Utils.kt +228 -0
  58. package/android/src/main/java/com/guichaguri/trackplayer/service/metadata/ButtonEvents.kt +141 -0
  59. package/android/src/main/java/com/guichaguri/trackplayer/service/metadata/MetadataManager.kt +396 -0
  60. package/android/src/main/res/drawable-hdpi/ic_home.png +0 -0
  61. package/android/src/main/res/drawable-mdpi/ic_home.png +0 -0
  62. package/android/src/main/res/drawable-xhdpi/ic_home.png +0 -0
  63. package/android/src/main/res/drawable-xxhdpi/ic_home.png +0 -0
  64. package/android/src/main/res/drawable-xxxhdpi/ic_home.png +0 -0
  65. package/android/src/main/res/mipmap-hdpi/ic_arrow_down_circle_foreground.png +0 -0
  66. package/android/src/main/res/mipmap-hdpi/ic_clock_now_foreground.png +0 -0
  67. package/android/src/main/res/mipmap-hdpi/ic_close_foreground.png +0 -0
  68. package/android/src/main/res/mipmap-hdpi/ic_heart_foreground.png +0 -0
  69. package/android/src/main/res/mipmap-hdpi/ic_heart_outlined_foreground.png +0 -0
  70. package/android/src/main/res/mipmap-hdpi/ic_repeat_off_foreground.png +0 -0
  71. package/android/src/main/res/mipmap-hdpi/ic_repeat_on_foreground.png +0 -0
  72. package/android/src/main/res/mipmap-hdpi/ic_shuffle_off_foreground.png +0 -0
  73. package/android/src/main/res/mipmap-hdpi/ic_shuffle_on_foreground.png +0 -0
  74. package/android/src/main/res/mipmap-mdpi/ic_arrow_down_circle_foreground.png +0 -0
  75. package/android/src/main/res/mipmap-mdpi/ic_clock_now_foreground.png +0 -0
  76. package/android/src/main/res/mipmap-mdpi/ic_close_foreground.png +0 -0
  77. package/android/src/main/res/mipmap-mdpi/ic_heart_foreground.png +0 -0
  78. package/android/src/main/res/mipmap-mdpi/ic_heart_outlined_foreground.png +0 -0
  79. package/android/src/main/res/mipmap-mdpi/ic_repeat_off_foreground.png +0 -0
  80. package/android/src/main/res/mipmap-mdpi/ic_repeat_on_foreground.png +0 -0
  81. package/android/src/main/res/mipmap-mdpi/ic_shuffle_off_foreground.png +0 -0
  82. package/android/src/main/res/mipmap-mdpi/ic_shuffle_on_foreground.png +0 -0
  83. package/android/src/main/res/mipmap-xhdpi/ic_arrow_down_circle_foreground.png +0 -0
  84. package/android/src/main/res/mipmap-xhdpi/ic_clock_now_foreground.png +0 -0
  85. package/android/src/main/res/mipmap-xhdpi/ic_close_foreground.png +0 -0
  86. package/android/src/main/res/mipmap-xhdpi/ic_heart_foreground.png +0 -0
  87. package/android/src/main/res/mipmap-xhdpi/ic_heart_outlined_foreground.png +0 -0
  88. package/android/src/main/res/mipmap-xhdpi/ic_repeat_off_foreground.png +0 -0
  89. package/android/src/main/res/mipmap-xhdpi/ic_repeat_on_foreground.png +0 -0
  90. package/android/src/main/res/mipmap-xhdpi/ic_shuffle_off_foreground.png +0 -0
  91. package/android/src/main/res/mipmap-xhdpi/ic_shuffle_on_foreground.png +0 -0
  92. package/android/src/main/res/mipmap-xxhdpi/ic_arrow_down_circle_foreground.png +0 -0
  93. package/android/src/main/res/mipmap-xxhdpi/ic_clock_now_foreground.png +0 -0
  94. package/android/src/main/res/mipmap-xxhdpi/ic_close_foreground.png +0 -0
  95. package/android/src/main/res/mipmap-xxhdpi/ic_heart_foreground.png +0 -0
  96. package/android/src/main/res/mipmap-xxhdpi/ic_heart_outlined_foreground.png +0 -0
  97. package/android/src/main/res/mipmap-xxhdpi/ic_repeat_off_foreground.png +0 -0
  98. package/android/src/main/res/mipmap-xxhdpi/ic_repeat_on_foreground.png +0 -0
  99. package/android/src/main/res/mipmap-xxhdpi/ic_shuffle_off_foreground.png +0 -0
  100. package/android/src/main/res/mipmap-xxhdpi/ic_shuffle_on_foreground.png +0 -0
  101. package/android/src/main/res/mipmap-xxxhdpi/ic_arrow_down_circle_foreground.png +0 -0
  102. package/android/src/main/res/mipmap-xxxhdpi/ic_clock_now_foreground.png +0 -0
  103. package/android/src/main/res/mipmap-xxxhdpi/ic_close_foreground.png +0 -0
  104. package/android/src/main/res/mipmap-xxxhdpi/ic_heart_foreground.png +0 -0
  105. package/android/src/main/res/mipmap-xxxhdpi/ic_heart_outlined_foreground.png +0 -0
  106. package/android/src/main/res/mipmap-xxxhdpi/ic_repeat_off_foreground.png +0 -0
  107. package/android/src/main/res/mipmap-xxxhdpi/ic_repeat_on_foreground.png +0 -0
  108. package/android/src/main/res/mipmap-xxxhdpi/ic_shuffle_off_foreground.png +0 -0
  109. package/android/src/main/res/mipmap-xxxhdpi/ic_shuffle_on_foreground.png +0 -0
  110. package/android/src/main/res/raw/silent_5_seconds.mp3 +0 -0
  111. package/android/src/main/res/strings.xml +6 -0
  112. package/android/src/main/res/values/strings.xml +6 -0
  113. package/index.d.ts +62 -1
  114. package/lib/index.js +10 -9
  115. package/package.json +1 -1
  116. package/android/src/main/java/com/guichaguri/trackplayer/TrackPlayer.java +0 -28
  117. package/android/src/main/java/com/guichaguri/trackplayer/module/MusicEvents.java +0 -55
  118. package/android/src/main/java/com/guichaguri/trackplayer/module/MusicModule.java +0 -298
  119. package/android/src/main/java/com/guichaguri/trackplayer/service/MusicBinder.java +0 -47
  120. package/android/src/main/java/com/guichaguri/trackplayer/service/MusicManager.java +0 -383
  121. package/android/src/main/java/com/guichaguri/trackplayer/service/MusicService.java +0 -271
  122. package/android/src/main/java/com/guichaguri/trackplayer/service/Utils.java +0 -243
  123. package/android/src/main/java/com/guichaguri/trackplayer/service/metadata/ButtonEvents.java +0 -148
  124. package/android/src/main/java/com/guichaguri/trackplayer/service/metadata/MetadataManager.java +0 -379
  125. package/android/src/main/java/com/guichaguri/trackplayer/service/models/Track.java +0 -141
  126. package/android/src/main/java/com/guichaguri/trackplayer/service/models/TrackType.java +0 -35
  127. package/android/src/main/res/drawable-hdpi/ic_logo.png +0 -0
  128. package/android/src/main/res/drawable-mdpi/ic_logo.png +0 -0
  129. package/android/src/main/res/drawable-xhdpi/ic_logo.png +0 -0
  130. package/android/src/main/res/drawable-xxhdpi/ic_logo.png +0 -0
  131. package/android/src/main/res/drawable-xxxhdpi/ic_logo.png +0 -0
@@ -0,0 +1,1192 @@
1
+ package com.guichaguri.trackplayer.module
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.AlarmManager
5
+ import android.app.PendingIntent
6
+ import android.content.*
7
+ import android.graphics.Bitmap
8
+ import android.graphics.BitmapFactory
9
+ import android.graphics.BitmapShader
10
+ import android.graphics.Canvas
11
+ import android.graphics.Color
12
+ import android.graphics.Paint
13
+ import android.graphics.Shader
14
+ import android.graphics.drawable.Drawable
15
+ import android.net.Uri
16
+ import android.os.*
17
+ import android.support.v4.media.MediaBrowserCompat
18
+ import android.support.v4.media.MediaBrowserCompat.MediaItem
19
+ import android.support.v4.media.MediaDescriptionCompat
20
+ import android.support.v4.media.RatingCompat
21
+ import android.support.v4.media.session.PlaybackStateCompat
22
+ import androidx.localbroadcastmanager.content.LocalBroadcastManager
23
+ import androidx.media.utils.MediaConstants
24
+ import com.bumptech.glide.Glide
25
+ import com.bumptech.glide.request.target.CustomTarget
26
+ import com.bumptech.glide.request.transition.Transition
27
+ import com.facebook.react.ReactApplication
28
+ import com.facebook.react.ReactHost
29
+ import com.facebook.react.ReactNativeHost
30
+ import com.facebook.react.bridge.*
31
+ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.bridgelessEnabled
32
+ import com.facebook.react.modules.core.DeviceEventManagerModule
33
+ import com.google.android.exoplayer2.DefaultLoadControl.*
34
+ import com.guichaguri.trackplayer.kotlinaudio.models.AudioPlayerState
35
+ import com.guichaguri.trackplayer.service.MusicBinder
36
+ import com.guichaguri.trackplayer.service.MusicService
37
+ import com.guichaguri.trackplayer.service.Utils
38
+ import com.guichaguri.trackplayer.service.Utils.EVENT_INTENT
39
+ import com.guichaguri.trackplayer.model.Track
40
+ import kotlinx.coroutines.MainScope
41
+ import kotlinx.coroutines.delay
42
+ import kotlinx.coroutines.launch
43
+ import timber.log.Timber
44
+ import java.util.*
45
+ import java.util.concurrent.Callable
46
+ import java.util.concurrent.ExecutorService
47
+ import java.util.concurrent.Executors
48
+ import java.util.concurrent.Future
49
+ import javax.annotation.Nonnull
50
+ import kotlin.collections.HashMap
51
+
52
+ /**
53
+ * @author Guichaguri
54
+ */
55
+ class MusicModule(reactContext: ReactApplicationContext?) :
56
+ ReactContextBaseJavaModule(reactContext), ServiceConnection, LifecycleEventListener {
57
+ private var playerOptions: Bundle? = null
58
+ private var isServiceBound = false
59
+
60
+ // private var binder: MusicBinder? = null
61
+ private var eventHandler: MusicEvents? = null
62
+ private val initCallbacks = ArrayDeque<Runnable>()
63
+ private var connecting = false
64
+ private var options: Bundle? = null
65
+ private var musicService: MusicService? = null
66
+ private val scope = MainScope()
67
+ private val executor: ExecutorService = Executors.newSingleThreadExecutor()
68
+
69
+ private val reactNativeHost: ReactNativeHost?
70
+ get() = (reactApplicationContext.applicationContext as? ReactApplication)?.reactNativeHost
71
+
72
+ private val reactHost: ReactHost?
73
+ get() = (reactApplicationContext.applicationContext as? ReactApplication)?.reactHost
74
+
75
+ companion object {
76
+ var autoConnectionDetector: AutoConnectionDetector? = null
77
+ var binder: MusicBinder? = null
78
+ var isAppOpen: Boolean = false
79
+ var isAndroidTv: Boolean = false
80
+ var firstPlayDone = false
81
+ }
82
+
83
+ @Nonnull
84
+ override fun getName(): String {
85
+ return "TrackPlayerModule"
86
+ }
87
+
88
+ private fun onCarConnected() {}
89
+
90
+ private fun onCarDisconnected() {
91
+ firstPlayDone = false
92
+ resetAndroidAutoService()
93
+ if (!isAppOpen) {
94
+ destroy()
95
+ }
96
+ }
97
+
98
+ override fun initialize() {
99
+ val context: ReactContext = reactApplicationContext
100
+ context.addLifecycleEventListener(this)
101
+ @Suppress("DEPRECATION")
102
+ val manager = LocalBroadcastManager.getInstance(context)
103
+ eventHandler = MusicEvents(context)
104
+ manager.registerReceiver(eventHandler!!, IntentFilter(EVENT_INTENT))
105
+ autoConnectionDetector = AutoConnectionDetector(context) { connected ->
106
+ if (connected) {
107
+ onCarConnected()
108
+ } else {
109
+ onCarDisconnected()
110
+ }
111
+ }
112
+ autoConnectionDetector?.registerCarConnectionReceiver()
113
+ }
114
+
115
+ override fun invalidate() {
116
+ val context: ReactContext = reactApplicationContext
117
+ if (eventHandler != null) {
118
+ @Suppress("DEPRECATION")
119
+ val manager = LocalBroadcastManager.getInstance(context)
120
+ manager.unregisterReceiver(eventHandler!!)
121
+ eventHandler = null
122
+ }
123
+ autoConnectionDetector?.unRegisterCarConnectionReceiver()
124
+ }
125
+
126
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
127
+ scope.launch {
128
+ binder = service as MusicBinder
129
+ connecting = false
130
+ if (musicService == null) {
131
+ val binder: MusicBinder = service
132
+ musicService = binder.service
133
+ musicService?.setupPlayer(playerOptions);
134
+ }
135
+
136
+ isServiceBound = true
137
+
138
+ // Reapply options that user set before with updateOptions
139
+ if (options != null) {
140
+ binder!!.updateOptions(options!!)
141
+ }
142
+
143
+ // Triggers all callbacks
144
+ while (!initCallbacks.isEmpty()) {
145
+ binder!!.post(initCallbacks.remove())
146
+ }
147
+ }
148
+ }
149
+
150
+ override fun onServiceDisconnected(name: ComponentName) {
151
+ binder = null
152
+ connecting = false
153
+ isServiceBound = false
154
+ }
155
+
156
+ private fun verifyServiceBoundOrReject(promise: Promise): Boolean {
157
+ if (!isServiceBound) {
158
+ promise.reject(
159
+ "player_not_initialized",
160
+ "The player is not initialized. Call setupPlayer first."
161
+ )
162
+ return true
163
+ }
164
+
165
+ return false
166
+ }
167
+
168
+ /**
169
+ * Waits for a connection to the service and/or runs the [Runnable] in the player thread
170
+ */
171
+ private fun waitForConnection(r: Runnable) {
172
+ if (binder != null) {
173
+ binder!!.post(r)
174
+ return
175
+ } else {
176
+ initCallbacks.add(r)
177
+ }
178
+ if (connecting) return
179
+ val context = reactApplicationContext
180
+
181
+ // Binds the service to get a MediaWrapper instance
182
+ val intent = Intent(context, MusicService::class.java)
183
+ //context.startService(intent);
184
+ try {
185
+ if (Build.VERSION.SDK_INT >= 26) {
186
+ context.startForegroundService(intent)
187
+ } else {
188
+ context.startService(intent)
189
+ }
190
+ } catch (ex: Exception) {
191
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
192
+ val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
193
+ val i = Intent(context, MusicService::class.java)
194
+ val pi = PendingIntent.getForegroundService(
195
+ context,
196
+ 0,
197
+ i,
198
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
199
+ )
200
+ val calendar = Calendar.getInstance()
201
+ calendar.timeInMillis = System.currentTimeMillis()
202
+ calendar.add(Calendar.MILLISECOND, 200)
203
+ mgr[AlarmManager.RTC_WAKEUP, calendar.timeInMillis] = pi
204
+ }
205
+ }
206
+ intent.action = Utils.CONNECT_INTENT
207
+ context.bindService(intent, this, 0)
208
+ connecting = true
209
+ }
210
+
211
+ /* ****************************** API ****************************** */
212
+ override fun getConstants(): Map<String, Any> {
213
+ val constants: MutableMap<String, Any> = HashMap()
214
+
215
+ // Capabilities
216
+ constants["CAPABILITY_PLAY"] = PlaybackStateCompat.ACTION_PLAY
217
+ constants["CAPABILITY_PLAY_FROM_ID"] = PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
218
+ constants["CAPABILITY_PLAY_FROM_SEARCH"] = PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
219
+ constants["CAPABILITY_PAUSE"] = PlaybackStateCompat.ACTION_PAUSE
220
+ constants["CAPABILITY_STOP"] = PlaybackStateCompat.ACTION_STOP
221
+ constants["CAPABILITY_SEEK_TO"] = PlaybackStateCompat.ACTION_SEEK_TO
222
+ constants["CAPABILITY_SKIP"] = PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
223
+ constants["CAPABILITY_SKIP_TO_NEXT"] = PlaybackStateCompat.ACTION_SKIP_TO_NEXT
224
+ constants["CAPABILITY_SKIP_TO_PREVIOUS"] = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
225
+ constants["CAPABILITY_SET_RATING"] = PlaybackStateCompat.ACTION_SET_RATING
226
+ constants["CAPABILITY_JUMP_FORWARD"] = PlaybackStateCompat.ACTION_FAST_FORWARD
227
+ constants["CAPABILITY_JUMP_BACKWARD"] = PlaybackStateCompat.ACTION_REWIND
228
+
229
+ // States
230
+ constants["STATE_NONE"] = PlaybackStateCompat.STATE_NONE
231
+ constants["STATE_READY"] = PlaybackStateCompat.STATE_PAUSED
232
+ constants["STATE_PLAYING"] = PlaybackStateCompat.STATE_PLAYING
233
+ constants["STATE_PAUSED"] = PlaybackStateCompat.STATE_PAUSED
234
+ constants["STATE_STOPPED"] = PlaybackStateCompat.STATE_STOPPED
235
+ constants["STATE_BUFFERING"] = PlaybackStateCompat.STATE_BUFFERING
236
+ constants["STATE_CONNECTING"] = PlaybackStateCompat.STATE_CONNECTING
237
+
238
+ // Rating Types
239
+ constants["RATING_HEART"] = RatingCompat.RATING_HEART
240
+ constants["RATING_THUMBS_UP_DOWN"] = RatingCompat.RATING_THUMB_UP_DOWN
241
+ constants["RATING_3_STARS"] = RatingCompat.RATING_3_STARS
242
+ constants["RATING_4_STARS"] = RatingCompat.RATING_4_STARS
243
+ constants["RATING_5_STARS"] = RatingCompat.RATING_5_STARS
244
+ constants["RATING_PERCENTAGE"] = RatingCompat.RATING_PERCENTAGE
245
+ return constants
246
+ }
247
+
248
+ private fun convertBlackToWhite(bitmap: Bitmap): Bitmap {
249
+ val width = bitmap.width
250
+ val height = bitmap.height
251
+
252
+ val outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
253
+
254
+ for (x in 0 until width) {
255
+ for (y in 0 until height) {
256
+ val pixel = bitmap.getPixel(x, y)
257
+ val alpha = Color.alpha(pixel)
258
+ val red = Color.red(pixel)
259
+ val green = Color.green(pixel)
260
+ val blue = Color.blue(pixel)
261
+
262
+ outputBitmap.setPixel(
263
+ x,
264
+ y,
265
+ if (red == 0 && green == 0 && blue == 0) Color.argb(
266
+ alpha,
267
+ 255,
268
+ 255,
269
+ 255
270
+ ) else pixel
271
+ )
272
+ }
273
+ }
274
+
275
+ return outputBitmap
276
+ }
277
+
278
+ private fun loadBitmapFromFile(filePath: String): Bitmap? {
279
+ return try {
280
+ val uri = Uri.parse(filePath)
281
+ val path = uri.path ?: ""
282
+ BitmapFactory.decodeFile(path)
283
+ } catch (e: Exception) {
284
+ null
285
+ }
286
+ }
287
+
288
+ private fun getCircularBitmap(bitmap: Bitmap): Bitmap {
289
+ val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
290
+ val canvas = Canvas(output)
291
+
292
+ val paint = Paint()
293
+ paint.isAntiAlias = true
294
+ paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
295
+
296
+ val radius = bitmap.width.coerceAtMost(bitmap.height) / 2f
297
+ canvas.drawCircle(bitmap.width / 2f, bitmap.height / 2f, radius, paint)
298
+
299
+ return output
300
+ }
301
+
302
+ private fun getCroppedBitmap(bitmap: Bitmap): Bitmap {
303
+ val output = if (bitmap.width >= bitmap.height) {
304
+ Bitmap.createBitmap(
305
+ bitmap,
306
+ (bitmap.width - bitmap.height) / 2,
307
+ 0,
308
+ bitmap.height,
309
+ bitmap.height
310
+ )
311
+ } else {
312
+ Bitmap.createBitmap(
313
+ bitmap,
314
+ 0,
315
+ (bitmap.height - bitmap.width) / 2,
316
+ bitmap.width,
317
+ bitmap.width
318
+ )
319
+ }
320
+
321
+ return output
322
+ }
323
+
324
+ var placeholderBitmap: Bitmap? = null
325
+
326
+ @SuppressLint("DiscouragedApi")
327
+ private fun hashmapToMediaItem(
328
+ parentMediaId: String,
329
+ hashmap: HashMap<String, String>,
330
+ iconBitmap: Bitmap? = null
331
+ ): MediaBrowserCompat.MediaItem {
332
+ val mediaId = hashmap["mediaId"]
333
+ val title = hashmap["title"]
334
+ val subtitle = hashmap["subtitle"]
335
+ val mediaUri = hashmap["mediaUri"]
336
+ val iconUri = hashmap["iconUri"]
337
+ val iconName = hashmap["iconName"]
338
+ val cropThumbnail = hashmap["cropThumbnail"].toBoolean()
339
+
340
+ val mediaIdParts = mediaId?.split("-/-")
341
+ val itemType = mediaIdParts?.getOrNull(1)
342
+
343
+ val isArtist = itemType == "artist"
344
+ val playableFlag =
345
+ if (itemType == "track" || itemType == "empty" || itemType == "shuffle") MediaItem.FLAG_PLAYABLE else MediaItem.FLAG_BROWSABLE
346
+
347
+ val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
348
+ mediaDescriptionBuilder.setMediaId(mediaId)
349
+ mediaDescriptionBuilder.setTitle(title)
350
+ mediaDescriptionBuilder.setSubtitle(subtitle)
351
+ mediaDescriptionBuilder.setMediaUri(if (mediaUri != null) Uri.parse(mediaUri) else null)
352
+
353
+ if (iconBitmap != null) {
354
+ var bitmap = iconBitmap
355
+ if (cropThumbnail) {
356
+ bitmap = if (isArtist) {
357
+ getCircularBitmap(bitmap)
358
+ } else {
359
+ getCroppedBitmap(bitmap)
360
+ }
361
+ }
362
+
363
+ mediaDescriptionBuilder.setIconBitmap(bitmap)
364
+ } else if (iconName != null) {
365
+ if (iconName.contains("placeholder") && placeholderBitmap != null) {
366
+ mediaDescriptionBuilder.setIconBitmap(placeholderBitmap)
367
+ } else {
368
+ val currentResourceId = reactApplicationContext.resources.getIdentifier(
369
+ iconName,
370
+ "mipmap",
371
+ reactApplicationContext.packageName
372
+ )
373
+ if (currentResourceId != 0) {
374
+ var bitmap = BitmapFactory.decodeResource(
375
+ reactApplicationContext.resources,
376
+ currentResourceId
377
+ )
378
+ if (bitmap != null) {
379
+ if (iconName == "ic_auto_placeholder_foreground") {
380
+ placeholderBitmap = bitmap
381
+ } else if (iconName != "ic_auto_play_shuffle_foreground") {
382
+ bitmap = convertBlackToWhite(bitmap)
383
+ }
384
+ mediaDescriptionBuilder.setIconBitmap(bitmap)
385
+ }
386
+ }
387
+ }
388
+ } else if (iconUri != null) {
389
+ if (iconUri.startsWith("http")) {
390
+ if (cropThumbnail) {
391
+ if (placeholderBitmap == null) {
392
+ val currentResourceId = reactApplicationContext.resources.getIdentifier(
393
+ "placeholder",
394
+ "mipmap",
395
+ reactApplicationContext.packageName
396
+ )
397
+ if (currentResourceId != 0) {
398
+ placeholderBitmap = BitmapFactory.decodeResource(
399
+ reactApplicationContext.resources,
400
+ currentResourceId
401
+ )
402
+ }
403
+ }
404
+ if (placeholderBitmap != null) {
405
+ val bitmap =
406
+ if (isArtist) getCircularBitmap(placeholderBitmap!!) else placeholderBitmap
407
+ mediaDescriptionBuilder.setIconBitmap(bitmap)
408
+ }
409
+
410
+ Glide.with(reactApplicationContext)
411
+ .asBitmap()
412
+ .load(iconUri)
413
+ .into(object : CustomTarget<Bitmap>() {
414
+ override fun onResourceReady(
415
+ resource: Bitmap,
416
+ transition: Transition<in Bitmap>?
417
+ ) {
418
+ if (mediaId == null) return
419
+
420
+ executor.submit(Callable {
421
+ if (musicService != null) {
422
+ val toUpdateMediaItem =
423
+ hashmapToMediaItem(parentMediaId, hashmap, resource)
424
+ val items =
425
+ musicService!!.mediaTree.getOrPut(parentMediaId) { mutableListOf() }
426
+ val index =
427
+ items.indexOfFirst { it.mediaId == toUpdateMediaItem.mediaId }
428
+ if (index != -1) {
429
+ items[index] = toUpdateMediaItem
430
+ }
431
+ musicService!!.mediaTree[parentMediaId] = items
432
+ musicService!!.notifyChildrenChanged(parentMediaId)
433
+ }
434
+ })
435
+ }
436
+
437
+ override fun onLoadCleared(placeholder: Drawable?) {}
438
+ })
439
+ } else {
440
+ mediaDescriptionBuilder.setIconUri(Uri.parse(iconUri))
441
+ }
442
+ } else if (iconUri.startsWith("file")) {
443
+ var bitmap = loadBitmapFromFile(iconUri)
444
+ if (bitmap != null) {
445
+ if (cropThumbnail) {
446
+ bitmap = if (isArtist) {
447
+ getCircularBitmap(bitmap)
448
+ } else {
449
+ getCroppedBitmap(bitmap)
450
+ }
451
+ }
452
+
453
+ mediaDescriptionBuilder.setIconBitmap(bitmap)
454
+ }
455
+ }
456
+ }
457
+
458
+ val extras = Bundle()
459
+ if (hashmap["groupTitle"] != null) {
460
+ extras.putString(
461
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
462
+ hashmap["groupTitle"]
463
+ )
464
+ }
465
+ if (hashmap["contentStyle"] != null) {
466
+ extras.putInt(
467
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
468
+ hashmap["contentStyle"]!!
469
+ .toInt()
470
+ )
471
+ }
472
+ if (hashmap["childrenPlayableContentStyle"] != null) {
473
+ extras.putInt(
474
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
475
+ hashmap["childrenPlayableContentStyle"]!!
476
+ .toInt()
477
+ )
478
+ }
479
+ if (hashmap["childrenBrowsableContentStyle"] != null) {
480
+ extras.putInt(
481
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
482
+ hashmap["childrenBrowsableContentStyle"]!!
483
+ .toInt()
484
+ )
485
+ }
486
+ if (hashmap["playbackProgress"] != null) {
487
+ val playbackProgress = hashmap["playbackProgress"]!!.toDouble()
488
+ if (playbackProgress > 0.98) {
489
+ extras.putInt(
490
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
491
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
492
+ )
493
+ } else if (playbackProgress == 0.0) {
494
+ extras.putInt(
495
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
496
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
497
+ )
498
+ } else {
499
+ extras.putInt(
500
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
501
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
502
+ )
503
+ extras.putDouble(
504
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE,
505
+ playbackProgress
506
+ )
507
+ }
508
+ }
509
+ mediaDescriptionBuilder.setExtras(extras)
510
+ return MediaItem(mediaDescriptionBuilder.build(), playableFlag)
511
+ }
512
+
513
+ private fun readableArrayToMediaItems(
514
+ parentMediaId: String,
515
+ data: ArrayList<HashMap<String, String>>
516
+ ): MutableList<MediaItem> {
517
+ val executor: ExecutorService =
518
+ Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
519
+ val mediaItemList: MutableList<MediaItem> = ArrayList()
520
+
521
+ val futures: List<Future<MediaItem>> = data.map { hashmap ->
522
+ executor.submit(Callable {
523
+ hashmapToMediaItem(parentMediaId, hashmap)
524
+ })
525
+ }
526
+
527
+ for (future in futures) {
528
+ try {
529
+ mediaItemList.add(future.get())
530
+ } catch (_: InterruptedException) {
531
+ }
532
+ }
533
+
534
+ executor.shutdown()
535
+ return mediaItemList
536
+ }
537
+
538
+ @ReactMethod
539
+ fun setupPlayer(data: ReadableMap?, promise: Promise) {
540
+ try {
541
+ if (isServiceBound) {
542
+ promise.reject(
543
+ "player_already_initialized",
544
+ "The player has already been initialized via setupPlayer."
545
+ )
546
+ return
547
+ }
548
+
549
+ // prevent crash Fatal Exception: android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException
550
+ /* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && AppForegroundTracker.backgrounded) {
551
+ promise.reject(
552
+ "android_cannot_setup_player_in_background",
553
+ "On Android the app must be in the foreground when setting up the player."
554
+ )
555
+ return
556
+ }*/
557
+
558
+ // Validate buffer keys.
559
+ val bundledData = Arguments.toBundle(data)
560
+ val minBuffer =
561
+ bundledData?.getDouble(MusicService.MIN_BUFFER_KEY)?.toInt()
562
+ ?: DEFAULT_MIN_BUFFER_MS
563
+ val maxBuffer =
564
+ bundledData?.getDouble(MusicService.MAX_BUFFER_KEY)?.toInt()
565
+ ?: DEFAULT_MAX_BUFFER_MS
566
+ val playBuffer =
567
+ bundledData?.getDouble(MusicService.PLAY_BUFFER_KEY)?.toInt()
568
+ ?: DEFAULT_BUFFER_FOR_PLAYBACK_MS
569
+ val backBuffer =
570
+ bundledData?.getDouble(MusicService.BACK_BUFFER_KEY)?.toInt()
571
+ ?: DEFAULT_BACK_BUFFER_DURATION_MS
572
+
573
+ if (playBuffer < 0) {
574
+ promise.reject(
575
+ "play_buffer_error",
576
+ "The value for playBuffer should be greater than or equal to zero."
577
+ )
578
+ return
579
+ }
580
+
581
+ if (backBuffer < 0) {
582
+ promise.reject(
583
+ "back_buffer_error",
584
+ "The value for backBuffer should be greater than or equal to zero."
585
+ )
586
+ return
587
+ }
588
+
589
+ if (minBuffer < playBuffer) {
590
+ promise.reject(
591
+ "min_buffer_error",
592
+ "The value for minBuffer should be greater than or equal to playBuffer."
593
+ )
594
+ return
595
+ }
596
+
597
+ if (maxBuffer < minBuffer) {
598
+ promise.reject(
599
+ "min_buffer_error",
600
+ "The value for maxBuffer should be greater than or equal to minBuffer."
601
+ )
602
+ return
603
+ }
604
+
605
+ // playerSetUpPromise = promise
606
+ playerOptions = bundledData
607
+
608
+ @Suppress("DEPRECATION")
609
+ LocalBroadcastManager.getInstance(reactApplicationContext).registerReceiver(
610
+ MusicEvents(reactApplicationContext),
611
+ IntentFilter(EVENT_INTENT)
612
+ )
613
+
614
+ val musicModule = this
615
+ scope.launch {
616
+ var retries = 0
617
+ while (true) {
618
+ try {
619
+ Intent(reactApplicationContext, MusicService::class.java).also { intent ->
620
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
621
+ reactApplicationContext.startForegroundService(intent)
622
+ } else {
623
+ reactApplicationContext.startService(intent)
624
+ }
625
+ reactApplicationContext.bindService(intent, musicModule, Context.BIND_AUTO_CREATE)
626
+ }
627
+ break
628
+ } catch (exception: Exception) {
629
+ retries += 1
630
+ if (retries > 10) {
631
+ throw exception
632
+ }
633
+ delay(500)
634
+ }
635
+ }
636
+ }
637
+ } catch (_: Exception) {}
638
+ }
639
+
640
+ @ReactMethod
641
+ fun setBrowseTree(mediaItems: ReadableMap?, callback: Promise) {
642
+ waitForConnection {
643
+ try {
644
+ if (musicService != null) {
645
+ executor.submit(Callable {
646
+ val mediaItemsMap = mediaItems?.toHashMap()
647
+ if (mediaItemsMap != null) {
648
+ musicService!!.mediaTree =
649
+ mediaItemsMap.mapValues {
650
+ @Suppress("UNCHECKED_CAST")
651
+ readableArrayToMediaItems(
652
+ it.key,
653
+ it.value as ArrayList<HashMap<String, String>>
654
+ )
655
+ } as MutableMap<String, MutableList<MediaItem>>
656
+ }
657
+
658
+ musicService!!.toUpdateMediaItems.forEach { (parentMediaId, toUpdateItems) ->
659
+ val items =
660
+ musicService!!.mediaTree.getOrPut(parentMediaId) { mutableListOf() }
661
+ toUpdateItems.forEach { toUpdateItem ->
662
+ val index =
663
+ items.indexOfFirst { it.mediaId == toUpdateItem.mediaId }
664
+ if (index != -1) {
665
+ items[index] = toUpdateItem
666
+ }
667
+ }
668
+ musicService!!.mediaTree[parentMediaId] = items
669
+ }
670
+
671
+ musicService!!.mediaTree.keys.forEach {
672
+ musicService!!.notifyChildrenChanged(it)
673
+ }
674
+ callback.resolve(null)
675
+ })
676
+ }
677
+ } catch (_: Exception) {}
678
+ }
679
+ }
680
+
681
+ @ReactMethod
682
+ fun updateBrowseTree(mediaItems: ReadableMap, callback: Promise) {
683
+ try {
684
+ if (musicService != null) {
685
+ executor.submit(Callable {
686
+ val mediaItemsMap = mediaItems.toHashMap()
687
+ musicService!!.mediaTree += mediaItemsMap.mapValues {
688
+ @Suppress("UNCHECKED_CAST")
689
+ readableArrayToMediaItems(
690
+ it.key,
691
+ it.value as ArrayList<HashMap<String, String>>
692
+ )
693
+ } as MutableMap<String, MutableList<MediaItem>>
694
+ mediaItemsMap.keys.forEach {
695
+ musicService!!.notifyChildrenChanged(it)
696
+ }
697
+ callback.resolve(null)
698
+ })
699
+ }
700
+ } catch (_: Exception) {}
701
+ }
702
+
703
+ @ReactMethod
704
+ fun setSearchResult(mediaItems: ReadableArray, data: ReadableMap, callback: Promise) {
705
+ waitForConnection {
706
+ try {
707
+ if (musicService != null) {
708
+ val bundle = Arguments.toBundle(data)
709
+ val query = bundle?.getString("query")
710
+ if (query == musicService!!.searchQuery) {
711
+ musicService!!.searchResult?.sendResult(
712
+ @Suppress("UNCHECKED_CAST")
713
+ readableArrayToMediaItems(
714
+ "search",
715
+ mediaItems.toArrayList() as ArrayList<HashMap<String, String>>
716
+ )
717
+ )
718
+ }
719
+ callback.resolve(null)
720
+ }
721
+ } catch (_: Exception) {}
722
+ }
723
+ }
724
+
725
+ @ReactMethod
726
+ fun destroy() {
727
+ val serviceConnection = this
728
+ scope.launch {
729
+ musicService = null
730
+ isServiceBound = false
731
+ connecting = false
732
+ firstPlayDone = false
733
+
734
+ // Ignore if it was already destroyed
735
+ if (binder == null && !connecting) return@launch
736
+ try {
737
+ if (binder != null) {
738
+ binder!!.manager.metadata.removeNotifications()
739
+ delay(300)
740
+ binder!!.destroy()
741
+ binder = null
742
+ }
743
+ val context: ReactContext? = reactApplicationContext
744
+ context?.unbindService(serviceConnection)
745
+ } catch (ex: Exception) {
746
+ // This method shouldn't be throwing unhandled errors even if something goes wrong.
747
+ Timber.tag(Utils.LOG).e(ex, "An error occurred while destroying the service")
748
+ }
749
+ }
750
+ }
751
+
752
+ @ReactMethod
753
+ fun updateAndroidAutoPlayerOptions(data: ReadableMap?, callback: Promise) {
754
+ scope.launch {
755
+ waitForConnection {
756
+ try {
757
+ val options = Arguments.toBundle(data)
758
+
759
+ options?.let {
760
+ musicService?.updateOptions(it)
761
+ }
762
+
763
+ callback.resolve(null)
764
+ } catch (_: Exception) {}
765
+ }
766
+ }
767
+ }
768
+
769
+ @ReactMethod
770
+ fun updateOptions(data: ReadableMap?, callback: Promise) {
771
+ scope.launch {
772
+ try {
773
+ // keep options as we may need them for correct MetadataManager reinitialization later
774
+ options = Arguments.toBundle(data)
775
+ waitForConnection {
776
+ options?.let { binder?.updateOptions(it) }
777
+ callback.resolve(null)
778
+ }
779
+ } catch (_: Exception) {
780
+ }
781
+ }
782
+ }
783
+
784
+ // @ReactMethod
785
+ // fun setNowPlaying(trackMap: ReadableMap, callback: Promise) {
786
+ // val bundle = Arguments.toBundle(trackMap)
787
+ //
788
+ // waitForConnection {
789
+ // try {
790
+ // val track = bundle?.let {
791
+ // binder?.let { it1 ->
792
+ // Track(
793
+ // reactApplicationContext,
794
+ // it,
795
+ // it1.ratingType
796
+ // )
797
+ // }
798
+ // }
799
+ // if (track != null) {
800
+ // binder?.manager?.currentTrack = track
801
+ // binder?.manager?.metadata?.updateMetadata(track)
802
+ // }
803
+ // } catch (ex: Exception) {
804
+ // callback.reject("invalid_track_object", ex)
805
+ // return@waitForConnection // @waitForConnection
806
+ // }
807
+ //
808
+ // if (binder != null && binder!!.manager != null && binder!!.manager.currentTrack == null)
809
+ // callback.reject("invalid_track_object", "Track is missing a required key")
810
+ //
811
+ // callback.resolve(null)
812
+ // }
813
+ // }
814
+
815
+ @ReactMethod
816
+ fun setNowPlaying(trackMap: ReadableMap, callback: Promise) {
817
+ scope.launch {
818
+ val bundle = Arguments.toBundle(trackMap)
819
+
820
+ waitForConnection {
821
+ try {
822
+ val state = if (trackMap.hasKey("state")) trackMap.getInt("state") else 0
823
+
824
+ var elapsedTime: Long = -1
825
+
826
+ if (trackMap.hasKey("elapsedTime")) {
827
+ elapsedTime = try {
828
+ Utils.toMillis(trackMap.getDouble("elapsedTime"))
829
+ } catch (ex: Exception) {
830
+ Utils.toMillis(trackMap.getInt("elapsedTime").toDouble())
831
+ }
832
+ }
833
+
834
+ if (bundle != null && binder != null) {
835
+ val track = Track(reactApplicationContext, bundle, binder!!.ratingType)
836
+ binder!!.manager.currentTrack = track
837
+ binder!!.manager.metadata.updateMetadata(track)
838
+ binder!!.manager.setState(state, elapsedTime)
839
+ }
840
+ } catch (ex: Exception) {
841
+ callback.reject("invalid_track_object", ex)
842
+ return@waitForConnection
843
+ }
844
+
845
+ if (binder?.manager?.currentTrack == null) {
846
+ callback.reject("invalid_track_object", "Track is missing a required key")
847
+ }
848
+
849
+ callback.resolve(null)
850
+ }
851
+ }
852
+ }
853
+
854
+
855
+ @ReactMethod
856
+ fun setAndroidAutoPlayerTracks(tracksArray: ReadableArray, options: ReadableMap, callback: Promise) {
857
+ scope.launch {
858
+ waitForConnection {
859
+ try {
860
+ Handler(Looper.getMainLooper()).postDelayed({
861
+ val bundle = Arguments.toBundle(options)
862
+ val editQueue = bundle?.getBoolean("editQueue")
863
+
864
+ val tracks = mutableListOf<Track>()
865
+
866
+ for (i in 0 until tracksArray.size()) {
867
+ val trackMap = tracksArray.getMap(i)
868
+ val trackBundle = Arguments.toBundle(trackMap)
869
+
870
+ val track = trackBundle?.let {
871
+ binder?.let { it1 ->
872
+ Track(
873
+ reactApplicationContext,
874
+ it,
875
+ it1.ratingType
876
+ )
877
+ }
878
+ }
879
+
880
+ track?.let { tracks.add(it) }
881
+ }
882
+
883
+ if (tracks.isNotEmpty()) {
884
+ if (editQueue == true) {
885
+ musicService?.removeUpcomingTracks()
886
+ musicService?.removePreviousTracks()
887
+
888
+ musicService?.add(
889
+ tracks,
890
+ 1
891
+ )
892
+ } else {
893
+ if (tracks[0].url.isEmpty()) {
894
+ musicService?.playWhenReady = false
895
+ if (musicService?.state != AudioPlayerState.PAUSED) musicService?.pause()
896
+ } else {
897
+ musicService?.playWhenReady = firstPlayDone
898
+ }
899
+
900
+ if (musicService?.getPlayerQueueHead() == null) {
901
+ musicService?.add(
902
+ tracks,
903
+ 0
904
+ )
905
+ } else {
906
+ musicService?.removeUpcomingTracks()
907
+ musicService?.removePreviousTracks()
908
+
909
+ musicService?.add(
910
+ tracks,
911
+ 1
912
+ )
913
+
914
+ musicService?.skipToNext()
915
+
916
+ musicService?.updateMetadataForTrack(0, tracks[0])
917
+
918
+ musicService?.remove(0)
919
+ }
920
+ }
921
+ }
922
+
923
+ callback.resolve(null)
924
+ }, 100)
925
+ } catch (_: Exception) {}
926
+ }
927
+ }
928
+
929
+ }
930
+
931
+ @ReactMethod
932
+ fun updatePlayback(trackMap: ReadableMap, callback: Promise) {
933
+ val bundle = Arguments.toBundle(trackMap)
934
+ waitForConnection {
935
+ try {
936
+ val state =
937
+ if (trackMap.hasKey("state")) trackMap.getInt("state") else -1
938
+ var elapsedTime: Long = -1
939
+ if (trackMap.hasKey("elapsedTime")) {
940
+ elapsedTime = try {
941
+ Utils.toMillis(trackMap.getInt("elapsedTime").toDouble())
942
+ } catch (ex: Exception) {
943
+ Utils.toMillis(trackMap.getDouble("elapsedTime"))
944
+ }
945
+ }
946
+
947
+ var duration: Long = -1
948
+ if (trackMap.hasKey("duration")) {
949
+ duration = try {
950
+ Utils.toMillis(trackMap.getInt("duration").toDouble())
951
+ } catch (ex: Exception) {
952
+ Utils.toMillis(trackMap.getDouble("duration"))
953
+ }
954
+ }
955
+ //update current track duration
956
+ val currentTrack: Track? =
957
+ binder?.manager?.currentTrack
958
+ if (currentTrack != null && duration > -1) {
959
+ currentTrack.duration = duration
960
+ binder?.manager?.metadata?.updateMetadata(currentTrack)
961
+ }
962
+ if (state > -1) {
963
+ binder?.manager?.setState(state, elapsedTime)
964
+ }
965
+
966
+ if (autoConnectionDetector?.isCarConnected == true || isAndroidTv) {
967
+ Handler(Looper.getMainLooper()).postDelayed({
968
+ val isLoading = bundle?.getBoolean("isLoading")
969
+ if (state == 3) {
970
+ musicService?.play()
971
+ } else if (state == 2) {
972
+ musicService?.pause()
973
+ }
974
+ if (elapsedTime >= 0 && isLoading == false) {
975
+ musicService?.seekTo((elapsedTime / 1000).toFloat())
976
+ }
977
+ }, 100)
978
+ }
979
+
980
+ callback.resolve(null)
981
+
982
+ } catch (ex: Exception) {
983
+ callback.reject("invalid_track_object", ex)
984
+ return@waitForConnection
985
+ }
986
+ if (binder != null && binder!!.manager
987
+ .currentTrack == null
988
+ ) callback.reject("invalid_track_object", "Track is missing a required key")
989
+ }
990
+ }
991
+
992
+
993
+ private fun bundleToTrack(bundle: Bundle): Track? {
994
+ return musicService?.let {
995
+ Track(
996
+ reactApplicationContext.baseContext,
997
+ bundle,
998
+ it.ratingType
999
+ )
1000
+ }
1001
+ }
1002
+
1003
+ private fun readableArrayToTrackList(data: ReadableArray?): MutableList<Track> {
1004
+ val bundleList = Arguments.toList(data)
1005
+ if (bundleList != null) {
1006
+ return bundleList.mapNotNull {
1007
+ if (it is Bundle) {
1008
+ bundleToTrack(it)
1009
+ } else {
1010
+ null
1011
+ // or handle invalid bundle case
1012
+ }
1013
+ }.toMutableList()
1014
+ }
1015
+ return mutableListOf()
1016
+ }
1017
+
1018
+ @ReactMethod
1019
+ fun add(data: ReadableArray?, insertBeforeIndex: Int, callback: Promise) {
1020
+ scope.launch {
1021
+ if (verifyServiceBoundOrReject(callback)) return@launch
1022
+
1023
+ try {
1024
+ val tracks = readableArrayToTrackList(data);
1025
+ if (insertBeforeIndex < -1 || insertBeforeIndex > (musicService?.tracks?.size
1026
+ ?: 0)
1027
+ ) {
1028
+ callback.reject("index_out_of_bounds", "The track index is out of bounds")
1029
+ return@launch
1030
+ }
1031
+ val index =
1032
+ if (insertBeforeIndex == -1) musicService?.tracks?.size else insertBeforeIndex
1033
+ if (index != null) {
1034
+ musicService?.add(
1035
+ tracks,
1036
+ index
1037
+ )
1038
+ }
1039
+ callback.resolve(index)
1040
+ } catch (exception: Exception) {
1041
+ // rejectWithException(callback, exception)
1042
+ }
1043
+ }
1044
+ }
1045
+
1046
+ @ReactMethod
1047
+ fun load(data: ReadableMap?, callback: Promise) {
1048
+ scope.launch {
1049
+ if (verifyServiceBoundOrReject(callback)) return@launch
1050
+
1051
+ try {
1052
+ if (data == null) {
1053
+ callback.resolve(null)
1054
+ return@launch
1055
+ }
1056
+ val bundle = Arguments.toBundle(data);
1057
+ if (bundle is Bundle) {
1058
+ bundleToTrack(bundle)?.let { musicService?.load(it) }
1059
+ callback.resolve(null)
1060
+ } else {
1061
+ callback.reject("invalid_track_object", "Track was not a dictionary type")
1062
+ }
1063
+ } catch (_: Exception) {}
1064
+ }
1065
+ }
1066
+
1067
+ @ReactMethod
1068
+ fun reset(callback: Promise) {
1069
+ waitForConnection {
1070
+ try {
1071
+ if (binder != null) {
1072
+ binder!!.manager.onReset()
1073
+ callback.resolve(null)
1074
+ }
1075
+ } catch (_: Exception) {}
1076
+ }
1077
+ }
1078
+
1079
+ @ReactMethod
1080
+ fun removeNotifications(callback: Promise) {
1081
+ waitForConnection {
1082
+ try {
1083
+ if (binder != null) {
1084
+ binder!!.manager.metadata.removeNotifications()
1085
+ callback.resolve(null)
1086
+ }
1087
+ } catch (_: Exception) {}
1088
+ }
1089
+ }
1090
+
1091
+ @ReactMethod
1092
+ fun resetAndroidAutoService() {
1093
+ scope.launch {
1094
+ // if (verifyServiceBoundOrReject(callback)) return@launch
1095
+
1096
+ try {
1097
+ musicService?.stop()
1098
+ delay(300) // Allow playback to stop
1099
+ musicService?.clear()
1100
+
1101
+ // callback.resolve(null)
1102
+ } catch (_: Exception) {}
1103
+ }
1104
+ }
1105
+
1106
+ @ReactMethod
1107
+ fun clear(callback: Promise) {
1108
+ scope.launch {
1109
+ if (verifyServiceBoundOrReject(callback)) return@launch
1110
+
1111
+ try {
1112
+ musicService?.clear()
1113
+ callback.resolve(null)
1114
+ } catch (_: Exception) {}
1115
+ }
1116
+ }
1117
+
1118
+ @ReactMethod
1119
+ fun play(callback: Promise) {
1120
+ scope.launch {
1121
+ if (verifyServiceBoundOrReject(callback)) return@launch
1122
+
1123
+ try {
1124
+ musicService?.play()
1125
+ callback.resolve(null)
1126
+ } catch (_: Exception) {}
1127
+ }
1128
+ }
1129
+
1130
+ @ReactMethod
1131
+ fun pause(callback: Promise) {
1132
+ scope.launch {
1133
+ if (verifyServiceBoundOrReject(callback)) return@launch
1134
+
1135
+ try {
1136
+ musicService?.pause()
1137
+ callback.resolve(null)
1138
+ } catch (_: Exception) {}
1139
+ }
1140
+ }
1141
+
1142
+ @ReactMethod
1143
+ fun setIsAndroidTv(value: Boolean, callback: Promise) {
1144
+ scope.launch {
1145
+ isAndroidTv = value
1146
+ }
1147
+ }
1148
+
1149
+ @SuppressLint("VisibleForTests")
1150
+ @ReactMethod
1151
+ fun checkAndroidAutoConnection(callback: Promise) {
1152
+ scope.launch {
1153
+ val params = Arguments.createMap()
1154
+ params.putBoolean("connected", autoConnectionDetector?.isCarConnected == true)
1155
+
1156
+ val currentReactContext = if (bridgelessEnabled) reactHost?.currentReactContext else reactNativeHost?.reactInstanceManager?.currentReactContext
1157
+ currentReactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
1158
+ ?.emit(
1159
+ "car-connection-update", params
1160
+ )
1161
+ }
1162
+ }
1163
+
1164
+ override fun onHostResume() {
1165
+ isAppOpen = true
1166
+ }
1167
+
1168
+ override fun onHostPause() {}
1169
+
1170
+ @SuppressLint("VisibleForTests")
1171
+ override fun onHostDestroy() {
1172
+ val currentReactContext = if (bridgelessEnabled) reactHost?.currentReactContext else reactNativeHost?.reactInstanceManager?.currentReactContext
1173
+ isAppOpen = false
1174
+ if (autoConnectionDetector?.isCarConnected == true) {
1175
+ // musicService?.clear()
1176
+
1177
+ Handler(Looper.getMainLooper()).postDelayed({
1178
+ if (autoConnectionDetector?.isCarConnected == true && currentReactContext == null) {
1179
+ musicService?.invokeStartTask(reactApplicationContext, true)
1180
+ }
1181
+ }, 2000)
1182
+ }
1183
+
1184
+ if (isAndroidTv) {
1185
+ musicService?.clear()
1186
+ }
1187
+
1188
+ if (autoConnectionDetector?.isCarConnected != true) {
1189
+ destroy()
1190
+ }
1191
+ }
1192
+ }