react-native-nitro-player 0.5.8 → 0.5.9-alpha.1

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 (271) 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 +119 -59
  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 +328 -84
  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 +83 -42
  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/lib/hooks/usePlaylist.js +25 -4
  21. package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +1 -1
  22. package/nitrogen/generated/android/NitroPlayer+autolinking.gradle +1 -1
  23. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +76 -73
  24. package/nitrogen/generated/android/NitroPlayerOnLoad.hpp +14 -5
  25. package/nitrogen/generated/android/c++/JCurrentPlayingType.hpp +5 -6
  26. package/nitrogen/generated/android/c++/JDownloadConfig.hpp +1 -1
  27. package/nitrogen/generated/android/c++/JDownloadError.hpp +1 -1
  28. package/nitrogen/generated/android/c++/JDownloadErrorReason.hpp +8 -9
  29. package/nitrogen/generated/android/c++/JDownloadProgress.hpp +1 -1
  30. package/nitrogen/generated/android/c++/JDownloadQueueStatus.hpp +1 -1
  31. package/nitrogen/generated/android/c++/JDownloadState.hpp +7 -8
  32. package/nitrogen/generated/android/c++/JDownloadStorageInfo.hpp +1 -1
  33. package/nitrogen/generated/android/c++/JDownloadTask.hpp +1 -1
  34. package/nitrogen/generated/android/c++/JDownloadedPlaylist.hpp +1 -1
  35. package/nitrogen/generated/android/c++/JDownloadedTrack.hpp +1 -1
  36. package/nitrogen/generated/android/c++/JEqualizerBand.hpp +1 -1
  37. package/nitrogen/generated/android/c++/JEqualizerPreset.hpp +1 -1
  38. package/nitrogen/generated/android/c++/JEqualizerState.hpp +1 -1
  39. package/nitrogen/generated/android/c++/JFunc_void_DownloadProgress.hpp +1 -1
  40. package/nitrogen/generated/android/c++/JFunc_void_DownloadedTrack.hpp +1 -1
  41. package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +1 -1
  42. package/nitrogen/generated/android/c++/JFunc_void_TrackPlayerState_std__optional_Reason_.hpp +1 -1
  43. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +1 -1
  44. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +1 -1
  45. package/nitrogen/generated/android/c++/JFunc_void_double_double_std__optional_bool_.hpp +1 -1
  46. package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__std__string__.hpp +1 -1
  47. package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +1 -1
  48. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_DownloadState_std__optional_DownloadError_.hpp +1 -1
  49. package/nitrogen/generated/android/c++/JFunc_void_std__vector_EqualizerBand_.hpp +1 -1
  50. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +1 -1
  51. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double.hpp +1 -1
  52. package/nitrogen/generated/android/c++/JGainRange.hpp +1 -1
  53. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +8 -1
  54. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +2 -1
  55. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +8 -1
  56. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +2 -1
  57. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +9 -1
  58. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +2 -1
  59. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +8 -1
  60. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +2 -1
  61. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +8 -1
  62. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +2 -1
  63. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +9 -1
  64. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +2 -1
  65. package/nitrogen/generated/android/c++/JPlaybackSource.hpp +4 -5
  66. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +1 -1
  67. package/nitrogen/generated/android/c++/JPlayerState.hpp +1 -1
  68. package/nitrogen/generated/android/c++/JPlaylist.hpp +1 -1
  69. package/nitrogen/generated/android/c++/JPresetType.hpp +3 -4
  70. package/nitrogen/generated/android/c++/JQueueOperation.hpp +5 -6
  71. package/nitrogen/generated/android/c++/JReason.hpp +6 -7
  72. package/nitrogen/generated/android/c++/JRepeatMode.hpp +4 -5
  73. package/nitrogen/generated/android/c++/JStorageLocation.hpp +3 -4
  74. package/nitrogen/generated/android/c++/JTAudioDevice.hpp +1 -1
  75. package/nitrogen/generated/android/c++/JTrackItem.hpp +1 -1
  76. package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +4 -5
  77. package/nitrogen/generated/android/c++/JVariant_NullType_Double.cpp +1 -1
  78. package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +3 -3
  79. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.cpp +1 -1
  80. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.hpp +3 -3
  81. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.cpp +1 -1
  82. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.hpp +3 -3
  83. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.cpp +1 -1
  84. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.hpp +3 -3
  85. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.cpp +1 -1
  86. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.hpp +3 -3
  87. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.cpp +1 -1
  88. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +3 -3
  89. package/nitrogen/generated/android/c++/JVariant_NullType_String.cpp +1 -1
  90. package/nitrogen/generated/android/c++/JVariant_NullType_String.hpp +3 -3
  91. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.cpp +1 -1
  92. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +3 -3
  93. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/CurrentPlayingType.kt +3 -1
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadConfig.kt +2 -2
  95. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadError.kt +2 -2
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadErrorReason.kt +3 -1
  97. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadProgress.kt +2 -2
  98. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadQueueStatus.kt +2 -2
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadState.kt +3 -1
  100. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadStorageInfo.kt +2 -2
  101. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadTask.kt +2 -2
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedPlaylist.kt +2 -2
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedTrack.kt +2 -2
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerBand.kt +2 -2
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerPreset.kt +2 -2
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerState.kt +2 -2
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadProgress.kt +1 -1
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadedTrack.kt +1 -1
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackItem_std__optional_Reason_.kt +1 -1
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_TrackPlayerState_std__optional_Reason_.kt +1 -1
  111. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_bool.kt +1 -1
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double.kt +1 -1
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_double_double_std__optional_bool_.kt +1 -1
  114. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__optional_std__variant_nitro__NullType__std__string__.kt +1 -1
  115. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_Playlist_std__optional_QueueOperation_.kt +1 -1
  116. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.kt +1 -1
  117. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_EqualizerBand_.kt +1 -1
  118. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_Playlist__std__optional_QueueOperation_.kt +1 -1
  119. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_TrackItem__double.kt +1 -1
  120. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/GainRange.kt +2 -2
  121. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +1 -1
  122. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +1 -1
  123. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +1 -1
  124. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +1 -1
  125. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +1 -1
  126. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +1 -1
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/NitroPlayerOnLoad.kt +1 -1
  128. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlaybackSource.kt +3 -1
  129. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerConfig.kt +2 -2
  130. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerState.kt +2 -2
  131. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Playlist.kt +2 -2
  132. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PresetType.kt +3 -1
  133. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/QueueOperation.kt +3 -1
  134. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Reason.kt +3 -1
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/RepeatMode.kt +3 -1
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/StorageLocation.kt +3 -1
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TAudioDevice.kt +2 -2
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackItem.kt +2 -2
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackPlayerState.kt +3 -1
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Double.kt +1 -1
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadError.kt +1 -1
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadTask.kt +1 -1
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedPlaylist.kt +1 -1
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedTrack.kt +1 -1
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Playlist.kt +1 -1
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_String.kt +1 -1
  147. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_TrackItem.kt +1 -1
  148. package/nitrogen/generated/ios/NitroPlayer+autolinking.rb +2 -2
  149. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +1 -1
  150. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +21 -21
  151. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +1 -1
  152. package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +1 -1
  153. package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +25 -36
  154. package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.cpp +1 -1
  155. package/nitrogen/generated/ios/c++/HybridAudioRoutePickerSpecSwift.hpp +7 -1
  156. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.cpp +1 -1
  157. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +7 -1
  158. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.cpp +1 -1
  159. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +7 -1
  160. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.cpp +1 -1
  161. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +7 -1
  162. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.cpp +1 -1
  163. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +7 -1
  164. package/nitrogen/generated/ios/swift/CurrentPlayingType.swift +1 -1
  165. package/nitrogen/generated/ios/swift/DownloadConfig.swift +78 -168
  166. package/nitrogen/generated/ios/swift/DownloadError.swift +9 -34
  167. package/nitrogen/generated/ios/swift/DownloadErrorReason.swift +1 -1
  168. package/nitrogen/generated/ios/swift/DownloadProgress.swift +13 -50
  169. package/nitrogen/generated/ios/swift/DownloadQueueStatus.swift +15 -58
  170. package/nitrogen/generated/ios/swift/DownloadState.swift +1 -1
  171. package/nitrogen/generated/ios/swift/DownloadStorageInfo.swift +11 -42
  172. package/nitrogen/generated/ios/swift/DownloadTask.swift +97 -210
  173. package/nitrogen/generated/ios/swift/DownloadedPlaylist.swift +13 -56
  174. package/nitrogen/generated/ios/swift/DownloadedTrack.swift +34 -90
  175. package/nitrogen/generated/ios/swift/EqualizerBand.swift +9 -34
  176. package/nitrogen/generated/ios/swift/EqualizerPreset.swift +7 -32
  177. package/nitrogen/generated/ios/swift/EqualizerState.swift +26 -64
  178. package/nitrogen/generated/ios/swift/Func_void.swift +1 -2
  179. package/nitrogen/generated/ios/swift/Func_void_DownloadProgress.swift +1 -2
  180. package/nitrogen/generated/ios/swift/Func_void_DownloadStorageInfo.swift +1 -2
  181. package/nitrogen/generated/ios/swift/Func_void_DownloadedTrack.swift +1 -2
  182. package/nitrogen/generated/ios/swift/Func_void_PlayerState.swift +1 -2
  183. package/nitrogen/generated/ios/swift/Func_void_TrackItem_std__optional_Reason_.swift +1 -2
  184. package/nitrogen/generated/ios/swift/Func_void_TrackPlayerState_std__optional_Reason_.swift +1 -2
  185. package/nitrogen/generated/ios/swift/Func_void_bool.swift +1 -2
  186. package/nitrogen/generated/ios/swift/Func_void_double.swift +1 -2
  187. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +1 -2
  188. package/nitrogen/generated/ios/swift/Func_void_double_double_std__optional_bool_.swift +1 -2
  189. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -2
  190. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__variant_nitro__NullType__std__string__.swift +1 -2
  191. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +1 -2
  192. package/nitrogen/generated/ios/swift/Func_void_std__string_Playlist_std__optional_QueueOperation_.swift +1 -2
  193. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.swift +1 -2
  194. package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +1 -2
  195. package/nitrogen/generated/ios/swift/Func_void_std__vector_Playlist__std__optional_QueueOperation_.swift +1 -2
  196. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem_.swift +1 -2
  197. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double.swift +1 -2
  198. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +1 -2
  199. package/nitrogen/generated/ios/swift/GainRange.swift +5 -18
  200. package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec.swift +3 -4
  201. package/nitrogen/generated/ios/swift/HybridAudioRoutePickerSpec_cxx.swift +9 -2
  202. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +3 -4
  203. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +9 -2
  204. package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +3 -4
  205. package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +9 -2
  206. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +3 -4
  207. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +25 -4
  208. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -4
  209. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +9 -2
  210. package/nitrogen/generated/ios/swift/PlaybackSource.swift +1 -1
  211. package/nitrogen/generated/ios/swift/PlayerConfig.swift +37 -79
  212. package/nitrogen/generated/ios/swift/PlayerState.swift +53 -122
  213. package/nitrogen/generated/ios/swift/Playlist.swift +49 -112
  214. package/nitrogen/generated/ios/swift/PresetType.swift +1 -1
  215. package/nitrogen/generated/ios/swift/QueueOperation.swift +1 -1
  216. package/nitrogen/generated/ios/swift/Reason.swift +1 -1
  217. package/nitrogen/generated/ios/swift/RepeatMode.swift +1 -1
  218. package/nitrogen/generated/ios/swift/StorageLocation.swift +1 -1
  219. package/nitrogen/generated/ios/swift/TrackItem.swift +43 -111
  220. package/nitrogen/generated/ios/swift/TrackPlayerState.swift +1 -1
  221. package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +1 -1
  222. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadError.swift +1 -1
  223. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadTask.swift +1 -1
  224. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedPlaylist.swift +1 -1
  225. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedTrack.swift +1 -1
  226. package/nitrogen/generated/ios/swift/Variant_NullType_Playlist.swift +1 -1
  227. package/nitrogen/generated/ios/swift/Variant_NullType_String.swift +1 -1
  228. package/nitrogen/generated/ios/swift/Variant_NullType_TrackItem.swift +1 -1
  229. package/nitrogen/generated/shared/c++/CurrentPlayingType.hpp +1 -1
  230. package/nitrogen/generated/shared/c++/DownloadConfig.hpp +34 -26
  231. package/nitrogen/generated/shared/c++/DownloadError.hpp +22 -14
  232. package/nitrogen/generated/shared/c++/DownloadErrorReason.hpp +1 -1
  233. package/nitrogen/generated/shared/c++/DownloadProgress.hpp +28 -20
  234. package/nitrogen/generated/shared/c++/DownloadQueueStatus.hpp +31 -23
  235. package/nitrogen/generated/shared/c++/DownloadState.hpp +1 -1
  236. package/nitrogen/generated/shared/c++/DownloadStorageInfo.hpp +25 -17
  237. package/nitrogen/generated/shared/c++/DownloadTask.hpp +40 -32
  238. package/nitrogen/generated/shared/c++/DownloadedPlaylist.hpp +28 -20
  239. package/nitrogen/generated/shared/c++/DownloadedTrack.hpp +31 -23
  240. package/nitrogen/generated/shared/c++/EqualizerBand.hpp +22 -14
  241. package/nitrogen/generated/shared/c++/EqualizerPreset.hpp +19 -11
  242. package/nitrogen/generated/shared/c++/EqualizerState.hpp +19 -11
  243. package/nitrogen/generated/shared/c++/GainRange.hpp +16 -8
  244. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.cpp +1 -1
  245. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +1 -1
  246. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.cpp +1 -1
  247. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +1 -1
  248. package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.cpp +1 -1
  249. package/nitrogen/generated/shared/c++/HybridAudioRoutePickerSpec.hpp +1 -1
  250. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.cpp +1 -1
  251. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +1 -1
  252. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.cpp +1 -1
  253. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +1 -1
  254. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.cpp +1 -1
  255. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +1 -1
  256. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -1
  257. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -1
  258. package/nitrogen/generated/shared/c++/PlaybackSource.hpp +1 -1
  259. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +22 -14
  260. package/nitrogen/generated/shared/c++/PlayerState.hpp +31 -23
  261. package/nitrogen/generated/shared/c++/Playlist.hpp +25 -17
  262. package/nitrogen/generated/shared/c++/PresetType.hpp +1 -1
  263. package/nitrogen/generated/shared/c++/QueueOperation.hpp +1 -1
  264. package/nitrogen/generated/shared/c++/Reason.hpp +1 -1
  265. package/nitrogen/generated/shared/c++/RepeatMode.hpp +1 -1
  266. package/nitrogen/generated/shared/c++/StorageLocation.hpp +1 -1
  267. package/nitrogen/generated/shared/c++/TAudioDevice.hpp +22 -14
  268. package/nitrogen/generated/shared/c++/TrackItem.hpp +34 -26
  269. package/nitrogen/generated/shared/c++/TrackPlayerState.hpp +1 -1
  270. package/package.json +3 -3
  271. package/src/hooks/usePlaylist.ts +26 -4
@@ -82,6 +82,12 @@ final class DownloadDatabase {
82
82
  }
83
83
  }
84
84
 
85
+ private func _isTrackDownloadedUnsafe(trackId: String) -> Bool {
86
+ guard let record = downloadedTracks[trackId] else { return false }
87
+ let absolutePath = resolveAbsolutePath(for: record)
88
+ return FileManager.default.fileExists(atPath: absolutePath)
89
+ }
90
+
85
91
  func isPlaylistDownloaded(playlistId: String) -> Bool {
86
92
  return queue.sync {
87
93
  guard let trackIds = playlistTracks[playlistId], !trackIds.isEmpty else { return false }
@@ -93,7 +99,7 @@ final class DownloadDatabase {
93
99
 
94
100
  // Check if all tracks are downloaded
95
101
  for track in playlistModel.tracks {
96
- if !isTrackDownloaded(trackId: track.id) {
102
+ if !_isTrackDownloadedUnsafe(trackId: track.id) {
97
103
  return false
98
104
  }
99
105
  }
@@ -108,7 +114,7 @@ final class DownloadDatabase {
108
114
 
109
115
  // Check if at least one track is downloaded
110
116
  for trackId in trackIds {
111
- if isTrackDownloaded(trackId: trackId) {
117
+ if _isTrackDownloadedUnsafe(trackId: trackId) {
112
118
  return true
113
119
  }
114
120
  }
@@ -304,8 +304,18 @@ final class DownloadFileManager {
304
304
  return bytesFreed
305
305
  }
306
306
 
307
+ private static let commonAudioExtensions = ["mp3", "m4a", "aac", "wav", "flac", "ogg", "opus", "mp4"]
308
+
307
309
  func getLocalPath(for trackId: String) -> String? {
308
- // Check private directory first
310
+ // Try common extensions first (fast O(1) path)
311
+ for ext in Self.commonAudioExtensions {
312
+ let privatePath = privateDownloadsDirectory.appendingPathComponent("\(trackId).\(ext)").path
313
+ if fileManager.fileExists(atPath: privatePath) { return privatePath }
314
+ let publicPath = publicDownloadsDirectory.appendingPathComponent("\(trackId).\(ext)").path
315
+ if fileManager.fileExists(atPath: publicPath) { return publicPath }
316
+ }
317
+
318
+ // Fallback to directory enumeration for uncommon extensions
309
319
  let privateFiles =
310
320
  (try? fileManager.contentsOfDirectory(
311
321
  at: privateDownloadsDirectory, includingPropertiesForKeys: nil)) ?? []
@@ -315,7 +325,6 @@ final class DownloadFileManager {
315
325
  }
316
326
  }
317
327
 
318
- // Check public directory
319
328
  let publicFiles =
320
329
  (try? fileManager.contentsOfDirectory(
321
330
  at: publicDownloadsDirectory, includingPropertiesForKeys: nil)) ?? []
@@ -58,6 +58,9 @@ final class DownloadManagerCore: NSObject {
58
58
  /// Playlist associations (downloadId -> playlistId)
59
59
  private var playlistAssociations: [String: String] = [:]
60
60
 
61
+ /// Cached parsed (downloadId, trackId) tuples per task description to avoid repeated string splitting
62
+ private var parsedDescriptionCache: [String: (downloadId: String, trackId: String)] = [:]
63
+
61
64
  /// Background completion handler from AppDelegate
62
65
  var backgroundCompletionHandler: (() -> Void)?
63
66
 
@@ -258,10 +261,50 @@ final class DownloadManagerCore: NSObject {
258
261
  }
259
262
  }
260
263
 
264
+ private func _pauseDownloadUnsafe(downloadId: String) {
265
+ guard let task = self.activeTasks[downloadId] else { return }
266
+ task.cancel(byProducingResumeData: { resumeData in
267
+ self.taskMetadata[downloadId]?.resumeData = resumeData
268
+ })
269
+ self.taskMetadata[downloadId]?.state = .paused
270
+ if let trackId = self.taskMetadata[downloadId]?.trackId {
271
+ self.notifyStateChange(downloadId: downloadId, trackId: trackId, state: .paused, error: nil)
272
+ }
273
+ }
274
+
275
+ private func _resumeDownloadUnsafe(downloadId: String) {
276
+ guard let metadata = self.taskMetadata[downloadId] else { return }
277
+ var task: URLSessionDownloadTask
278
+ if let resumeData = metadata.resumeData {
279
+ task = self.backgroundSession.downloadTask(withResumeData: resumeData)
280
+ } else if let track = self.trackMetadata[metadata.trackId],
281
+ let url = URL(string: track.url) {
282
+ task = self.backgroundSession.downloadTask(with: url)
283
+ } else { return }
284
+ task.taskDescription = "\(downloadId)|\(metadata.trackId)"
285
+ self.activeTasks[downloadId] = task
286
+ self.taskMetadata[downloadId]?.state = .downloading
287
+ self.taskMetadata[downloadId]?.resumeData = nil
288
+ task.resume()
289
+ self.notifyStateChange(downloadId: downloadId, trackId: metadata.trackId, state: .downloading, error: nil)
290
+ }
291
+
292
+ private func _cancelDownloadUnsafe(downloadId: String) {
293
+ guard let task = self.activeTasks[downloadId] else { return }
294
+ task.cancel()
295
+ if let trackId = self.taskMetadata[downloadId]?.trackId {
296
+ self.taskMetadata[downloadId]?.state = .cancelled
297
+ self.notifyStateChange(downloadId: downloadId, trackId: trackId, state: .cancelled, error: nil)
298
+ self.cleanupPersistedMetadata(trackId: trackId, downloadId: downloadId)
299
+ }
300
+ self.activeTasks.removeValue(forKey: downloadId)
301
+ self.taskMetadata.removeValue(forKey: downloadId)
302
+ }
303
+
261
304
  func pauseAllDownloads() {
262
305
  queue.async(flags: .barrier) {
263
306
  for downloadId in self.activeTasks.keys {
264
- self.pauseDownload(downloadId: downloadId)
307
+ self._pauseDownloadUnsafe(downloadId: downloadId)
265
308
  }
266
309
  }
267
310
  }
@@ -270,7 +313,7 @@ final class DownloadManagerCore: NSObject {
270
313
  queue.async(flags: .barrier) {
271
314
  for downloadId in self.taskMetadata.keys where self.taskMetadata[downloadId]?.state == .paused
272
315
  {
273
- self.resumeDownload(downloadId: downloadId)
316
+ self._resumeDownloadUnsafe(downloadId: downloadId)
274
317
  }
275
318
  }
276
319
  }
@@ -278,7 +321,7 @@ final class DownloadManagerCore: NSObject {
278
321
  func cancelAllDownloads() {
279
322
  queue.async(flags: .barrier) {
280
323
  for downloadId in self.activeTasks.keys {
281
- self.cancelDownload(downloadId: downloadId)
324
+ self._cancelDownloadUnsafe(downloadId: downloadId)
282
325
  }
283
326
  }
284
327
  }
@@ -302,15 +345,24 @@ final class DownloadManagerCore: NSObject {
302
345
 
303
346
  func getQueueStatus() -> DownloadQueueStatus {
304
347
  return queue.sync {
305
- let metadata = Array(taskMetadata.values)
348
+ var pendingCount = 0
349
+ var activeCount = 0
350
+ var failedCount = 0
351
+ var totalBytes = 0.0
352
+ var downloadedBytes = 0.0
353
+
354
+ for m in taskMetadata.values {
355
+ switch m.state {
356
+ case .pending: pendingCount += 1
357
+ case .downloading: activeCount += 1
358
+ case .failed: failedCount += 1
359
+ default: break
360
+ }
361
+ totalBytes += m.totalBytes ?? 0
362
+ downloadedBytes += m.bytesDownloaded
363
+ }
306
364
 
307
- let pendingCount = metadata.filter { $0.state == .pending }.count
308
- let activeCount = metadata.filter { $0.state == .downloading }.count
309
365
  let completedCount = DownloadDatabase.shared.getAllDownloadedTracks().count
310
- let failedCount = metadata.filter { $0.state == .failed }.count
311
-
312
- let totalBytes = metadata.reduce(0.0) { $0 + ($1.totalBytes ?? 0) }
313
- let downloadedBytes = metadata.reduce(0.0) { $0 + $1.bytesDownloaded }
314
366
 
315
367
  return DownloadQueueStatus(
316
368
  pendingCount: Double(pendingCount),
@@ -601,6 +653,17 @@ final class DownloadManagerCore: NSObject {
601
653
  savePersistedMetadata()
602
654
  }
603
655
 
656
+ private func parseTaskDescription(_ description: String) -> (downloadId: String, trackId: String)? {
657
+ if let cached = parsedDescriptionCache[description] {
658
+ return cached
659
+ }
660
+ let parts = description.split(separator: "|")
661
+ guard parts.count == 2 else { return nil }
662
+ let result = (downloadId: String(parts[0]), trackId: String(parts[1]))
663
+ parsedDescriptionCache[description] = result
664
+ return result
665
+ }
666
+
604
667
  // MARK: - TrackItem Serialization
605
668
 
606
669
  private func trackItemToRecord(_ track: TrackItem) -> TrackItemRecord {
@@ -725,14 +788,13 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
725
788
  NitroPlayerLogger.log("DownloadManagerCore", "❌ No task description")
726
789
  return
727
790
  }
728
- let parts = description.split(separator: "|")
729
- guard parts.count == 2 else {
791
+ guard let parsed = parseTaskDescription(description) else {
730
792
  NitroPlayerLogger.log("DownloadManagerCore", "❌ Invalid task description format: \(description)")
731
793
  return
732
794
  }
733
795
 
734
- let downloadId = String(parts[0])
735
- let trackId = String(parts[1])
796
+ let downloadId = parsed.downloadId
797
+ let trackId = parsed.trackId
736
798
 
737
799
  NitroPlayerLogger.log("DownloadManagerCore", "🎯 Processing completion for downloadId=\(downloadId), trackId=\(trackId)")
738
800
 
@@ -834,12 +896,11 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
834
896
  _ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64,
835
897
  totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64
836
898
  ) {
837
- guard let description = downloadTask.taskDescription else { return }
838
- let parts = description.split(separator: "|")
839
- guard parts.count == 2 else { return }
899
+ guard let description = downloadTask.taskDescription,
900
+ let parsed = parseTaskDescription(description) else { return }
840
901
 
841
- let downloadId = String(parts[0])
842
- let trackId = String(parts[1])
902
+ let downloadId = parsed.downloadId
903
+ let trackId = parsed.trackId
843
904
 
844
905
  queue.async(flags: .barrier) {
845
906
  self.taskMetadata[downloadId]?.bytesDownloaded = Double(totalBytesWritten)
@@ -862,14 +923,12 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
862
923
 
863
924
  func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
864
925
  guard let downloadTask = task as? URLSessionDownloadTask,
865
- let description = downloadTask.taskDescription
926
+ let description = downloadTask.taskDescription,
927
+ let parsed = parseTaskDescription(description)
866
928
  else { return }
867
929
 
868
- let parts = description.split(separator: "|")
869
- guard parts.count == 2 else { return }
870
-
871
- let downloadId = String(parts[0])
872
- let trackId = String(parts[1])
930
+ let downloadId = parsed.downloadId
931
+ let trackId = parsed.trackId
873
932
 
874
933
  guard let error = error else { return } // Success case handled in didFinishDownloadingTo
875
934
 
@@ -28,6 +28,8 @@ class EqualizerCore {
28
28
 
29
29
  // Current gains storage - internal so TapContext can access
30
30
  private(set) var currentGains: [Double] = [0, 0, 0, 0, 0]
31
+ private var cachedBands: [EqualizerBand]?
32
+ private var cachedCustomPresets: [EqualizerPreset]?
31
33
 
32
34
  // Dirty flag: set when gains change so TapContext only recalculates when needed
33
35
  var gainsDirty: Bool = true
@@ -89,11 +91,35 @@ class EqualizerCore {
89
91
 
90
92
  // MARK: - Audio Mix Creation for AVPlayerItem
91
93
 
92
- /// Applies an AVAudioMix with equalizer processing for the given AVPlayerItem asynchronously
94
+ /// Applies an AVAudioMix with equalizer processing for the given AVPlayerItem.
95
+ /// No-ops when the equalizer is disabled so that AVPlayerItems remain tap-free,
96
+ /// keeping the audio pipeline configuration identical across all queued items
97
+ /// and allowing AVQueuePlayer to perform seamless gapless transitions.
98
+ ///
99
+ /// When the asset's "tracks" key is already loaded (e.g. from preloading),
100
+ /// the mix is applied **synchronously** so the item enters AVQueuePlayer with
101
+ /// the correct tap from the start — avoiding a pipeline reconfiguration that
102
+ /// causes an audible gap at the transition.
93
103
  func applyAudioMix(to playerItem: AVPlayerItem) {
104
+ guard isEqualizerEnabled else {
105
+ // Ensure no stale tap remains from a previous enable/disable cycle
106
+ if playerItem.audioMix != nil {
107
+ playerItem.audioMix = nil
108
+ }
109
+ return
110
+ }
111
+
94
112
  let asset = playerItem.asset
95
113
 
96
- // Load "tracks" key asynchronously to avoid blocking
114
+ // Fast path: "tracks" already loaded (from preloadUpcomingTracks) — apply synchronously.
115
+ var keyError: NSError?
116
+ if asset.statusOfValue(forKey: "tracks", error: &keyError) == .loaded {
117
+ buildAndApplyAudioMix(to: playerItem, asset: asset)
118
+ NitroPlayerLogger.log("EqualizerCore", "✅ Applied audio mix with EQ tap to player item (sync — preloaded)")
119
+ return
120
+ }
121
+
122
+ // Slow path: load "tracks" key asynchronously to avoid blocking
97
123
  asset.loadValuesAsynchronously(forKeys: ["tracks"]) { [weak self] in
98
124
  guard let self = self else { return }
99
125
 
@@ -111,50 +137,55 @@ class EqualizerCore {
111
137
  return
112
138
  }
113
139
 
114
- guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
115
- NitroPlayerLogger.log("EqualizerCore", "⚠️ No audio track found in asset")
116
- return
117
- }
140
+ // Apply directly audioMix is thread-safe and applying on the
141
+ // loadValues completion queue avoids a main-thread hop that would
142
+ // delay tap attachment and risk a pipeline mismatch at pre-roll time.
143
+ self.buildAndApplyAudioMix(to: playerItem, asset: asset)
144
+ NitroPlayerLogger.log("EqualizerCore", "✅ Applied audio mix with EQ tap to player item (async)")
145
+ }
146
+ }
118
147
 
119
- // Create audio mix input parameters
120
- let inputParams = AVMutableAudioMixInputParameters(track: audioTrack)
121
-
122
- // Create the audio processing tap
123
- var callbacks = MTAudioProcessingTapCallbacks(
124
- version: kMTAudioProcessingTapCallbacksVersion_0,
125
- clientInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()),
126
- init: tapInitCallback,
127
- finalize: tapFinalizeCallback,
128
- prepare: tapPrepareCallback,
129
- unprepare: tapUnprepareCallback,
130
- process: tapProcessCallback
131
- )
148
+ /// Creates an MTAudioProcessingTap-backed AVAudioMix and sets it on the player item.
149
+ /// Must be called when the asset's "tracks" key is already loaded.
150
+ private func buildAndApplyAudioMix(to playerItem: AVPlayerItem, asset: AVAsset) {
151
+ guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
152
+ NitroPlayerLogger.log("EqualizerCore", "⚠️ No audio track found in asset")
153
+ return
154
+ }
132
155
 
133
- var tap: MTAudioProcessingTap?
134
- let createStatus = MTAudioProcessingTapCreate(
135
- kCFAllocatorDefault,
136
- &callbacks,
137
- kMTAudioProcessingTapCreationFlag_PreEffects,
138
- &tap
139
- )
156
+ // Create audio mix input parameters
157
+ let inputParams = AVMutableAudioMixInputParameters(track: audioTrack)
158
+
159
+ // Create the audio processing tap
160
+ var callbacks = MTAudioProcessingTapCallbacks(
161
+ version: kMTAudioProcessingTapCallbacksVersion_0,
162
+ clientInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()),
163
+ init: tapInitCallback,
164
+ finalize: tapFinalizeCallback,
165
+ prepare: tapPrepareCallback,
166
+ unprepare: tapUnprepareCallback,
167
+ process: tapProcessCallback
168
+ )
140
169
 
141
- guard createStatus == noErr, let audioTap = tap else {
142
- NitroPlayerLogger.log("EqualizerCore", "❌ Failed to create audio processing tap, status: \(createStatus)")
143
- return
144
- }
170
+ var tap: MTAudioProcessingTap?
171
+ let createStatus = MTAudioProcessingTapCreate(
172
+ kCFAllocatorDefault,
173
+ &callbacks,
174
+ kMTAudioProcessingTapCreationFlag_PreEffects,
175
+ &tap
176
+ )
145
177
 
146
- inputParams.audioTapProcessor = audioTap
178
+ guard createStatus == noErr, let audioTap = tap else {
179
+ NitroPlayerLogger.log("EqualizerCore", "❌ Failed to create audio processing tap, status: \(createStatus)")
180
+ return
181
+ }
147
182
 
148
- // Create audio mix
149
- let audioMix = AVMutableAudioMix()
150
- audioMix.inputParameters = [inputParams]
183
+ inputParams.audioTapProcessor = audioTap
151
184
 
152
- // Apply to player item on main thread (AVPlayerItem properties should be accessed/modified on main thread or serial queue usually, but audioMix is thread safe - safely done on main to be sure)
153
- DispatchQueue.main.async {
154
- playerItem.audioMix = audioMix
155
- NitroPlayerLogger.log("EqualizerCore", "✅ Applied audio mix with EQ tap to player item (async)")
156
- }
157
- }
185
+ // Create and apply audio mix
186
+ let audioMix = AVMutableAudioMix()
187
+ audioMix.inputParameters = [inputParams]
188
+ playerItem.audioMix = audioMix
158
189
  }
159
190
 
160
191
  // MARK: - Public Methods
@@ -174,7 +205,8 @@ class EqualizerCore {
174
205
  }
175
206
 
176
207
  func getBands() -> [EqualizerBand] {
177
- return (0..<5).map { i in
208
+ if let cached = cachedBands { return cached }
209
+ let bands = (0..<5).map { i in
178
210
  EqualizerBand(
179
211
  index: Double(i),
180
212
  centerFrequency: Double(frequencies[i]),
@@ -182,6 +214,8 @@ class EqualizerCore {
182
214
  frequencyLabel: frequencyLabels[i]
183
215
  )
184
216
  }
217
+ cachedBands = bands
218
+ return bands
185
219
  }
186
220
 
187
221
  func setBandGain(bandIndex: Int, gainDb: Double) -> Bool {
@@ -189,6 +223,7 @@ class EqualizerCore {
189
223
 
190
224
  let clampedGain = max(-12.0, min(12.0, gainDb))
191
225
  currentGains[bandIndex] = clampedGain
226
+ cachedBands = nil
192
227
  gainsDirty = true
193
228
 
194
229
  currentPresetName = nil
@@ -207,6 +242,7 @@ class EqualizerCore {
207
242
  for i in 0..<5 {
208
243
  currentGains[i] = max(-12.0, min(12.0, gains[i]))
209
244
  }
245
+ cachedBands = nil
210
246
  gainsDirty = true
211
247
 
212
248
  notifyBandChange(getBands())
@@ -231,15 +267,18 @@ class EqualizerCore {
231
267
  }
232
268
 
233
269
  func getCustomPresets() -> [EqualizerPreset] {
270
+ if let cached = cachedCustomPresets { return cached }
234
271
  guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
235
272
  let presets = try? JSONDecoder().decode([String: [Double]].self, from: data)
236
273
  else {
237
274
  return []
238
275
  }
239
276
 
240
- return presets.map { name, gains in
277
+ let result = presets.map { name, gains in
241
278
  EqualizerPreset(name: name, gains: gains, type: .custom)
242
279
  }
280
+ cachedCustomPresets = result
281
+ return result
243
282
  }
244
283
 
245
284
  func applyPreset(_ presetName: String) -> Bool {
@@ -292,6 +331,7 @@ class EqualizerCore {
292
331
 
293
332
  if let data = try? JSONEncoder().encode(presets) {
294
333
  UserDefaults.standard.set(data, forKey: customPresetsKey)
334
+ cachedCustomPresets = nil
295
335
  currentPresetName = name
296
336
  notifyPresetChange(name)
297
337
  saveCurrentPreset(name)
@@ -321,6 +361,7 @@ class EqualizerCore {
321
361
 
322
362
  if let data = try? JSONEncoder().encode(presets) {
323
363
  UserDefaults.standard.set(data, forKey: customPresetsKey)
364
+ cachedCustomPresets = nil
324
365
 
325
366
  if currentPresetName == name {
326
367
  currentPresetName = nil
@@ -21,7 +21,7 @@ class MediaSessionManager {
21
21
  // MARK: - Properties
22
22
 
23
23
  private var trackPlayerCore: TrackPlayerCore?
24
- private var artworkCache: [String: UIImage] = [:]
24
+ private let artworkCache = NSCache<NSString, UIImage>()
25
25
 
26
26
  private var showInNotification: Bool = true
27
27
 
@@ -135,7 +135,7 @@ class MediaSessionManager {
135
135
  // Artwork: use cache synchronously when available, otherwise kick off async load
136
136
  if let artwork = track.artwork, case .second(let artworkUrl) = artwork {
137
137
  lastArtworkUrl = artworkUrl
138
- if let cachedImage = artworkCache[artworkUrl] {
138
+ if let cachedImage = artworkCache.object(forKey: artworkUrl as NSString) {
139
139
  nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(
140
140
  boundsSize: CGSize(width: Constants.artworkSize, height: Constants.artworkSize),
141
141
  requestHandler: { _ in cachedImage }
@@ -285,7 +285,7 @@ class MediaSessionManager {
285
285
  }
286
286
 
287
287
  private func loadArtwork(url: String, completion: @escaping (UIImage?) -> Void) {
288
- if let cached = artworkCache[url] {
288
+ if let cached = artworkCache.object(forKey: url as NSString) {
289
289
  completion(cached)
290
290
  return
291
291
  }
@@ -301,7 +301,7 @@ class MediaSessionManager {
301
301
  return
302
302
  }
303
303
  DispatchQueue.main.async {
304
- self?.artworkCache[url] = image
304
+ self?.artworkCache.setObject(image, forKey: url as NSString)
305
305
  completion(image)
306
306
  }
307
307
  }.resume()
@@ -310,6 +310,6 @@ class MediaSessionManager {
310
310
  func release() {
311
311
  clearNowPlayingInfo()
312
312
  disableAllCommands()
313
- artworkCache.removeAll()
313
+ artworkCache.removeAllObjects()
314
314
  }
315
315
  }
@@ -15,7 +15,7 @@ class PlaylistModel {
15
15
  let name: String
16
16
  let description: String?
17
17
  let artwork: String?
18
- var tracks: [TrackItem]
18
+ let tracks: [TrackItem]
19
19
 
20
20
  init(
21
21
  id: String, name: String, description: String? = nil, artwork: String? = nil,
@@ -77,6 +77,7 @@ final class HybridPlayerQueue: HybridPlayerQueueSpec {
77
77
 
78
78
  func onPlaylistsChanged(callback: @escaping ([Playlist], QueueOperation?) -> Void) throws {
79
79
  // Store callback in static storage so it persists across HybridPlayerQueue instances
80
+ HybridPlayerQueue.playlistsChangeCallbacks.removeAll()
80
81
  HybridPlayerQueue.playlistsChangeCallbacks.append(callback)
81
82
 
82
83
  // Register a single listener with PlaylistManager that dispatches to all callbacks
@@ -94,6 +95,7 @@ final class HybridPlayerQueue: HybridPlayerQueueSpec {
94
95
 
95
96
  func onPlaylistChanged(callback: @escaping (String, Playlist, QueueOperation?) -> Void) throws {
96
97
  // Store callback in static storage so it persists across HybridPlayerQueue instances
98
+ HybridPlayerQueue.playlistChangeCallbacks.removeAll()
97
99
  HybridPlayerQueue.playlistChangeCallbacks.append(callback)
98
100
 
99
101
  // Register listeners for all existing playlists (only once per playlist)
@@ -35,10 +35,12 @@ enum NitroPlayerStorage {
35
35
  /// Uses `FileManager` APIs — never hardcodes the UUID-based container path
36
36
  /// so this resolves correctly regardless of which device or simulator the
37
37
  /// app runs on.
38
- private static func storageDirectory() -> URL {
38
+ private static let storageDir: URL = {
39
39
  let appSupport = FileManager.default.urls(
40
40
  for: .applicationSupportDirectory, in: .userDomainMask
41
41
  ).first!
42
42
  return appSupport.appendingPathComponent("NitroPlayer", isDirectory: true)
43
- }
43
+ }()
44
+
45
+ private static func storageDirectory() -> URL { storageDir }
44
46
  }
@@ -41,6 +41,9 @@ export function usePlaylist() {
41
41
  const [isLoading, setIsLoading] = useState(true);
42
42
  const isMounted = useRef(true);
43
43
  const hasSubscribed = useRef(false);
44
+ // Tracks the last fetched playlist ID so track-change events can skip
45
+ // a full refresh when the playlist itself hasn't changed.
46
+ const lastPlaylistIdRef = useRef(undefined);
44
47
  const refreshPlaylists = useCallback(() => {
45
48
  if (!isMounted.current)
46
49
  return;
@@ -49,6 +52,7 @@ export function usePlaylist() {
49
52
  const playlistId = PlayerQueue.getCurrentPlaylistId();
50
53
  if (!isMounted.current)
51
54
  return;
55
+ lastPlaylistIdRef.current = playlistId;
52
56
  setCurrentPlaylistId(playlistId);
53
57
  // Get current playlist details
54
58
  if (playlistId) {
@@ -88,6 +92,24 @@ export function usePlaylist() {
88
92
  }
89
93
  }
90
94
  }, []);
95
+ // Lightweight track-change handler: only does a full refresh when the
96
+ // active playlist ID has actually changed (e.g. cross-playlist navigation).
97
+ // Within-playlist track changes — the common case — are skipped with a
98
+ // single bridge call instead of three.
99
+ const refreshOnTrackChange = useCallback(() => {
100
+ if (!isMounted.current)
101
+ return;
102
+ try {
103
+ const newPlaylistId = PlayerQueue.getCurrentPlaylistId();
104
+ if (newPlaylistId === lastPlaylistIdRef.current)
105
+ return;
106
+ lastPlaylistIdRef.current = newPlaylistId;
107
+ refreshPlaylists();
108
+ }
109
+ catch (error) {
110
+ console.error('[usePlaylist] Error checking playlist ID on track change:', error);
111
+ }
112
+ }, [refreshPlaylists]);
91
113
  // Initialize and setup mounted ref
92
114
  useEffect(() => {
93
115
  isMounted.current = true;
@@ -113,18 +135,17 @@ export function usePlaylist() {
113
135
  console.error('[usePlaylist] Error setting up playlist listener:', error);
114
136
  }
115
137
  }, [refreshPlaylists]);
116
- // Also refresh when track changes (as it might indicate playlist loaded)
138
+ // Refresh on track change only if the active playlist ID changed.
117
139
  useEffect(() => {
118
140
  const unsubscribe = callbackManager.subscribeToTrackChange(() => {
119
- // Refresh to update currentPlaylistId when track changes
120
141
  if (isMounted.current) {
121
- refreshPlaylists();
142
+ refreshOnTrackChange();
122
143
  }
123
144
  });
124
145
  return () => {
125
146
  unsubscribe();
126
147
  };
127
- }, [refreshPlaylists]);
148
+ }, [refreshOnTrackChange]);
128
149
  return {
129
150
  currentPlaylist,
130
151
  currentPlaylistId,
@@ -2,7 +2,7 @@
2
2
  # NitroPlayer+autolinking.cmake
3
3
  # This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
4
  # https://github.com/mrousavy/nitro
5
- # Copyright © 2026 Marc Rousavy @ Margelo
5
+ # Copyright © Marc Rousavy @ Margelo
6
6
  #
7
7
 
8
8
  # This is a CMake file that adds all files generated by Nitrogen
@@ -2,7 +2,7 @@
2
2
  /// NitroPlayer+autolinking.gradle
3
3
  /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
4
  /// https://github.com/mrousavy/nitro
5
- /// Copyright © 2026 Marc Rousavy @ Margelo
5
+ /// Copyright © Marc Rousavy @ Margelo
6
6
  ///
7
7
 
8
8
  /// This is a Gradle file that adds all files generated by Nitrogen