react-native-nitro-player 0.5.4 → 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.
@@ -8,17 +8,17 @@
8
8
  import Foundation
9
9
  import NitroModules
10
10
 
11
- /// Manages persistence of downloaded track metadata using UserDefaults
11
+ /// Manages persistence of downloaded track metadata using file storage
12
12
  final class DownloadDatabase {
13
13
 
14
14
  // MARK: - Singleton
15
15
 
16
16
  static let shared = DownloadDatabase()
17
17
 
18
- // MARK: - Constants
18
+ // MARK: - Legacy UserDefaults Keys (migration only)
19
19
 
20
- private static let downloadedTracksKey = "NitroPlayerDownloadedTracks"
21
- private static let playlistTracksKey = "NitroPlayerPlaylistTracks"
20
+ private static let legacyDownloadedTracksKey = "NitroPlayerDownloadedTracks"
21
+ private static let legacyPlaylistTracksKey = "NitroPlayerPlaylistTracks"
22
22
 
23
23
  // MARK: - Properties
24
24
 
@@ -353,12 +353,21 @@ final class DownloadDatabase {
353
353
  private func saveToDisk() {
354
354
  do {
355
355
  let tracksData = try JSONEncoder().encode(downloadedTracks)
356
- UserDefaults.standard.set(tracksData, forKey: Self.downloadedTracksKey)
357
-
358
356
  // Convert Set to Array for encoding
359
357
  let playlistTracksDict = playlistTracks.mapValues { Array($0) }
360
358
  let playlistData = try JSONEncoder().encode(playlistTracksDict)
361
- UserDefaults.standard.set(playlistData, forKey: Self.playlistTracksKey)
359
+
360
+ // Combine both into a single JSON wrapper object
361
+ guard let tracksJson = try JSONSerialization.jsonObject(with: tracksData) as? [String: Any],
362
+ let playlistJson = try JSONSerialization.jsonObject(with: playlistData) as? [String: Any]
363
+ else { return }
364
+
365
+ let wrapper: [String: Any] = [
366
+ "downloadedTracks": tracksJson,
367
+ "playlistTracks": playlistJson,
368
+ ]
369
+ let data = try JSONSerialization.data(withJSONObject: wrapper, options: [])
370
+ try NitroPlayerStorage.write(filename: "downloads.json", data: data)
362
371
  } catch {
363
372
  NitroPlayerLogger.log("DownloadDatabase", "Failed to save to disk: \(error)")
364
373
  }
@@ -369,16 +378,42 @@ final class DownloadDatabase {
369
378
  NitroPlayerLogger.log("DownloadDatabase", "📀 LOADING FROM DISK")
370
379
  NitroPlayerLogger.log("DownloadDatabase", String(repeating: "📀", count: 40))
371
380
 
372
- // Load synchronously to ensure data is available immediately
373
- // Load downloaded tracks
374
- if let tracksData = UserDefaults.standard.data(forKey: Self.downloadedTracksKey) {
381
+ // 1. Try new JSON file (post-migration)
382
+ if let data = NitroPlayerStorage.read(filename: "downloads.json") {
383
+ do {
384
+ if let wrapper = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
385
+ if let tracksObj = wrapper["downloadedTracks"] as? [String: Any] {
386
+ let tracksData = try JSONSerialization.data(withJSONObject: tracksObj)
387
+ self.downloadedTracks = try JSONDecoder().decode(
388
+ [String: DownloadedTrackRecord].self, from: tracksData)
389
+ NitroPlayerLogger.log("DownloadDatabase", "✅ Loaded \(self.downloadedTracks.count) tracks from file")
390
+ }
391
+ if let playlistObj = wrapper["playlistTracks"] as? [String: Any] {
392
+ let playlistData = try JSONSerialization.data(withJSONObject: playlistObj)
393
+ let playlistTracksDict = try JSONDecoder().decode(
394
+ [String: [String]].self, from: playlistData)
395
+ self.playlistTracks = playlistTracksDict.mapValues { Set($0) }
396
+ NitroPlayerLogger.log("DownloadDatabase", "✅ Loaded \(self.playlistTracks.count) playlist associations from file")
397
+ }
398
+ }
399
+ } catch {
400
+ NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to load from file: \(error)")
401
+ }
402
+ NitroPlayerLogger.log("DownloadDatabase", String(repeating: "📀", count: 40) + "\n")
403
+ return
404
+ }
405
+
406
+ // 2. Migrate from UserDefaults (one-time, existing installs)
407
+ var didMigrate = false
408
+
409
+ if let tracksData = UserDefaults.standard.data(forKey: Self.legacyDownloadedTracksKey) {
375
410
  do {
376
411
  self.downloadedTracks = try JSONDecoder().decode(
377
412
  [String: DownloadedTrackRecord].self, from: tracksData)
378
- NitroPlayerLogger.log("DownloadDatabase", "✅ Loaded \(self.downloadedTracks.count) tracks from disk")
413
+ NitroPlayerLogger.log("DownloadDatabase", "✅ Migrated \(self.downloadedTracks.count) tracks from UserDefaults")
379
414
 
380
- // Migrate absolute paths → filenames (one-time, for existing installs)
381
- var needsMigration = false
415
+ // Migrate absolute paths → filenames (pre-existing migration)
416
+ var needsPathMigration = false
382
417
  for (trackId, record) in self.downloadedTracks {
383
418
  if record.localPath.contains("/") {
384
419
  let filename = URL(fileURLWithPath: record.localPath).lastPathComponent
@@ -391,41 +426,40 @@ final class DownloadDatabase {
391
426
  fileSize: record.fileSize,
392
427
  storageLocation: record.storageLocation
393
428
  )
394
- needsMigration = true
429
+ needsPathMigration = true
395
430
  }
396
431
  }
397
- if needsMigration { self.saveToDisk() }
398
-
399
- // Log each downloaded track
400
- for (trackId, record) in self.downloadedTracks {
401
- NitroPlayerLogger.log("DownloadDatabase", " 📥 \(trackId)")
402
- NitroPlayerLogger.log("DownloadDatabase", " Title: \(record.originalTrack.title)")
403
- NitroPlayerLogger.log("DownloadDatabase", " Path (filename): \(record.localPath)")
432
+ if needsPathMigration {
433
+ NitroPlayerLogger.log("DownloadDatabase", "✅ Migrated absolute paths to filenames")
404
434
  }
435
+
436
+ UserDefaults.standard.removeObject(forKey: Self.legacyDownloadedTracksKey)
437
+ didMigrate = true
405
438
  } catch {
406
- NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to load tracks from disk: \(error)")
439
+ NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to migrate tracks from UserDefaults: \(error)")
407
440
  }
408
441
  } else {
409
442
  NitroPlayerLogger.log("DownloadDatabase", "⚠️ No saved tracks found in UserDefaults")
410
443
  }
411
444
 
412
- // Load playlist associations
413
- if let playlistData = UserDefaults.standard.data(forKey: Self.playlistTracksKey) {
445
+ if let playlistData = UserDefaults.standard.data(forKey: Self.legacyPlaylistTracksKey) {
414
446
  do {
415
447
  let playlistTracksDict = try JSONDecoder().decode(
416
448
  [String: [String]].self, from: playlistData)
417
449
  self.playlistTracks = playlistTracksDict.mapValues { Set($0) }
418
- NitroPlayerLogger.log("DownloadDatabase", "✅ Loaded \(self.playlistTracks.count) playlist associations from disk")
419
-
420
- // Log playlist associations
421
- for (playlistId, trackIds) in self.playlistTracks {
422
- NitroPlayerLogger.log("DownloadDatabase", " 📋 Playlist \(playlistId): \(trackIds.count) tracks")
423
- }
450
+ NitroPlayerLogger.log("DownloadDatabase", "✅ Migrated \(self.playlistTracks.count) playlist associations from UserDefaults")
451
+ UserDefaults.standard.removeObject(forKey: Self.legacyPlaylistTracksKey)
452
+ didMigrate = true
424
453
  } catch {
425
- NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to load playlist tracks from disk: \(error)")
454
+ NitroPlayerLogger.log("DownloadDatabase", "❌ Failed to migrate playlist tracks from UserDefaults: \(error)")
426
455
  }
427
456
  } else {
428
- NitroPlayerLogger.log("DownloadDatabase", "⚠️ No playlist associations found")
457
+ NitroPlayerLogger.log("DownloadDatabase", "⚠️ No playlist associations found in UserDefaults")
458
+ }
459
+
460
+ if didMigrate {
461
+ // Persist migrated data in new file format
462
+ saveToDisk()
429
463
  }
430
464
 
431
465
  NitroPlayerLogger.log("DownloadDatabase", String(repeating: "📀", count: 40) + "\n")
@@ -456,6 +490,11 @@ final class DownloadDatabase {
456
490
  }
457
491
 
458
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
+
459
498
  return TrackItemRecord(
460
499
  id: track.id,
461
500
  title: track.title,
@@ -463,11 +502,28 @@ final class DownloadDatabase {
463
502
  album: track.album,
464
503
  duration: track.duration,
465
504
  url: track.url,
466
- artwork: variantToString(track.artwork)
505
+ artwork: variantToString(track.artwork),
506
+ extraPayload: extraPayloadDict
467
507
  )
468
508
  }
469
509
 
470
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
+
471
527
  return TrackItem(
472
528
  id: record.id,
473
529
  title: record.title,
@@ -476,7 +532,7 @@ final class DownloadDatabase {
476
532
  duration: record.duration,
477
533
  url: record.url,
478
534
  artwork: stringToVariant(record.artwork),
479
- extraPayload: nil
535
+ extraPayload: extraPayload
480
536
  )
481
537
  }
482
538
 
@@ -513,4 +569,59 @@ private struct TrackItemRecord: Codable {
513
569
  let duration: Double
514
570
  let url: String
515
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
+ }
516
627
  }
@@ -18,8 +18,9 @@ final class DownloadManagerCore: NSObject {
18
18
  // MARK: - Constants
19
19
 
20
20
  private static let backgroundSessionIdentifier = "com.nitroplayer.backgroundDownloads"
21
- private static let trackMetadataKey = "NitroPlayerTrackMetadata"
22
- private static let playlistAssociationsKey = "NitroPlayerPlaylistAssociations"
21
+ // Legacy UserDefaults keys (migration only)
22
+ private static let legacyTrackMetadataKey = "NitroPlayerTrackMetadata"
23
+ private static let legacyPlaylistAssociationsKey = "NitroPlayerPlaylistAssociations"
23
24
 
24
25
  // MARK: - Properties
25
26
 
@@ -507,48 +508,87 @@ final class DownloadManagerCore: NSObject {
507
508
  private func loadPersistedMetadata() {
508
509
  NitroPlayerLogger.log("DownloadManagerCore", "📦 Loading persisted metadata...")
509
510
 
510
- // Load track metadata
511
- if let data = UserDefaults.standard.data(forKey: Self.trackMetadataKey) {
511
+ // 1. Try new JSON file (post-migration)
512
+ if let data = NitroPlayerStorage.read(filename: "download_metadata.json") {
513
+ do {
514
+ if let wrapper = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
515
+ if let tracksObj = wrapper["trackMetadata"] as? [String: Any] {
516
+ let tracksData = try JSONSerialization.data(withJSONObject: tracksObj)
517
+ let records = try JSONDecoder().decode([String: TrackItemRecord].self, from: tracksData)
518
+ for (trackId, record) in records {
519
+ trackMetadata[trackId] = recordToTrackItem(record)
520
+ }
521
+ NitroPlayerLogger.log("DownloadManagerCore", " ✅ Loaded \(trackMetadata.count) track metadata entries from file")
522
+ }
523
+ if let assocObj = wrapper["playlistAssociations"] as? [String: String] {
524
+ playlistAssociations = assocObj
525
+ NitroPlayerLogger.log("DownloadManagerCore", " ✅ Loaded \(playlistAssociations.count) playlist associations from file")
526
+ }
527
+ }
528
+ } catch {
529
+ NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to load metadata from file: \(error)")
530
+ }
531
+ return
532
+ }
533
+
534
+ // 2. Migrate from UserDefaults (one-time, existing installs)
535
+ var didMigrate = false
536
+
537
+ if let data = UserDefaults.standard.data(forKey: Self.legacyTrackMetadataKey) {
512
538
  do {
513
539
  let records = try JSONDecoder().decode([String: TrackItemRecord].self, from: data)
514
540
  for (trackId, record) in records {
515
541
  trackMetadata[trackId] = recordToTrackItem(record)
516
542
  }
517
- NitroPlayerLogger.log("DownloadManagerCore", " ✅ Loaded \(trackMetadata.count) track metadata entries")
543
+ NitroPlayerLogger.log("DownloadManagerCore", " ✅ Migrated \(trackMetadata.count) track metadata entries from UserDefaults")
544
+ UserDefaults.standard.removeObject(forKey: Self.legacyTrackMetadataKey)
545
+ didMigrate = true
518
546
  } catch {
519
- NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to load track metadata: \(error)")
547
+ NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to migrate track metadata: \(error)")
520
548
  }
521
549
  } else {
522
550
  NitroPlayerLogger.log("DownloadManagerCore", " ⚠️ No persisted track metadata found")
523
551
  }
524
552
 
525
- // Load playlist associations
526
- if let data = UserDefaults.standard.data(forKey: Self.playlistAssociationsKey) {
553
+ if let data = UserDefaults.standard.data(forKey: Self.legacyPlaylistAssociationsKey) {
527
554
  do {
528
555
  playlistAssociations = try JSONDecoder().decode([String: String].self, from: data)
529
- NitroPlayerLogger.log("DownloadManagerCore", " ✅ Loaded \(playlistAssociations.count) playlist associations")
556
+ NitroPlayerLogger.log("DownloadManagerCore", " ✅ Migrated \(playlistAssociations.count) playlist associations from UserDefaults")
557
+ UserDefaults.standard.removeObject(forKey: Self.legacyPlaylistAssociationsKey)
558
+ didMigrate = true
530
559
  } catch {
531
- NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to load playlist associations: \(error)")
560
+ NitroPlayerLogger.log("DownloadManagerCore", " ❌ Failed to migrate playlist associations: \(error)")
532
561
  }
533
562
  } else {
534
563
  NitroPlayerLogger.log("DownloadManagerCore", " ⚠️ No persisted playlist associations found")
535
564
  }
565
+
566
+ if didMigrate {
567
+ savePersistedMetadata()
568
+ }
536
569
  }
537
570
 
538
571
  /// Persist track metadata and playlist associations to disk
539
572
  private func savePersistedMetadata() {
540
- // Convert TrackItem to TrackItemRecord for encoding
541
573
  var records: [String: TrackItemRecord] = [:]
542
574
  for (trackId, track) in trackMetadata {
543
575
  records[trackId] = trackItemToRecord(track)
544
576
  }
545
577
 
546
578
  do {
547
- let trackData = try JSONEncoder().encode(records)
548
- UserDefaults.standard.set(trackData, forKey: Self.trackMetadataKey)
549
-
579
+ let tracksData = try JSONEncoder().encode(records)
550
580
  let playlistData = try JSONEncoder().encode(playlistAssociations)
551
- UserDefaults.standard.set(playlistData, forKey: Self.playlistAssociationsKey)
581
+
582
+ guard let tracksJson = try JSONSerialization.jsonObject(with: tracksData) as? [String: Any],
583
+ let assocJson = try JSONSerialization.jsonObject(with: playlistData) as? [String: Any]
584
+ else { return }
585
+
586
+ let wrapper: [String: Any] = [
587
+ "trackMetadata": tracksJson,
588
+ "playlistAssociations": assocJson,
589
+ ]
590
+ let data = try JSONSerialization.data(withJSONObject: wrapper, options: [])
591
+ try NitroPlayerStorage.write(filename: "download_metadata.json", data: data)
552
592
  } catch {
553
593
  NitroPlayerLogger.log("DownloadManagerCore", "❌ Failed to save metadata: \(error)")
554
594
  }
@@ -573,6 +613,12 @@ final class DownloadManagerCore: NSObject {
573
613
  artworkString = value
574
614
  }
575
615
  }
616
+
617
+ var extraPayloadDict: [String: Any]? = nil
618
+ if let extraPayload = track.extraPayload {
619
+ extraPayloadDict = extraPayload.toDictionary()
620
+ }
621
+
576
622
  return TrackItemRecord(
577
623
  id: track.id,
578
624
  title: track.title,
@@ -580,12 +626,30 @@ final class DownloadManagerCore: NSObject {
580
626
  album: track.album,
581
627
  duration: track.duration,
582
628
  url: track.url,
583
- artwork: artworkString
629
+ artwork: artworkString,
630
+ extraPayload: extraPayloadDict
584
631
  )
585
632
  }
586
633
 
587
634
  private func recordToTrackItem(_ record: TrackItemRecord) -> TrackItem {
588
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
+
589
653
  return TrackItem(
590
654
  id: record.id,
591
655
  title: record.title,
@@ -594,7 +658,7 @@ final class DownloadManagerCore: NSObject {
594
658
  duration: record.duration,
595
659
  url: record.url,
596
660
  artwork: artwork,
597
- extraPayload: nil
661
+ extraPayload: extraPayload
598
662
  )
599
663
  }
600
664
 
@@ -923,4 +987,59 @@ private struct TrackItemRecord: Codable {
923
987
  let duration: Double
924
988
  let url: String
925
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
+ }
926
1045
  }