@javascriptcommon/react-native-track-player 4.1.14 → 4.1.16

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.
@@ -21,8 +21,8 @@ object BundleUtils {
21
21
  if (!data!!.containsKey(key)) return null
22
22
  val obj = data[key]
23
23
  if (obj is String) {
24
- // Remote or Local Uri
25
- if (obj.trim { it <= ' ' }.isEmpty()) throw RuntimeException("$key: The URL cannot be empty")
24
+ // Remote or Local Uri - allow empty URLs for placeholder tracks
25
+ if (obj.trim { it <= ' ' }.isEmpty()) return Uri.EMPTY
26
26
  return Uri.parse(obj as String?)
27
27
  } else if (obj is Bundle) {
28
28
  // require/import
@@ -77,15 +77,23 @@ enum class MediaType(val value: String) {
77
77
 
78
78
 
79
79
  fun audioItem2MediaItem(audioItem: AudioItem, context: Context? = null): MediaItem {
80
+ // Check if this is a placeholder track (notPlayable flag from TrackAudioItem)
81
+ val isNotPlayable = (audioItem as? com.doublesymmetry.trackplayer.model.TrackAudioItem)?.notPlayable ?: false
82
+ val hasValidUrl = audioItem.audioUrl.isNotBlank()
83
+
80
84
  return MediaItem.Builder()
81
85
  .setMediaId(audioItem.mediaId ?: UUID.randomUUID().toString())
86
+ // Always set URI (even empty string) so ExoPlayer adds item to timeline
82
87
  .setUri(audioItem.audioUrl)
83
88
  .setMediaMetadata(
84
89
  MediaMetadata.Builder()
85
90
  .setTitle(audioItem.title)
86
91
  .setArtist(audioItem.artist)
92
+ // Mark placeholder tracks as not playable so Android Auto shows them correctly in queue
93
+ .setIsPlayable(true)
94
+ .setIsBrowsable(false)
87
95
  .setArtworkUri((
88
- if (context != null && audioItem.audioUrl.startsWith("file://")) {
96
+ if (context != null && hasValidUrl && audioItem.audioUrl.startsWith("file://")) {
89
97
  saveMediaCoverToPng(
90
98
  audioItem.audioUrl,
91
99
  context.contentResolver,
@@ -94,7 +102,7 @@ fun audioItem2MediaItem(audioItem: AudioItem, context: Context? = null): MediaIt
94
102
  ?: audioItem.artwork
95
103
  }
96
104
  else audioItem.artwork)?.toUri())
97
- .setArtworkData(if (audioItem.audioUrl.startsWith("file://")) getEmbeddedBitmapArray(
105
+ .setArtworkData(if (hasValidUrl && audioItem.audioUrl.startsWith("file://")) getEmbeddedBitmapArray(
98
106
  audioItem.audioUrl.substring(7)) else null, MediaMetadata.PICTURE_TYPE_MEDIA)
99
107
  .setExtras(Bundle().apply {
100
108
  audioItem.options?.headers?.let {
@@ -108,6 +116,7 @@ fun audioItem2MediaItem(audioItem: AudioItem, context: Context? = null): MediaIt
108
116
  }
109
117
  putString("type", audioItem.type.toString())
110
118
  putString("uri", audioItem.audioUrl)
119
+ putBoolean("notPlayable", isNotPlayable)
111
120
  }).build())
112
121
  .setTag(audioItem)
113
122
  .build()
@@ -214,7 +214,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
214
214
  // Reset playback values without updating, because that will happen in
215
215
  // the loadNowPlayingMetaValues call straight after:
216
216
  nowPlayingInfoController.setWithoutUpdate(keyValues: [
217
- MediaItemProperty.duration(nil),
217
+ MediaItemProperty.duration(item.getDuration()),
218
218
  NowPlayingInfoProperty.playbackRate(nil),
219
219
  NowPlayingInfoProperty.elapsedPlaybackTime(nil)
220
220
  ])
@@ -328,6 +328,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
328
328
  MediaItemProperty.artist(item.getArtist()),
329
329
  MediaItemProperty.title(item.getTitle()),
330
330
  MediaItemProperty.albumTitle(item.getAlbumTitle()),
331
+ MediaItemProperty.duration(item.getDuration()),
331
332
  ])
332
333
  loadArtwork(forItem: item)
333
334
  }
@@ -341,8 +342,10 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
341
342
  - Playback rate
342
343
  */
343
344
  func updateNowPlayingPlaybackValues() {
345
+ // Use wrapper.duration if valid, otherwise fallback to currentItem's duration (from metadata)
346
+ let duration = wrapper.duration > 0 ? wrapper.duration : (currentItem?.getDuration() ?? 0)
344
347
  nowPlayingInfoController.set(keyValues: [
345
- MediaItemProperty.duration(wrapper.duration),
348
+ MediaItemProperty.duration(duration),
346
349
  NowPlayingInfoProperty.playbackRate(wrapper.playWhenReady ? Double(wrapper.rate) : 0),
347
350
  NowPlayingInfoProperty.elapsedPlaybackTime(wrapper.currentTime)
348
351
  ])
@@ -422,6 +425,9 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
422
425
 
423
426
  func AVWrapper(didUpdateDuration duration: Double) {
424
427
  event.updateDuration.emit(data: duration)
428
+ /*if(automaticallyUpdateNowPlayingInfo){
429
+ updateNowPlayingPlaybackValues()
430
+ }*/
425
431
  }
426
432
 
427
433
  func AVWrapper(didReceiveCommonMetadata metadata: [AVMetadataItem]) {
@@ -104,9 +104,9 @@ class AVPlayerItemObserver: NSObject {
104
104
  }
105
105
 
106
106
  case AVPlayerItemKeyPath.loadedTimeRanges:
107
- if let ranges = change?[.newKey] as? [NSValue], let duration = ranges.first?.timeRangeValue.duration {
108
- delegate?.item(didUpdateDuration: duration.seconds)
109
- }
107
+ // Note: loadedTimeRanges represents buffered content, not total duration.
108
+ // Do NOT call didUpdateDuration here - it would report buffer size instead of actual duration.
109
+ break
110
110
 
111
111
  case AVPlayerItemKeyPath.playbackLikelyToKeepUp:
112
112
  if let playbackLikelyToKeepUp = change?[.newKey] as? Bool {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javascriptcommon/react-native-track-player",
3
- "version": "4.1.14",
3
+ "version": "4.1.16",
4
4
  "description": "A fully fledged audio module created for music apps",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",