react-native-nitro-player 0.5.0 → 0.5.1

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.
@@ -125,9 +125,10 @@ class TrackPlayerCore: NSObject {
125
125
 
126
126
  // MARK: - Gapless Playback Configuration
127
127
 
128
- // Disable automatic waiting to minimize stalling - this allows smoother transitions
129
- // between tracks as AVPlayer won't pause to buffer excessively
130
- player?.automaticallyWaitsToMinimizeStalling = false
128
+ // Start with stall-waiting enabled so the first track buffers before playing.
129
+ // Once the first item is ready (readyToPlay), this is flipped to false for
130
+ // gapless inter-track transitions (see setupCurrentItemObservers).
131
+ player?.automaticallyWaitsToMinimizeStalling = true
131
132
 
132
133
  // Set playback rate to 1.0 immediately when ready (reduces gap between tracks)
133
134
  player?.actionAtItemEnd = .advance
@@ -138,7 +139,7 @@ class TrackPlayerCore: NSObject {
138
139
  }
139
140
 
140
141
  print(
141
- "šŸŽµ TrackPlayerCore: Gapless playback configured - automaticallyWaitsToMinimizeStalling=false")
142
+ "šŸŽµ TrackPlayerCore: Gapless playback configured - automaticallyWaitsToMinimizeStalling=true (flipped to false on first readyToPlay)")
142
143
 
143
144
  setupPlayerObservers()
144
145
  }
@@ -569,6 +570,8 @@ class TrackPlayerCore: NSObject {
569
570
  if item.status == .readyToPlay {
570
571
  print("āœ… TrackPlayerCore: Item ready, setting up boundaries")
571
572
  self?.setupBoundaryTimeObserver()
573
+ // First item is buffered and ready — disable stall waiting for gapless inter-track transitions
574
+ self?.player?.automaticallyWaitsToMinimizeStalling = false
572
575
  // Update now playing info now that duration is available
573
576
  self?.mediaSessionManager?.updateNowPlayingInfo()
574
577
  } else if item.status == .failed {
@@ -736,12 +739,11 @@ class TrackPlayerCore: NSObject {
736
739
  asset = preloadedAsset
737
740
  print("šŸš€ TrackPlayerCore: Using preloaded asset for \(track.title)")
738
741
  } else {
739
- // Create asset with options optimized for gapless playback
740
- asset = AVURLAsset(
741
- url: url,
742
- options: [
743
- AVURLAssetPreferPreciseDurationAndTimingKey: true // Ensures accurate duration for gapless transitions
744
- ])
742
+ // No AVURLAssetPreferPreciseDurationAndTimingKey — gapless playback is achieved via
743
+ // AVQueuePlayer's internal audio buffer pre-roll, not timing metadata.
744
+ // Precise timing only helps with accurate VBR duration display, at the cost of
745
+ // deep file scanning that delays readyToPlay.
746
+ asset = AVURLAsset(url: url)
745
747
  }
746
748
 
747
749
  let item = AVPlayerItem(asset: asset)
@@ -807,11 +809,7 @@ class TrackPlayerCore: NSObject {
807
809
 
808
810
  guard let url = URL(string: track.url) else { continue }
809
811
 
810
- let asset = AVURLAsset(
811
- url: url,
812
- options: [
813
- AVURLAssetPreferPreciseDurationAndTimingKey: true
814
- ])
812
+ let asset = AVURLAsset(url: url)
815
813
 
816
814
  // Preload essential keys for gapless playback
817
815
  asset.loadValuesAsynchronously(forKeys: Constants.preloadAssetKeys) { [weak self] in
@@ -1000,7 +998,7 @@ class TrackPlayerCore: NSObject {
1000
998
  print("šŸ“‹ TrackPlayerCore: UPDATE PLAYER QUEUE - Received \(tracks.count) tracks")
1001
999
  print(String(repeating: "=", count: Constants.separatorLineLength))
1002
1000
 
1003
- // Print the full playlist being fed and check download status
1001
+ #if DEBUG
1004
1002
  for (index, track) in tracks.enumerated() {
1005
1003
  let isDownloaded = DownloadManagerCore.shared.isTrackDownloaded(trackId: track.id)
1006
1004
  let downloadStatus = isDownloaded ? "šŸ“„ DOWNLOADED" : "🌐 REMOTE"
@@ -1013,6 +1011,7 @@ class TrackPlayerCore: NSObject {
1013
1011
  }
1014
1012
  }
1015
1013
  print(String(repeating: "=", count: Constants.separatorLineLength) + "\n")
1014
+ #endif
1016
1015
 
1017
1016
  // Store tracks for index tracking
1018
1017
  currentTracks = tracks
@@ -1025,12 +1024,15 @@ class TrackPlayerCore: NSObject {
1025
1024
  boundaryTimeObserver = nil
1026
1025
  }
1027
1026
 
1027
+ // Re-enable stall waiting for the new first track so it buffers before playing.
1028
+ // Will be flipped back to false once the first item reaches readyToPlay.
1029
+ player?.automaticallyWaitsToMinimizeStalling = true
1030
+
1028
1031
  // Clear old preloaded assets when loading new queue
1029
1032
  preloadedAssets.removeAll()
1030
1033
 
1031
1034
  // Create gapless-optimized AVPlayerItems from tracks
1032
1035
  let items = tracks.enumerated().compactMap { (index, track) -> AVPlayerItem? in
1033
- // First few items get preload treatment for faster initial playback
1034
1036
  let isPreload = index < Constants.gaplessPreloadCount
1035
1037
  return createGaplessPlayerItem(for: track, isPreload: isPreload)
1036
1038
  }
@@ -1068,23 +1070,25 @@ class TrackPlayerCore: NSObject {
1068
1070
  }
1069
1071
  }
1070
1072
 
1071
- // Verify what's actually in the player now
1073
+ #if DEBUG
1074
+ let trackById = Dictionary(uniqueKeysWithValues: tracks.map { ($0.id, $0) })
1072
1075
  print(
1073
1076
  "\nšŸ” TrackPlayerCore: VERIFICATION - Player now has \(existingPlayer.items().count) items:")
1074
1077
  for (index, item) in existingPlayer.items().enumerated() {
1075
- if let trackId = item.trackId, let track = tracks.first(where: { $0.id == trackId }) {
1078
+ if let trackId = item.trackId, let track = trackById[trackId] {
1076
1079
  print(" [\(index + 1)] āœ“ \(track.title) - \(track.artist) (ID: \(track.id))")
1077
1080
  } else {
1078
1081
  print(" [\(index + 1)] āš ļø Unknown item (no trackId)")
1079
1082
  }
1080
1083
  }
1081
-
1082
- if let currentItem = existingPlayer.currentItem, let trackId = currentItem.trackId {
1083
- if let track = tracks.first(where: { $0.id == trackId }) {
1084
- print("ā–¶ļø Current item: \(track.title)")
1085
- }
1084
+ if let currentItem = existingPlayer.currentItem,
1085
+ let trackId = currentItem.trackId,
1086
+ let track = trackById[trackId]
1087
+ {
1088
+ print("ā–¶ļø Current item: \(track.title)")
1086
1089
  }
1087
1090
  print(String(repeating: "=", count: Constants.separatorLineLength) + "\n")
1091
+ #endif
1088
1092
 
1089
1093
  // Note: Boundary time observers will be set up automatically when item becomes ready
1090
1094
  // This happens in setupCurrentItemObservers() -> status observer -> setupBoundaryTimeObserver()
@@ -1715,7 +1719,6 @@ class TrackPlayerCore: NSObject {
1715
1719
 
1716
1720
  // Create gapless-optimized player items
1717
1721
  let items = tracksToPlay.enumerated().compactMap { (offset, track) -> AVPlayerItem? in
1718
- // First few items get preload treatment for faster playback
1719
1722
  let isPreload = offset < Constants.gaplessPreloadCount
1720
1723
  return self.createGaplessPlayerItem(for: track, isPreload: isPreload)
1721
1724
  }
@@ -1731,6 +1734,10 @@ class TrackPlayerCore: NSObject {
1731
1734
  self.boundaryTimeObserver = nil
1732
1735
  }
1733
1736
 
1737
+ // Re-enable stall waiting for the new first track so it buffers before playing.
1738
+ // Will be flipped back to false once the first item reaches readyToPlay.
1739
+ player.automaticallyWaitsToMinimizeStalling = true
1740
+
1734
1741
  // Clear and rebuild queue
1735
1742
  player.removeAllItems()
1736
1743
  var lastItem: AVPlayerItem? = nil
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "A powerful audio player library for React Native with playlist management, playback controls, and support for Android Auto and CarPlay",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",