@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,678 @@
1
+ package com.guichaguri.trackplayer.kotlinaudio.notification
2
+
3
+ import android.app.Notification
4
+ import android.app.PendingIntent
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.graphics.Bitmap
8
+ import android.graphics.BitmapFactory
9
+ import android.graphics.Color
10
+ import android.graphics.drawable.BitmapDrawable
11
+ import android.net.Uri
12
+ import android.os.Build
13
+ import android.os.Bundle
14
+ import android.support.v4.media.MediaDescriptionCompat
15
+ import android.support.v4.media.MediaMetadataCompat
16
+ import android.support.v4.media.session.MediaSessionCompat
17
+ import android.support.v4.media.session.PlaybackStateCompat
18
+ import android.util.Log
19
+ import androidx.annotation.DrawableRes
20
+ import androidx.core.app.NotificationCompat
21
+ import coil.imageLoader
22
+ import coil.request.Disposable
23
+ import coil.request.ImageRequest
24
+ import com.guichaguri.trackplayer.R
25
+ import com.guichaguri.trackplayer.kotlinaudio.event.NotificationEventHolder
26
+ import com.guichaguri.trackplayer.kotlinaudio.event.PlayerEventHolder
27
+ import com.guichaguri.trackplayer.kotlinaudio.models.AudioItemHolder
28
+ import com.guichaguri.trackplayer.kotlinaudio.models.MediaSessionCallback
29
+ import com.guichaguri.trackplayer.kotlinaudio.models.NotificationButton
30
+ import com.guichaguri.trackplayer.kotlinaudio.models.NotificationConfig
31
+ import com.guichaguri.trackplayer.kotlinaudio.models.NotificationMetadata
32
+ import com.guichaguri.trackplayer.kotlinaudio.models.NotificationState
33
+ import com.google.android.exoplayer2.Player
34
+ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
35
+ import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
36
+ import com.google.android.exoplayer2.ui.PlayerNotificationManager
37
+ import com.google.android.exoplayer2.ui.PlayerNotificationManager.CustomActionReceiver
38
+ import kotlinx.coroutines.MainScope
39
+ import kotlinx.coroutines.launch
40
+ import java.io.File
41
+ import java.net.URI
42
+
43
+ class NotificationManager internal constructor(
44
+ private val context: Context,
45
+ private val player: Player,
46
+ private val mediaSession: MediaSessionCompat,
47
+ private val mediaSessionConnector: MediaSessionConnector,
48
+ val event: NotificationEventHolder,
49
+ val playerEventHolder: PlayerEventHolder
50
+ ) : PlayerNotificationManager.NotificationListener {
51
+ private lateinit var descriptionAdapter: PlayerNotificationManager.MediaDescriptionAdapter
52
+ private var internalNotificationManager: PlayerNotificationManager? = null
53
+ private val scope = MainScope()
54
+ private val buttons = mutableSetOf<NotificationButton?>()
55
+ private var notificationMetadataArtworkDisposable: Disposable? = null
56
+ var notificationMetadata: NotificationMetadata? = null
57
+ set(value) {
58
+ // Clear bitmap cache if artwork changes
59
+ if (field?.artworkUrl != value?.artworkUrl) {
60
+ val holder = getCurrentItemHolder()
61
+ if (holder != null) {
62
+ holder.artworkBitmap = null
63
+ if (value?.artworkUrl != null) {
64
+ notificationMetadataArtworkDisposable?.dispose()
65
+
66
+ try {
67
+ if (value.artworkUrl.startsWith("file")) {
68
+ val uri = URI(value.artworkUrl)
69
+ val sanitizedUri = URI(uri.scheme, null, uri.path, null, null)
70
+ if (sanitizedUri.query == null) {
71
+ val file = File(sanitizedUri)
72
+ if (file.exists()) {
73
+ val bitmap = BitmapFactory.decodeFile(file.absolutePath)
74
+ holder.artworkBitmap = bitmap
75
+ }
76
+ }
77
+ } else {
78
+ notificationMetadataArtworkDisposable = context.imageLoader.enqueue(
79
+ ImageRequest.Builder(context)
80
+ .data(value.artworkUrl)
81
+ .target { result ->
82
+ val bitmap = (result as BitmapDrawable).bitmap
83
+ holder.artworkBitmap = bitmap
84
+ }
85
+ .build()
86
+ )
87
+ }
88
+ } catch (_: Exception) {}
89
+ }
90
+ }
91
+ }
92
+ field = value
93
+ invalidate()
94
+ }
95
+
96
+
97
+ var showPlayPauseButton = false
98
+ set(value) {
99
+ scope.launch {
100
+ field = value
101
+ internalNotificationManager?.setUsePlayPauseActions(value)
102
+ }
103
+ }
104
+
105
+ var showStopButton = false
106
+ set(value) {
107
+ scope.launch {
108
+ field = value
109
+ internalNotificationManager?.setUseStopAction(value)
110
+ }
111
+ }
112
+
113
+ var showForwardButton = false
114
+ set(value) {
115
+ scope.launch {
116
+ field = value
117
+ internalNotificationManager?.setUseFastForwardAction(value)
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Controls whether or not this button should appear when the notification is compact (collapsed).
123
+ */
124
+ var showForwardButtonCompact = false
125
+ set(value) {
126
+ scope.launch {
127
+ field = value
128
+ internalNotificationManager?.setUseFastForwardActionInCompactView(value)
129
+ }
130
+ }
131
+
132
+ var showRewindButton = false
133
+ set(value) {
134
+ scope.launch {
135
+ field = value
136
+ internalNotificationManager?.setUseRewindAction(value)
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Controls whether or not this button should appear when the notification is compact (collapsed).
142
+ */
143
+ var showRewindButtonCompact = false
144
+ set(value) {
145
+ scope.launch {
146
+ field = value
147
+ internalNotificationManager?.setUseRewindActionInCompactView(value)
148
+ }
149
+ }
150
+
151
+ var showNextButton = false
152
+ set(value) {
153
+ scope.launch {
154
+ field = value
155
+ internalNotificationManager?.setUseNextAction(value)
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Controls whether or not this button should appear when the notification is compact (collapsed).
161
+ */
162
+ var showNextButtonCompact = false
163
+ set(value) {
164
+ scope.launch {
165
+ field = value
166
+ internalNotificationManager?.setUseNextActionInCompactView(value)
167
+ }
168
+ }
169
+
170
+ var showPreviousButton = false
171
+ set(value) {
172
+ scope.launch {
173
+ field = value
174
+ internalNotificationManager?.setUsePreviousAction(value)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Controls whether or not this button should appear when the notification is compact (collapsed).
180
+ */
181
+ var showPreviousButtonCompact = false
182
+ set(value) {
183
+ scope.launch {
184
+ field = value
185
+ internalNotificationManager?.setUsePreviousActionInCompactView(value)
186
+ }
187
+ }
188
+
189
+ var stopIcon: Int? = null
190
+ var forwardIcon: Int? = null
191
+ var rewindIcon: Int? = null
192
+ var customIcons: MutableMap<String, Int?> = mutableMapOf()
193
+
194
+ private fun getCurrentItemHolder(): AudioItemHolder? {
195
+ return player?.currentMediaItem?.localConfiguration?.tag as AudioItemHolder?
196
+ }
197
+
198
+ init {
199
+ mediaSessionConnector.setQueueNavigator(
200
+ object : TimelineQueueNavigator(mediaSession) {
201
+ override fun getSupportedQueueNavigatorActions(player: Player): Long {
202
+ return buttons.fold(0) { acc, button ->
203
+ acc or when (button) {
204
+ is NotificationButton.NEXT -> {
205
+ PlaybackStateCompat.ACTION_SKIP_TO_NEXT
206
+ }
207
+ is NotificationButton.PREVIOUS -> {
208
+ PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
209
+ }
210
+ else -> {
211
+ 0
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ override fun getMediaDescription(
218
+ player: Player,
219
+ windowIndex: Int
220
+ ): MediaDescriptionCompat {
221
+ val currentNotificationMetadata =
222
+ if (windowIndex == player.currentMediaItemIndex)
223
+ notificationMetadata else null
224
+ val mediaItem = player.getMediaItemAt(windowIndex)
225
+ val audioItemHolder = (mediaItem.localConfiguration?.tag as AudioItemHolder)
226
+ var title = currentNotificationMetadata?.title ?: mediaItem.mediaMetadata.title
227
+ ?: audioItemHolder.audioItem.title
228
+ var artist =
229
+ currentNotificationMetadata?.artist ?: mediaItem.mediaMetadata.artist
230
+ ?: audioItemHolder.audioItem.artist
231
+ return MediaDescriptionCompat.Builder().apply {
232
+ setTitle(title)
233
+ setSubtitle(artist)
234
+ setExtras(Bundle().apply {
235
+ putString(MediaMetadataCompat.METADATA_KEY_TITLE, title as String?)
236
+ putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist as String?)
237
+ })
238
+ setIconUri(mediaItem?.mediaMetadata?.artworkUri ?: Uri.parse(audioItemHolder?.audioItem?.artwork
239
+ ?: ""))
240
+ setIconBitmap(audioItemHolder?.artworkBitmap)
241
+ }.build()
242
+ }
243
+ }
244
+ )
245
+ mediaSessionConnector.setMetadataDeduplicationEnabled(true)
246
+ }
247
+
248
+ fun getMediaMetadataCompat(): MediaMetadataCompat {
249
+ return MediaMetadataCompat.Builder().apply {
250
+ val audioHolder = getCurrentItemHolder()
251
+ val mediaItem = audioHolder?.audioItem
252
+ val currentMediaMetadata = player.currentMediaItem?.mediaMetadata
253
+ putString(MediaMetadataCompat.METADATA_KEY_ARTIST, notificationMetadata?.artist?: mediaItem?.artist)
254
+ putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationMetadata?.title?: mediaItem?.title)
255
+ putString(MediaMetadataCompat.METADATA_KEY_ALBUM, mediaItem?.albumTitle)
256
+ putString(MediaMetadataCompat.METADATA_KEY_GENRE, currentMediaMetadata?.genre.toString())
257
+ putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationMetadata?.duration ?: mediaItem?.duration?: player.duration)
258
+
259
+ if (audioHolder?.artworkBitmap != null) {
260
+ putBitmap(MediaMetadataCompat.METADATA_KEY_ART, audioHolder.artworkBitmap)
261
+ } else {
262
+ val artworkUrl = notificationMetadata?.artworkUrl ?: mediaItem?.artwork
263
+ if (artworkUrl != null) {
264
+ if (artworkUrl.startsWith("file")) {
265
+ try {
266
+ val uri = URI(artworkUrl)
267
+ val sanitizedUri = URI(uri.scheme, null, uri.path, null, null)
268
+ if (sanitizedUri.query == null) {
269
+ val file = File(sanitizedUri)
270
+ if (file.exists()) {
271
+ val bitmap = BitmapFactory.decodeFile(file.absolutePath)
272
+ putBitmap(
273
+ MediaMetadataCompat.METADATA_KEY_ART,
274
+ bitmap
275
+ )
276
+ }
277
+ }
278
+ } catch (_: Exception) {}
279
+ } else {
280
+ putString(
281
+ MediaMetadataCompat.METADATA_KEY_ART_URI,
282
+ artworkUrl
283
+ )
284
+ }
285
+ }
286
+ }
287
+ }.build()
288
+ }
289
+
290
+ private fun createNotificationAction(
291
+ drawable: Int,
292
+ action: String,
293
+ instanceId: Int
294
+ ): NotificationCompat.Action {
295
+ val intent: Intent = Intent(action).setPackage(context.packageName)
296
+ val pendingIntent = PendingIntent.getBroadcast(
297
+ context,
298
+ instanceId,
299
+ intent,
300
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
301
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
302
+ } else {
303
+ PendingIntent.FLAG_CANCEL_CURRENT
304
+ }
305
+ )
306
+ return NotificationCompat.Action.Builder(drawable, action, pendingIntent).build()
307
+ }
308
+
309
+ private fun handlePlayerAction(action: String) {
310
+ when (action) {
311
+ REWIND -> {
312
+ playerEventHolder.updateOnPlayerActionTriggeredExternally(MediaSessionCallback.REWIND)
313
+ }
314
+ FORWARD -> {
315
+ playerEventHolder.updateOnPlayerActionTriggeredExternally(MediaSessionCallback.FORWARD)
316
+ }
317
+ STOP -> {
318
+ playerEventHolder.updateOnPlayerActionTriggeredExternally(MediaSessionCallback.STOP)
319
+ }
320
+ else -> {
321
+ playerEventHolder.updateOnPlayerActionTriggeredExternally(MediaSessionCallback.CUSTOMACTION(action))
322
+ }
323
+
324
+ }
325
+ }
326
+
327
+ private val customActionReceiver = object : CustomActionReceiver {
328
+ override fun createCustomActions(
329
+ context: Context,
330
+ instanceId: Int
331
+ ): MutableMap<String, NotificationCompat.Action> {
332
+ if (!needsCustomActionsToAddMissingButtons) return mutableMapOf()
333
+ var actionMap: MutableMap<String, NotificationCompat.Action> = mutableMapOf()
334
+ // only use rewind/forward/stop mapping with android >13.
335
+ if (Build.VERSION.SDK_INT >= 33) {
336
+ actionMap = mutableMapOf(
337
+ REWIND to createNotificationAction(
338
+ rewindIcon ?: DEFAULT_REWIND_ICON,
339
+ REWIND,
340
+ instanceId
341
+ ),
342
+ FORWARD to createNotificationAction(
343
+ forwardIcon ?: DEFAULT_FORWARD_ICON,
344
+ FORWARD,
345
+ instanceId
346
+ ),
347
+ STOP to createNotificationAction(
348
+ stopIcon ?: DEFAULT_STOP_ICON,
349
+ STOP,
350
+ instanceId
351
+ )
352
+ )
353
+ }
354
+ customIcons.forEach { (key, value) ->
355
+ actionMap[key] = createNotificationAction(value?: DEFAULT_STOP_ICON, key, instanceId)
356
+ }
357
+ return actionMap
358
+ }
359
+
360
+ override fun getCustomActions(player: Player): List<String> {
361
+ if (!needsCustomActionsToAddMissingButtons) return emptyList()
362
+ return buttons.mapNotNull {
363
+ when (it) {
364
+ is NotificationButton.BACKWARD -> {
365
+ REWIND
366
+ }
367
+ is NotificationButton.FORWARD -> {
368
+ FORWARD
369
+ }
370
+ is NotificationButton.STOP -> {
371
+ STOP
372
+ }
373
+ is NotificationButton.CUSTOM_ACTION -> {
374
+ it.customAction
375
+ }
376
+ else -> {
377
+ null
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ override fun onCustomAction(player: Player, action: String, intent: Intent) {
384
+ handlePlayerAction(action)
385
+ }
386
+ }
387
+
388
+ fun invalidate() {
389
+ internalNotificationManager?.invalidate()
390
+ mediaSessionConnector.invalidateMediaSessionQueue()
391
+ mediaSessionConnector.invalidateMediaSessionMetadata()
392
+ }
393
+
394
+ /**
395
+ * Create a media player notification that automatically updates.
396
+ *
397
+ * **NOTE:** You should only call this once. Subsequent calls will result in an error.
398
+ */
399
+ fun createNotification(config: NotificationConfig) = scope.launch {
400
+ buttons.apply {
401
+ clear()
402
+ addAll(config.buttons)
403
+ }
404
+
405
+ stopIcon = null
406
+ forwardIcon = null
407
+ rewindIcon = null
408
+ mediaSessionConnector.setEnabledPlaybackActions(
409
+ buttons.fold(
410
+ PlaybackStateCompat.ACTION_SET_REPEAT_MODE
411
+ or PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
412
+ or PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED
413
+ ) { acc, button ->
414
+ acc or when (button) {
415
+ is NotificationButton.PLAY_PAUSE -> {
416
+ PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE
417
+ }
418
+ is NotificationButton.PREVIOUS -> {
419
+ rewindIcon = button.icon ?: rewindIcon
420
+ PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
421
+ }
422
+ is NotificationButton.NEXT -> {
423
+ forwardIcon = button.icon ?: forwardIcon
424
+ PlaybackStateCompat.ACTION_SKIP_TO_NEXT
425
+ }
426
+ is NotificationButton.SEEK_TO -> {
427
+ PlaybackStateCompat.ACTION_SEEK_TO
428
+ }
429
+ is NotificationButton.STOP -> {
430
+ stopIcon = button.icon ?: stopIcon
431
+ PlaybackStateCompat.ACTION_STOP
432
+ }
433
+ is NotificationButton.CUSTOM_ACTION -> {
434
+ stopIcon = button.icon ?: stopIcon
435
+ customIcons[button.customAction ?: "DEFAULT_CUSTOM"] = button.icon ?: stopIcon
436
+ PlaybackStateCompat.ACTION_SET_RATING
437
+ }
438
+ else -> {
439
+ 0
440
+ }
441
+ }
442
+ }
443
+ )
444
+ descriptionAdapter = object : PlayerNotificationManager.MediaDescriptionAdapter {
445
+ override fun getCurrentContentTitle(player: Player): CharSequence {
446
+ return notificationMetadata?.title
447
+ ?: player.mediaMetadata.title
448
+ ?: ""
449
+ }
450
+
451
+ override fun createCurrentContentIntent(player: Player): PendingIntent? {
452
+ return config.pendingIntent
453
+ }
454
+
455
+ override fun getCurrentContentText(player: Player): CharSequence? {
456
+ return notificationMetadata?.artist
457
+ ?: player.mediaMetadata.artist
458
+ ?: player.mediaMetadata.albumArtist
459
+ ?: ""
460
+ }
461
+
462
+ override fun getCurrentSubText(player: Player): CharSequence? {
463
+ return player.mediaMetadata.displayTitle
464
+ }
465
+
466
+ override fun getCurrentLargeIcon(
467
+ player: Player,
468
+ callback: PlayerNotificationManager.BitmapCallback,
469
+ ): Bitmap? {
470
+ val holder = getCurrentItemHolder() ?: return null
471
+ val source = notificationMetadata?.artworkUrl ?: player.mediaMetadata.artworkUri
472
+ val data = player.mediaMetadata.artworkData
473
+
474
+ if (notificationMetadata?.artworkUrl == null && data != null) {
475
+ return BitmapFactory.decodeByteArray(data, 0, data.size)
476
+ }
477
+
478
+ if (source == null) {
479
+ return null
480
+ }
481
+
482
+ if (holder.artworkBitmap != null) {
483
+ return holder.artworkBitmap
484
+ }
485
+
486
+ context.imageLoader.enqueue(
487
+ ImageRequest.Builder(context)
488
+ .data(source)
489
+ .target { result ->
490
+ val bitmap = (result as BitmapDrawable).bitmap
491
+ holder.artworkBitmap = bitmap
492
+ callback.onBitmap(bitmap)
493
+ }
494
+ .build()
495
+ )
496
+ return holder.artworkBitmap
497
+ }
498
+ }
499
+
500
+ if (needsCustomActionsToAddMissingButtons) {
501
+ val customActionProviders = buttons
502
+ // .sortedBy {
503
+ // when (it) {
504
+ // is NotificationButton.BACKWARD -> 1
505
+ // is NotificationButton.FORWARD -> 2
506
+ // is NotificationButton.STOP -> 3
507
+ // else -> 4
508
+ // }
509
+ // }
510
+ .mapNotNull {
511
+ when (it) {
512
+ // is NotificationButton.BACKWARD -> {
513
+ // Log.d("CustomActionProvider IN", "CustomActionProvider IN rewind " + it.customAction.toString())
514
+ // createMediaSessionAction(rewindIcon ?: DEFAULT_REWIND_ICON, REWIND)
515
+ // }
516
+ // is NotificationButton.FORWARD -> {
517
+ // Log.d("CustomActionProvider IN", "CustomActionProvider IN forward " + it.customAction.toString())
518
+ // createMediaSessionAction(forwardIcon ?: DEFAULT_FORWARD_ICON, FORWARD)
519
+ // }
520
+ // is NotificationButton.STOP -> {
521
+ // Log.d("CustomActionProvider IN", "CustomActionProvider IN stop " + it.customAction.toString())
522
+ // createMediaSessionAction(stopIcon ?: DEFAULT_STOP_ICON, STOP)
523
+ // }
524
+ is NotificationButton.CUSTOM_ACTION -> {
525
+ createMediaSessionAction(customIcons[it.customAction] ?: DEFAULT_CUSTOM_ICON, it.customAction ?: "NO_ACTION_CODE_PROVIDED")
526
+ }
527
+ else -> {
528
+ null
529
+ }
530
+ }
531
+ }
532
+
533
+ mediaSessionConnector.setCustomActionProviders(*customActionProviders.toTypedArray())
534
+ }
535
+
536
+ internalNotificationManager =
537
+ PlayerNotificationManager.Builder(context, NOTIFICATION_ID, CHANNEL_ID)
538
+ .apply {
539
+ // setChannelNameResourceId(R.string.playback_channel_name)
540
+ setMediaDescriptionAdapter(descriptionAdapter)
541
+ setCustomActionReceiver(customActionReceiver)
542
+ setNotificationListener(this@NotificationManager)
543
+
544
+ internalNotificationManager.apply {
545
+ showPlayPauseButton = false
546
+ showForwardButton = false
547
+ showRewindButton = false
548
+ showNextButton = false
549
+ showPreviousButton = false
550
+ showStopButton = false
551
+ }
552
+
553
+ for (button in buttons) {
554
+ if (button == null) continue
555
+ when (button) {
556
+ is NotificationButton.PLAY_PAUSE -> {
557
+ button.playIcon?.let { setPlayActionIconResourceId(it) }
558
+ button.pauseIcon?.let { setPauseActionIconResourceId(it) }
559
+ }
560
+ is NotificationButton.NEXT -> button.icon?.let {
561
+ setNextActionIconResourceId(
562
+ it
563
+ )
564
+ }
565
+ is NotificationButton.PREVIOUS -> button.icon?.let {
566
+ setPreviousActionIconResourceId(
567
+ it
568
+ )
569
+ }
570
+ else -> {}
571
+ }
572
+ }
573
+ }.build().apply {
574
+ setColor(config.accentColor ?: Color.TRANSPARENT)
575
+ config.smallIcon?.let { setSmallIcon(it) }
576
+ for (button in buttons) {
577
+ if (button == null) continue
578
+ when (button) {
579
+ is NotificationButton.PLAY_PAUSE -> {
580
+ showPlayPauseButton = true
581
+ }
582
+ is NotificationButton.STOP -> {
583
+ showStopButton = true
584
+ }
585
+ is NotificationButton.FORWARD -> {
586
+ showForwardButton = true
587
+ showForwardButtonCompact = button.isCompact
588
+ }
589
+ is NotificationButton.BACKWARD -> {
590
+ showRewindButton = true
591
+ showRewindButtonCompact = button.isCompact
592
+ }
593
+ is NotificationButton.NEXT -> {
594
+ showNextButton = true
595
+ showNextButtonCompact = button.isCompact
596
+ }
597
+ is NotificationButton.PREVIOUS -> {
598
+ showPreviousButton = true
599
+ showPreviousButtonCompact = button.isCompact
600
+ }
601
+ else -> {}
602
+ }
603
+ }
604
+
605
+ // REMOVE THE COMMENTS BELOW TO USE ANDROID AUTO PLAYER NOTIFICATION
606
+
607
+ //setMediaSessionToken(mediaSession.sessionToken)
608
+ // setPlayer(player)
609
+ }
610
+ }
611
+
612
+ fun hideNotification() = scope.launch {
613
+ internalNotificationManager?.setPlayer(null)
614
+ }
615
+
616
+ override fun onNotificationPosted(
617
+ notificationId: Int,
618
+ notification: Notification,
619
+ ongoing: Boolean
620
+ ) {
621
+ scope.launch {
622
+ event.updateNotificationState(
623
+ NotificationState.POSTED(
624
+ notificationId,
625
+ notification,
626
+ ongoing
627
+ )
628
+ )
629
+ }
630
+ }
631
+
632
+ override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
633
+ scope.launch {
634
+ event.updateNotificationState(NotificationState.CANCELLED(notificationId))
635
+ }
636
+ }
637
+
638
+ internal fun destroy() = scope.launch {
639
+ internalNotificationManager?.setPlayer(null)
640
+ }
641
+
642
+ private fun createMediaSessionAction(
643
+ @DrawableRes drawableRes: Int,
644
+ actionName: String
645
+ ): MediaSessionConnector.CustomActionProvider {
646
+ return object : MediaSessionConnector.CustomActionProvider {
647
+ override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? {
648
+ return PlaybackStateCompat.CustomAction.Builder(actionName, actionName, drawableRes)
649
+ .build()
650
+ }
651
+
652
+ override fun onCustomAction(player: Player, action: String, extras: Bundle?) {
653
+ handlePlayerAction(action)
654
+ }
655
+ }
656
+ }
657
+
658
+ companion object {
659
+ // Due to the removal of rewind, forward, and stop buttons from the standard notification
660
+ // controls in Android 13, custom actions are implemented to support them
661
+ // https://developer.android.com/about/versions/13/behavior-changes-13#playback-controls
662
+ private val needsCustomActionsToAddMissingButtons = true
663
+ public const val REWIND = "rewind"
664
+ public const val FORWARD = "forward"
665
+ public const val STOP = "stop"
666
+ public const val SHUFFLE = "shuffle"
667
+ private const val NOTIFICATION_ID = 1
668
+ private const val CHANNEL_ID = "kotlin_audio_player"
669
+ private val DEFAULT_STOP_ICON =
670
+ com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop
671
+ private val DEFAULT_REWIND_ICON =
672
+ com.google.android.exoplayer2.ui.R.drawable.exo_notification_rewind
673
+ private val DEFAULT_FORWARD_ICON =
674
+ com.google.android.exoplayer2.ui.R.drawable.exo_notification_fastforward
675
+ private val DEFAULT_CUSTOM_ICON =
676
+ com.google.android.exoplayer2.ui.R.drawable.exo_edit_mode_logo
677
+ }
678
+ }
@@ -0,0 +1,10 @@
1
+ package com.guichaguri.trackplayer.kotlinaudio.players
2
+
3
+ import android.content.Context
4
+ import com.doublesymmetry.kotlinaudio.players.BaseAudioPlayer
5
+ import com.guichaguri.trackplayer.kotlinaudio.models.BufferConfig
6
+ import com.guichaguri.trackplayer.kotlinaudio.models.CacheConfig
7
+ import com.guichaguri.trackplayer.kotlinaudio.models.PlayerConfig
8
+ import com.guichaguri.trackplayer.kotlinaudio.models.AAMediaSessionCallBack
9
+
10
+ class AudioPlayer(context: Context, playerConfig: PlayerConfig = PlayerConfig(), bufferConfig: BufferConfig? = null, cacheConfig: CacheConfig? = null, mediaSessionCallback: AAMediaSessionCallBack): BaseAudioPlayer(context, playerConfig, bufferConfig, cacheConfig, mediaSessionCallback)