react-native-nitro-player 1.4.2 → 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.
@@ -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
  }
@@ -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.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
  }