@javascriptcommon/react-native-track-player 4.1.27 → 4.1.28

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.
@@ -26,7 +26,7 @@ class AudioController {
26
26
 
27
27
  init() {
28
28
  let controller = RemoteCommandController()
29
- player = QueuedAudioPlayer(remoteCommandController: controller)
29
+ player = QueuedAudioPlayer(remoteCommandController: controller, crossfade: true)
30
30
  player.remoteCommands = [
31
31
  .stop,
32
32
  .play,
@@ -36,7 +36,7 @@ class AudioController {
36
36
  .previous,
37
37
  .changePlaybackPosition
38
38
  ]
39
- player.audioTap = WaveformAudioTap()
39
+ // player.audioTap = WaveformAudioTap()
40
40
  player.repeatMode = .queue
41
41
  DispatchQueue.main.async {
42
42
  self.player.add(items: self.sources)
@@ -106,8 +106,25 @@ struct PlayerView: View {
106
106
  })
107
107
  .frame(maxWidth: .infinity)
108
108
  }
109
- .padding(.top, 80)
109
+ .padding(.top, 20)
110
110
 
111
+ HStack {
112
+ Button(action: { controller.player.crossfadePrepare() }, label: {
113
+ Text("Crossfade Prepare")
114
+ .font(.system(size: 14))
115
+ })
116
+ .frame(maxWidth: .infinity)
117
+
118
+ .frame(maxWidth: .infinity)
119
+ Button(action: { controller.player.switchExoPlayer() }, label: {
120
+ Text("Crossfade Switch")
121
+ .font(.system(size: 14))
122
+ })
123
+ .frame(maxWidth: .infinity)
124
+ }
125
+ .padding(.top, 20)
126
+
127
+
111
128
  VStack {
112
129
  if viewModel.playbackState == .failed {
113
130
  Text("Playback failed.")
@@ -33,7 +33,7 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
33
33
  @objc public weak var delegate: RNTPDelegate? = nil
34
34
  // MARK: - Attributes
35
35
  private var hasInitialized = false
36
- private let player = QueuedAudioPlayer()
36
+ private let player = QueuedAudioPlayer(crossfade: true)
37
37
  private let audioSessionController = AudioSessionController.shared
38
38
  private let equalizerTap = EqualizerAudioTap() // Always created, attached at setup
39
39
  private var shouldEmitProgressEvent: Bool = false
@@ -1067,6 +1067,22 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
1067
1067
  equalizerTap.balance = max(-1, min(1, balance))
1068
1068
  resolve(NSNull())
1069
1069
  }
1070
+
1071
+ // MARK: - Crossfade
1072
+
1073
+ @objc(crossFadePrepare:seekTo:resolver:rejecter:)
1074
+ public func crossFadePrepare(previous: Bool, seekTo: NSNumber, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
1075
+ if (rejectWhenNotInitialized(reject: reject)) { return }
1076
+ player.crossfadePrepare(previous: previous)
1077
+ resolve(NSNull())
1078
+ }
1079
+
1080
+ @objc(switchExoPlayer:fadeInterval:fadeToVolume:waitUntil:resolver:rejecter:)
1081
+ public func switchExoPlayer(fadeDuration: Double, fadeInterval: Double, fadeToVolume: Double, waitUntil: NSNumber, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
1082
+ if (rejectWhenNotInitialized(reject: reject)) { return }
1083
+ player.switchExoPlayer(fadeDuration: Int(fadeDuration), fadeInterval: Int(fadeInterval), fadeToVolume: Float(fadeToVolume))
1084
+ resolve(NSNull())
1085
+ }
1070
1086
  }
1071
1087
 
1072
1088
  extension RNTrackPlayer {
@@ -205,6 +205,7 @@ RCT_EXPORT_MODULE()
205
205
  }
206
206
 
207
207
  - (void)crossFadePrepare:(BOOL)previous seekTo:(nonnull NSNumber *)seekTo resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
208
+ [trackPlayer crossFadePrepare:previous seekTo:seekTo resolver:resolve rejecter:reject];
208
209
  }
209
210
 
210
211
  - (void)fadeOutJump:(double)index duration:(double)duration interval:(double)interval toVolume:(double)toVolume resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
@@ -244,6 +245,7 @@ RCT_EXPORT_MODULE()
244
245
  }
245
246
 
246
247
  - (void)switchExoPlayer:(double)fadeDuration fadeInterval:(double)fadeInterval fadeToVolume:(double)fadeToVolume waitUntil:(nonnull NSNumber *)waitUntil resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
248
+ [trackPlayer switchExoPlayer:fadeDuration fadeInterval:fadeInterval fadeToVolume:fadeToVolume waitUntil:waitUntil resolver:resolve rejecter:reject];
247
249
  }
248
250
 
249
251
  // iOS Equalizer methods
@@ -12,8 +12,24 @@ public typealias AudioPlayerState = AVPlayerWrapperState
12
12
 
13
13
  public class AudioPlayer: AVPlayerWrapperDelegate {
14
14
  /// The wrapper around the underlying AVPlayer
15
- let wrapper: AVPlayerWrapperProtocol = AVPlayerWrapper()
16
-
15
+ let wrapper1: AVPlayerWrapperProtocol = AVPlayerWrapper()
16
+ var wrapper2: AVPlayerWrapperProtocol? = nil
17
+ // this is the current wrapper
18
+ var wrapper: AVPlayerWrapperProtocol
19
+ // this is the secondary/crossfade wrapper
20
+ var crossfadeWrapper: AVPlayerWrapperProtocol
21
+ // when true, the current wrapper is wrapper1; else, wrapper2
22
+ // for crossfade tracking
23
+ var currentAVPlayer = true
24
+ // indicates if crossfade is enabled/wrapper2 is initialized
25
+ var crossfade: Bool = false
26
+
27
+ func players () -> [AVPlayerWrapperProtocol] {
28
+ if (self.crossfade) {
29
+ return [wrapper1, wrapper2!]
30
+ }
31
+ return [wrapper1]
32
+ }
17
33
 
18
34
  /**
19
35
  Set an instance of AudioTap, to receive frame information and audio buffer access during playback.
@@ -29,6 +45,9 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
29
45
  public let event = EventHolder()
30
46
 
31
47
  private(set) var currentItem: AudioItem?
48
+ // as audioplayer does NOT hold queue information, the crossfading audioItem needs to be
49
+ // stored as such - whereas exoplayer has a built in queue and can just skipToIndex
50
+ var crossfadeItem: AudioItem?
32
51
 
33
52
  /**
34
53
  Set this to false to disable automatic updating of now playing info for control center and lock screen.
@@ -190,10 +209,19 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
190
209
  - parameter infoCenter: The InfoCenter to update. Default is `MPNowPlayingInfoCenter.default()`.
191
210
  */
192
211
  public init(nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(),
193
- remoteCommandController: RemoteCommandController = RemoteCommandController()) {
212
+ remoteCommandController: RemoteCommandController = RemoteCommandController(),
213
+ crossfade: Bool = false
214
+ ) {
194
215
  self.nowPlayingInfoController = nowPlayingInfoController
195
216
  self.remoteCommandController = remoteCommandController
196
-
217
+ self.wrapper = self.wrapper1
218
+ self.crossfade = crossfade
219
+ if (crossfade) {
220
+ self.wrapper2 = AVPlayerWrapper()
221
+ self.crossfadeWrapper = self.wrapper2!
222
+ } else {
223
+ self.crossfadeWrapper = self.wrapper
224
+ }
197
225
  wrapper.delegate = self
198
226
  self.remoteCommandController.audioPlayer = self
199
227
  }
@@ -37,7 +37,7 @@ class QueueManager<Element> {
37
37
  /**
38
38
  The index of the current item. `-1` when there is no current item
39
39
  */
40
- private(set) var currentIndex: Int {
40
+ var currentIndex: Int {
41
41
  get {
42
42
  return synchronize {
43
43
  return _currentIndex
@@ -352,5 +352,17 @@ class QueueManager<Element> {
352
352
  delegate?.onCurrentItemChanged()
353
353
  }
354
354
  }
355
-
355
+
356
+ // peek for the next item. does not change currentIndex
357
+ public func peek(direction: Int = 1) -> Int {
358
+ if (items.isEmpty) {
359
+ return -1
360
+ }
361
+ var finalIndex = (currentIndex + direction) % items.count
362
+ if (finalIndex < 0) {
363
+ finalIndex += items.count
364
+ }
365
+ return finalIndex
366
+ }
367
+
356
368
  }
@@ -15,9 +15,107 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
15
15
  let queue: QueueManager = QueueManager<AudioItem>()
16
16
  fileprivate var lastIndex: Int = -1
17
17
  fileprivate var lastItem: AudioItem? = nil
18
+
19
+ func findOrInsert(item: AudioItem) -> Int {
20
+ var itemIndex = queue.items.firstIndex(where: {$0.getSourceUrl() == item.getSourceUrl()})
21
+ if (itemIndex == nil) {
22
+ add(item: item)
23
+ itemIndex = queue.items.count - 1
24
+ }
25
+ queue.currentIndex = itemIndex!
26
+ return itemIndex!
27
+ }
28
+
29
+ public func crossfadePrepare(item: AudioItem) {
30
+ if (!self.crossfade) {
31
+ return
32
+ }
33
+ self.crossfadeWrapper.load(
34
+ from: item.getSourceUrl(),
35
+ type: item.getSourceType(),
36
+ playWhenReady: false,
37
+ initialTime: (item as? InitialTiming)?.getInitialTime(),
38
+ options:(item as? AssetOptionsProviding)?.getAssetOptions()
39
+ )
40
+ self.crossfadeItem = item
41
+ }
42
+
43
+ public func crossfadePrepare(previous: Bool = false) {
44
+ let nextIndex = queue.peek(direction: previous ? -1 : 1)
45
+ if (nextIndex < 0) {
46
+ // TODO: should throw error instead
47
+ return
48
+ }
49
+ self.crossfadePrepare(item: queue.items[nextIndex])
50
+ }
51
+
52
+ public func switchExoPlayer(
53
+ fadeDuration: Int = 2500,
54
+ fadeInterval: Int = 20,
55
+ fadeToVolume: Float = 1
56
+ ) {
57
+ if (!self.crossfade || self.crossfadeItem == nil) {
58
+ return
59
+ }
60
+ if (self.currentAVPlayer) {
61
+ self.crossfadeWrapper = self.wrapper1
62
+ self.wrapper = self.wrapper2!
63
+ } else {
64
+ self.crossfadeWrapper = self.wrapper2!
65
+ self.wrapper = self.wrapper1
66
+ }
18
67
 
19
- public override init(nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(), remoteCommandController: RemoteCommandController = RemoteCommandController()) {
20
- super.init(nowPlayingInfoController: nowPlayingInfoController, remoteCommandController: remoteCommandController)
68
+ // switch the event emittting delegate
69
+ self.wrapper.delegate = self
70
+ self.crossfadeWrapper.delegate = nil
71
+
72
+ // broadcast nowplaying to system
73
+ self.findOrInsert(item: self.crossfadeItem!)
74
+ loadNowPlayingMetaValues()
75
+ emitCurrentItemEvent()
76
+
77
+ self.crossfadeItem = nil
78
+ self.currentAVPlayer = !self.currentAVPlayer
79
+
80
+ // fade volume
81
+ Task {
82
+ var fadeOutDuration = fadeDuration
83
+ let startFadeOutTime = DispatchTime.now()
84
+ let fadeFromVolume = self.crossfadeWrapper.volume
85
+ while (fadeOutDuration > 0) {
86
+ fadeOutDuration -= fadeInterval
87
+ let timeDiff = DispatchTime.now().uptimeNanoseconds - startFadeOutTime.uptimeNanoseconds
88
+ let timeElapsed = Float(min(Int(timeDiff) / 1_000_000, fadeDuration))
89
+ self.crossfadeWrapper.volume = fadeFromVolume * (1 - timeElapsed / Float(fadeDuration))
90
+ print("crossfade fading out...\(self.crossfadeWrapper.volume)")
91
+ try await Task.sleep(nanoseconds: UInt64(fadeInterval * 1000000))
92
+ }
93
+ }
94
+
95
+ Task {
96
+ self.wrapper.volume = 0
97
+ if (fadeToVolume > 0) {
98
+ self.wrapper.play()
99
+ var fadeInDuration = fadeDuration
100
+ let startTime = DispatchTime.now()
101
+ while (fadeInDuration > 0) {
102
+ fadeInDuration -= fadeInterval
103
+ let timeDiff = DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds
104
+ let timeElapsed = Float(min(Int(timeDiff) / 1_000_000, fadeDuration))
105
+ self.wrapper.volume = fadeToVolume * timeElapsed / Float(fadeDuration)
106
+ print("crossfade fading in...\(self.wrapper.volume)")
107
+ try await Task.sleep(nanoseconds: UInt64(fadeInterval * 1000000))
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ public override init(
114
+ nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(),
115
+ remoteCommandController: RemoteCommandController = RemoteCommandController(),
116
+ crossfade: Bool = false
117
+ ) {
118
+ super.init(nowPlayingInfoController: nowPlayingInfoController, remoteCommandController: remoteCommandController, crossfade: crossfade)
21
119
  queue.delegate = self
22
120
  }
23
121
 
@@ -224,6 +322,8 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
224
322
  } else {
225
323
  super.clear()
226
324
  }
325
+ func emitCurrentItemEvent(lastPosition: Double = 0) {
326
+ let currentItem = currentItem
227
327
  event.currentItem.emit(
228
328
  data: (
229
329
  item: currentItem,
@@ -248,6 +348,16 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
248
348
  }
249
349
  }
250
350
  return false
351
+ // MARK: - QueueManagerDelegate
352
+
353
+ func onCurrentItemChanged() {
354
+ let lastPosition = currentTime;
355
+ if let currentItem = currentItem {
356
+ super.load(item: currentItem)
357
+ } else {
358
+ super.clear()
359
+ }
360
+ emitCurrentItemEvent(lastPosition: lastPosition)
251
361
  }
252
362
 
253
363
  func onSkippedToSameCurrentItem() {
@@ -573,8 +573,6 @@ export async function abandonWakeLock() {
573
573
  * crossfade so the resource can be prepared.
574
574
  */
575
575
  export async function crossFadePrepare(previous = false, seekTo = 0) {
576
- if (!isAndroid)
577
- return;
578
576
  TrackPlayer.crossFadePrepare(previous, seekTo);
579
577
  }
580
578
  /**
@@ -587,8 +585,6 @@ export async function crossFadePrepare(previous = false, seekTo = 0) {
587
585
  * waitUntil is in ms.
588
586
  */
589
587
  export async function crossFade(fadeDuration = 2000, fadeInterval = 20, fadeToVolume = 1, waitUntil = 0) {
590
- if (!isAndroid)
591
- return;
592
588
  TrackPlayer.switchExoPlayer(fadeDuration, fadeInterval, fadeToVolume, waitUntil);
593
589
  }
594
590
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javascriptcommon/react-native-track-player",
3
- "version": "4.1.27",
3
+ "version": "4.1.28",
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",
@@ -747,7 +747,6 @@ export async function abandonWakeLock() {
747
747
  * crossfade so the resource can be prepared.
748
748
  */
749
749
  export async function crossFadePrepare(previous = false, seekTo = 0) {
750
- if (!isAndroid) return;
751
750
  TrackPlayer.crossFadePrepare(previous, seekTo);
752
751
  }
753
752
 
@@ -766,7 +765,6 @@ export async function crossFade(
766
765
  fadeToVolume = 1,
767
766
  waitUntil = 0,
768
767
  ) {
769
- if (!isAndroid) return;
770
768
  TrackPlayer.switchExoPlayer(
771
769
  fadeDuration,
772
770
  fadeInterval,