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.
- package/ios/core/TrackPlayerCore.swift +32 -25
- package/package.json +1 -1
|
@@ -125,9 +125,10 @@ class TrackPlayerCore: NSObject {
|
|
|
125
125
|
|
|
126
126
|
// MARK: - Gapless Playback Configuration
|
|
127
127
|
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
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
|
-
//
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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.
|
|
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",
|