react-native-nitro-player 0.5.8 → 0.5.9-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.
Files changed (269) hide show
  1. package/android/src/main/cpp/cpp-adapter.cpp +5 -1
  2. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +2 -2
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +112 -55
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +3 -1
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +19 -8
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadWorker.kt +2 -1
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +14 -2
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +29 -30
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +10 -31
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/queue/Queue.kt +1 -1
  11. package/ios/core/TrackPlayerCore.swift +142 -41
  12. package/ios/download/DownloadDatabase.swift +8 -2
  13. package/ios/download/DownloadFileManager.swift +11 -2
  14. package/ios/download/DownloadManagerCore.swift +84 -25
  15. package/ios/equalizer/EqualizerCore.swift +14 -2
  16. package/ios/media/MediaSessionManager.swift +5 -5
  17. package/ios/playlist/PlaylistModel.swift +1 -1
  18. package/ios/queue/HybridPlayerQueue.swift +2 -0
  19. package/ios/storage/NitroPlayerStorage.swift +4 -2
  20. package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +1 -1
  21. package/nitrogen/generated/android/NitroPlayer+autolinking.gradle +1 -1
  22. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +76 -73
  23. package/nitrogen/generated/android/NitroPlayerOnLoad.hpp +14 -5
  24. package/nitrogen/generated/android/c++/JCurrentPlayingType.hpp +5 -6
  25. package/nitrogen/generated/android/c++/JDownloadConfig.hpp +1 -1
  26. package/nitrogen/generated/android/c++/JDownloadError.hpp +1 -1
  27. package/nitrogen/generated/android/c++/JDownloadErrorReason.hpp +8 -9
  28. package/nitrogen/generated/android/c++/JDownloadProgress.hpp +1 -1
  29. package/nitrogen/generated/android/c++/JDownloadQueueStatus.hpp +1 -1
  30. package/nitrogen/generated/android/c++/JDownloadState.hpp +7 -8
  31. package/nitrogen/generated/android/c++/JDownloadStorageInfo.hpp +1 -1
  32. package/nitrogen/generated/android/c++/JDownloadTask.hpp +1 -1
  33. package/nitrogen/generated/android/c++/JDownloadedPlaylist.hpp +1 -1
  34. package/nitrogen/generated/android/c++/JDownloadedTrack.hpp +1 -1
  35. package/nitrogen/generated/android/c++/JEqualizerBand.hpp +1 -1
  36. package/nitrogen/generated/android/c++/JEqualizerPreset.hpp +1 -1
  37. package/nitrogen/generated/android/c++/JEqualizerState.hpp +1 -1
  38. package/nitrogen/generated/android/c++/JFunc_void_DownloadProgress.hpp +1 -1
  39. package/nitrogen/generated/android/c++/JFunc_void_DownloadedTrack.hpp +1 -1
  40. package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +1 -1
  41. package/nitrogen/generated/android/c++/JFunc_void_TrackPlayerState_std__optional_Reason_.hpp +1 -1
  42. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +1 -1
  43. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +1 -1
  44. package/nitrogen/generated/android/c++/JFunc_void_double_double_std__optional_bool_.hpp +1 -1
  45. package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__std__string__.hpp +1 -1
  46. package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +1 -1
  47. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_DownloadState_std__optional_DownloadError_.hpp +1 -1
  48. package/nitrogen/generated/android/c++/JFunc_void_std__vector_EqualizerBand_.hpp +1 -1
  49. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +1 -1
  50. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double.hpp +1 -1
  51. package/nitrogen/generated/android/c++/JGainRange.hpp +1 -1
  52. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +8 -1
  53. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +2 -1
  54. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +8 -1
  55. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +2 -1
  56. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +9 -1
  57. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +2 -1
  58. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +8 -1
  59. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +2 -1
  60. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +8 -1
  61. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +2 -1
  62. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +9 -1
  63. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +2 -1
  64. package/nitrogen/generated/android/c++/JPlaybackSource.hpp +4 -5
  65. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +1 -1
  66. package/nitrogen/generated/android/c++/JPlayerState.hpp +1 -1
  67. package/nitrogen/generated/android/c++/JPlaylist.hpp +1 -1
  68. package/nitrogen/generated/android/c++/JPresetType.hpp +3 -4
  69. package/nitrogen/generated/android/c++/JQueueOperation.hpp +5 -6
  70. package/nitrogen/generated/android/c++/JReason.hpp +6 -7
  71. package/nitrogen/generated/android/c++/JRepeatMode.hpp +4 -5
  72. package/nitrogen/generated/android/c++/JStorageLocation.hpp +3 -4
  73. package/nitrogen/generated/android/c++/JTAudioDevice.hpp +1 -1
  74. package/nitrogen/generated/android/c++/JTrackItem.hpp +1 -1
  75. package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +4 -5
  76. package/nitrogen/generated/android/c++/JVariant_NullType_Double.cpp +1 -1
  77. package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +3 -3
  78. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.cpp +1 -1
  79. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.hpp +3 -3
  80. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.cpp +1 -1
  81. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.hpp +3 -3
  82. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.cpp +1 -1
  83. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.hpp +3 -3
  84. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.cpp +1 -1
  85. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.hpp +3 -3
  86. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.cpp +1 -1
  87. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +3 -3
  88. package/nitrogen/generated/android/c++/JVariant_NullType_String.cpp +1 -1
  89. package/nitrogen/generated/android/c++/JVariant_NullType_String.hpp +3 -3
  90. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.cpp +1 -1
  91. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +3 -3
  92. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/CurrentPlayingType.kt +3 -1
  93. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadConfig.kt +2 -2
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadError.kt +2 -2
  95. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadErrorReason.kt +3 -1
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadProgress.kt +2 -2
  97. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadQueueStatus.kt +2 -2
  98. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadState.kt +3 -1
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadStorageInfo.kt +2 -2
  100. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadTask.kt +2 -2
  101. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedPlaylist.kt +2 -2
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedTrack.kt +2 -2
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerBand.kt +2 -2
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerPreset.kt +2 -2
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerState.kt +2 -2
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadProgress.kt +1 -1
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadedTrack.kt +1 -1
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackItem_std__optional_Reason_.kt +1 -1
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackPlayerState_std__optional_Reason_.kt +1 -1
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_bool.kt +1 -1
  111. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double.kt +1 -1
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double_std__optional_bool_.kt +1 -1
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__optional_std__variant_nitro__NullType__std__string__.kt +1 -1
  114. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_Playlist_std__optional_QueueOperation_.kt +1 -1
  115. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.kt +1 -1
  116. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_EqualizerBand_.kt +1 -1
  117. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_Playlist__std__optional_QueueOperation_.kt +1 -1
  118. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_TrackItem__double.kt +1 -1
  119. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/GainRange.kt +2 -2
  120. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +1 -1
  121. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +1 -1
  122. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +1 -1
  123. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +1 -1
  124. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +1 -1
  125. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +1 -1
  126. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/NitroPlayerOnLoad.kt +1 -1
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlaybackSource.kt +3 -1
  128. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerConfig.kt +2 -2
  129. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerState.kt +2 -2
  130. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Playlist.kt +2 -2
  131. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PresetType.kt +3 -1
  132. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/QueueOperation.kt +3 -1
  133. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Reason.kt +3 -1
  134. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/RepeatMode.kt +3 -1
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/StorageLocation.kt +3 -1
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TAudioDevice.kt +2 -2
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackItem.kt +2 -2
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackPlayerState.kt +3 -1
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Double.kt +1 -1
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadError.kt +1 -1
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadTask.kt +1 -1
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedPlaylist.kt +1 -1
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedTrack.kt +1 -1
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Playlist.kt +1 -1
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_String.kt +1 -1
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_TrackItem.kt +1 -1
  147. package/nitrogen/generated/ios/NitroPlayer+autolinking.rb +2 -2
  148. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +1 -1
  149. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +21 -21
  150. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +1 -1
  151. package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +1 -1
  152. package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +25 -36
  153. package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.cpp +1 -1
  154. package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.hpp +7 -1
  155. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.cpp +1 -1
  156. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +7 -1
  157. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.cpp +1 -1
  158. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +7 -1
  159. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.cpp +1 -1
  160. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +7 -1
  161. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.cpp +1 -1
  162. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +7 -1
  163. package/nitrogen/generated/ios/swift/CurrentPlayingType.swift +1 -1
  164. package/nitrogen/generated/ios/swift/DownloadConfig.swift +78 -168
  165. package/nitrogen/generated/ios/swift/DownloadError.swift +9 -34
  166. package/nitrogen/generated/ios/swift/DownloadErrorReason.swift +1 -1
  167. package/nitrogen/generated/ios/swift/DownloadProgress.swift +13 -50
  168. package/nitrogen/generated/ios/swift/DownloadQueueStatus.swift +15 -58
  169. package/nitrogen/generated/ios/swift/DownloadState.swift +1 -1
  170. package/nitrogen/generated/ios/swift/DownloadStorageInfo.swift +11 -42
  171. package/nitrogen/generated/ios/swift/DownloadTask.swift +97 -210
  172. package/nitrogen/generated/ios/swift/DownloadedPlaylist.swift +13 -56
  173. package/nitrogen/generated/ios/swift/DownloadedTrack.swift +34 -90
  174. package/nitrogen/generated/ios/swift/EqualizerBand.swift +9 -34
  175. package/nitrogen/generated/ios/swift/EqualizerPreset.swift +7 -32
  176. package/nitrogen/generated/ios/swift/EqualizerState.swift +26 -64
  177. package/nitrogen/generated/ios/swift/Func_void.swift +1 -2
  178. package/nitrogen/generated/ios/swift/Func_void_DownloadProgress.swift +1 -2
  179. package/nitrogen/generated/ios/swift/Func_void_DownloadStorageInfo.swift +1 -2
  180. package/nitrogen/generated/ios/swift/Func_void_DownloadedTrack.swift +1 -2
  181. package/nitrogen/generated/ios/swift/Func_void_PlayerState.swift +1 -2
  182. package/nitrogen/generated/ios/swift/Func_void_TrackItem_std__optional_Reason_.swift +1 -2
  183. package/nitrogen/generated/ios/swift/Func_void_TrackPlayerState_std__optional_Reason_.swift +1 -2
  184. package/nitrogen/generated/ios/swift/Func_void_bool.swift +1 -2
  185. package/nitrogen/generated/ios/swift/Func_void_double.swift +1 -2
  186. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +1 -2
  187. package/nitrogen/generated/ios/swift/Func_void_double_double_std__optional_bool_.swift +1 -2
  188. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -2
  189. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__variant_nitro__NullType__std__string__.swift +1 -2
  190. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +1 -2
  191. package/nitrogen/generated/ios/swift/Func_void_std__string_Playlist_std__optional_QueueOperation_.swift +1 -2
  192. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.swift +1 -2
  193. package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +1 -2
  194. package/nitrogen/generated/ios/swift/Func_void_std__vector_Playlist__std__optional_QueueOperation_.swift +1 -2
  195. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem_.swift +1 -2
  196. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double.swift +1 -2
  197. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +1 -2
  198. package/nitrogen/generated/ios/swift/GainRange.swift +5 -18
  199. package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec.swift +3 -4
  200. package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec_cxx.swift +9 -2
  201. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +3 -4
  202. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +9 -2
  203. package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +3 -4
  204. package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +9 -2
  205. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +3 -4
  206. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +25 -4
  207. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -4
  208. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +9 -2
  209. package/nitrogen/generated/ios/swift/PlaybackSource.swift +1 -1
  210. package/nitrogen/generated/ios/swift/PlayerConfig.swift +37 -79
  211. package/nitrogen/generated/ios/swift/PlayerState.swift +53 -122
  212. package/nitrogen/generated/ios/swift/Playlist.swift +49 -112
  213. package/nitrogen/generated/ios/swift/PresetType.swift +1 -1
  214. package/nitrogen/generated/ios/swift/QueueOperation.swift +1 -1
  215. package/nitrogen/generated/ios/swift/Reason.swift +1 -1
  216. package/nitrogen/generated/ios/swift/RepeatMode.swift +1 -1
  217. package/nitrogen/generated/ios/swift/StorageLocation.swift +1 -1
  218. package/nitrogen/generated/ios/swift/TrackItem.swift +43 -111
  219. package/nitrogen/generated/ios/swift/TrackPlayerState.swift +1 -1
  220. package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +1 -1
  221. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadError.swift +1 -1
  222. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadTask.swift +1 -1
  223. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedPlaylist.swift +1 -1
  224. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedTrack.swift +1 -1
  225. package/nitrogen/generated/ios/swift/Variant_NullType_Playlist.swift +1 -1
  226. package/nitrogen/generated/ios/swift/Variant_NullType_String.swift +1 -1
  227. package/nitrogen/generated/ios/swift/Variant_NullType_TrackItem.swift +1 -1
  228. package/nitrogen/generated/shared/c++/CurrentPlayingType.hpp +1 -1
  229. package/nitrogen/generated/shared/c++/DownloadConfig.hpp +34 -26
  230. package/nitrogen/generated/shared/c++/DownloadError.hpp +22 -14
  231. package/nitrogen/generated/shared/c++/DownloadErrorReason.hpp +1 -1
  232. package/nitrogen/generated/shared/c++/DownloadProgress.hpp +28 -20
  233. package/nitrogen/generated/shared/c++/DownloadQueueStatus.hpp +31 -23
  234. package/nitrogen/generated/shared/c++/DownloadState.hpp +1 -1
  235. package/nitrogen/generated/shared/c++/DownloadStorageInfo.hpp +25 -17
  236. package/nitrogen/generated/shared/c++/DownloadTask.hpp +40 -32
  237. package/nitrogen/generated/shared/c++/DownloadedPlaylist.hpp +28 -20
  238. package/nitrogen/generated/shared/c++/DownloadedTrack.hpp +31 -23
  239. package/nitrogen/generated/shared/c++/EqualizerBand.hpp +22 -14
  240. package/nitrogen/generated/shared/c++/EqualizerPreset.hpp +19 -11
  241. package/nitrogen/generated/shared/c++/EqualizerState.hpp +19 -11
  242. package/nitrogen/generated/shared/c++/GainRange.hpp +16 -8
  243. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.cpp +1 -1
  244. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +1 -1
  245. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.cpp +1 -1
  246. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +1 -1
  247. package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.cpp +1 -1
  248. package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.hpp +1 -1
  249. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.cpp +1 -1
  250. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +1 -1
  251. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.cpp +1 -1
  252. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +1 -1
  253. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.cpp +1 -1
  254. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +1 -1
  255. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -1
  256. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -1
  257. package/nitrogen/generated/shared/c++/PlaybackSource.hpp +1 -1
  258. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +22 -14
  259. package/nitrogen/generated/shared/c++/PlayerState.hpp +31 -23
  260. package/nitrogen/generated/shared/c++/Playlist.hpp +25 -17
  261. package/nitrogen/generated/shared/c++/PresetType.hpp +1 -1
  262. package/nitrogen/generated/shared/c++/QueueOperation.hpp +1 -1
  263. package/nitrogen/generated/shared/c++/Reason.hpp +1 -1
  264. package/nitrogen/generated/shared/c++/RepeatMode.hpp +1 -1
  265. package/nitrogen/generated/shared/c++/StorageLocation.hpp +1 -1
  266. package/nitrogen/generated/shared/c++/TAudioDevice.hpp +22 -14
  267. package/nitrogen/generated/shared/c++/TrackItem.hpp +34 -26
  268. package/nitrogen/generated/shared/c++/TrackPlayerState.hpp +1 -1
  269. package/package.json +3 -3
@@ -11,6 +11,7 @@ import android.graphics.BitmapFactory
11
11
  import android.net.Uri
12
12
  import android.os.Build
13
13
  import android.util.Log
14
+ import android.util.LruCache
14
15
  import androidx.core.app.NotificationCompat
15
16
  import androidx.media3.common.MediaItem
16
17
  import androidx.media3.common.MediaMetadata
@@ -49,7 +50,9 @@ class MediaSessionManager(
49
50
  private set
50
51
  private var notificationManager: NotificationManager? = null
51
52
  private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
52
- private val artworkCache = mutableMapOf<String, Bitmap>()
53
+ private val artworkCache = object : LruCache<String, Bitmap>(20) {
54
+ override fun sizeOf(key: String, value: Bitmap): Int = 1
55
+ }
53
56
 
54
57
  private var androidAutoEnabled: Boolean = false
55
58
  private var carPlayEnabled: Boolean = false
@@ -115,7 +118,7 @@ class MediaSessionManager(
115
118
  mediaItems: MutableList<MediaItem>,
116
119
  ): ListenableFuture<MutableList<MediaItem>> {
117
120
  // This is called when Android Auto requests to play a track
118
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: onAddMediaItems called with ${mediaItems.size} items")
121
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: onAddMediaItems called with ${mediaItems.size} items" }
119
122
 
120
123
  if (mediaItems.isEmpty()) {
121
124
  return Futures.immediateFuture(mutableListOf())
@@ -129,7 +132,7 @@ class MediaSessionManager(
129
132
  requestedMediaItem.requestMetadata.mediaUri?.toString()
130
133
  ?: requestedMediaItem.mediaId
131
134
 
132
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: Processing mediaId: $mediaId")
135
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: Processing mediaId: $mediaId" }
133
136
 
134
137
  try {
135
138
  // Parse mediaId format: "playlistId:trackId"
@@ -138,7 +141,7 @@ class MediaSessionManager(
138
141
  val playlistId = mediaId.substring(0, colonIndex)
139
142
  val trackId = mediaId.substring(colonIndex + 1)
140
143
 
141
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: Parsed playlistId: $playlistId, trackId: $trackId")
144
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: Parsed playlistId: $playlistId, trackId: $trackId" }
142
145
 
143
146
  // Get the playlist and track
144
147
  val playlist = playlistManager.getPlaylist(playlistId)
@@ -148,27 +151,27 @@ class MediaSessionManager(
148
151
  // Create a proper MediaItem with all metadata
149
152
  val resolvedMediaItem = createMediaItem(track, mediaId)
150
153
  updatedMediaItems.add(resolvedMediaItem)
151
- NitroPlayerLogger.log("MediaSessionManager", "✅ MediaSessionManager: Resolved track: ${track.title}")
154
+ NitroPlayerLogger.log("MediaSessionManager") { "✅ MediaSessionManager: Resolved track: ${track.title}" }
152
155
  } else {
153
- NitroPlayerLogger.log("MediaSessionManager", "⚠️ MediaSessionManager: Track $trackId not found in playlist")
156
+ NitroPlayerLogger.log("MediaSessionManager") { "⚠️ MediaSessionManager: Track $trackId not found in playlist" }
154
157
  updatedMediaItems.add(requestedMediaItem)
155
158
  }
156
159
  } else {
157
- NitroPlayerLogger.log("MediaSessionManager", "⚠️ MediaSessionManager: Playlist $playlistId not found")
160
+ NitroPlayerLogger.log("MediaSessionManager") { "⚠️ MediaSessionManager: Playlist $playlistId not found" }
158
161
  updatedMediaItems.add(requestedMediaItem)
159
162
  }
160
163
  } else {
161
- NitroPlayerLogger.log("MediaSessionManager", "⚠️ MediaSessionManager: Invalid mediaId format: $mediaId")
164
+ NitroPlayerLogger.log("MediaSessionManager") { "⚠️ MediaSessionManager: Invalid mediaId format: $mediaId" }
162
165
  updatedMediaItems.add(requestedMediaItem)
163
166
  }
164
167
  } catch (e: Exception) {
165
- NitroPlayerLogger.log("MediaSessionManager", "❌ MediaSessionManager: Error processing mediaId - ${e.message}")
168
+ NitroPlayerLogger.log("MediaSessionManager") { "❌ MediaSessionManager: Error processing mediaId - ${e.message}" }
166
169
  e.printStackTrace()
167
170
  updatedMediaItems.add(requestedMediaItem)
168
171
  }
169
172
  }
170
173
 
171
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: Returning ${updatedMediaItems.size} resolved media items")
174
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: Returning ${updatedMediaItems.size} resolved media items" }
172
175
  return Futures.immediateFuture(updatedMediaItems)
173
176
  }
174
177
 
@@ -180,7 +183,7 @@ class MediaSessionManager(
180
183
  startPositionMs: Long,
181
184
  ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
182
185
  // This is called when Android Auto wants to set and play media items
183
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: onSetMediaItems called with ${mediaItems.size} items, startIndex: $startIndex")
186
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: onSetMediaItems called with ${mediaItems.size} items, startIndex: $startIndex" }
184
187
 
185
188
  if (mediaItems.isEmpty()) {
186
189
  return Futures.immediateFuture(
@@ -195,7 +198,7 @@ class MediaSessionManager(
195
198
  try {
196
199
  // Get the first item's mediaId to determine the playlist
197
200
  val firstMediaId = mediaItems[0].mediaId
198
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: First mediaId: $firstMediaId")
201
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: First mediaId: $firstMediaId" }
199
202
 
200
203
  // Parse mediaId format: "playlistId:trackId"
201
204
  if (firstMediaId.contains(':')) {
@@ -203,7 +206,7 @@ class MediaSessionManager(
203
206
  val playlistId = firstMediaId.substring(0, colonIndex)
204
207
  val trackId = firstMediaId.substring(colonIndex + 1)
205
208
 
206
- NitroPlayerLogger.log("MediaSessionManager", "🎵 MediaSessionManager: Loading full playlist: $playlistId, starting at track: $trackId")
209
+ NitroPlayerLogger.log("MediaSessionManager") { "🎵 MediaSessionManager: Loading full playlist: $playlistId, starting at track: $trackId" }
207
210
 
208
211
  // Get the full playlist
209
212
  val playlist = playlistManager.getPlaylist(playlistId)
@@ -223,7 +226,7 @@ class MediaSessionManager(
223
226
  createMediaItem(track, trackMediaId)
224
227
  }.toMutableList()
225
228
 
226
- NitroPlayerLogger.log("MediaSessionManager", "✅ MediaSessionManager: Loaded ${playlistMediaItems.size} tracks, starting at index $trackIndex")
229
+ NitroPlayerLogger.log("MediaSessionManager") { "✅ MediaSessionManager: Loaded ${playlistMediaItems.size} tracks, starting at index $trackIndex" }
227
230
 
228
231
  // Return the full playlist with the correct start index
229
232
  return Futures.immediateFuture(
@@ -241,7 +244,7 @@ class MediaSessionManager(
241
244
  }
242
245
  }
243
246
  } catch (e: Exception) {
244
- NitroPlayerLogger.log("MediaSessionManager", "❌ MediaSessionManager: Error in onSetMediaItems - ${e.message}")
247
+ NitroPlayerLogger.log("MediaSessionManager") { "❌ MediaSessionManager: Error in onSetMediaItems - ${e.message}" }
245
248
  e.printStackTrace()
246
249
  }
247
250
 
@@ -283,7 +286,7 @@ class MediaSessionManager(
283
286
  if (artworkUrl.isNullOrEmpty()) return null
284
287
 
285
288
  // Check cache first
286
- artworkCache[artworkUrl]?.let { return it }
289
+ artworkCache.get(artworkUrl)?.let { return it }
287
290
 
288
291
  return try {
289
292
  val bitmap =
@@ -293,7 +296,7 @@ class MediaSessionManager(
293
296
  }
294
297
  // Cache the bitmap
295
298
  if (bitmap != null) {
296
- artworkCache[artworkUrl] = bitmap
299
+ artworkCache.put(artworkUrl, bitmap)
297
300
  }
298
301
  bitmap
299
302
  } catch (e: Exception) {
@@ -302,12 +305,6 @@ class MediaSessionManager(
302
305
  }
303
306
  }
304
307
 
305
- private fun bitmapToByteArray(bitmap: Bitmap): ByteArray {
306
- val stream = java.io.ByteArrayOutputStream()
307
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
308
- return stream.toByteArray()
309
- }
310
-
311
308
  private fun createNotificationChannel() {
312
309
  notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
313
310
 
@@ -342,10 +339,12 @@ class MediaSessionManager(
342
339
  // Find track in current playlist or all playlists
343
340
  return trackPlayerCore?.getCurrentPlaylistId()?.let { playlistId ->
344
341
  playlistManager.getPlaylist(playlistId)?.tracks?.find { it.id == trackId }
345
- } ?: playlistManager
346
- .getAllPlaylists()
347
- .flatMap { it.tracks }
348
- .find { it.id == trackId }
342
+ } ?: run {
343
+ for (playlist in playlistManager.getAllPlaylists()) {
344
+ playlist.tracks.find { it.id == trackId }?.let { return it }
345
+ }
346
+ null
347
+ }
349
348
  }
350
349
 
351
350
  private fun updateNotification() {
@@ -392,7 +391,7 @@ class MediaSessionManager(
392
391
  .setShowActionsInCompactView(0, 1, 2),
393
392
  )
394
393
  } catch (e: Exception) {
395
- NitroPlayerLogger.log("MediaSessionManager", "Failed to set media session token: ${e.message}")
394
+ NitroPlayerLogger.log("MediaSessionManager") { "Failed to set media session token: ${e.message}" }
396
395
  }
397
396
 
398
397
  // Add action buttons
@@ -483,7 +482,7 @@ class MediaSessionManager(
483
482
  hideNotification()
484
483
  mediaSession?.release()
485
484
  mediaSession = null
486
- artworkCache.clear()
485
+ artworkCache.evictAll()
487
486
  }
488
487
 
489
488
  private fun createMediaItem(
@@ -501,7 +500,7 @@ class MediaSessionManager(
501
500
  try {
502
501
  metadataBuilder.setArtworkUri(Uri.parse(artworkUrl))
503
502
  } catch (e: Exception) {
504
- NitroPlayerLogger.log("MediaSessionManager", "⚠️ MediaSessionManager: Invalid artwork URI: $artworkUrl")
503
+ NitroPlayerLogger.log("MediaSessionManager") { "⚠️ MediaSessionManager: Invalid artwork URI: $artworkUrl" }
505
504
  }
506
505
  }
507
506
 
@@ -112,12 +112,8 @@ class PlaylistManager private constructor(
112
112
  description: String? = null,
113
113
  artwork: String? = null,
114
114
  ): Boolean {
115
- val playlist =
116
- synchronized(playlists) {
117
- playlists[playlistId]
118
- } ?: return false
119
-
120
115
  synchronized(playlists) {
116
+ val playlist = playlists[playlistId] ?: return false
121
117
  playlists[playlistId] =
122
118
  playlist.copy(
123
119
  name = name ?: playlist.name,
@@ -161,12 +157,8 @@ class PlaylistManager private constructor(
161
157
  track: TrackItem,
162
158
  index: Int? = null,
163
159
  ): Boolean {
164
- val playlist =
165
- synchronized(playlists) {
166
- playlists[playlistId]
167
- } ?: return false
168
-
169
160
  synchronized(playlists) {
161
+ val playlist = playlists[playlistId] ?: return false
170
162
  val tracks = playlist.tracks.toMutableList()
171
163
  if (index != null && index >= 0 && index <= tracks.size) {
172
164
  tracks.add(index, track)
@@ -199,12 +191,8 @@ class PlaylistManager private constructor(
199
191
  tracks: List<TrackItem>,
200
192
  index: Int? = null,
201
193
  ): Boolean {
202
- val playlist =
203
- synchronized(playlists) {
204
- playlists[playlistId]
205
- } ?: return false
206
-
207
194
  synchronized(playlists) {
195
+ val playlist = playlists[playlistId] ?: return false
208
196
  val currentTracks = playlist.tracks.toMutableList()
209
197
  if (index != null && index >= 0 && index <= currentTracks.size) {
210
198
  currentTracks.addAll(index, tracks)
@@ -236,13 +224,9 @@ class PlaylistManager private constructor(
236
224
  playlistId: String,
237
225
  trackId: String,
238
226
  ): Boolean {
239
- val playlist =
240
- synchronized(playlists) {
241
- playlists[playlistId]
242
- } ?: return false
243
-
244
227
  val removed =
245
228
  synchronized(playlists) {
229
+ val playlist = playlists[playlistId] ?: return false
246
230
  val tracks = playlist.tracks.toMutableList()
247
231
  val removed = tracks.removeAll { it.id == trackId }
248
232
  if (removed) {
@@ -273,18 +257,13 @@ class PlaylistManager private constructor(
273
257
  trackId: String,
274
258
  newIndex: Int,
275
259
  ): Boolean {
276
- val playlist =
277
- synchronized(playlists) {
278
- playlists[playlistId]
279
- } ?: return false
280
-
281
- val tracks = playlist.tracks.toMutableList()
282
- val oldIndex = tracks.indexOfFirst { it.id == trackId }
283
- if (oldIndex < 0 || newIndex < 0 || newIndex >= tracks.size) {
284
- return false
285
- }
286
-
287
260
  synchronized(playlists) {
261
+ val playlist = playlists[playlistId] ?: return false
262
+ val tracks = playlist.tracks.toMutableList()
263
+ val oldIndex = tracks.indexOfFirst { it.id == trackId }
264
+ if (oldIndex < 0 || newIndex < 0 || newIndex >= tracks.size) {
265
+ return false
266
+ }
288
267
  val track = tracks.removeAt(oldIndex)
289
268
  tracks.add(newIndex, track)
290
269
  playlists[playlistId] = playlist.copy(tracks = tracks)
@@ -13,7 +13,7 @@ class Queue {
13
13
  /**
14
14
  * Get all tracks in the queue
15
15
  */
16
- fun getTracks(): List<TrackItem> = tracks.toList()
16
+ fun getTracks(): List<TrackItem> = ArrayList(tracks)
17
17
 
18
18
  /**
19
19
  * Get tracks as an array (for compatibility with existing API)
@@ -229,6 +229,7 @@ class TrackPlayerCore: NSObject {
229
229
 
230
230
  // Create boundary times at each interval
231
231
  var boundaryTimes: [NSValue] = []
232
+ boundaryTimes.reserveCapacity(Int(duration / interval) + 1)
232
233
  var time: Double = 0
233
234
  while time <= duration {
234
235
  let cmTime = CMTime(seconds: time, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
@@ -423,6 +424,7 @@ class TrackPlayerCore: NSObject {
423
424
  return
424
425
  }
425
426
 
427
+ #if DEBUG
426
428
  NitroPlayerLogger.log("TrackPlayerCore", "\n" + String(repeating: "▶", count: Constants.separatorLineLength))
427
429
  NitroPlayerLogger.log("TrackPlayerCore", "🔄 CURRENT ITEM CHANGED")
428
430
  NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "▶", count: Constants.separatorLineLength))
@@ -449,6 +451,7 @@ class TrackPlayerCore: NSObject {
449
451
  }
450
452
 
451
453
  NitroPlayerLogger.log("TrackPlayerCore", String(repeating: "▶", count: Constants.separatorLineLength) + "\n")
454
+ #endif
452
455
 
453
456
  // Log item status
454
457
  NitroPlayerLogger.log("TrackPlayerCore", "📱 Item status: \(currentItem.status.rawValue)")
@@ -510,10 +513,12 @@ class TrackPlayerCore: NSObject {
510
513
  }
511
514
  } else {
512
515
  NitroPlayerLogger.log("TrackPlayerCore", " ⚠️ Track ID '\(trackId)' NOT FOUND in currentTracks!")
516
+ #if DEBUG
513
517
  NitroPlayerLogger.log("TrackPlayerCore", " Current tracks:")
514
518
  for (idx, track) in currentTracks.enumerated() {
515
519
  NitroPlayerLogger.log("TrackPlayerCore", " [\(idx)] \(track.id) - \(track.title)")
516
520
  }
521
+ #endif
517
522
  }
518
523
  }
519
524
 
@@ -811,7 +816,8 @@ class TrackPlayerCore: NSObject {
811
816
 
812
817
  /// Clears preloaded assets that are no longer needed
813
818
  private func cleanupPreloadedAssets(keepingFrom currentIndex: Int) {
814
- preloadQueue.async { [weak self] in
819
+ // Must run on main thread — preloadedAssets is only mutated on main
820
+ DispatchQueue.main.async { [weak self] in
815
821
  guard let self = self else { return }
816
822
 
817
823
  // Keep assets for current track and upcoming tracks within preload range
@@ -991,6 +997,25 @@ class TrackPlayerCore: NSObject {
991
997
  // Clear old preloaded assets when loading new queue
992
998
  preloadedAssets.removeAll()
993
999
 
1000
+ // Replace current queue (player should always exist after setupPlayer)
1001
+ guard let existingPlayer = self.player else {
1002
+ NitroPlayerLogger.log("TrackPlayerCore", "❌ No player available")
1003
+ return
1004
+ }
1005
+
1006
+ // Always clear old items so a stale playlist doesn't keep playing.
1007
+ NitroPlayerLogger.log("TrackPlayerCore", "🔄 Removing \(existingPlayer.items().count) old items from player")
1008
+ existingPlayer.removeAllItems()
1009
+
1010
+ // Lazy-load mode: if any track has no URL yet, don't populate AVQueuePlayer items now.
1011
+ // Adding downloaded tracks at incorrect positions would cause the wrong track to play.
1012
+ // updateTracks will rebuild from the correct currentTrackIndex once URLs are resolved.
1013
+ let isLazyLoad = tracks.contains { $0.url.isEmpty }
1014
+ if isLazyLoad {
1015
+ NitroPlayerLogger.log("TrackPlayerCore", "⏳ Lazy-load mode — player cleared, awaiting URL resolution")
1016
+ return
1017
+ }
1018
+
994
1019
  // Create gapless-optimized AVPlayerItems from tracks
995
1020
  let items = tracks.enumerated().compactMap { (index, track) -> AVPlayerItem? in
996
1021
  let isPreload = index < Constants.gaplessPreloadCount
@@ -1004,16 +1029,7 @@ class TrackPlayerCore: NSObject {
1004
1029
  return
1005
1030
  }
1006
1031
 
1007
- // Replace current queue (player should always exist after setupPlayer)
1008
- guard let existingPlayer = self.player else {
1009
- NitroPlayerLogger.log("TrackPlayerCore", "❌ No player available - this should never happen!")
1010
- return
1011
- }
1012
-
1013
- NitroPlayerLogger.log("TrackPlayerCore", "🔄 Updating queue - removing \(existingPlayer.items().count) items, adding \(items.count) new items")
1014
-
1015
- // Remove all existing items
1016
- existingPlayer.removeAllItems()
1032
+ NitroPlayerLogger.log("TrackPlayerCore", "🔄 Adding \(items.count) new items to player")
1017
1033
 
1018
1034
  // Add new items IN ORDER
1019
1035
  // IMPORTANT: insert(after: nil) puts item at the start
@@ -1023,9 +1039,11 @@ class TrackPlayerCore: NSObject {
1023
1039
  existingPlayer.insert(item, after: lastItem)
1024
1040
  lastItem = item
1025
1041
 
1042
+ #if DEBUG
1026
1043
  if let trackId = item.trackId, let track = tracks.first(where: { $0.id == trackId }) {
1027
1044
  NitroPlayerLogger.log("TrackPlayerCore", " ➕ Added to player queue [\(index + 1)]: \(track.title)")
1028
1045
  }
1046
+ #endif
1029
1047
  }
1030
1048
 
1031
1049
  #if DEBUG
@@ -1296,6 +1314,18 @@ class TrackPlayerCore: NSObject {
1296
1314
  private func skipToNextInternal() {
1297
1315
  guard let queuePlayer = self.player else { return }
1298
1316
 
1317
+ // Lazy-load: AVQueuePlayer is empty because updatePlayerQueue deferred population.
1318
+ // Delegate to playFromIndexInternal which handles both the has-URL (rebuild queue)
1319
+ // and no-URL (defer + emit) cases correctly.
1320
+ if queuePlayer.items().isEmpty && !currentTracks.isEmpty {
1321
+ let nextIndex = currentTrackIndex + 1
1322
+ if nextIndex < currentTracks.count {
1323
+ _ = skipToIndexInternal(index: nextIndex)
1324
+ }
1325
+ checkUpcomingTracksForUrls(lookahead: lookaheadCount)
1326
+ return
1327
+ }
1328
+
1299
1329
  // Remove current temp track from its list before advancing
1300
1330
  if let trackId = queuePlayer.currentItem?.trackId {
1301
1331
  if currentTemporaryType == .playNext {
@@ -1352,11 +1382,11 @@ class TrackPlayerCore: NSObject {
1352
1382
  }
1353
1383
  }
1354
1384
  // Go to current original track position (skip back from temp)
1355
- self.playFromIndex(index: self.currentTrackIndex)
1385
+ self.rebuildQueueFromPlaylistIndex(index: self.currentTrackIndex)
1356
1386
  } else if self.currentTrackIndex > 0 {
1357
1387
  // Go to previous track in original playlist
1358
1388
  let previousIndex = self.currentTrackIndex - 1
1359
- self.playFromIndex(index: previousIndex)
1389
+ self.rebuildQueueFromPlaylistIndex(index: previousIndex)
1360
1390
  } else {
1361
1391
  // Already at first track, restart it
1362
1392
  queuePlayer.seek(to: .zero)
@@ -1533,10 +1563,10 @@ class TrackPlayerCore: NSObject {
1533
1563
 
1534
1564
  func playFromIndex(index: Int) {
1535
1565
  if Thread.isMainThread {
1536
- playFromIndexInternal(index: index)
1566
+ rebuildQueueFromPlaylistIndex(index: index)
1537
1567
  } else {
1538
1568
  DispatchQueue.main.async { [weak self] in
1539
- self?.playFromIndexInternal(index: index)
1569
+ self?.rebuildQueueFromPlaylistIndex(index: index)
1540
1570
  }
1541
1571
  }
1542
1572
  }
@@ -1580,9 +1610,9 @@ class TrackPlayerCore: NSObject {
1580
1610
  let upNextEnd = upNextStart + effectiveUpNextSize
1581
1611
  let originalRemainingStart = upNextEnd
1582
1612
 
1583
- // Case 1: Target is before current - use playFromIndex on original
1613
+ // Case 1: Target is before current - rebuild from that playlist index
1584
1614
  if index < currentPos {
1585
- playFromIndexInternal(index: index)
1615
+ rebuildQueueFromPlaylistIndex(index: index)
1586
1616
  return true
1587
1617
  }
1588
1618
 
@@ -1645,7 +1675,7 @@ class TrackPlayerCore: NSObject {
1645
1675
  upNextQueue.removeAll()
1646
1676
  currentTemporaryType = .none
1647
1677
 
1648
- let result = playFromIndexInternalWithResult(index: originalIndex)
1678
+ let result = rebuildQueueFromPlaylistIndex(index: originalIndex)
1649
1679
 
1650
1680
  // Check if upcoming tracks need URLs
1651
1681
  checkUpcomingTracksForUrls(lookahead: lookaheadCount)
@@ -1659,20 +1689,23 @@ class TrackPlayerCore: NSObject {
1659
1689
  return false
1660
1690
  }
1661
1691
 
1662
- private func playFromIndexInternal(index: Int) {
1663
- _ = playFromIndexInternalWithResult(index: index)
1664
- }
1665
-
1666
- private func playFromIndexInternalWithResult(index: Int) -> Bool {
1692
+ /// Clears temporary tracks, rebuilds AVQueuePlayer from `index` in the original playlist,
1693
+ /// and resumes playback only if the player was already playing (preserves paused state).
1694
+ @discardableResult
1695
+ private func rebuildQueueFromPlaylistIndex(index: Int) -> Bool {
1667
1696
  guard index >= 0 && index < self.currentTracks.count else {
1668
- NitroPlayerLogger.log("TrackPlayerCore", "❌ playFromIndex - invalid index \(index), currentTracks.count = \(self.currentTracks.count)")
1697
+ NitroPlayerLogger.log("TrackPlayerCore", "❌ rebuildQueueFromPlaylistIndex - invalid index \(index), currentTracks.count = \(self.currentTracks.count)")
1669
1698
  return false
1670
1699
  }
1671
1700
 
1672
- NitroPlayerLogger.log("TrackPlayerCore", "\n🎯 PLAY FROM INDEX \(index)")
1701
+ NitroPlayerLogger.log("TrackPlayerCore", "\n🎯 REBUILD QUEUE FROM PLAYLIST INDEX \(index)")
1673
1702
  NitroPlayerLogger.log("TrackPlayerCore", " Total tracks in playlist: \(self.currentTracks.count)")
1674
1703
  NitroPlayerLogger.log("TrackPlayerCore", " Current index: \(self.currentTrackIndex), target index: \(index)")
1675
1704
 
1705
+ // Preserve playback state — only resume if already playing.
1706
+ // This prevents auto-starting when called during queue setup (e.g. loadPlaylist → skipToIndex).
1707
+ let wasPlaying = self.player?.rate ?? 0 > 0
1708
+
1676
1709
  // Clear temporary tracks when jumping to specific index
1677
1710
  self.playNextStack.removeAll()
1678
1711
  self.upNextQueue.removeAll()
@@ -1685,6 +1718,22 @@ class TrackPlayerCore: NSObject {
1685
1718
  // Update currentTrackIndex BEFORE updating queue
1686
1719
  self.currentTrackIndex = index
1687
1720
 
1721
+ // Lazy-load guard: if the target track itself has no URL yet, the queue can't be built.
1722
+ // Emit the track change now (so UI reflects the navigation) and defer queue population
1723
+ // to updateTracks once URL resolution completes.
1724
+ // Only check the target — other unresolved tracks elsewhere in the playlist shouldn't
1725
+ // block a skip to a track that is already playable.
1726
+ let isLazyLoad = fullPlaylist[index].url.isEmpty
1727
+ if isLazyLoad {
1728
+ NitroPlayerLogger.log("TrackPlayerCore", " ⏳ Lazy-load — deferring AVQueuePlayer setup; emitting track change for index \(index)")
1729
+ self.currentTracks = fullPlaylist
1730
+ if let track = self.currentTracks[safe: index] {
1731
+ notifyTrackChange(track, .skip)
1732
+ self.mediaSessionManager?.onTrackChanged()
1733
+ }
1734
+ return true
1735
+ }
1736
+
1688
1737
  // Recreate the queue starting from the target index
1689
1738
  // This ensures all remaining tracks are in the queue
1690
1739
  let tracksToPlay = Array(fullPlaylist[index...])
@@ -1732,7 +1781,12 @@ class TrackPlayerCore: NSObject {
1732
1781
  // Start preloading upcoming tracks for gapless playback
1733
1782
  self.preloadUpcomingTracks(from: index + 1)
1734
1783
 
1735
- player.play()
1784
+ // Only resume playback if the player was already playing before we rebuilt
1785
+ // the loaded playlist. This prevents auto-starting when called during queue setup
1786
+ // (e.g. loadPlaylist → skipToIndex).
1787
+ if wasPlaying {
1788
+ player.play()
1789
+ }
1736
1790
  return true
1737
1791
  }
1738
1792
 
@@ -1813,7 +1867,7 @@ class TrackPlayerCore: NSObject {
1813
1867
  // Add playNext stack (LIFO - most recently added plays first)
1814
1868
  // Skip index 0 if current track is from playNext (it's already playing)
1815
1869
  if currentTemporaryType == .playNext && playNextStack.count > 1 {
1816
- newQueueTracks.append(contentsOf: Array(playNextStack.dropFirst()))
1870
+ newQueueTracks.append(contentsOf: playNextStack.dropFirst())
1817
1871
  } else if currentTemporaryType != .playNext {
1818
1872
  newQueueTracks.append(contentsOf: playNextStack)
1819
1873
  }
@@ -1821,15 +1875,14 @@ class TrackPlayerCore: NSObject {
1821
1875
  // Add upNext queue (in order, FIFO)
1822
1876
  // Skip index 0 if current track is from upNext (it's already playing)
1823
1877
  if currentTemporaryType == .upNext && upNextQueue.count > 1 {
1824
- newQueueTracks.append(contentsOf: Array(upNextQueue.dropFirst()))
1878
+ newQueueTracks.append(contentsOf: upNextQueue.dropFirst())
1825
1879
  } else if currentTemporaryType != .upNext {
1826
1880
  newQueueTracks.append(contentsOf: upNextQueue)
1827
1881
  }
1828
1882
 
1829
1883
  // Add remaining original tracks
1830
1884
  if currentTrackIndex + 1 < currentTracks.count {
1831
- let remainingOriginal = Array(currentTracks[(currentTrackIndex + 1)...])
1832
- newQueueTracks.append(contentsOf: remainingOriginal)
1885
+ newQueueTracks.append(contentsOf: currentTracks[(currentTrackIndex + 1)...])
1833
1886
  }
1834
1887
 
1835
1888
  // Remove all items from player EXCEPT the currently playing one
@@ -1904,17 +1957,26 @@ class TrackPlayerCore: NSObject {
1904
1957
 
1905
1958
  NitroPlayerLogger.log("TrackPlayerCore", "🔄 updateTracks: \(tracks.count) updates")
1906
1959
 
1907
- // Get current track ID to avoid updating it (preserves gapless playback)
1908
- let currentTrackId = self.getCurrentTrack()?.id
1960
+ // Get current track to decide how to handle it
1961
+ let currentTrack = self.getCurrentTrack()
1962
+ let currentTrackId = currentTrack?.id
1963
+ let currentTrackIsEmpty = currentTrack?.url.isEmpty ?? false
1909
1964
 
1910
1965
  // Filter out current track and validate
1911
1966
  let safeTracks = tracks.filter { track in
1912
1967
  switch true {
1913
- case track.id == currentTrackId:
1968
+ case track.id == currentTrackId && !currentTrackIsEmpty:
1969
+ // Has a real URL already — skip to preserve gapless playback
1914
1970
  NitroPlayerLogger.log(
1915
1971
  "TrackPlayerCore",
1916
1972
  "⚠️ Skipping update for currently playing track: \(track.id) (preserves gapless)")
1917
1973
  return false
1974
+ case track.id == currentTrackId && currentTrackIsEmpty:
1975
+ // Empty URL — must not be playing, allow the update (only if the new URL is real)
1976
+ NitroPlayerLogger.log(
1977
+ "TrackPlayerCore",
1978
+ "🔄 Updating current track with no URL: \(track.id)")
1979
+ return !track.url.isEmpty
1918
1980
  case track.url.isEmpty:
1919
1981
  NitroPlayerLogger.log(
1920
1982
  "TrackPlayerCore", "⚠️ Skipping track with empty URL: \(track.id)")
@@ -1943,7 +2005,16 @@ class TrackPlayerCore: NSObject {
1943
2005
  // Update in PlaylistManager
1944
2006
  let affectedPlaylists = self.playlistManager.updateTracks(tracks: safeTracks)
1945
2007
 
1946
- // Rebuild queue if current playlist was affected
2008
+ // If the current track had no URL and now has one, replace the current AVPlayerItem
2009
+ if let update = currentTrack, currentTrackIsEmpty, !update.url.isEmpty {
2010
+ NitroPlayerLogger.log(
2011
+ "TrackPlayerCore", "🔄 Replacing current AVPlayerItem for track with resolved URL: \(update.id)")
2012
+ if let newItem = self.createGaplessPlayerItem(for: update, isPreload: false) {
2013
+ self.player?.replaceCurrentItem(with: newItem)
2014
+ }
2015
+ }
2016
+
2017
+ // Rebuild queue if current playlist was affected
1947
2018
  if let currentId = self.currentPlaylistId,
1948
2019
  let updateCount = affectedPlaylists[currentId]
1949
2020
  {
@@ -1951,12 +2022,36 @@ class TrackPlayerCore: NSObject {
1951
2022
  "TrackPlayerCore",
1952
2023
  "🔄 Rebuilding queue - \(updateCount) tracks updated in current playlist")
1953
2024
 
1954
- // This method preserves current item
1955
- self.rebuildAVQueueFromCurrentPosition()
2025
+ // Sync currentTracks from the freshly-updated PlaylistManager so rebuilds use resolved URLs
2026
+ if let updatedPlaylist = self.playlistManager.getPlaylist(playlistId: currentId) {
2027
+ self.currentTracks = updatedPlaylist.tracks
2028
+ NitroPlayerLogger.log("TrackPlayerCore", "📥 Synced currentTracks from PlaylistManager (\(self.currentTracks.count) tracks)")
2029
+ }
1956
2030
 
1957
- // Re-preload upcoming tracks for gapless playback
1958
- // CRITICAL: This restores gapless buffering after queue rebuild
1959
- self.preloadUpcomingTracks(from: self.currentTrackIndex + 1)
2031
+ if self.player?.currentItem == nil, let player = self.player {
2032
+ // No AVPlayerItem exists yet — lazy-load mode: URLs were empty when the queue first
2033
+ // loaded. Rebuild the full queue from currentTrackIndex now that URLs are resolved.
2034
+ NitroPlayerLogger.log(
2035
+ "TrackPlayerCore",
2036
+ "🔄 No current item — full queue rebuild from currentTrackIndex \(self.currentTrackIndex)")
2037
+ player.removeAllItems()
2038
+ var lastItem: AVPlayerItem? = nil
2039
+ for (offset, track) in self.currentTracks[self.currentTrackIndex...].enumerated() {
2040
+ let isPreload = offset < Constants.gaplessPreloadCount
2041
+ if let newItem = self.createGaplessPlayerItem(for: track, isPreload: isPreload) {
2042
+ player.insert(newItem, after: lastItem)
2043
+ lastItem = newItem
2044
+ }
2045
+ }
2046
+ player.play()
2047
+ self.preloadUpcomingTracks(from: self.currentTrackIndex + 1)
2048
+ } else {
2049
+ // A current AVPlayerItem already exists — preserve it and only rebuild upcoming items.
2050
+ self.rebuildAVQueueFromCurrentPosition()
2051
+ // Re-preload upcoming tracks for gapless playback
2052
+ // CRITICAL: This restores gapless buffering after queue rebuild
2053
+ self.preloadUpcomingTracks(from: self.currentTrackIndex + 1)
2054
+ }
1960
2055
 
1961
2056
  NitroPlayerLogger.log("TrackPlayerCore", "✅ Queue rebuilt, gapless playback preserved")
1962
2057
  }
@@ -2099,8 +2194,14 @@ class TrackPlayerCore: NSObject {
2099
2194
  * Call this in playerItemDidPlayToEndTime or after skip operations
2100
2195
  */
2101
2196
  private func checkUpcomingTracksForUrls(lookahead: Int = 5) {
2102
- let nextTracks = getNextTracksInternal(count: lookahead)
2103
- let tracksNeedingUrls = nextTracks.filter { $0.url.isEmpty }
2197
+ let upcomingTracks = getNextTracksInternal(count: lookahead)
2198
+
2199
+ // Always include the current track if it has no URL — it can't play without one
2200
+ let currentTrack = getCurrentTrack()
2201
+ let currentNeedsUrl = currentTrack.map { $0.url.isEmpty } ?? false
2202
+ let candidateTracks = currentNeedsUrl ? [currentTrack!] + upcomingTracks : upcomingTracks
2203
+
2204
+ let tracksNeedingUrls = candidateTracks.filter { $0.url.isEmpty }
2104
2205
 
2105
2206
  if !tracksNeedingUrls.isEmpty {
2106
2207
  NitroPlayerLogger.log(