react-native-nitro-player 0.3.0-alpha.9 → 0.4.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 (257) hide show
  1. package/README.md +444 -4
  2. package/android/build.gradle +4 -1
  3. package/android/src/main/AndroidManifest.xml +16 -1
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +2 -0
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +8 -0
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridDownloadManager.kt +225 -0
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridEqualizer.kt +105 -0
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +6 -6
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +37 -12
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +970 -213
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +475 -0
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadFileManager.kt +159 -0
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +489 -0
  14. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadWorker.kt +209 -0
  15. package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +486 -0
  16. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaBrowserService.kt +3 -1
  17. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +14 -6
  18. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +27 -0
  19. package/ios/HybridDownloadManager.swift +226 -0
  20. package/ios/HybridEqualizer.swift +111 -0
  21. package/ios/HybridTrackPlayer.swift +36 -8
  22. package/ios/core/TrackPlayerCore.swift +998 -276
  23. package/ios/download/DownloadDatabase.swift +493 -0
  24. package/ios/download/DownloadFileManager.swift +241 -0
  25. package/ios/download/DownloadManagerCore.swift +923 -0
  26. package/ios/equalizer/EqualizerCore.swift +685 -0
  27. package/ios/media/MediaSessionManager.swift +40 -28
  28. package/ios/playlist/PlaylistManager.swift +40 -9
  29. package/ios/queue/HybridPlayerQueue.swift +33 -13
  30. package/lib/hooks/downloadCallbackManager.d.ts +36 -0
  31. package/lib/hooks/downloadCallbackManager.js +108 -0
  32. package/lib/hooks/equalizerCallbackManager.d.ts +37 -0
  33. package/lib/hooks/equalizerCallbackManager.js +109 -0
  34. package/lib/hooks/index.d.ts +16 -0
  35. package/lib/hooks/index.js +10 -0
  36. package/lib/hooks/useActualQueue.d.ts +48 -0
  37. package/lib/hooks/useActualQueue.js +98 -0
  38. package/lib/hooks/useDownloadActions.d.ts +26 -0
  39. package/lib/hooks/useDownloadActions.js +117 -0
  40. package/lib/hooks/useDownloadProgress.d.ts +25 -0
  41. package/lib/hooks/useDownloadProgress.js +79 -0
  42. package/lib/hooks/useDownloadStorage.d.ts +19 -0
  43. package/lib/hooks/useDownloadStorage.js +60 -0
  44. package/lib/hooks/useDownloadedTracks.d.ts +25 -0
  45. package/lib/hooks/useDownloadedTracks.js +69 -0
  46. package/lib/hooks/useEqualizer.d.ts +25 -0
  47. package/lib/hooks/useEqualizer.js +124 -0
  48. package/lib/hooks/useEqualizerPresets.d.ts +22 -0
  49. package/lib/hooks/useEqualizerPresets.js +96 -0
  50. package/lib/hooks/useNowPlaying.js +3 -2
  51. package/lib/hooks/useOnChangeTrack.js +15 -12
  52. package/lib/hooks/useOnPlaybackStateChange.js +16 -13
  53. package/lib/hooks/usePlaylist.d.ts +48 -0
  54. package/lib/hooks/usePlaylist.js +136 -0
  55. package/lib/index.d.ts +6 -0
  56. package/lib/index.js +6 -0
  57. package/lib/specs/DownloadManager.nitro.d.ts +152 -0
  58. package/lib/specs/DownloadManager.nitro.js +1 -0
  59. package/lib/specs/Equalizer.nitro.d.ts +43 -0
  60. package/lib/specs/Equalizer.nitro.js +1 -0
  61. package/lib/specs/TrackPlayer.nitro.d.ts +6 -2
  62. package/lib/types/DownloadTypes.d.ts +110 -0
  63. package/lib/types/DownloadTypes.js +1 -0
  64. package/lib/types/EqualizerTypes.d.ts +52 -0
  65. package/lib/types/EqualizerTypes.js +1 -0
  66. package/lib/types/PlayerQueue.d.ts +4 -0
  67. package/nitro.json +8 -0
  68. package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +10 -1
  69. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +32 -2
  70. package/nitrogen/generated/android/c++/JCurrentPlayingType.hpp +65 -0
  71. package/nitrogen/generated/android/c++/JDownloadConfig.hpp +92 -0
  72. package/nitrogen/generated/android/c++/JDownloadError.hpp +71 -0
  73. package/nitrogen/generated/android/c++/JDownloadErrorReason.hpp +74 -0
  74. package/nitrogen/generated/android/c++/JDownloadProgress.hpp +79 -0
  75. package/nitrogen/generated/android/c++/JDownloadQueueStatus.hpp +81 -0
  76. package/nitrogen/generated/android/c++/JDownloadState.hpp +71 -0
  77. package/nitrogen/generated/android/c++/JDownloadStorageInfo.hpp +73 -0
  78. package/nitrogen/generated/android/c++/JDownloadTask.hpp +108 -0
  79. package/nitrogen/generated/android/c++/JDownloadedPlaylist.hpp +111 -0
  80. package/nitrogen/generated/android/c++/JDownloadedTrack.hpp +92 -0
  81. package/nitrogen/generated/android/c++/JEqualizerBand.hpp +69 -0
  82. package/nitrogen/generated/android/c++/JEqualizerPreset.hpp +78 -0
  83. package/nitrogen/generated/android/c++/JEqualizerState.hpp +91 -0
  84. package/nitrogen/generated/android/c++/JFunc_void_DownloadProgress.hpp +80 -0
  85. package/nitrogen/generated/android/c++/JFunc_void_DownloadedTrack.hpp +89 -0
  86. package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +2 -0
  87. package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__std__string__.hpp +81 -0
  88. package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +2 -0
  89. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_DownloadState_std__optional_DownloadError_.hpp +83 -0
  90. package/nitrogen/generated/android/c++/JFunc_void_std__vector_EqualizerBand_.hpp +97 -0
  91. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +2 -0
  92. package/nitrogen/generated/android/c++/JGainRange.hpp +61 -0
  93. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +470 -0
  94. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +99 -0
  95. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +204 -0
  96. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +82 -0
  97. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +2 -0
  98. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +117 -15
  99. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +6 -2
  100. package/nitrogen/generated/android/c++/JPlaybackSource.hpp +62 -0
  101. package/nitrogen/generated/android/c++/JPlayerState.hpp +11 -3
  102. package/nitrogen/generated/android/c++/JPlaylist.hpp +2 -0
  103. package/nitrogen/generated/android/c++/JPresetType.hpp +59 -0
  104. package/nitrogen/generated/android/c++/JStorageLocation.hpp +59 -0
  105. package/nitrogen/generated/android/c++/JTrackItem.hpp +9 -3
  106. package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +3 -3
  107. package/nitrogen/generated/android/c++/JVariant_NullType_Double.cpp +26 -0
  108. package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +69 -0
  109. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.cpp +26 -0
  110. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.hpp +74 -0
  111. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.cpp +26 -0
  112. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.hpp +84 -0
  113. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.cpp +26 -0
  114. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.hpp +85 -0
  115. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.cpp +26 -0
  116. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.hpp +80 -0
  117. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +2 -0
  118. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +2 -0
  119. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/CurrentPlayingType.kt +23 -0
  120. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadConfig.kt +59 -0
  121. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadError.kt +47 -0
  122. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadErrorReason.kt +26 -0
  123. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadProgress.kt +53 -0
  124. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadQueueStatus.kt +56 -0
  125. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadState.kt +25 -0
  126. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadStorageInfo.kt +50 -0
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadTask.kt +65 -0
  128. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedPlaylist.kt +53 -0
  129. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/DownloadedTrack.kt +56 -0
  130. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerBand.kt +47 -0
  131. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerPreset.kt +44 -0
  132. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/EqualizerState.kt +44 -0
  133. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadProgress.kt +80 -0
  134. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_DownloadedTrack.kt +80 -0
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__optional_std__variant_nitro__NullType__std__string__.kt +80 -0
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.kt +80 -0
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_EqualizerBand_.kt +80 -0
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/GainRange.kt +41 -0
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +210 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +141 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +19 -2
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlaybackSource.kt +22 -0
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerState.kt +6 -3
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PresetType.kt +21 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/StorageLocation.kt +21 -0
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackItem.kt +7 -3
  147. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/TrackPlayerState.kt +2 -2
  148. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Double.kt +59 -0
  149. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadError.kt +59 -0
  150. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadTask.kt +59 -0
  151. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedPlaylist.kt +59 -0
  152. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedTrack.kt +59 -0
  153. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +138 -8
  154. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +1046 -121
  155. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +66 -0
  156. package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +16 -0
  157. package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +30 -0
  158. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.cpp +11 -0
  159. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +386 -0
  160. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.cpp +11 -0
  161. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +223 -0
  162. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +1 -0
  163. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +46 -6
  164. package/nitrogen/generated/ios/swift/CurrentPlayingType.swift +48 -0
  165. package/nitrogen/generated/ios/swift/DownloadConfig.swift +270 -0
  166. package/nitrogen/generated/ios/swift/DownloadError.swift +69 -0
  167. package/nitrogen/generated/ios/swift/DownloadErrorReason.swift +60 -0
  168. package/nitrogen/generated/ios/swift/DownloadProgress.swift +91 -0
  169. package/nitrogen/generated/ios/swift/DownloadQueueStatus.swift +102 -0
  170. package/nitrogen/generated/ios/swift/DownloadState.swift +56 -0
  171. package/nitrogen/generated/ios/swift/DownloadStorageInfo.swift +80 -0
  172. package/nitrogen/generated/ios/swift/DownloadTask.swift +315 -0
  173. package/nitrogen/generated/ios/swift/DownloadedPlaylist.swift +103 -0
  174. package/nitrogen/generated/ios/swift/DownloadedTrack.swift +147 -0
  175. package/nitrogen/generated/ios/swift/EqualizerBand.swift +69 -0
  176. package/nitrogen/generated/ios/swift/EqualizerPreset.swift +70 -0
  177. package/nitrogen/generated/ios/swift/EqualizerState.swift +115 -0
  178. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  179. package/nitrogen/generated/ios/swift/Func_void_DownloadProgress.swift +47 -0
  180. package/nitrogen/generated/ios/swift/Func_void_DownloadStorageInfo.swift +47 -0
  181. package/nitrogen/generated/ios/swift/Func_void_DownloadedTrack.swift +47 -0
  182. package/nitrogen/generated/ios/swift/Func_void_PlayerState.swift +47 -0
  183. package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
  184. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
  185. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__variant_nitro__NullType__std__string__.swift +66 -0
  186. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  187. package/nitrogen/generated/ios/swift/Func_void_std__string_std__string_DownloadState_std__optional_DownloadError_.swift +47 -0
  188. package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +47 -0
  189. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem_.swift +47 -0
  190. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +47 -0
  191. package/nitrogen/generated/ios/swift/GainRange.swift +47 -0
  192. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +90 -0
  193. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +705 -0
  194. package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +73 -0
  195. package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +396 -0
  196. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +6 -2
  197. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +105 -8
  198. package/nitrogen/generated/ios/swift/PlaybackSource.swift +44 -0
  199. package/nitrogen/generated/ios/swift/PlayerState.swift +13 -2
  200. package/nitrogen/generated/ios/swift/PresetType.swift +40 -0
  201. package/nitrogen/generated/ios/swift/StorageLocation.swift +40 -0
  202. package/nitrogen/generated/ios/swift/TrackItem.swift +31 -1
  203. package/nitrogen/generated/ios/swift/TrackPlayerState.swift +4 -4
  204. package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +18 -0
  205. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadError.swift +18 -0
  206. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadTask.swift +18 -0
  207. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedPlaylist.swift +18 -0
  208. package/nitrogen/generated/ios/swift/Variant_NullType_DownloadedTrack.swift +18 -0
  209. package/nitrogen/generated/shared/c++/CurrentPlayingType.hpp +84 -0
  210. package/nitrogen/generated/shared/c++/DownloadConfig.hpp +108 -0
  211. package/nitrogen/generated/shared/c++/DownloadError.hpp +89 -0
  212. package/nitrogen/generated/shared/c++/DownloadErrorReason.hpp +96 -0
  213. package/nitrogen/generated/shared/c++/DownloadProgress.hpp +97 -0
  214. package/nitrogen/generated/shared/c++/DownloadQueueStatus.hpp +99 -0
  215. package/nitrogen/generated/shared/c++/DownloadState.hpp +92 -0
  216. package/nitrogen/generated/shared/c++/DownloadStorageInfo.hpp +91 -0
  217. package/nitrogen/generated/shared/c++/DownloadTask.hpp +122 -0
  218. package/nitrogen/generated/shared/c++/DownloadedPlaylist.hpp +101 -0
  219. package/nitrogen/generated/shared/c++/DownloadedTrack.hpp +107 -0
  220. package/nitrogen/generated/shared/c++/EqualizerBand.hpp +87 -0
  221. package/nitrogen/generated/shared/c++/EqualizerPreset.hpp +86 -0
  222. package/nitrogen/generated/shared/c++/EqualizerState.hpp +89 -0
  223. package/nitrogen/generated/shared/c++/GainRange.hpp +79 -0
  224. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.cpp +55 -0
  225. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +134 -0
  226. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.cpp +38 -0
  227. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +95 -0
  228. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +4 -0
  229. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +11 -5
  230. package/nitrogen/generated/shared/c++/PlaybackSource.hpp +80 -0
  231. package/nitrogen/generated/shared/c++/PlayerState.hpp +9 -2
  232. package/nitrogen/generated/shared/c++/PresetType.hpp +76 -0
  233. package/nitrogen/generated/shared/c++/StorageLocation.hpp +76 -0
  234. package/nitrogen/generated/shared/c++/TrackItem.hpp +7 -2
  235. package/nitrogen/generated/shared/c++/TrackPlayerState.hpp +5 -5
  236. package/package.json +1 -1
  237. package/src/hooks/downloadCallbackManager.ts +149 -0
  238. package/src/hooks/equalizerCallbackManager.ts +138 -0
  239. package/src/hooks/index.ts +23 -0
  240. package/src/hooks/useActualQueue.ts +116 -0
  241. package/src/hooks/useDownloadActions.ts +179 -0
  242. package/src/hooks/useDownloadProgress.ts +126 -0
  243. package/src/hooks/useDownloadStorage.ts +84 -0
  244. package/src/hooks/useDownloadedTracks.ts +138 -0
  245. package/src/hooks/useEqualizer.ts +173 -0
  246. package/src/hooks/useEqualizerPresets.ts +140 -0
  247. package/src/hooks/useNowPlaying.ts +3 -2
  248. package/src/hooks/useOnChangeTrack.ts +15 -11
  249. package/src/hooks/useOnPlaybackStateChange.ts +19 -15
  250. package/src/hooks/usePlaylist.ts +161 -0
  251. package/src/index.ts +12 -0
  252. package/src/specs/DownloadManager.nitro.ts +203 -0
  253. package/src/specs/Equalizer.nitro.ts +69 -0
  254. package/src/specs/TrackPlayer.nitro.ts +6 -2
  255. package/src/types/DownloadTypes.ts +135 -0
  256. package/src/types/EqualizerTypes.ts +72 -0
  257. package/src/types/PlayerQueue.ts +9 -0
@@ -0,0 +1,685 @@
1
+ //
2
+ // EqualizerCore.swift
3
+ // NitroPlayer
4
+ //
5
+ // Created by Ritesh Shukla on 04/02/26.
6
+ //
7
+
8
+ import AVFoundation
9
+ import Accelerate
10
+ import Foundation
11
+ import MediaToolbox
12
+ import NitroModules
13
+
14
+ class EqualizerCore {
15
+ // MARK: - Singleton
16
+
17
+ static let shared = EqualizerCore()
18
+
19
+ // MARK: - Properties
20
+
21
+ // Internal so TapContext can access it
22
+ private(set) var isEqualizerEnabled: Bool = false
23
+ private var currentPresetName: String?
24
+
25
+ // Standard 5-band frequencies: 60Hz, 230Hz, 910Hz, 3.6kHz, 14kHz
26
+ let frequencies: [Float] = [60, 230, 910, 3600, 14000]
27
+ private let frequencyLabels = ["60 Hz", "230 Hz", "910 Hz", "3.6 kHz", "14 kHz"]
28
+
29
+ // Current gains storage - internal so TapContext can access
30
+ private(set) var currentGains: [Double] = [0, 0, 0, 0, 0]
31
+
32
+ // UserDefaults keys
33
+ private let enabledKey = "eq_enabled"
34
+ private let bandGainsKey = "eq_band_gains"
35
+ private let currentPresetKey = "eq_current_preset"
36
+ private let customPresetsKey = "eq_custom_presets"
37
+
38
+ // MARK: - Weak Callback Wrapper
39
+
40
+ private class WeakCallbackBox<T> {
41
+ private(set) weak var owner: AnyObject?
42
+ let callback: T
43
+
44
+ init(owner: AnyObject, callback: T) {
45
+ self.owner = owner
46
+ self.callback = callback
47
+ }
48
+
49
+ var isAlive: Bool { owner != nil }
50
+ }
51
+
52
+ // Event callbacks
53
+ private var onEnabledChangeListeners: [WeakCallbackBox<(Bool) -> Void>] = []
54
+ private var onBandChangeListeners: [WeakCallbackBox<([EqualizerBand]) -> Void>] = []
55
+ private var onPresetChangeListeners: [WeakCallbackBox<(Variant_NullType_String?) -> Void>] = []
56
+
57
+ private let listenersQueue = DispatchQueue(
58
+ label: "com.equalizer.listeners", attributes: .concurrent)
59
+
60
+ // MARK: - Built-in Presets
61
+
62
+ private static let builtInPresets: [String: [Double]] = [
63
+ "Flat": [0, 0, 0, 0, 0],
64
+ "Bass Boost": [6, 4, 0, 0, 0],
65
+ "Bass Reducer": [-6, -4, 0, 0, 0],
66
+ "Treble Boost": [0, 0, 0, 4, 6],
67
+ "Treble Reducer": [0, 0, 0, -4, -6],
68
+ "Vocal Boost": [-2, 0, 4, 2, 0],
69
+ "Rock": [5, 3, -1, 3, 5],
70
+ "Pop": [-1, 2, 4, 2, -1],
71
+ "Jazz": [3, 1, -2, 2, 4],
72
+ "Classical": [4, 2, -1, 2, 3],
73
+ "Hip Hop": [6, 4, 0, 1, 3],
74
+ "Electronic": [5, 3, 0, 2, 5],
75
+ "Acoustic": [4, 2, 1, 3, 3],
76
+ "R&B": [3, 6, 2, -1, 2],
77
+ "Loudness": [6, 3, -1, 3, 6],
78
+ ]
79
+
80
+ // MARK: - Initialization
81
+
82
+ private init() {
83
+ restoreSettings()
84
+ print("✅ EqualizerCore: Initialized with MTAudioProcessingTap support")
85
+ }
86
+
87
+ // MARK: - Audio Mix Creation for AVPlayerItem
88
+
89
+ /// Applies an AVAudioMix with equalizer processing for the given AVPlayerItem asynchronously
90
+ func applyAudioMix(to playerItem: AVPlayerItem) {
91
+ let asset = playerItem.asset
92
+
93
+ // Load "tracks" key asynchronously to avoid blocking
94
+ asset.loadValuesAsynchronously(forKeys: ["tracks"]) { [weak self] in
95
+ guard let self = self else { return }
96
+
97
+ var error: NSError?
98
+ let status = asset.statusOfValue(forKey: "tracks", error: &error)
99
+
100
+ if status == .failed {
101
+ print(
102
+ "⚠️ EqualizerCore: Failed to load tracks key: \(error?.localizedDescription ?? "unknown")")
103
+ return
104
+ }
105
+
106
+ // Proceed only if loaded successfully
107
+ guard status == .loaded else {
108
+ print("⚠️ EqualizerCore: Tracks not loaded, status: \(status.rawValue)")
109
+ return
110
+ }
111
+
112
+ guard let audioTrack = asset.tracks(withMediaType: .audio).first else {
113
+ print("⚠️ EqualizerCore: No audio track found in asset")
114
+ return
115
+ }
116
+
117
+ // Create audio mix input parameters
118
+ let inputParams = AVMutableAudioMixInputParameters(track: audioTrack)
119
+
120
+ // Create the audio processing tap
121
+ var callbacks = MTAudioProcessingTapCallbacks(
122
+ version: kMTAudioProcessingTapCallbacksVersion_0,
123
+ clientInfo: UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()),
124
+ init: tapInitCallback,
125
+ finalize: tapFinalizeCallback,
126
+ prepare: tapPrepareCallback,
127
+ unprepare: tapUnprepareCallback,
128
+ process: tapProcessCallback
129
+ )
130
+
131
+ var tap: MTAudioProcessingTap?
132
+ let createStatus = MTAudioProcessingTapCreate(
133
+ kCFAllocatorDefault,
134
+ &callbacks,
135
+ kMTAudioProcessingTapCreationFlag_PreEffects,
136
+ &tap
137
+ )
138
+
139
+ guard createStatus == noErr, let audioTap = tap else {
140
+ print("❌ EqualizerCore: Failed to create audio processing tap, status: \(createStatus)")
141
+ return
142
+ }
143
+
144
+ inputParams.audioTapProcessor = audioTap
145
+
146
+ // Create audio mix
147
+ let audioMix = AVMutableAudioMix()
148
+ audioMix.inputParameters = [inputParams]
149
+
150
+ // 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)
151
+ DispatchQueue.main.async {
152
+ playerItem.audioMix = audioMix
153
+ print("✅ EqualizerCore: Applied audio mix with EQ tap to player item (async)")
154
+ }
155
+ }
156
+ }
157
+
158
+ // MARK: - Public Methods
159
+
160
+ func setEnabled(_ enabled: Bool) -> Bool {
161
+ isEqualizerEnabled = enabled
162
+
163
+ notifyEnabledChange(enabled)
164
+ saveEnabled(enabled)
165
+
166
+ print("🎚️ EqualizerCore: Equalizer \(enabled ? "enabled" : "disabled")")
167
+ return true
168
+ }
169
+
170
+ func isEnabled() -> Bool {
171
+ return isEqualizerEnabled
172
+ }
173
+
174
+ func getBands() -> [EqualizerBand] {
175
+ return (0..<5).map { i in
176
+ EqualizerBand(
177
+ index: Double(i),
178
+ centerFrequency: Double(frequencies[i]),
179
+ gainDb: currentGains[i],
180
+ frequencyLabel: frequencyLabels[i]
181
+ )
182
+ }
183
+ }
184
+
185
+ func setBandGain(bandIndex: Int, gainDb: Double) -> Bool {
186
+ guard bandIndex >= 0 && bandIndex < 5 else { return false }
187
+
188
+ let clampedGain = max(-12.0, min(12.0, gainDb))
189
+ currentGains[bandIndex] = clampedGain
190
+
191
+ currentPresetName = nil
192
+ notifyBandChange(getBands())
193
+ notifyPresetChange(nil)
194
+ saveBandGains(currentGains)
195
+ saveCurrentPreset(nil)
196
+
197
+ print("🎚️ EqualizerCore: Band \(bandIndex) gain set to \(clampedGain) dB")
198
+ return true
199
+ }
200
+
201
+ func setAllBandGains(_ gains: [Double]) -> Bool {
202
+ guard gains.count == 5 else { return false }
203
+
204
+ for i in 0..<5 {
205
+ currentGains[i] = max(-12.0, min(12.0, gains[i]))
206
+ }
207
+
208
+ notifyBandChange(getBands())
209
+ saveBandGains(currentGains)
210
+
211
+ print("🎚️ EqualizerCore: All band gains updated")
212
+ return true
213
+ }
214
+
215
+ func getBandRange() -> GainRange {
216
+ return GainRange(min: -12.0, max: 12.0)
217
+ }
218
+
219
+ func getPresets() -> [EqualizerPreset] {
220
+ return getBuiltInPresets() + getCustomPresets()
221
+ }
222
+
223
+ func getBuiltInPresets() -> [EqualizerPreset] {
224
+ return Self.builtInPresets.map { name, gains in
225
+ EqualizerPreset(name: name, gains: gains, type: .builtIn)
226
+ }
227
+ }
228
+
229
+ func getCustomPresets() -> [EqualizerPreset] {
230
+ guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
231
+ let presets = try? JSONDecoder().decode([String: [Double]].self, from: data)
232
+ else {
233
+ return []
234
+ }
235
+
236
+ return presets.map { name, gains in
237
+ EqualizerPreset(name: name, gains: gains, type: .custom)
238
+ }
239
+ }
240
+
241
+ func applyPreset(_ presetName: String) -> Bool {
242
+ // Try built-in preset first
243
+ if let gains = Self.builtInPresets[presetName] {
244
+ if setAllBandGains(gains) {
245
+ currentPresetName = presetName
246
+ notifyPresetChange(presetName)
247
+ saveCurrentPreset(presetName)
248
+ return true
249
+ }
250
+ }
251
+
252
+ // Try custom preset
253
+ if let gains = getCustomPresetGains(presetName) {
254
+ if setAllBandGains(gains) {
255
+ currentPresetName = presetName
256
+ notifyPresetChange(presetName)
257
+ saveCurrentPreset(presetName)
258
+ return true
259
+ }
260
+ }
261
+
262
+ return false
263
+ }
264
+
265
+ private func getCustomPresetGains(_ name: String) -> [Double]? {
266
+ guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
267
+ let presets = try? JSONDecoder().decode([String: [Double]].self, from: data)
268
+ else {
269
+ return nil
270
+ }
271
+ return presets[name]
272
+ }
273
+
274
+ func getCurrentPresetName() -> String? {
275
+ return currentPresetName
276
+ }
277
+
278
+ func saveCustomPreset(_ name: String) -> Bool {
279
+ var presets: [String: [Double]] = [:]
280
+
281
+ if let data = UserDefaults.standard.data(forKey: customPresetsKey),
282
+ let existing = try? JSONDecoder().decode([String: [Double]].self, from: data)
283
+ {
284
+ presets = existing
285
+ }
286
+
287
+ presets[name] = currentGains
288
+
289
+ if let data = try? JSONEncoder().encode(presets) {
290
+ UserDefaults.standard.set(data, forKey: customPresetsKey)
291
+ currentPresetName = name
292
+ notifyPresetChange(name)
293
+ saveCurrentPreset(name)
294
+ return true
295
+ }
296
+
297
+ return false
298
+ }
299
+
300
+ func deleteCustomPreset(_ name: String) -> Bool {
301
+ guard
302
+ var presets: [String: [Double]] = {
303
+ guard let data = UserDefaults.standard.data(forKey: customPresetsKey),
304
+ let existing = try? JSONDecoder().decode([String: [Double]].self, from: data)
305
+ else {
306
+ return nil
307
+ }
308
+ return existing
309
+ }()
310
+ else {
311
+ return false
312
+ }
313
+
314
+ guard presets[name] != nil else { return false }
315
+
316
+ presets.removeValue(forKey: name)
317
+
318
+ if let data = try? JSONEncoder().encode(presets) {
319
+ UserDefaults.standard.set(data, forKey: customPresetsKey)
320
+
321
+ if currentPresetName == name {
322
+ currentPresetName = nil
323
+ notifyPresetChange(nil)
324
+ saveCurrentPreset(nil)
325
+ }
326
+ return true
327
+ }
328
+
329
+ return false
330
+ }
331
+
332
+ func getState() -> EqualizerState {
333
+ let presetVariant: Variant_NullType_String?
334
+ if let name = currentPresetName {
335
+ presetVariant = .second(name)
336
+ } else {
337
+ presetVariant = .first(NullType.null)
338
+ }
339
+
340
+ return EqualizerState(
341
+ enabled: isEqualizerEnabled,
342
+ bands: getBands(),
343
+ currentPreset: presetVariant
344
+ )
345
+ }
346
+
347
+ func reset() {
348
+ _ = setAllBandGains([0, 0, 0, 0, 0])
349
+ currentPresetName = "Flat"
350
+ notifyPresetChange("Flat")
351
+ saveCurrentPreset("Flat")
352
+ }
353
+
354
+ // MARK: - Persistence
355
+
356
+ private func saveEnabled(_ enabled: Bool) {
357
+ UserDefaults.standard.set(enabled, forKey: enabledKey)
358
+ }
359
+
360
+ private func saveBandGains(_ gains: [Double]) {
361
+ if let data = try? JSONEncoder().encode(gains) {
362
+ UserDefaults.standard.set(data, forKey: bandGainsKey)
363
+ }
364
+ }
365
+
366
+ private func saveCurrentPreset(_ name: String?) {
367
+ UserDefaults.standard.set(name, forKey: currentPresetKey)
368
+ }
369
+
370
+ private func restoreSettings() {
371
+ let enabled = UserDefaults.standard.bool(forKey: enabledKey)
372
+
373
+ if let data = UserDefaults.standard.data(forKey: bandGainsKey),
374
+ let gains = try? JSONDecoder().decode([Double].self, from: data),
375
+ gains.count == 5
376
+ {
377
+ currentGains = gains
378
+ }
379
+
380
+ currentPresetName = UserDefaults.standard.string(forKey: currentPresetKey)
381
+ isEqualizerEnabled = enabled
382
+
383
+ print("✅ EqualizerCore: Restored settings - enabled: \(enabled), gains: \(currentGains)")
384
+ }
385
+
386
+ // MARK: - Callback Management
387
+
388
+ func addOnEnabledChangeListener(owner: AnyObject, _ callback: @escaping (Bool) -> Void) {
389
+ listenersQueue.async(flags: .barrier) { [weak self] in
390
+ let box = WeakCallbackBox(owner: owner, callback: callback)
391
+ self?.onEnabledChangeListeners.append(box)
392
+ }
393
+ }
394
+
395
+ func addOnBandChangeListener(owner: AnyObject, _ callback: @escaping ([EqualizerBand]) -> Void) {
396
+ listenersQueue.async(flags: .barrier) { [weak self] in
397
+ let box = WeakCallbackBox(owner: owner, callback: callback)
398
+ self?.onBandChangeListeners.append(box)
399
+ }
400
+ }
401
+
402
+ func addOnPresetChangeListener(
403
+ owner: AnyObject, _ callback: @escaping (Variant_NullType_String?) -> Void
404
+ ) {
405
+ listenersQueue.async(flags: .barrier) { [weak self] in
406
+ let box = WeakCallbackBox(owner: owner, callback: callback)
407
+ self?.onPresetChangeListeners.append(box)
408
+ }
409
+ }
410
+
411
+ private func notifyEnabledChange(_ enabled: Bool) {
412
+ listenersQueue.async(flags: .barrier) { [weak self] in
413
+ guard let self = self else { return }
414
+ self.onEnabledChangeListeners.removeAll { !$0.isAlive }
415
+
416
+ let callbacks = self.onEnabledChangeListeners.compactMap {
417
+ $0.isAlive ? $0.callback : nil
418
+ }
419
+
420
+ if !callbacks.isEmpty {
421
+ DispatchQueue.main.async {
422
+ for callback in callbacks {
423
+ callback(enabled)
424
+ }
425
+ }
426
+ }
427
+ }
428
+ }
429
+
430
+ private func notifyBandChange(_ bands: [EqualizerBand]) {
431
+ listenersQueue.async(flags: .barrier) { [weak self] in
432
+ guard let self = self else { return }
433
+ self.onBandChangeListeners.removeAll { !$0.isAlive }
434
+
435
+ let callbacks = self.onBandChangeListeners.compactMap {
436
+ $0.isAlive ? $0.callback : nil
437
+ }
438
+
439
+ if !callbacks.isEmpty {
440
+ DispatchQueue.main.async {
441
+ for callback in callbacks {
442
+ callback(bands)
443
+ }
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+ private func notifyPresetChange(_ presetName: String?) {
450
+ listenersQueue.async(flags: .barrier) { [weak self] in
451
+ guard let self = self else { return }
452
+ self.onPresetChangeListeners.removeAll { !$0.isAlive }
453
+
454
+ let callbacks = self.onPresetChangeListeners.compactMap {
455
+ $0.isAlive ? $0.callback : nil
456
+ }
457
+
458
+ if !callbacks.isEmpty {
459
+ let variant: Variant_NullType_String? = presetName.map { .second($0) }
460
+
461
+ DispatchQueue.main.async {
462
+ for callback in callbacks {
463
+ callback(variant)
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ // MARK: - MTAudioProcessingTap Context
472
+
473
+ /// Context passed to the audio processing tap
474
+ private class TapContext {
475
+ weak var eqCore: EqualizerCore?
476
+ var sampleRate: Float = 44100.0
477
+ var channelCount: Int = 2
478
+
479
+ // Biquad filter states for 5 bands
480
+ // Each band needs 4 delay elements per channel (x[n-1], x[n-2], y[n-1], y[n-2])
481
+ var filterStates: [[Float]] = []
482
+
483
+ // Biquad coefficients for 5 bands
484
+ // Each band: [b0, b1, b2, a1, a2] (normalized, a0 = 1)
485
+ var filterCoeffs: [[Double]] = []
486
+
487
+ init(eqCore: EqualizerCore) {
488
+ self.eqCore = eqCore
489
+ // Initialize 5 bands with flat coefficients
490
+ for _ in 0..<5 {
491
+ filterCoeffs.append([1.0, 0.0, 0.0, 0.0, 0.0]) // Flat/bypass
492
+ }
493
+ }
494
+
495
+ func updateCoefficients() {
496
+ guard let eqCore = eqCore else { return }
497
+ let frequencies: [Float] = [60, 230, 910, 3600, 14000]
498
+ let gains = eqCore.currentGains
499
+
500
+ for i in 0..<5 {
501
+ filterCoeffs[i] = calculatePeakingEQCoefficients(
502
+ frequency: Double(frequencies[i]),
503
+ gain: gains[i],
504
+ q: 1.41, // Standard Q for graphic EQ
505
+ sampleRate: Double(sampleRate)
506
+ )
507
+ }
508
+ }
509
+
510
+ /// Calculate biquad coefficients for a peaking EQ filter
511
+ private func calculatePeakingEQCoefficients(
512
+ frequency: Double, gain: Double, q: Double, sampleRate: Double
513
+ ) -> [Double] {
514
+ // If gain is essentially 0, return bypass coefficients
515
+ let absGain: Double = Swift.abs(gain)
516
+ if absGain < 0.01 {
517
+ return [1.0, 0.0, 0.0, 0.0, 0.0]
518
+ }
519
+
520
+ let A = pow(10.0, gain / 40.0) // sqrt(10^(gain/20))
521
+ let omega = 2.0 * Double.pi * frequency / sampleRate
522
+ let sinOmega = sin(omega)
523
+ let cosOmega = cos(omega)
524
+ let alpha = sinOmega / (2.0 * q)
525
+
526
+ let b0 = 1.0 + alpha * A
527
+ let b1 = -2.0 * cosOmega
528
+ let b2 = 1.0 - alpha * A
529
+ let a0 = 1.0 + alpha / A
530
+ let a1 = -2.0 * cosOmega
531
+ let a2 = 1.0 - alpha / A
532
+
533
+ // Normalize by a0
534
+ return [b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0]
535
+ }
536
+
537
+ func resetFilterStates() {
538
+ filterStates = []
539
+ for _ in 0..<5 {
540
+ // 4 delay elements per channel (2 for input history, 2 for output history)
541
+ filterStates.append(Array(repeating: Float(0.0), count: channelCount * 4))
542
+ }
543
+ }
544
+ }
545
+
546
+ // MARK: - MTAudioProcessingTap Callbacks
547
+
548
+ private func tapInitCallback(
549
+ tap: MTAudioProcessingTap,
550
+ clientInfo: UnsafeMutableRawPointer?,
551
+ tapStorageOut: UnsafeMutablePointer<UnsafeMutableRawPointer?>
552
+ ) {
553
+ guard let clientInfo = clientInfo else { return }
554
+
555
+ let eqCore = Unmanaged<EqualizerCore>.fromOpaque(clientInfo).takeUnretainedValue()
556
+ let context = TapContext(eqCore: eqCore)
557
+ tapStorageOut.pointee = Unmanaged.passRetained(context).toOpaque()
558
+
559
+ print("🎛️ EqualizerCore: Tap initialized")
560
+ }
561
+
562
+ private func tapFinalizeCallback(tap: MTAudioProcessingTap) {
563
+ let storage = MTAudioProcessingTapGetStorage(tap)
564
+ Unmanaged<TapContext>.fromOpaque(storage).release()
565
+ print("🎛️ EqualizerCore: Tap finalized")
566
+ }
567
+
568
+ private func tapPrepareCallback(
569
+ tap: MTAudioProcessingTap,
570
+ maxFrames: CMItemCount,
571
+ processingFormat: UnsafePointer<AudioStreamBasicDescription>
572
+ ) {
573
+ let storage = MTAudioProcessingTapGetStorage(tap)
574
+ let context = Unmanaged<TapContext>.fromOpaque(storage).takeUnretainedValue()
575
+
576
+ context.sampleRate = Float(processingFormat.pointee.mSampleRate)
577
+ context.channelCount = Int(processingFormat.pointee.mChannelsPerFrame)
578
+ context.updateCoefficients()
579
+ context.resetFilterStates()
580
+
581
+ print(
582
+ "🎛️ EqualizerCore: Tap prepared - sampleRate: \(context.sampleRate), channels: \(context.channelCount)"
583
+ )
584
+ }
585
+
586
+ private func tapUnprepareCallback(tap: MTAudioProcessingTap) {
587
+ print("🎛️ EqualizerCore: Tap unprepared")
588
+ }
589
+
590
+ private func tapProcessCallback(
591
+ tap: MTAudioProcessingTap,
592
+ numberFrames: CMItemCount,
593
+ flags: MTAudioProcessingTapFlags,
594
+ bufferListInOut: UnsafeMutablePointer<AudioBufferList>,
595
+ numberFramesOut: UnsafeMutablePointer<CMItemCount>,
596
+ flagsOut: UnsafeMutablePointer<MTAudioProcessingTapFlags>
597
+ ) {
598
+ let storage = MTAudioProcessingTapGetStorage(tap)
599
+ let context = Unmanaged<TapContext>.fromOpaque(storage).takeUnretainedValue()
600
+
601
+ // Get source audio
602
+ var sourceFlags = MTAudioProcessingTapFlags()
603
+ let status = MTAudioProcessingTapGetSourceAudio(
604
+ tap,
605
+ numberFrames,
606
+ bufferListInOut,
607
+ &sourceFlags,
608
+ nil,
609
+ numberFramesOut
610
+ )
611
+
612
+ guard status == noErr else {
613
+ print("❌ EqualizerCore: Failed to get source audio: \(status)")
614
+ return
615
+ }
616
+
617
+ // Check if equalizer is enabled
618
+ guard let eqCore = context.eqCore, eqCore.isEqualizerEnabled else {
619
+ // Bypass - audio is already in bufferListInOut
620
+ return
621
+ }
622
+
623
+ // Update coefficients (in case gains changed)
624
+ context.updateCoefficients()
625
+
626
+ // Process each buffer (channel)
627
+ let bufferList = UnsafeMutableAudioBufferListPointer(bufferListInOut)
628
+
629
+ for bufferIndex in 0..<bufferList.count {
630
+ guard let data = bufferList[bufferIndex].mData else { continue }
631
+
632
+ let frameCount = Int(numberFramesOut.pointee)
633
+ let samples = data.assumingMemoryBound(to: Float.self)
634
+
635
+ // Apply all 5 EQ bands in series
636
+ for bandIndex in 0..<5 {
637
+ let coeffs: [Double] = context.filterCoeffs[bandIndex]
638
+
639
+ // Skip if essentially flat
640
+ let c0: Double = coeffs[0]
641
+ let c1: Double = coeffs[1]
642
+ let c2: Double = coeffs[2]
643
+ if Swift.abs(c0 - 1.0) < 0.001 && Swift.abs(c1) < 0.001 && Swift.abs(c2) < 0.001 {
644
+ continue
645
+ }
646
+
647
+ // Ensure we have enough filter states for this channel
648
+ guard bufferIndex * 4 + 3 < context.filterStates[bandIndex].count else {
649
+ continue
650
+ }
651
+
652
+ // Get filter state for this band and channel
653
+ let stateOffset = bufferIndex * 4
654
+ var x1 = context.filterStates[bandIndex][stateOffset]
655
+ var x2 = context.filterStates[bandIndex][stateOffset + 1]
656
+ var y1 = context.filterStates[bandIndex][stateOffset + 2]
657
+ var y2 = context.filterStates[bandIndex][stateOffset + 3]
658
+
659
+ let b0 = Float(coeffs[0])
660
+ let b1 = Float(coeffs[1])
661
+ let b2 = Float(coeffs[2])
662
+ let a1 = Float(coeffs[3])
663
+ let a2 = Float(coeffs[4])
664
+
665
+ // Process samples using Direct Form II transposed
666
+ for i in 0..<frameCount {
667
+ let x = samples[i]
668
+ let y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2
669
+
670
+ x2 = x1
671
+ x1 = x
672
+ y2 = y1
673
+ y1 = y
674
+
675
+ samples[i] = y
676
+ }
677
+
678
+ // Save filter state
679
+ context.filterStates[bandIndex][stateOffset] = x1
680
+ context.filterStates[bandIndex][stateOffset + 1] = x2
681
+ context.filterStates[bandIndex][stateOffset + 2] = y1
682
+ context.filterStates[bandIndex][stateOffset + 3] = y2
683
+ }
684
+ }
685
+ }