react-native-mp3-player 1.0.3 → 1.0.5
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/README.md
CHANGED
|
@@ -34,6 +34,7 @@ For audio to continue when the app is backgrounded or the screen is locked (and
|
|
|
34
34
|
1. **Enable Background Modes → Audio** (or “Audio, AirPlay, and Picture in Picture”) in your app’s Xcode project: select your target → **Signing & Capabilities** → **+ Capability** → **Background Modes** → check **Audio**.
|
|
35
35
|
2. The package configures **AVAudioSession** (category `.playback` with options for Bluetooth, AirPlay, ducking) and handles **interruptions** and **background transitions** so that playback can continue when the app is backgrounded.
|
|
36
36
|
3. **Lock screen and Control Center** controls (play, pause, seek, 15-second skip) are handled **natively**, so they work even when the JavaScript thread is suspended (e.g. screen locked). When the app returns to the foreground, events are emitted so your UI stays in sync.
|
|
37
|
+
4. **Now Playing widget:** The package sets and updates **MPNowPlayingInfoCenter** as soon as a track is loaded (title, artist, duration, elapsed, rate, artwork) and keeps it updated every second during playback. When you pause, the widget shows the track as paused (rate 0), not "Not Playing". Now playing info is only cleared when there is no current track (e.g. after `reset()`).
|
|
37
38
|
|
|
38
39
|
### Android background playback
|
|
39
40
|
|
|
@@ -79,8 +80,8 @@ TrackPlayer.registerPlaybackService(() => PlaybackService);
|
|
|
79
80
|
- **Lifecycle:** `setupPlayer(options?, background?)`, `registerPlaybackService(factory)`, `reset()`
|
|
80
81
|
- **Queue:** `add()`, `load()`, `remove()`, `skip()`, `skipToNext()`, `skipToPrevious()`, `setQueue()`, `getQueue()`, **`getActiveTrack()`** (current track), `getActiveTrackIndex()`
|
|
81
82
|
- **Playback:** `play()`, `pause()`, `stop()`, `seekTo()`, `seekBy()`, `setVolume()`, `setRate()`, `setRepeatMode()`
|
|
82
|
-
- **State:**
|
|
83
|
-
- **Events:** `addEventListener(event, listener)` – see `Event` enum.
|
|
83
|
+
- **State & progress:** **`getPlaybackState()`** (returns `{ state }`; use this, not `getState`), **`getProgress()`** (returns `{ position, duration, buffered }` in seconds), **`getPosition()`** and **`getDuration()`** (convenience wrappers around `getProgress()`), `getVolume()`, `getRate()`
|
|
84
|
+
- **Events:** `addEventListener(event, listener)` – see `Event` enum. Listen for `Event.PlaybackState` so the UI stays in sync when the user taps play/pause.
|
|
84
85
|
- **Hooks:** **`useProgress(updateInterval?, background?)`** (interval in **milliseconds**; e.g. `useProgress(250)` = every 250 ms), `usePlaybackState()`, `useActiveTrack()`, `useIsPlaying()`, `useTrackPlayerEvents()`, etc.
|
|
85
86
|
|
|
86
87
|
**Setup options** (e.g. in `setupPlayer` / `updateOptions`): `iosCategory` (e.g. `'playback'`), `iosCategoryOptions` (e.g. `['allowAirPlay','allowBluetooth','duckOthers']`), `autoHandleInterruptions`, `autoUpdateMetadata`, `waitForBuffer`, `minBuffer` / buffer-related options, `forwardJumpInterval` / `backwardJumpInterval` (seconds, e.g. 15), `progressUpdateEventInterval` (seconds). Types and options are in the package TypeScript definitions.
|
|
@@ -27,6 +27,9 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
27
27
|
private var forwardJumpInterval: NSNumber? = nil;
|
|
28
28
|
private var backwardJumpInterval: NSNumber? = nil;
|
|
29
29
|
private var sessionCategory: AVAudioSession.Category = .playback
|
|
30
|
+
/// Timer work item for updating Now Playing elapsed/duration every second so the lock screen widget stays in sync.
|
|
31
|
+
private var nowPlayingUpdateWorkItem: DispatchWorkItem? = nil
|
|
32
|
+
private let nowPlayingUpdateQueue = DispatchQueue.main
|
|
30
33
|
private var sessionCategoryMode: AVAudioSession.Mode = .default
|
|
31
34
|
private var sessionCategoryPolicy: AVAudioSession.RouteSharingPolicy = .longFormAudio
|
|
32
35
|
private var sessionCategoryOptions: AVAudioSession.CategoryOptions = []
|
|
@@ -199,6 +202,10 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
199
202
|
player.remoteCommandController.handlePauseCommand = { [weak self] _ in
|
|
200
203
|
guard let self = self else { return MPRemoteCommandHandlerStatus.commandFailed }
|
|
201
204
|
self.player.pause()
|
|
205
|
+
if self.player.currentItem != nil && self.player.automaticallyUpdateNowPlayingInfo {
|
|
206
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
207
|
+
}
|
|
208
|
+
self.emit(event: EventType.PlaybackState, body: self.getPlaybackStateBodyKeyValues(state: .paused))
|
|
202
209
|
self.emit(event: EventType.RemotePause)
|
|
203
210
|
return MPRemoteCommandHandlerStatus.success
|
|
204
211
|
}
|
|
@@ -206,6 +213,10 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
206
213
|
player.remoteCommandController.handlePlayCommand = { [weak self] _ in
|
|
207
214
|
guard let self = self else { return MPRemoteCommandHandlerStatus.commandFailed }
|
|
208
215
|
self.player.play()
|
|
216
|
+
if self.player.currentItem != nil && self.player.automaticallyUpdateNowPlayingInfo {
|
|
217
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
218
|
+
}
|
|
219
|
+
self.emit(event: EventType.PlaybackState, body: self.getPlaybackStateBodyKeyValues(state: .playing))
|
|
209
220
|
self.emit(event: EventType.RemotePlay)
|
|
210
221
|
return MPRemoteCommandHandlerStatus.success
|
|
211
222
|
}
|
|
@@ -244,6 +255,10 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
244
255
|
player.remoteCommandController.handleStopCommand = { [weak self] _ in
|
|
245
256
|
guard let self = self else { return MPRemoteCommandHandlerStatus.commandFailed }
|
|
246
257
|
self.player.stop()
|
|
258
|
+
if self.player.currentItem != nil && self.player.automaticallyUpdateNowPlayingInfo {
|
|
259
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
260
|
+
}
|
|
261
|
+
self.emit(event: EventType.PlaybackState, body: self.getPlaybackStateBodyKeyValues(state: .stopped))
|
|
247
262
|
self.emit(event: EventType.RemoteStop)
|
|
248
263
|
return MPRemoteCommandHandlerStatus.success
|
|
249
264
|
}
|
|
@@ -252,9 +267,17 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
252
267
|
guard let self = self else { return MPRemoteCommandHandlerStatus.commandFailed }
|
|
253
268
|
if self.player.playerState == .paused {
|
|
254
269
|
self.player.play()
|
|
270
|
+
if self.player.currentItem != nil && self.player.automaticallyUpdateNowPlayingInfo {
|
|
271
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
272
|
+
}
|
|
273
|
+
self.emit(event: EventType.PlaybackState, body: self.getPlaybackStateBodyKeyValues(state: .playing))
|
|
255
274
|
self.emit(event: EventType.RemotePlay)
|
|
256
275
|
} else {
|
|
257
276
|
self.player.pause()
|
|
277
|
+
if self.player.currentItem != nil && self.player.automaticallyUpdateNowPlayingInfo {
|
|
278
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
279
|
+
}
|
|
280
|
+
self.emit(event: EventType.PlaybackState, body: self.getPlaybackStateBodyKeyValues(state: .paused))
|
|
258
281
|
self.emit(event: EventType.RemotePause)
|
|
259
282
|
}
|
|
260
283
|
return MPRemoteCommandHandlerStatus.success
|
|
@@ -531,6 +554,7 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
531
554
|
public func reset(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
532
555
|
if (rejectWhenNotInitialized(reject: reject)) { return }
|
|
533
556
|
|
|
557
|
+
stopNowPlayingUpdateTimer()
|
|
534
558
|
player.stop()
|
|
535
559
|
player.clear()
|
|
536
560
|
resolve(NSNull())
|
|
@@ -540,7 +564,12 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
540
564
|
public func play(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
541
565
|
if (rejectWhenNotInitialized(reject: reject)) { return }
|
|
542
566
|
player.play()
|
|
567
|
+
// Update Now Playing widget immediately (rate 1, current elapsed) so it never shows "Not Playing".
|
|
568
|
+
if player.currentItem != nil && player.automaticallyUpdateNowPlayingInfo {
|
|
569
|
+
player.updateNowPlayingPlaybackValues()
|
|
570
|
+
}
|
|
543
571
|
resolve(NSNull())
|
|
572
|
+
emit(event: EventType.PlaybackState, body: getPlaybackStateBodyKeyValues(state: .playing))
|
|
544
573
|
}
|
|
545
574
|
|
|
546
575
|
@objc(pause:rejecter:)
|
|
@@ -548,7 +577,12 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
548
577
|
if (rejectWhenNotInitialized(reject: reject)) { return }
|
|
549
578
|
|
|
550
579
|
player.pause()
|
|
580
|
+
// Update Now Playing widget immediately (rate 0, current elapsed) so it shows "Paused" not "Not Playing".
|
|
581
|
+
if player.currentItem != nil && player.automaticallyUpdateNowPlayingInfo {
|
|
582
|
+
player.updateNowPlayingPlaybackValues()
|
|
583
|
+
}
|
|
551
584
|
resolve(NSNull())
|
|
585
|
+
emit(event: EventType.PlaybackState, body: getPlaybackStateBodyKeyValues(state: .paused))
|
|
552
586
|
}
|
|
553
587
|
|
|
554
588
|
@objc(setPlayWhenReady:resolver:rejecter:)
|
|
@@ -823,6 +857,33 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
823
857
|
"position": player.currentTime,
|
|
824
858
|
] as [String : Any])
|
|
825
859
|
}
|
|
860
|
+
// Keep Now Playing widget elapsed/duration in sync: update every second when we have a current item and are ready/playing/paused.
|
|
861
|
+
switch state {
|
|
862
|
+
case .ready, .playing, .paused:
|
|
863
|
+
if player.currentItem != nil && player.automaticallyUpdateNowPlayingInfo {
|
|
864
|
+
scheduleNextNowPlayingUpdate()
|
|
865
|
+
} else {
|
|
866
|
+
stopNowPlayingUpdateTimer()
|
|
867
|
+
}
|
|
868
|
+
default:
|
|
869
|
+
stopNowPlayingUpdateTimer()
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private func scheduleNextNowPlayingUpdate() {
|
|
874
|
+
stopNowPlayingUpdateTimer()
|
|
875
|
+
let workItem = DispatchWorkItem { [weak self] in
|
|
876
|
+
guard let self = self, self.player.currentItem != nil, self.player.automaticallyUpdateNowPlayingInfo else { return }
|
|
877
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
878
|
+
self.scheduleNextNowPlayingUpdate()
|
|
879
|
+
}
|
|
880
|
+
nowPlayingUpdateWorkItem = workItem
|
|
881
|
+
nowPlayingUpdateQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
private func stopNowPlayingUpdateTimer() {
|
|
885
|
+
nowPlayingUpdateWorkItem?.cancel()
|
|
886
|
+
nowPlayingUpdateWorkItem = nil
|
|
826
887
|
}
|
|
827
888
|
|
|
828
889
|
func handleAudioPlayerCommonMetadataReceived(metadata: [AVMetadataItem]) {
|
|
@@ -211,12 +211,12 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
|
|
211
211
|
currentItem = item
|
|
212
212
|
|
|
213
213
|
if (automaticallyUpdateNowPlayingInfo) {
|
|
214
|
-
//
|
|
215
|
-
// the
|
|
216
|
-
nowPlayingInfoController.
|
|
217
|
-
MediaItemProperty.duration(
|
|
218
|
-
NowPlayingInfoProperty.playbackRate(
|
|
219
|
-
NowPlayingInfoProperty.elapsedPlaybackTime(
|
|
214
|
+
// Set initial playback values so the Now Playing widget never shows "Not Playing"
|
|
215
|
+
// (duration/elapsed/rate must be set; use 0 until the player reports real values).
|
|
216
|
+
nowPlayingInfoController.set(keyValues: [
|
|
217
|
+
MediaItemProperty.duration(0),
|
|
218
|
+
NowPlayingInfoProperty.playbackRate(playWhenReady ? 1.0 : 0.0),
|
|
219
|
+
NowPlayingInfoProperty.elapsedPlaybackTime(0)
|
|
220
220
|
])
|
|
221
221
|
loadNowPlayingMetaValues()
|
|
222
222
|
}
|
package/lib/src/trackPlayer.d.ts
CHANGED
|
@@ -269,6 +269,14 @@ export declare function getActiveTrack(): Promise<Track | undefined>;
|
|
|
269
269
|
* duration in seconds.
|
|
270
270
|
*/
|
|
271
271
|
export declare function getProgress(): Promise<Progress>;
|
|
272
|
+
/**
|
|
273
|
+
* Gets the current playback position in seconds. Convenience wrapper around getProgress().
|
|
274
|
+
*/
|
|
275
|
+
export declare function getPosition(): Promise<number>;
|
|
276
|
+
/**
|
|
277
|
+
* Gets the duration of the current track in seconds. Convenience wrapper around getProgress().
|
|
278
|
+
*/
|
|
279
|
+
export declare function getDuration(): Promise<number>;
|
|
272
280
|
/**
|
|
273
281
|
* Gets the playback state of the player.
|
|
274
282
|
*
|
package/lib/src/trackPlayer.js
CHANGED
|
@@ -474,6 +474,20 @@ export async function getProgress() {
|
|
|
474
474
|
// @ts-expect-error codegen issues
|
|
475
475
|
return TrackPlayer.getProgress();
|
|
476
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Gets the current playback position in seconds. Convenience wrapper around getProgress().
|
|
479
|
+
*/
|
|
480
|
+
export async function getPosition() {
|
|
481
|
+
const { position } = await getProgress();
|
|
482
|
+
return position;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Gets the duration of the current track in seconds. Convenience wrapper around getProgress().
|
|
486
|
+
*/
|
|
487
|
+
export async function getDuration() {
|
|
488
|
+
const { duration } = await getProgress();
|
|
489
|
+
return duration;
|
|
490
|
+
}
|
|
477
491
|
/**
|
|
478
492
|
* Gets the playback state of the player.
|
|
479
493
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-mp3-player",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "React Native audio player with reliable iOS background playback. Media controls, queue, hooks. Built for stability and long-running playback.",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"types": "lib/src/index.d.ts",
|
package/src/trackPlayer.ts
CHANGED
|
@@ -633,6 +633,22 @@ export async function getProgress(): Promise<Progress> {
|
|
|
633
633
|
return TrackPlayer.getProgress();
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Gets the current playback position in seconds. Convenience wrapper around getProgress().
|
|
638
|
+
*/
|
|
639
|
+
export async function getPosition(): Promise<number> {
|
|
640
|
+
const { position } = await getProgress();
|
|
641
|
+
return position;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Gets the duration of the current track in seconds. Convenience wrapper around getProgress().
|
|
646
|
+
*/
|
|
647
|
+
export async function getDuration(): Promise<number> {
|
|
648
|
+
const { duration } = await getProgress();
|
|
649
|
+
return duration;
|
|
650
|
+
}
|
|
651
|
+
|
|
636
652
|
/**
|
|
637
653
|
* Gets the playback state of the player.
|
|
638
654
|
*
|