react-native-nitro-player 1.4.1-alpha.0 → 1.4.2

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.
@@ -244,6 +244,7 @@ class TrackPlayerCore private constructor(
244
244
  }
245
245
  }
246
246
  scope.cancel()
247
+ com.margelo.nitro.nitroplayer.media.AuthAwareHttpDataSourceFactory.clear()
247
248
  // Do NOT stop the service — it owns the player.
248
249
  // Unbind so Android can clean up if needed.
249
250
  if (serviceBound) {
@@ -150,6 +150,23 @@ internal fun TrackPlayerCore.makeMediaItem(
150
150
 
151
151
  val effectiveUrl = downloadManager.getEffectiveUrl(track)
152
152
 
153
+ // Register custom HTTP headers (e.g. Authorization) from extraPayload.headers so they are
154
+ // injected into the request for this URL. Applies to remote streams only — see ExoPlayerBuilder.
155
+ track.extraPayload?.let { payload ->
156
+ try {
157
+ val headersRaw = payload.toHashMap()["headers"]
158
+ if (headersRaw is Map<*, *>) {
159
+ val headers = headersRaw
160
+ .mapNotNull { (k, v) -> if (k is String && v is String) k to v else null }
161
+ .toMap()
162
+ if (headers.isNotEmpty()) {
163
+ com.margelo.nitro.nitroplayer.media.AuthAwareHttpDataSourceFactory
164
+ .setHeadersForUrl(effectiveUrl, headers)
165
+ }
166
+ }
167
+ } catch (_: Exception) {}
168
+ }
169
+
153
170
  return MediaItem
154
171
  .Builder()
155
172
  .setMediaId(customMediaId ?: track.id)
@@ -0,0 +1,48 @@
1
+ package com.margelo.nitro.nitroplayer.media
2
+
3
+ import androidx.media3.common.util.UnstableApi
4
+ import androidx.media3.datasource.DataSource
5
+ import androidx.media3.datasource.DefaultHttpDataSource
6
+ import androidx.media3.datasource.ResolvingDataSource
7
+ import java.util.concurrent.ConcurrentHashMap
8
+
9
+ /**
10
+ * HTTP [DataSource.Factory] that injects per-URL request headers (e.g. `Authorization`)
11
+ * into ExoPlayer's HTTP requests, enabling playback from authenticated endpoints.
12
+ *
13
+ * Headers are registered per effective URL via [setHeadersForUrl] during MediaItem
14
+ * construction and applied through a [ResolvingDataSource] just before each request opens.
15
+ *
16
+ * The registry is a [ConcurrentHashMap] because it is written on the player thread but
17
+ * read on ExoPlayer's loader threads.
18
+ */
19
+ @UnstableApi
20
+ class AuthAwareHttpDataSourceFactory : DataSource.Factory {
21
+ private val factory =
22
+ ResolvingDataSource.Factory(
23
+ DefaultHttpDataSource
24
+ .Factory()
25
+ .setAllowCrossProtocolRedirects(true)
26
+ .setConnectTimeoutMs(15_000)
27
+ .setReadTimeoutMs(15_000),
28
+ ResolvingDataSource.Resolver { dataSpec ->
29
+ val headers = headersMap[dataSpec.uri.toString()]
30
+ if (headers.isNullOrEmpty()) dataSpec else dataSpec.withAdditionalHeaders(headers)
31
+ },
32
+ )
33
+
34
+ override fun createDataSource(): DataSource = factory.createDataSource()
35
+
36
+ companion object {
37
+ private val headersMap = ConcurrentHashMap<String, Map<String, String>>()
38
+
39
+ fun setHeadersForUrl(url: String, headers: Map<String, String>) {
40
+ headersMap[url] = headers
41
+ }
42
+
43
+ /** Releases all registered headers. Call when the player is torn down. */
44
+ fun clear() {
45
+ headersMap.clear()
46
+ }
47
+ }
48
+ }
@@ -3,14 +3,18 @@ package com.margelo.nitro.nitroplayer.media
3
3
  import android.content.Context
4
4
  import androidx.media3.common.AudioAttributes
5
5
  import androidx.media3.common.C
6
+ import androidx.media3.common.util.UnstableApi
7
+ import androidx.media3.datasource.DefaultDataSource
6
8
  import androidx.media3.exoplayer.DefaultLoadControl
7
9
  import androidx.media3.exoplayer.ExoPlayer
10
+ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
8
11
 
9
12
  /**
10
13
  * Builds an [ExoPlayer] with the standard NitroPlayer configuration.
11
14
  * The player uses the main application looper so that Media3's
12
15
  * [MediaSessionService] notification system works without thread conflicts.
13
16
  */
17
+ @UnstableApi
14
18
  object ExoPlayerBuilder {
15
19
  fun build(context: Context): ExoPlayer {
16
20
  val loadControl =
@@ -37,9 +41,15 @@ object ExoPlayerBuilder {
37
41
  .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
38
42
  .build()
39
43
 
44
+ // DefaultDataSource routes http/https through AuthAwareHttpDataSourceFactory (so custom
45
+ // request headers can be injected) while handling file://, content:// and asset:// natively.
46
+ val dataSourceFactory = DefaultDataSource.Factory(context, AuthAwareHttpDataSourceFactory())
47
+ val mediaSourceFactory = DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
48
+
40
49
  return ExoPlayer
41
50
  .Builder(context)
42
51
  .setLoadControl(loadControl)
52
+ .setMediaSourceFactory(mediaSourceFactory)
43
53
  .setAudioAttributes(audioAttrs, /* handleAudioFocus */ true)
44
54
  .setHandleAudioBecomingNoisy(true)
45
55
  .setWakeMode(C.WAKE_MODE_NETWORK)
@@ -358,6 +358,37 @@ extension TrackPlayerCore {
358
358
  }
359
359
  }
360
360
 
361
+ /// Extracts custom HTTP headers (e.g. `Authorization`) from `extraPayload.headers`.
362
+ /// These are passed to AVURLAsset so authenticated remote streams can be played.
363
+ /// Returns nil when no string headers are present.
364
+ func httpHeaders(for track: TrackItem) -> [String: String]? {
365
+ // AnyMap.toDictionary() returns nested objects as [String: Any?] (optional values),
366
+ // so cast to that and unwrap each value rather than [String: Any].
367
+ guard let payload = track.extraPayload?.toDictionary(),
368
+ let headersValue = payload["headers"],
369
+ let raw = headersValue as? [String: Any?]
370
+ else { return nil }
371
+
372
+ var headers: [String: String] = [:]
373
+ for (key, value) in raw {
374
+ if let stringValue = value as? String {
375
+ headers[key] = stringValue
376
+ }
377
+ }
378
+ return headers.isEmpty ? nil : headers
379
+ }
380
+
381
+ /// Builds AVURLAsset options, attaching custom HTTP headers for remote (non-local) tracks.
382
+ private func assetOptions(for track: TrackItem, isLocal: Bool) -> [String: Any] {
383
+ var options: [String: Any] = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
384
+ if !isLocal, let headers = httpHeaders(for: track) {
385
+ // AVURLAssetHTTPHeaderFieldsKey is the de-facto mechanism for per-asset request
386
+ // headers (also used by react-native-track-player). Applies to non-DRM HTTP(S).
387
+ options["AVURLAssetHTTPHeaderFieldsKey"] = headers
388
+ }
389
+ return options
390
+ }
391
+
361
392
  /// Creates a gapless-optimized AVPlayerItem with proper buffering configuration
362
393
  func createGaplessPlayerItem(for track: TrackItem, isPreload: Bool = false) -> AVPlayerItem? {
363
394
  let effectiveUrlString = DownloadManagerCore.shared.getEffectiveUrl(track: track)
@@ -396,9 +427,7 @@ extension TrackPlayerCore {
396
427
  asset = preloadedAsset
397
428
  NitroPlayerLogger.log("TrackPlayerCore", "🚀 Using preloaded asset for \(track.title)")
398
429
  } else {
399
- asset = AVURLAsset(url: url, options: [
400
- AVURLAssetPreferPreciseDurationAndTimingKey: true
401
- ])
430
+ asset = AVURLAsset(url: url, options: assetOptions(for: track, isLocal: isLocal))
402
431
  }
403
432
 
404
433
  let item = AVPlayerItem(asset: asset)
@@ -463,9 +492,7 @@ extension TrackPlayerCore {
463
492
  url = remoteUrl
464
493
  }
465
494
 
466
- let asset = AVURLAsset(url: url, options: [
467
- AVURLAssetPreferPreciseDurationAndTimingKey: true
468
- ])
495
+ let asset = AVURLAsset(url: url, options: self.assetOptions(for: track, isLocal: isLocal))
469
496
 
470
497
  asset.loadValuesAsynchronously(forKeys: Constants.preloadAssetKeys) { [weak self] in
471
498
  var allKeysLoaded = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "1.4.1-alpha.0",
3
+ "version": "1.4.2",
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",