react-native-nitro-player 0.5.5 → 0.5.6

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.
@@ -12,7 +12,10 @@ object NitroPlayerLogger {
12
12
  * Use trailing lambda syntax: NitroPlayerLogger.log("Tag") { "msg $value" }
13
13
  * The lambda is inlined (no heap allocation) and skipped entirely when disabled.
14
14
  */
15
- inline fun log(header: String = "NitroPlayer", message: () -> String) {
15
+ inline fun log(
16
+ header: String = "NitroPlayer",
17
+ message: () -> String,
18
+ ) {
16
19
  if (isEnabled) {
17
20
  Log.d(header, message())
18
21
  }
@@ -23,7 +26,10 @@ object NitroPlayerLogger {
23
26
  * Note: the String is evaluated at the call site before this function runs.
24
27
  * Migrate to the lambda overload for hot paths.
25
28
  */
26
- fun log(header: String = "NitroPlayer", message: String) {
29
+ fun log(
30
+ header: String = "NitroPlayer",
31
+ message: String,
32
+ ) {
27
33
  if (isEnabled) {
28
34
  Log.d(header, message)
29
35
  }
@@ -698,10 +698,11 @@ class TrackPlayerCore private constructor(
698
698
  currentRepeatMode = mode
699
699
  if (::player.isInitialized) {
700
700
  handler.post {
701
- player.repeatMode = when (mode) {
702
- RepeatMode.TRACK -> Player.REPEAT_MODE_ONE
703
- else -> Player.REPEAT_MODE_OFF
704
- }
701
+ player.repeatMode =
702
+ when (mode) {
703
+ RepeatMode.TRACK -> Player.REPEAT_MODE_ONE
704
+ else -> Player.REPEAT_MODE_OFF
705
+ }
705
706
  }
706
707
  }
707
708
  NitroPlayerLogger.log("TrackPlayerCore", "🔁 setRepeatMode: $mode")
@@ -1,6 +1,7 @@
1
1
  package com.margelo.nitro.nitroplayer.download
2
2
 
3
3
  import android.content.Context
4
+ import com.margelo.nitro.core.AnyMap
4
5
  import com.margelo.nitro.core.NullType
5
6
  import com.margelo.nitro.nitroplayer.*
6
7
  import com.margelo.nitro.nitroplayer.core.NitroPlayerLogger
@@ -18,6 +19,7 @@ class DownloadDatabase private constructor(
18
19
  ) {
19
20
  companion object {
20
21
  private const val TAG = "DownloadDatabase"
22
+
21
23
  // Legacy SharedPreferences keys (migration only)
22
24
  private const val LEGACY_PREFS_NAME = "NitroPlayerDownloads"
23
25
  private const val LEGACY_KEY_DOWNLOADED_TRACKS = "downloaded_tracks"
@@ -304,10 +306,11 @@ class DownloadDatabase private constructor(
304
306
  playlistJson.put(playlistId, JSONArray(trackIds.toList()))
305
307
  }
306
308
 
307
- val wrapper = JSONObject().apply {
308
- put("downloadedTracks", tracksJson)
309
- put("playlistTracks", playlistJson)
310
- }
309
+ val wrapper =
310
+ JSONObject().apply {
311
+ put("downloadedTracks", tracksJson)
312
+ put("playlistTracks", playlistJson)
313
+ }
311
314
  NitroPlayerStorage.write(context, "downloads.json", wrapper.toString())
312
315
  } catch (e: Exception) {
313
316
  e.printStackTrace()
@@ -390,8 +393,14 @@ class DownloadDatabase private constructor(
390
393
  }
391
394
 
392
395
  // Conversion Helpers
393
- private fun trackItemToRecord(track: TrackItem): TrackItemRecord =
394
- TrackItemRecord(
396
+ private fun trackItemToRecord(track: TrackItem): TrackItemRecord {
397
+ val extraPayloadJson =
398
+ track.extraPayload?.let { payload ->
399
+ val extraPayloadMap = payload.toHashMap()
400
+ JSONObject(extraPayloadMap)
401
+ }
402
+
403
+ return TrackItemRecord(
395
404
  id = track.id,
396
405
  title = track.title,
397
406
  artist = track.artist,
@@ -399,7 +408,9 @@ class DownloadDatabase private constructor(
399
408
  duration = track.duration,
400
409
  url = track.url,
401
410
  artwork = track.artwork?.asSecondOrNull(),
411
+ extraPayload = extraPayloadJson,
402
412
  )
413
+ }
403
414
 
404
415
  private fun recordToTrackItem(record: TrackItemRecord): TrackItem {
405
416
  val artworkVariant =
@@ -409,6 +420,21 @@ class DownloadDatabase private constructor(
409
420
  null
410
421
  }
411
422
 
423
+ val extraPayload: AnyMap? =
424
+ record.extraPayload?.let { extraPayloadJson ->
425
+ val map = AnyMap()
426
+ val keyIterator = extraPayloadJson.keys()
427
+ while (keyIterator.hasNext()) {
428
+ val key = keyIterator.next()
429
+ when (val value = extraPayloadJson.get(key)) {
430
+ is String -> map.setString(key, value)
431
+ is Number -> map.setDouble(key, value.toDouble())
432
+ is Boolean -> map.setBoolean(key, value)
433
+ }
434
+ }
435
+ map
436
+ }
437
+
412
438
  return TrackItem(
413
439
  id = record.id,
414
440
  title = record.title,
@@ -417,7 +443,7 @@ class DownloadDatabase private constructor(
417
443
  duration = record.duration,
418
444
  url = record.url,
419
445
  artwork = artworkVariant,
420
- extraPayload = null,
446
+ extraPayload = extraPayload,
421
447
  )
422
448
  }
423
449
 
@@ -440,15 +466,14 @@ class DownloadDatabase private constructor(
440
466
  )
441
467
  }
442
468
 
443
- private fun convertPlaylistManagerToNitro(playlist: com.margelo.nitro.nitroplayer.playlist.Playlist): Playlist {
444
- return Playlist(
469
+ private fun convertPlaylistManagerToNitro(playlist: com.margelo.nitro.nitroplayer.playlist.Playlist): Playlist =
470
+ Playlist(
445
471
  id = playlist.id,
446
472
  name = playlist.name,
447
473
  description = null,
448
474
  artwork = null,
449
475
  tracks = playlist.tracks.toTypedArray(),
450
476
  )
451
- }
452
477
  }
453
478
 
454
479
  // Internal record classes
@@ -494,6 +519,7 @@ internal data class TrackItemRecord(
494
519
  val duration: Double,
495
520
  val url: String,
496
521
  val artwork: String?,
522
+ val extraPayload: JSONObject?,
497
523
  ) {
498
524
  fun toJson(): JSONObject =
499
525
  JSONObject().apply {
@@ -504,6 +530,7 @@ internal data class TrackItemRecord(
504
530
  put("duration", duration)
505
531
  put("url", url)
506
532
  put("artwork", artwork)
533
+ put("extraPayload", extraPayload)
507
534
  }
508
535
 
509
536
  companion object {
@@ -516,6 +543,12 @@ internal data class TrackItemRecord(
516
543
  duration = json.getDouble("duration"),
517
544
  url = json.getString("url"),
518
545
  artwork = if (json.isNull("artwork")) null else json.getString("artwork"),
546
+ extraPayload =
547
+ if (json.has("extraPayload") && !json.isNull("extraPayload")) {
548
+ json.getJSONObject("extraPayload")
549
+ } else {
550
+ null
551
+ },
519
552
  )
520
553
  }
521
554
  }
@@ -405,10 +405,11 @@ class PlaylistManager private constructor(
405
405
  }
406
406
  }
407
407
 
408
- val wrapper = JSONObject().apply {
409
- put("playlists", jsonArray)
410
- put("currentPlaylistId", currentPlaylistId)
411
- }
408
+ val wrapper =
409
+ JSONObject().apply {
410
+ put("playlists", jsonArray)
411
+ put("currentPlaylistId", currentPlaylistId)
412
+ }
412
413
  NitroPlayerStorage.write(context, "playlists.json", wrapper.toString())
413
414
  } catch (e: Exception) {
414
415
  e.printStackTrace()
@@ -423,8 +424,12 @@ class PlaylistManager private constructor(
423
424
  val wrapper = JSONObject(json)
424
425
  val jsonArray = wrapper.optJSONArray("playlists") ?: JSONArray()
425
426
  parseAndLoadPlaylists(jsonArray)
426
- currentPlaylistId = if (wrapper.isNull("currentPlaylistId")) null
427
- else wrapper.optString("currentPlaylistId", null.toString()).takeIf { it != "null" }
427
+ currentPlaylistId =
428
+ if (wrapper.isNull("currentPlaylistId")) {
429
+ null
430
+ } else {
431
+ wrapper.optString("currentPlaylistId", null.toString()).takeIf { it != "null" }
432
+ }
428
433
  } catch (e: Exception) {
429
434
  e.printStackTrace()
430
435
  }
@@ -440,7 +445,11 @@ class PlaylistManager private constructor(
440
445
  parseAndLoadPlaylists(jsonArray)
441
446
  currentPlaylistId = prefs.getString("currentPlaylistId", null)
442
447
  // Remove old SharedPreferences data to free space
443
- prefs.edit().remove("playlists").remove("currentPlaylistId").apply()
448
+ prefs
449
+ .edit()
450
+ .remove("playlists")
451
+ .remove("currentPlaylistId")
452
+ .apply()
444
453
  // Persist in new format
445
454
  saveToFile()
446
455
  } catch (e: Exception) {
@@ -9,7 +9,10 @@ object NitroPlayerStorage {
9
9
  private const val DIR_NAME = "nitroplayer"
10
10
 
11
11
  /** Reads the contents of [filename] from the NitroPlayer storage directory, or null if absent. */
12
- fun read(context: Context, filename: String): String? {
12
+ fun read(
13
+ context: Context,
14
+ filename: String,
15
+ ): String? {
13
16
  val file = File(storageDirectory(context), filename)
14
17
  return if (file.exists()) {
15
18
  try {
@@ -28,7 +31,11 @@ object NitroPlayerStorage {
28
31
  * Writes to `<filename>.tmp` first, then renames — leaving the prior file
29
32
  * untouched on failure (crash-safe).
30
33
  */
31
- fun write(context: Context, filename: String, json: String) {
34
+ fun write(
35
+ context: Context,
36
+ filename: String,
37
+ json: String,
38
+ ) {
32
39
  try {
33
40
  val dir = storageDirectory(context)
34
41
  dir.mkdirs()
@@ -490,6 +490,11 @@ final class DownloadDatabase {
490
490
  }
491
491
 
492
492
  private func trackItemToRecord(_ track: TrackItem) -> TrackItemRecord {
493
+ var extraPayloadDict: [String: Any]? = nil
494
+ if let extraPayload = track.extraPayload {
495
+ extraPayloadDict = extraPayload.toDictionary()
496
+ }
497
+
493
498
  return TrackItemRecord(
494
499
  id: track.id,
495
500
  title: track.title,
@@ -497,11 +502,28 @@ final class DownloadDatabase {
497
502
  album: track.album,
498
503
  duration: track.duration,
499
504
  url: track.url,
500
- artwork: variantToString(track.artwork)
505
+ artwork: variantToString(track.artwork),
506
+ extraPayload: extraPayloadDict
501
507
  )
502
508
  }
503
509
 
504
510
  private func recordToTrackItem(_ record: TrackItemRecord) -> TrackItem {
511
+ var extraPayload: AnyMap? = nil
512
+ if let extraPayloadDict = record.extraPayload {
513
+ extraPayload = AnyMap()
514
+ for (key, value) in extraPayloadDict {
515
+ if let stringValue = value as? String {
516
+ extraPayload?.setString(key: key, value: stringValue)
517
+ } else if let doubleValue = value as? Double {
518
+ extraPayload?.setDouble(key: key, value: doubleValue)
519
+ } else if let intValue = value as? Int {
520
+ extraPayload?.setDouble(key: key, value: Double(intValue))
521
+ } else if let boolValue = value as? Bool {
522
+ extraPayload?.setBoolean(key: key, value: boolValue)
523
+ }
524
+ }
525
+ }
526
+
505
527
  return TrackItem(
506
528
  id: record.id,
507
529
  title: record.title,
@@ -510,7 +532,7 @@ final class DownloadDatabase {
510
532
  duration: record.duration,
511
533
  url: record.url,
512
534
  artwork: stringToVariant(record.artwork),
513
- extraPayload: nil
535
+ extraPayload: extraPayload
514
536
  )
515
537
  }
516
538
 
@@ -547,4 +569,59 @@ private struct TrackItemRecord: Codable {
547
569
  let duration: Double
548
570
  let url: String
549
571
  let artwork: String?
572
+ let extraPayload: [String: Any]?
573
+
574
+ enum CodingKeys: String, CodingKey {
575
+ case id, title, artist, album, duration, url, artwork, extraPayload
576
+ }
577
+
578
+ // Manual encoding to handle [String: Any]
579
+ func encode(to encoder: Encoder) throws {
580
+ var container = encoder.container(keyedBy: CodingKeys.self)
581
+ try container.encode(id, forKey: .id)
582
+ try container.encode(title, forKey: .title)
583
+ try container.encode(artist, forKey: .artist)
584
+ try container.encode(album, forKey: .album)
585
+ try container.encode(duration, forKey: .duration)
586
+ try container.encode(url, forKey: .url)
587
+ try container.encodeIfPresent(artwork, forKey: .artwork)
588
+
589
+ if let extraPayload = extraPayload {
590
+ let jsonData = try JSONSerialization.data(withJSONObject: extraPayload)
591
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
592
+ try container.encode(jsonString, forKey: .extraPayload)
593
+ }
594
+ }
595
+ }
596
+
597
+ // Manual decoding to handle [String: Any]
598
+ init(from decoder: Decoder) throws {
599
+ let container = try decoder.container(keyedBy: CodingKeys.self)
600
+ id = try container.decode(String.self, forKey: .id)
601
+ title = try container.decode(String.self, forKey: .title)
602
+ artist = try container.decode(String.self, forKey: .artist)
603
+ album = try container.decode(String.self, forKey: .album)
604
+ duration = try container.decode(Double.self, forKey: .duration)
605
+ url = try container.decode(String.self, forKey: .url)
606
+ artwork = try container.decodeIfPresent(String.self, forKey: .artwork)
607
+
608
+ if let jsonString = try? container.decodeIfPresent(String.self, forKey: .extraPayload),
609
+ let jsonData = jsonString.data(using: .utf8) {
610
+ extraPayload = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
611
+ } else {
612
+ extraPayload = nil
613
+ }
614
+ }
615
+
616
+ // Initializer for code creation
617
+ init(id: String, title: String, artist: String, album: String, duration: Double, url: String, artwork: String?, extraPayload: [String: Any]?) {
618
+ self.id = id
619
+ self.title = title
620
+ self.artist = artist
621
+ self.album = album
622
+ self.duration = duration
623
+ self.url = url
624
+ self.artwork = artwork
625
+ self.extraPayload = extraPayload
626
+ }
550
627
  }
@@ -613,6 +613,12 @@ final class DownloadManagerCore: NSObject {
613
613
  artworkString = value
614
614
  }
615
615
  }
616
+
617
+ var extraPayloadDict: [String: Any]? = nil
618
+ if let extraPayload = track.extraPayload {
619
+ extraPayloadDict = extraPayload.toDictionary()
620
+ }
621
+
616
622
  return TrackItemRecord(
617
623
  id: track.id,
618
624
  title: track.title,
@@ -620,12 +626,30 @@ final class DownloadManagerCore: NSObject {
620
626
  album: track.album,
621
627
  duration: track.duration,
622
628
  url: track.url,
623
- artwork: artworkString
629
+ artwork: artworkString,
630
+ extraPayload: extraPayloadDict
624
631
  )
625
632
  }
626
633
 
627
634
  private func recordToTrackItem(_ record: TrackItemRecord) -> TrackItem {
628
635
  let artwork: Variant_NullType_String? = record.artwork.map { .second($0) }
636
+
637
+ var extraPayload: AnyMap? = nil
638
+ if let extraPayloadDict = record.extraPayload {
639
+ extraPayload = AnyMap()
640
+ for (key, value) in extraPayloadDict {
641
+ if let stringValue = value as? String {
642
+ extraPayload?.setString(key: key, value: stringValue)
643
+ } else if let doubleValue = value as? Double {
644
+ extraPayload?.setDouble(key: key, value: doubleValue)
645
+ } else if let intValue = value as? Int {
646
+ extraPayload?.setDouble(key: key, value: Double(intValue))
647
+ } else if let boolValue = value as? Bool {
648
+ extraPayload?.setBoolean(key: key, value: boolValue)
649
+ }
650
+ }
651
+ }
652
+
629
653
  return TrackItem(
630
654
  id: record.id,
631
655
  title: record.title,
@@ -634,7 +658,7 @@ final class DownloadManagerCore: NSObject {
634
658
  duration: record.duration,
635
659
  url: record.url,
636
660
  artwork: artwork,
637
- extraPayload: nil
661
+ extraPayload: extraPayload
638
662
  )
639
663
  }
640
664
 
@@ -963,4 +987,59 @@ private struct TrackItemRecord: Codable {
963
987
  let duration: Double
964
988
  let url: String
965
989
  let artwork: String?
990
+ let extraPayload: [String: Any]?
991
+
992
+ enum CodingKeys: String, CodingKey {
993
+ case id, title, artist, album, duration, url, artwork, extraPayload
994
+ }
995
+
996
+ // Manual encoding to handle [String: Any]
997
+ func encode(to encoder: Encoder) throws {
998
+ var container = encoder.container(keyedBy: CodingKeys.self)
999
+ try container.encode(id, forKey: .id)
1000
+ try container.encode(title, forKey: .title)
1001
+ try container.encode(artist, forKey: .artist)
1002
+ try container.encode(album, forKey: .album)
1003
+ try container.encode(duration, forKey: .duration)
1004
+ try container.encode(url, forKey: .url)
1005
+ try container.encodeIfPresent(artwork, forKey: .artwork)
1006
+
1007
+ if let extraPayload = extraPayload {
1008
+ let jsonData = try JSONSerialization.data(withJSONObject: extraPayload)
1009
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
1010
+ try container.encode(jsonString, forKey: .extraPayload)
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ // Manual decoding to handle [String: Any]
1016
+ init(from decoder: Decoder) throws {
1017
+ let container = try decoder.container(keyedBy: CodingKeys.self)
1018
+ id = try container.decode(String.self, forKey: .id)
1019
+ title = try container.decode(String.self, forKey: .title)
1020
+ artist = try container.decode(String.self, forKey: .artist)
1021
+ album = try container.decode(String.self, forKey: .album)
1022
+ duration = try container.decode(Double.self, forKey: .duration)
1023
+ url = try container.decode(String.self, forKey: .url)
1024
+ artwork = try container.decodeIfPresent(String.self, forKey: .artwork)
1025
+
1026
+ if let jsonString = try? container.decodeIfPresent(String.self, forKey: .extraPayload),
1027
+ let jsonData = jsonString.data(using: .utf8) {
1028
+ extraPayload = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
1029
+ } else {
1030
+ extraPayload = nil
1031
+ }
1032
+ }
1033
+
1034
+ // Initializer for code creation
1035
+ init(id: String, title: String, artist: String, album: String, duration: Double, url: String, artwork: String?, extraPayload: [String: Any]?) {
1036
+ self.id = id
1037
+ self.title = title
1038
+ self.artist = artist
1039
+ self.album = album
1040
+ self.duration = duration
1041
+ self.url = url
1042
+ self.artwork = artwork
1043
+ self.extraPayload = extraPayload
1044
+ }
966
1045
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
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",