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

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.
@@ -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
 
@@ -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
  }
@@ -57,6 +57,10 @@ class TrackPlayerCore: NSObject {
57
57
  internal let preloadQueue = DispatchQueue(label: "com.nitroplayer.preload", qos: .utility)
58
58
  internal var didRequestUrlsForCurrentItem = false
59
59
 
60
+ // Follows http→https redirects that AVURLAsset otherwise silently drops (issue #111).
61
+ // Attached as the resource-loader delegate for cleartext-http assets only.
62
+ internal let redirectResolver = TrackPlayerRedirectResolver()
63
+
60
64
  // MARK: - Stall & network recovery
61
65
  // Whether the user/app wants playback ongoing (true after play(), false after pause()).
62
66
  internal var intendedToPlay = false
@@ -216,6 +220,7 @@ class TrackPlayerCore: NSObject {
216
220
  self.pathMonitor = nil
217
221
  self.preloadedAssets.removeAll()
218
222
  self.failedItemRetryCounts.removeAll()
223
+ self.redirectResolver.clear()
219
224
  }
220
225
  }
221
226
 
@@ -389,6 +389,20 @@ extension TrackPlayerCore {
389
389
  return options
390
390
  }
391
391
 
392
+ /// Builds the AVURLAsset for a track URL. Cleartext-`http` remote URLs are routed through
393
+ /// `redirectResolver` so AVFoundation follows http→https redirects it would otherwise drop
394
+ /// (issue #111). HTTPS and local URLs load natively, unchanged.
395
+ func makeAsset(for track: TrackItem, url: URL, isLocal: Bool) -> AVURLAsset {
396
+ let options = assetOptions(for: track, isLocal: isLocal)
397
+ guard !isLocal, let wrappedURL = TrackPlayerRedirectResolver.wrap(url) else {
398
+ return AVURLAsset(url: url, options: options)
399
+ }
400
+ redirectResolver.registerHeaders(httpHeaders(for: track), for: wrappedURL)
401
+ let asset = AVURLAsset(url: wrappedURL, options: options)
402
+ asset.resourceLoader.setDelegate(redirectResolver, queue: redirectResolver.queue)
403
+ return asset
404
+ }
405
+
392
406
  /// Creates a gapless-optimized AVPlayerItem with proper buffering configuration
393
407
  func createGaplessPlayerItem(for track: TrackItem, isPreload: Bool = false) -> AVPlayerItem? {
394
408
  let effectiveUrlString = DownloadManagerCore.shared.getEffectiveUrl(track: track)
@@ -427,7 +441,7 @@ extension TrackPlayerCore {
427
441
  asset = preloadedAsset
428
442
  NitroPlayerLogger.log("TrackPlayerCore", "🚀 Using preloaded asset for \(track.title)")
429
443
  } else {
430
- asset = AVURLAsset(url: url, options: assetOptions(for: track, isLocal: isLocal))
444
+ asset = makeAsset(for: track, url: url, isLocal: isLocal)
431
445
  }
432
446
 
433
447
  let item = AVPlayerItem(asset: asset)
@@ -492,7 +506,7 @@ extension TrackPlayerCore {
492
506
  url = remoteUrl
493
507
  }
494
508
 
495
- let asset = AVURLAsset(url: url, options: self.assetOptions(for: track, isLocal: isLocal))
509
+ let asset = self.makeAsset(for: track, url: url, isLocal: isLocal)
496
510
 
497
511
  asset.loadValuesAsynchronously(forKeys: Constants.preloadAssetKeys) { [weak self] in
498
512
  var allKeysLoaded = true
@@ -0,0 +1,150 @@
1
+ //
2
+ // TrackPlayerRedirectResolver.swift
3
+ // NitroPlayer
4
+ //
5
+ // Created by Ritesh Shukla.
6
+ //
7
+ // AVURLAsset does not reliably follow HTTP→HTTPS redirects: when a cleartext `http://`
8
+ // track URL answers with a 30x (e.g. a universal `308 → https://...` upgrade), AVFoundation
9
+ // silently drops the redirect and the item fails to load (issue #111). Unlike ExoPlayer's
10
+ // `setAllowCrossProtocolRedirects(true)`, AVFoundation exposes no such flag.
11
+ //
12
+ // We work around it by giving such assets a sentinel URL scheme so AVFoundation has to ask
13
+ // this resource-loader delegate to load them. We then resolve the redirect chain ourselves
14
+ // with URLSession (which follows redirects) and hand AVFoundation a redirect to the final
15
+ // URL — after which it loads the media natively from the resolved location.
16
+ //
17
+ // Only cleartext-`http` URLs are routed here. HTTPS assets are left untouched: AVFoundation
18
+ // follows same-scheme redirects on its own, so only the failing http path pays the extra
19
+ // resolution request. The app must still permit the initial cleartext connection via ATS
20
+ // (`NSAppTransportSecurity`); this delegate only fixes the redirect-following, not ATS.
21
+ //
22
+
23
+ import AVFoundation
24
+ import Foundation
25
+
26
+ final class TrackPlayerRedirectResolver: NSObject, AVAssetResourceLoaderDelegate {
27
+
28
+ /// Sentinel scheme prefix. A wrapped URL looks like `nitroredirect+http://host/path`.
29
+ private static let schemePrefix = "nitroredirect+"
30
+
31
+ /// Serial queue the resource-loader delegate methods (and our task bookkeeping) run on.
32
+ let queue = DispatchQueue(label: "com.nitroplayer.redirect", qos: .userInitiated)
33
+
34
+ private let session: URLSession
35
+
36
+ // In-flight resolution tasks, keyed by loading request, so a cancelled request cancels its
37
+ // task. Touched only on `queue` (delegate callbacks + the task completion is hopped onto it).
38
+ private var tasksByRequest: [ObjectIdentifier: URLSessionDataTask] = [:]
39
+
40
+ // Custom request headers (e.g. `Authorization`) per wrapped-URL, so the resolution request can
41
+ // authenticate when a server requires auth even to return the redirect. Written on the player
42
+ // queue (registerHeaders), read on `queue`; guarded by `headersLock`.
43
+ private var headersByURL: [String: [String: String]] = [:]
44
+ private let headersLock = NSLock()
45
+
46
+ override init() {
47
+ let config = URLSessionConfiguration.ephemeral
48
+ config.requestCachePolicy = .reloadIgnoringLocalCacheData
49
+ session = URLSession(configuration: config)
50
+ super.init()
51
+ }
52
+
53
+ // MARK: - URL wrapping
54
+
55
+ /// Wraps a cleartext-`http` URL in the sentinel scheme so AVFoundation routes it here.
56
+ /// Returns nil for anything else (https, file, custom schemes) — those load natively.
57
+ static func wrap(_ url: URL) -> URL? {
58
+ guard url.scheme?.lowercased() == "http",
59
+ var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
60
+ else { return nil }
61
+ components.scheme = schemePrefix + "http"
62
+ return components.url
63
+ }
64
+
65
+ /// Restores the real URL from a sentinel-wrapped URL.
66
+ private static func unwrap(_ url: URL) -> URL? {
67
+ guard let scheme = url.scheme?.lowercased(), scheme.hasPrefix(schemePrefix),
68
+ var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
69
+ else { return nil }
70
+ components.scheme = String(scheme.dropFirst(schemePrefix.count))
71
+ return components.url
72
+ }
73
+
74
+ // MARK: - Header registry
75
+
76
+ func registerHeaders(_ headers: [String: String]?, for wrappedURL: URL) {
77
+ guard let headers, !headers.isEmpty else { return }
78
+ headersLock.lock()
79
+ defer { headersLock.unlock() }
80
+ headersByURL[wrappedURL.absoluteString] = headers
81
+ }
82
+
83
+ /// Releases all registered headers. Call when the player is torn down.
84
+ func clear() {
85
+ headersLock.lock()
86
+ defer { headersLock.unlock() }
87
+ headersByURL.removeAll()
88
+ }
89
+
90
+ private func headers(for wrappedURL: URL) -> [String: String]? {
91
+ headersLock.lock()
92
+ defer { headersLock.unlock() }
93
+ return headersByURL[wrappedURL.absoluteString]
94
+ }
95
+
96
+ // MARK: - AVAssetResourceLoaderDelegate
97
+
98
+ func resourceLoader(
99
+ _ resourceLoader: AVAssetResourceLoader,
100
+ shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest
101
+ ) -> Bool {
102
+ guard let wrappedURL = loadingRequest.request.url,
103
+ let realURL = Self.unwrap(wrappedURL)
104
+ else { return false }
105
+
106
+ var request = URLRequest(url: realURL)
107
+ // HEAD is enough: we only need `response.url` after URLSession follows the redirect chain.
108
+ // Even if the resolved endpoint rejects HEAD (405), the post-redirect URL is still reported.
109
+ request.httpMethod = "HEAD"
110
+ headers(for: wrappedURL)?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
111
+ loadingRequest.request.allHTTPHeaderFields?.forEach { request.setValue($1, forHTTPHeaderField: $0) }
112
+
113
+ let key = ObjectIdentifier(loadingRequest)
114
+ let task = session.dataTask(with: request) { [weak self] _, response, error in
115
+ self?.queue.async {
116
+ guard let self else { return }
117
+ self.tasksByRequest.removeValue(forKey: key)
118
+ guard !loadingRequest.isCancelled, !loadingRequest.isFinished else { return }
119
+
120
+ if let error {
121
+ NitroPlayerLogger.log("TrackPlayerCore", "❌ Redirect resolution failed for \(realURL): \(error.localizedDescription)")
122
+ loadingRequest.finishLoading(with: error)
123
+ return
124
+ }
125
+
126
+ // `response.url` is the final URL after URLSession followed all redirects.
127
+ let finalURL = response?.url ?? realURL
128
+ if finalURL != realURL {
129
+ NitroPlayerLogger.log("TrackPlayerCore", "↪️ Resolved redirect \(realURL) → \(finalURL)")
130
+ }
131
+ var redirect = URLRequest(url: finalURL)
132
+ self.headers(for: wrappedURL)?.forEach { redirect.setValue($1, forHTTPHeaderField: $0) }
133
+ loadingRequest.redirect = redirect
134
+ loadingRequest.response = HTTPURLResponse(
135
+ url: finalURL, statusCode: 302, httpVersion: "HTTP/1.1", headerFields: nil)
136
+ loadingRequest.finishLoading()
137
+ }
138
+ }
139
+ tasksByRequest[key] = task
140
+ task.resume()
141
+ return true
142
+ }
143
+
144
+ func resourceLoader(
145
+ _ resourceLoader: AVAssetResourceLoader,
146
+ didCancel loadingRequest: AVAssetResourceLoadingRequest
147
+ ) {
148
+ tasksByRequest.removeValue(forKey: ObjectIdentifier(loadingRequest))?.cancel()
149
+ }
150
+ }
@@ -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.2",
3
+ "version": "1.4.4-alpha.0",
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
  }