react-native-mp3-player 1.0.2 → 1.0.4
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 +14 -3
- package/android/src/main/AndroidManifest.xml +1 -0
- package/ios/RNTrackPlayer/Models/MetadataAdapter.swift +1 -1
- package/ios/RNTrackPlayer/RNTrackPlayer.swift +35 -0
- package/ios/RNTrackPlayer/TrackPlayer.mm +3 -3
- package/lib/src/trackPlayer.d.ts +8 -0
- package/lib/src/trackPlayer.js +14 -0
- package/package.json +1 -1
- package/src/trackPlayer.ts +16 -0
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ React Native audio player with **reliable iOS background playback**, media contr
|
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- **iOS
|
|
12
|
+
- **Background playback (iOS & Android)** – Audio continues when the app is in the background or the screen is locked. No patches required; compatible with current Xcode and Android 14/15.
|
|
13
|
+
- **iOS** – Uses `AVAudioSession` with `.longFormAudio` and interruption handling; lock screen and Control Center work natively.
|
|
14
|
+
- **Android** – Uses Media3 `MediaLibraryService` with `foregroundServiceType="mediaPlayback"`; system media notification, lock screen, and Android Auto are supported.
|
|
13
15
|
- **Multi-platform** – Android, iOS, Windows.
|
|
14
16
|
- **Media controls** – Lock screen, notification, Bluetooth, Android Auto.
|
|
15
17
|
- **Queue & playback** – Add, remove, reorder tracks; play, pause, seek, repeat, crossfade (where supported).
|
|
@@ -33,6 +35,15 @@ For audio to continue when the app is backgrounded or the screen is locked (and
|
|
|
33
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.
|
|
34
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.
|
|
35
37
|
|
|
38
|
+
### Android background playback
|
|
39
|
+
|
|
40
|
+
The package is built for **Android 14+** compatibility and works when the app is in the background or the screen is off:
|
|
41
|
+
|
|
42
|
+
1. **Foreground service:** The library declares `FOREGROUND_SERVICE_MEDIA_PLAYBACK` and uses `android:foregroundServiceType="mediaPlayback"` on the playback service, as required since Android 14. No extra setup in your app is needed.
|
|
43
|
+
2. **Media3 / ExoPlayer:** Playback runs in a **MediaLibraryService** (Media3), which correctly starts as a foreground service with type `mediaPlayback`, so background playback and the media notification are allowed by the system.
|
|
44
|
+
3. **Media controls:** The service is advertised via **MediaSessionService** and **MediaLibraryService** so the system media notification, lock screen, Bluetooth, and Android Auto can discover and control playback.
|
|
45
|
+
4. **Target SDK:** The library compiles with `compileSdkVersion` 35 and defaults to `targetSdkVersion` 34. Your app can override these via `react-native-mp3-player`’s build extras if needed. **Android 15:** Do not start the media service from a `BOOT_COMPLETED` receiver; the platform no longer allows that for media playback.
|
|
46
|
+
|
|
36
47
|
## Quick start
|
|
37
48
|
|
|
38
49
|
```javascript
|
|
@@ -68,8 +79,8 @@ TrackPlayer.registerPlaybackService(() => PlaybackService);
|
|
|
68
79
|
- **Lifecycle:** `setupPlayer(options?, background?)`, `registerPlaybackService(factory)`, `reset()`
|
|
69
80
|
- **Queue:** `add()`, `load()`, `remove()`, `skip()`, `skipToNext()`, `skipToPrevious()`, `setQueue()`, `getQueue()`, **`getActiveTrack()`** (current track), `getActiveTrackIndex()`
|
|
70
81
|
- **Playback:** `play()`, `pause()`, `stop()`, `seekTo()`, `seekBy()`, `setVolume()`, `setRate()`, `setRepeatMode()`
|
|
71
|
-
- **State:**
|
|
72
|
-
- **Events:** `addEventListener(event, listener)` – see `Event` enum.
|
|
82
|
+
- **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()`
|
|
83
|
+
- **Events:** `addEventListener(event, listener)` – see `Event` enum. Listen for `Event.PlaybackState` so the UI stays in sync when the user taps play/pause.
|
|
73
84
|
- **Hooks:** **`useProgress(updateInterval?, background?)`** (interval in **milliseconds**; e.g. `useProgress(250)` = every 250 ms), `usePlaybackState()`, `useActiveTrack()`, `useIsPlaying()`, `useTrackPlayerEvents()`, etc.
|
|
74
85
|
|
|
75
86
|
**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.
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
android:foregroundServiceType="mediaPlayback">
|
|
18
18
|
<intent-filter>
|
|
19
19
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
|
20
|
+
<action android:name="androidx.media3.session.MediaSessionService" />
|
|
20
21
|
<action android:name="androidx.media3.session.MediaLibraryService" />
|
|
21
22
|
<action android:name="android.media.browse.MediaBrowserService" />
|
|
22
23
|
</intent-filter>
|
|
@@ -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 = []
|
|
@@ -531,6 +534,7 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
531
534
|
public func reset(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
532
535
|
if (rejectWhenNotInitialized(reject: reject)) { return }
|
|
533
536
|
|
|
537
|
+
stopNowPlayingUpdateTimer()
|
|
534
538
|
player.stop()
|
|
535
539
|
player.clear()
|
|
536
540
|
resolve(NSNull())
|
|
@@ -541,6 +545,8 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
541
545
|
if (rejectWhenNotInitialized(reject: reject)) { return }
|
|
542
546
|
player.play()
|
|
543
547
|
resolve(NSNull())
|
|
548
|
+
// Emit PlaybackState immediately so in-app UI (play/pause button) updates without waiting for native state transition.
|
|
549
|
+
emit(event: EventType.PlaybackState, body: getPlaybackStateBodyKeyValues(state: .playing))
|
|
544
550
|
}
|
|
545
551
|
|
|
546
552
|
@objc(pause:rejecter:)
|
|
@@ -549,6 +555,8 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
549
555
|
|
|
550
556
|
player.pause()
|
|
551
557
|
resolve(NSNull())
|
|
558
|
+
// Emit PlaybackState immediately so in-app UI (play/pause button) updates without waiting for native state transition.
|
|
559
|
+
emit(event: EventType.PlaybackState, body: getPlaybackStateBodyKeyValues(state: .paused))
|
|
552
560
|
}
|
|
553
561
|
|
|
554
562
|
@objc(setPlayWhenReady:resolver:rejecter:)
|
|
@@ -823,6 +831,33 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
823
831
|
"position": player.currentTime,
|
|
824
832
|
] as [String : Any])
|
|
825
833
|
}
|
|
834
|
+
// Keep Now Playing widget elapsed/duration in sync: update every second when we have a current item and are ready/playing/paused.
|
|
835
|
+
switch state {
|
|
836
|
+
case .ready, .playing, .paused:
|
|
837
|
+
if player.currentItem != nil && player.automaticallyUpdateNowPlayingInfo {
|
|
838
|
+
scheduleNextNowPlayingUpdate()
|
|
839
|
+
} else {
|
|
840
|
+
stopNowPlayingUpdateTimer()
|
|
841
|
+
}
|
|
842
|
+
default:
|
|
843
|
+
stopNowPlayingUpdateTimer()
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
private func scheduleNextNowPlayingUpdate() {
|
|
848
|
+
stopNowPlayingUpdateTimer()
|
|
849
|
+
let workItem = DispatchWorkItem { [weak self] in
|
|
850
|
+
guard let self = self, self.player.currentItem != nil, self.player.automaticallyUpdateNowPlayingInfo else { return }
|
|
851
|
+
self.player.updateNowPlayingPlaybackValues()
|
|
852
|
+
self.scheduleNextNowPlayingUpdate()
|
|
853
|
+
}
|
|
854
|
+
nowPlayingUpdateWorkItem = workItem
|
|
855
|
+
nowPlayingUpdateQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
private func stopNowPlayingUpdateTimer() {
|
|
859
|
+
nowPlayingUpdateWorkItem?.cancel()
|
|
860
|
+
nowPlayingUpdateWorkItem = nil
|
|
826
861
|
}
|
|
827
862
|
|
|
828
863
|
func handleAudioPlayerCommonMetadataReceived(metadata: [AVMetadataItem]) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#import "TrackPlayer.h"
|
|
2
2
|
|
|
3
|
-
#if __has_include("
|
|
4
|
-
#import "
|
|
3
|
+
#if __has_include("react_native_mp3_player-Swift.h")
|
|
4
|
+
#import "react_native_mp3_player-Swift.h"
|
|
5
5
|
#else
|
|
6
|
-
#import "
|
|
6
|
+
#import "react_native_mp3_player/react_native_mp3_player-Swift.h"
|
|
7
7
|
#endif
|
|
8
8
|
|
|
9
9
|
@interface NativeTrackPlayer () <RNTPDelegate>
|
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.4",
|
|
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
|
*
|