react-native-nitro-player 1.4.1 → 1.4.3

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) {
@@ -8,6 +8,7 @@ import com.margelo.nitro.nitroplayer.Reason
8
8
  import com.margelo.nitro.nitroplayer.RepeatMode
9
9
  import com.margelo.nitro.nitroplayer.TrackPlayerState
10
10
  import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
11
+ import com.margelo.nitro.nitroplayer.media.NitroPlayerPlaybackService
11
12
 
12
13
  /**
13
14
  * Playback control — all public functions are suspend and execute on the player thread
@@ -95,6 +96,7 @@ suspend fun TrackPlayerCore.configure(config: PlayerConfig) =
95
96
  withPlayerContext {
96
97
  config.androidAutoEnabled?.let { NitroPlayerMediaBrowserService.isAndroidAutoEnabled = it }
97
98
  config.lookaheadCount?.let { lookaheadCount = it.toInt() }
99
+ config.androidNotificationIcon?.let { NitroPlayerPlaybackService.notificationSmallIconResName = it }
98
100
  mediaSessionManager?.configure(config.androidAutoEnabled, config.carPlayEnabled, config.showInNotification)
99
101
  }
100
102
 
@@ -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)
@@ -7,6 +7,7 @@ import android.os.IBinder
7
7
  import android.os.Looper
8
8
  import androidx.media3.common.util.UnstableApi
9
9
  import androidx.media3.exoplayer.ExoPlayer
10
+ import androidx.media3.session.DefaultMediaNotificationProvider
10
11
  import androidx.media3.session.MediaSession
11
12
  import androidx.media3.session.MediaSessionService
12
13
  import com.margelo.nitro.nitroplayer.core.NitroPlayerLogger
@@ -29,11 +30,24 @@ class NitroPlayerPlaybackService : MediaSessionService() {
29
30
 
30
31
  companion object {
31
32
  const val ACTION_LOCAL_BIND = "com.margelo.nitro.nitroplayer.LOCAL_BIND"
33
+
34
+ @Volatile
35
+ var notificationSmallIconResName: String? = null
36
+ set(value) {
37
+ field = value
38
+ instance?.let { service ->
39
+ service.mainHandler.post { service.applyNotificationSmallIcon() }
40
+ }
41
+ }
42
+
43
+ @Volatile
44
+ private var instance: NitroPlayerPlaybackService? = null
32
45
  }
33
46
 
34
47
  // ── Created in onCreate ────────────────────────────────────────────────
35
48
  private lateinit var player: ExoPlayer
36
49
  private var mediaSession: MediaSession? = null
50
+ private var notificationProvider: DefaultMediaNotificationProvider? = null
37
51
  private val mainHandler = Handler(Looper.getMainLooper())
38
52
 
39
53
  @Volatile
@@ -54,6 +68,7 @@ class NitroPlayerPlaybackService : MediaSessionService() {
54
68
  override fun onCreate() {
55
69
  super.onCreate()
56
70
  NitroPlayerLogger.log("PlaybackService") { "onCreate" }
71
+ instance = this
57
72
 
58
73
  // Build ExoPlayer on main looper (default)
59
74
  player = ExoPlayerBuilder.build(this)
@@ -65,12 +80,35 @@ class NitroPlayerPlaybackService : MediaSessionService() {
65
80
  .setCallback(MediaSessionCallbackFactory.create(this, playlistManager))
66
81
  .build()
67
82
 
68
- // Explicitly register the session with the service so that
69
- // MediaNotificationManager (created in super.onCreate()) can
70
- // connect its internal MediaController and post notifications.
83
+ val provider = DefaultMediaNotificationProvider.Builder(this).build()
84
+ notificationProvider = provider
85
+ applyNotificationSmallIcon()
86
+ setMediaNotificationProvider(provider)
87
+
71
88
  addSession(mediaSession!!)
89
+ }
72
90
 
73
- // Media3 automatically handles the notification via DefaultMediaNotificationProvider.
91
+ private fun applyNotificationSmallIcon() {
92
+ val name = notificationSmallIconResName ?: return
93
+ val provider = notificationProvider ?: return
94
+ val resId = resources.getIdentifier(name, "drawable", packageName)
95
+ if (resId == 0) {
96
+ NitroPlayerLogger.log("PlaybackService") {
97
+ "androidNotificationIcon '$name' not found in drawable resources — using default"
98
+ }
99
+ return
100
+ }
101
+ provider.setSmallIcon(resId)
102
+
103
+ // Live Refresh :)
104
+ val session = mediaSession
105
+ if (session != null && session.player.isPlaying) {
106
+ try {
107
+ onUpdateNotification(session, false)
108
+ } catch (e: Exception) {
109
+ NitroPlayerLogger.log("PlaybackService") { "notification refresh failed: ${e.message}" }
110
+ }
111
+ }
74
112
  }
75
113
 
76
114
  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
@@ -93,6 +131,8 @@ class NitroPlayerPlaybackService : MediaSessionService() {
93
131
 
94
132
  override fun onDestroy() {
95
133
  NitroPlayerLogger.log("PlaybackService") { "onDestroy" }
134
+ if (instance === this) instance = null
135
+ notificationProvider = null
96
136
  mediaSession?.release()
97
137
  mediaSession = null
98
138
  player.release()
@@ -113,7 +113,6 @@ final class HybridTrackPlayer: HybridTrackPlayerSpec {
113
113
  }
114
114
 
115
115
  // MARK: - Playback speed
116
-
117
116
  func setPlaybackSpeed(speed: Double) throws -> Promise<Void> {
118
117
  Promise.async { await self.core.setPlaybackSpeed(speed) }
119
118
  }
@@ -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
@@ -38,4 +38,5 @@ export interface PlayerConfig {
38
38
  * Higher values = more proactive loading, but more network requests
39
39
  */
40
40
  lookaheadCount?: number;
41
+ androidNotificationIcon?: string;
41
42
  }
@@ -11,6 +11,7 @@
11
11
  #include "PlayerConfig.hpp"
12
12
 
13
13
  #include <optional>
14
+ #include <string>
14
15
 
15
16
  namespace margelo::nitro::nitroplayer {
16
17
 
@@ -39,11 +40,14 @@ namespace margelo::nitro::nitroplayer {
39
40
  jni::local_ref<jni::JBoolean> showInNotification = this->getFieldValue(fieldShowInNotification);
40
41
  static const auto fieldLookaheadCount = clazz->getField<jni::JDouble>("lookaheadCount");
41
42
  jni::local_ref<jni::JDouble> lookaheadCount = this->getFieldValue(fieldLookaheadCount);
43
+ static const auto fieldAndroidNotificationIcon = clazz->getField<jni::JString>("androidNotificationIcon");
44
+ jni::local_ref<jni::JString> androidNotificationIcon = this->getFieldValue(fieldAndroidNotificationIcon);
42
45
  return PlayerConfig(
43
46
  androidAutoEnabled != nullptr ? std::make_optional(static_cast<bool>(androidAutoEnabled->value())) : std::nullopt,
44
47
  carPlayEnabled != nullptr ? std::make_optional(static_cast<bool>(carPlayEnabled->value())) : std::nullopt,
45
48
  showInNotification != nullptr ? std::make_optional(static_cast<bool>(showInNotification->value())) : std::nullopt,
46
- lookaheadCount != nullptr ? std::make_optional(lookaheadCount->value()) : std::nullopt
49
+ lookaheadCount != nullptr ? std::make_optional(lookaheadCount->value()) : std::nullopt,
50
+ androidNotificationIcon != nullptr ? std::make_optional(androidNotificationIcon->toStdString()) : std::nullopt
47
51
  );
48
52
  }
49
53
 
@@ -53,7 +57,7 @@ namespace margelo::nitro::nitroplayer {
53
57
  */
54
58
  [[maybe_unused]]
55
59
  static jni::local_ref<JPlayerConfig::javaobject> fromCpp(const PlayerConfig& value) {
56
- using JSignature = JPlayerConfig(jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JDouble>);
60
+ using JSignature = JPlayerConfig(jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JBoolean>, jni::alias_ref<jni::JDouble>, jni::alias_ref<jni::JString>);
57
61
  static const auto clazz = javaClassStatic();
58
62
  static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
59
63
  return create(
@@ -61,7 +65,8 @@ namespace margelo::nitro::nitroplayer {
61
65
  value.androidAutoEnabled.has_value() ? jni::JBoolean::valueOf(value.androidAutoEnabled.value()) : nullptr,
62
66
  value.carPlayEnabled.has_value() ? jni::JBoolean::valueOf(value.carPlayEnabled.value()) : nullptr,
63
67
  value.showInNotification.has_value() ? jni::JBoolean::valueOf(value.showInNotification.value()) : nullptr,
64
- value.lookaheadCount.has_value() ? jni::JDouble::valueOf(value.lookaheadCount.value()) : nullptr
68
+ value.lookaheadCount.has_value() ? jni::JDouble::valueOf(value.lookaheadCount.value()) : nullptr,
69
+ value.androidNotificationIcon.has_value() ? jni::make_jstring(value.androidNotificationIcon.value()) : nullptr
65
70
  );
66
71
  }
67
72
  };
@@ -28,7 +28,10 @@ data class PlayerConfig(
28
28
  val showInNotification: Boolean?,
29
29
  @DoNotStrip
30
30
  @Keep
31
- val lookaheadCount: Double?
31
+ val lookaheadCount: Double?,
32
+ @DoNotStrip
33
+ @Keep
34
+ val androidNotificationIcon: String?
32
35
  ) {
33
36
  /* primary constructor */
34
37
 
@@ -40,8 +43,8 @@ data class PlayerConfig(
40
43
  @Keep
41
44
  @Suppress("unused")
42
45
  @JvmStatic
43
- private fun fromCpp(androidAutoEnabled: Boolean?, carPlayEnabled: Boolean?, showInNotification: Boolean?, lookaheadCount: Double?): PlayerConfig {
44
- return PlayerConfig(androidAutoEnabled, carPlayEnabled, showInNotification, lookaheadCount)
46
+ private fun fromCpp(androidAutoEnabled: Boolean?, carPlayEnabled: Boolean?, showInNotification: Boolean?, lookaheadCount: Double?, androidNotificationIcon: String?): PlayerConfig {
47
+ return PlayerConfig(androidAutoEnabled, carPlayEnabled, showInNotification, lookaheadCount, androidNotificationIcon)
45
48
  }
46
49
  }
47
50
  }
@@ -18,7 +18,7 @@ public extension PlayerConfig {
18
18
  /**
19
19
  * Create a new instance of `PlayerConfig`.
20
20
  */
21
- init(androidAutoEnabled: Bool?, carPlayEnabled: Bool?, showInNotification: Bool?, lookaheadCount: Double?) {
21
+ init(androidAutoEnabled: Bool?, carPlayEnabled: Bool?, showInNotification: Bool?, lookaheadCount: Double?, androidNotificationIcon: String?) {
22
22
  self.init({ () -> bridge.std__optional_bool_ in
23
23
  if let __unwrappedValue = androidAutoEnabled {
24
24
  return bridge.create_std__optional_bool_(__unwrappedValue)
@@ -43,6 +43,12 @@ public extension PlayerConfig {
43
43
  } else {
44
44
  return .init()
45
45
  }
46
+ }(), { () -> bridge.std__optional_std__string_ in
47
+ if let __unwrappedValue = androidNotificationIcon {
48
+ return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
49
+ } else {
50
+ return .init()
51
+ }
46
52
  }())
47
53
  }
48
54
 
@@ -93,4 +99,16 @@ public extension PlayerConfig {
93
99
  }
94
100
  }()
95
101
  }
102
+
103
+ @inline(__always)
104
+ var androidNotificationIcon: String? {
105
+ return { () -> String? in
106
+ if bridge.has_value_std__optional_std__string_(self.__androidNotificationIcon) {
107
+ let __unwrapped = bridge.get_std__optional_std__string_(self.__androidNotificationIcon)
108
+ return String(__unwrapped)
109
+ } else {
110
+ return nil
111
+ }
112
+ }()
113
+ }
96
114
  }
@@ -31,6 +31,7 @@
31
31
 
32
32
 
33
33
  #include <optional>
34
+ #include <string>
34
35
 
35
36
  namespace margelo::nitro::nitroplayer {
36
37
 
@@ -43,10 +44,11 @@ namespace margelo::nitro::nitroplayer {
43
44
  std::optional<bool> carPlayEnabled SWIFT_PRIVATE;
44
45
  std::optional<bool> showInNotification SWIFT_PRIVATE;
45
46
  std::optional<double> lookaheadCount SWIFT_PRIVATE;
47
+ std::optional<std::string> androidNotificationIcon SWIFT_PRIVATE;
46
48
 
47
49
  public:
48
50
  PlayerConfig() = default;
49
- explicit PlayerConfig(std::optional<bool> androidAutoEnabled, std::optional<bool> carPlayEnabled, std::optional<bool> showInNotification, std::optional<double> lookaheadCount): androidAutoEnabled(androidAutoEnabled), carPlayEnabled(carPlayEnabled), showInNotification(showInNotification), lookaheadCount(lookaheadCount) {}
51
+ explicit PlayerConfig(std::optional<bool> androidAutoEnabled, std::optional<bool> carPlayEnabled, std::optional<bool> showInNotification, std::optional<double> lookaheadCount, std::optional<std::string> androidNotificationIcon): androidAutoEnabled(androidAutoEnabled), carPlayEnabled(carPlayEnabled), showInNotification(showInNotification), lookaheadCount(lookaheadCount), androidNotificationIcon(androidNotificationIcon) {}
50
52
 
51
53
  public:
52
54
  friend bool operator==(const PlayerConfig& lhs, const PlayerConfig& rhs) = default;
@@ -65,7 +67,8 @@ namespace margelo::nitro {
65
67
  JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "androidAutoEnabled"))),
66
68
  JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "carPlayEnabled"))),
67
69
  JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "showInNotification"))),
68
- JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "lookaheadCount")))
70
+ JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "lookaheadCount"))),
71
+ JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "androidNotificationIcon")))
69
72
  );
70
73
  }
71
74
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitroplayer::PlayerConfig& arg) {
@@ -74,6 +77,7 @@ namespace margelo::nitro {
74
77
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "carPlayEnabled"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.carPlayEnabled));
75
78
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "showInNotification"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.showInNotification));
76
79
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "lookaheadCount"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.lookaheadCount));
80
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "androidNotificationIcon"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.androidNotificationIcon));
77
81
  return obj;
78
82
  }
79
83
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -88,6 +92,7 @@ namespace margelo::nitro {
88
92
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "carPlayEnabled")))) return false;
89
93
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "showInNotification")))) return false;
90
94
  if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "lookaheadCount")))) return false;
95
+ if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "androidNotificationIcon")))) return false;
91
96
  return true;
92
97
  }
93
98
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
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",
@@ -49,4 +49,6 @@ export interface PlayerConfig {
49
49
  * Higher values = more proactive loading, but more network requests
50
50
  */
51
51
  lookaheadCount?: number
52
+
53
+ androidNotificationIcon?: string
52
54
  }