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 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 background playback** – Audio continues when the app is in the background (no ~50s cutoff). Uses `AVAudioSession` with `.longFormAudio` and interruption handling.
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:** `getPlaybackState()`, `getProgress()`, `getVolume()`, `getRate()`
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>
@@ -1,6 +1,6 @@
1
1
  //
2
2
  // MetadataAdapter.swift
3
- // react-native-track-player
3
+ // react-native-mp3-player
4
4
  //
5
5
  // Created by David Chavez on 01.08.23.
6
6
  // Copyright © 2023 Double Symmetry. All rights reserved.
@@ -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("react_native_track_player-Swift.h")
4
- #import "react_native_track_player-Swift.h"
3
+ #if __has_include("react_native_mp3_player-Swift.h")
4
+ #import "react_native_mp3_player-Swift.h"
5
5
  #else
6
- #import "react_native_track_player/react_native_track_player-Swift.h"
6
+ #import "react_native_mp3_player/react_native_mp3_player-Swift.h"
7
7
  #endif
8
8
 
9
9
  @interface NativeTrackPlayer () <RNTPDelegate>
@@ -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
  *
@@ -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.2",
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",
@@ -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
  *