react-native-nitro-player 0.7.0 → 0.7.1-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 (110) hide show
  1. package/README.md +47 -46
  2. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +9 -13
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +45 -90
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridDownloadManager.kt +48 -182
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridEqualizer.kt +21 -77
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +55 -104
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +113 -123
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ExoPlayerCore.kt +82 -0
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ListenerRegistry.kt +48 -0
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerAndroidAuto.kt +62 -0
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +153 -1887
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerListener.kt +122 -0
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerNotify.kt +44 -0
  14. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerPlayback.kt +162 -0
  15. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueue.kt +179 -0
  16. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueueBuild.kt +170 -0
  17. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerSetup.kt +28 -0
  18. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerTempQueue.kt +121 -0
  19. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerUrlLoader.kt +98 -0
  20. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +27 -18
  21. package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +150 -135
  22. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +13 -30
  23. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +102 -162
  24. package/ios/HybridDownloadManager.swift +32 -26
  25. package/ios/HybridEqualizer.swift +48 -35
  26. package/ios/HybridTrackPlayer.swift +127 -102
  27. package/ios/core/ListenerRegistry.swift +60 -0
  28. package/ios/core/TrackPlayerCore.swift +130 -2356
  29. package/ios/core/TrackPlayerListener.swift +395 -0
  30. package/ios/core/TrackPlayerNotify.swift +52 -0
  31. package/ios/core/TrackPlayerPlayback.swift +274 -0
  32. package/ios/core/TrackPlayerQueue.swift +221 -0
  33. package/ios/core/TrackPlayerQueueBuild.swift +493 -0
  34. package/ios/core/TrackPlayerTempQueue.swift +167 -0
  35. package/ios/core/TrackPlayerUrlLoader.swift +169 -0
  36. package/ios/equalizer/EqualizerCore.swift +63 -123
  37. package/ios/media/MediaSessionManager.swift +32 -49
  38. package/ios/playlist/PlaylistManager.swift +2 -9
  39. package/ios/queue/HybridPlayerQueue.swift +69 -66
  40. package/lib/hooks/useDownloadedTracks.js +16 -13
  41. package/lib/hooks/useEqualizer.d.ts +4 -4
  42. package/lib/hooks/useEqualizer.js +22 -17
  43. package/lib/hooks/useEqualizerPresets.d.ts +3 -3
  44. package/lib/hooks/useEqualizerPresets.js +12 -18
  45. package/lib/specs/AndroidAutoMediaLibrary.nitro.d.ts +2 -2
  46. package/lib/specs/AudioDevices.nitro.d.ts +2 -2
  47. package/lib/specs/DownloadManager.nitro.d.ts +10 -10
  48. package/lib/specs/Equalizer.nitro.d.ts +10 -10
  49. package/lib/specs/TrackPlayer.nitro.d.ts +38 -16
  50. package/lib/types/EqualizerTypes.d.ts +3 -3
  51. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +2 -0
  52. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__std__vector_TrackItem_.hpp +122 -0
  53. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +31 -6
  54. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +2 -2
  55. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +16 -3
  56. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +1 -1
  57. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +154 -44
  58. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +10 -10
  59. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +130 -34
  60. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +9 -9
  61. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +115 -24
  62. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +8 -8
  63. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +243 -24
  64. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +16 -8
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_TrackItem__std__vector_TrackItem_.kt +80 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +3 -2
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +2 -1
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +10 -10
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +10 -9
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +9 -8
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +45 -8
  72. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +74 -18
  73. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +380 -151
  74. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +10 -10
  75. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +12 -9
  76. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +23 -8
  77. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +82 -8
  78. package/nitrogen/generated/ios/swift/Func_void_EqualizerState.swift +46 -0
  79. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__DownloadedPlaylist_.swift +58 -0
  80. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__DownloadedTrack_.swift +58 -0
  81. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__std__string_.swift +58 -0
  82. package/nitrogen/generated/ios/swift/Func_void_std__vector_DownloadedPlaylist_.swift +46 -0
  83. package/nitrogen/generated/ios/swift/Func_void_std__vector_DownloadedTrack_.swift +46 -0
  84. package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +5 -5
  85. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__std__vector_TrackItem_.swift +46 -0
  86. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +10 -10
  87. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +141 -71
  88. package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +9 -9
  89. package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +105 -41
  90. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +8 -8
  91. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +95 -32
  92. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +16 -8
  93. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +267 -32
  94. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +3 -2
  95. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +2 -1
  96. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +10 -10
  97. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +10 -9
  98. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +9 -8
  99. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +8 -0
  100. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +16 -8
  101. package/package.json +5 -5
  102. package/src/hooks/useDownloadedTracks.ts +17 -13
  103. package/src/hooks/useEqualizer.ts +26 -21
  104. package/src/hooks/useEqualizerPresets.ts +15 -21
  105. package/src/specs/AndroidAutoMediaLibrary.nitro.ts +2 -2
  106. package/src/specs/AudioDevices.nitro.ts +2 -2
  107. package/src/specs/DownloadManager.nitro.ts +10 -10
  108. package/src/specs/Equalizer.nitro.ts +10 -10
  109. package/src/specs/TrackPlayer.nitro.ts +52 -16
  110. package/src/types/EqualizerTypes.ts +17 -13
@@ -2,8 +2,10 @@ package com.margelo.nitro.nitroplayer.equalizer
2
2
 
3
3
  import android.content.Context
4
4
  import android.content.SharedPreferences
5
+ import android.media.audiofx.DynamicsProcessing
5
6
  import android.media.audiofx.Equalizer
6
- import android.util.Log
7
+ import android.os.Build
8
+ import androidx.annotation.RequiresApi
7
9
  import com.margelo.nitro.core.NullType
8
10
  import com.margelo.nitro.nitroplayer.EqualizerBand
9
11
  import com.margelo.nitro.nitroplayer.EqualizerPreset
@@ -14,42 +16,33 @@ import com.margelo.nitro.nitroplayer.Variant_NullType_String
14
16
  import com.margelo.nitro.nitroplayer.core.NitroPlayerLogger
15
17
  import org.json.JSONArray
16
18
  import org.json.JSONObject
17
- import java.lang.ref.WeakReference
18
- import java.util.Collections
19
+ import com.margelo.nitro.nitroplayer.core.ListenerRegistry
19
20
 
20
21
  class EqualizerCore private constructor(
21
22
  private val context: Context,
22
23
  ) {
23
24
  private var equalizer: Equalizer? = null
25
+ private var dynamicsProcessing: DynamicsProcessing? = null
26
+ private var usingDynamicsProcessing: Boolean = false
24
27
  private var audioSessionId: Int = 0
25
28
  private var isUsingFallbackSession: Boolean = false // Track if using fallback session 0
26
29
  private var isEqualizerEnabled: Boolean = false
27
30
  private var currentPresetName: String? = null
28
31
 
29
- // Standard 5-band frequencies: 60Hz, 230Hz, 910Hz, 3.6kHz, 14kHz
30
- private val targetFrequencies = intArrayOf(60000, 230000, 910000, 3600000, 14000000) // milliHz
31
- private val frequencyLabels = arrayOf("60 Hz", "230 Hz", "910 Hz", "3.6 kHz", "14 kHz")
32
- private val frequencies = intArrayOf(60, 230, 910, 3600, 14000)
33
- private var bandMapping = IntArray(5) // Maps our 5 bands to actual EQ bands
32
+ // Standard 10-band frequencies: 31Hz, 63Hz, 125Hz, 250Hz, 500Hz, 1kHz, 2kHz, 4kHz, 8kHz, 16kHz
33
+ private val targetFrequencies = intArrayOf(31000, 63000, 125000, 250000, 500000, 1000000, 2000000, 4000000, 8000000, 16000000) // milliHz
34
+ private val frequencyLabels = arrayOf("31 Hz", "63 Hz", "125 Hz", "250 Hz", "500 Hz", "1 kHz", "2 kHz", "4 kHz", "8 kHz", "16 kHz")
35
+ private val frequencies = intArrayOf(31, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000)
36
+ private var bandMapping = IntArray(10) // Maps our 10 bands to actual EQ bands (fallback only)
37
+ private val currentGainsArray = DoubleArray(10) { 0.0 } // Local gain cache (both DP and fallback paths)
34
38
 
35
39
  private val prefs: SharedPreferences =
36
40
  context.getSharedPreferences("equalizer_settings", Context.MODE_PRIVATE)
37
41
 
38
- // Weak callback wrapper for auto-cleanup
39
- private data class WeakCallbackBox<T>(
40
- private val ownerRef: WeakReference<Any>,
41
- val callback: T,
42
- ) {
43
- val isAlive: Boolean get() = ownerRef.get() != null
44
- }
45
-
46
42
  // Event listeners
47
- private val onEnabledChangeListeners =
48
- Collections.synchronizedList(mutableListOf<WeakCallbackBox<(Boolean) -> Unit>>())
49
- private val onBandChangeListeners =
50
- Collections.synchronizedList(mutableListOf<WeakCallbackBox<(Array<EqualizerBand>) -> Unit>>())
51
- private val onPresetChangeListeners =
52
- Collections.synchronizedList(mutableListOf<WeakCallbackBox<(Variant_NullType_String?) -> Unit>>())
43
+ private val onEnabledChangeListeners = ListenerRegistry<(Boolean) -> Unit>()
44
+ private val onBandChangeListeners = ListenerRegistry<(Array<EqualizerBand>) -> Unit>()
45
+ private val onPresetChangeListeners = ListenerRegistry<(Variant_NullType_String?) -> Unit>()
53
46
 
54
47
  companion object {
55
48
  private const val TAG = "EqualizerCore"
@@ -62,24 +55,28 @@ class EqualizerCore private constructor(
62
55
  INSTANCE ?: EqualizerCore(context).also { INSTANCE = it }
63
56
  }
64
57
 
65
- // Built-in presets: name -> [60Hz, 230Hz, 910Hz, 3.6kHz, 14kHz] in dB
58
+ // Built-in presets: name -> [31Hz, 63Hz, 125Hz, 250Hz, 500Hz, 1kHz, 2kHz, 4kHz, 8kHz, 16kHz] in dB
66
59
  private val BUILT_IN_PRESETS =
67
60
  mapOf(
68
- "Flat" to doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0),
69
- "Bass Boost" to doubleArrayOf(6.0, 4.0, 0.0, 0.0, 0.0),
70
- "Bass Reducer" to doubleArrayOf(-6.0, -4.0, 0.0, 0.0, 0.0),
71
- "Treble Boost" to doubleArrayOf(0.0, 0.0, 0.0, 4.0, 6.0),
72
- "Treble Reducer" to doubleArrayOf(0.0, 0.0, 0.0, -4.0, -6.0),
73
- "Vocal Boost" to doubleArrayOf(-2.0, 0.0, 4.0, 2.0, 0.0),
74
- "Rock" to doubleArrayOf(5.0, 3.0, -1.0, 3.0, 5.0),
75
- "Pop" to doubleArrayOf(-1.0, 2.0, 4.0, 2.0, -1.0),
76
- "Jazz" to doubleArrayOf(3.0, 1.0, -2.0, 2.0, 4.0),
77
- "Classical" to doubleArrayOf(4.0, 2.0, -1.0, 2.0, 3.0),
78
- "Hip Hop" to doubleArrayOf(6.0, 4.0, 0.0, 1.0, 3.0),
79
- "Electronic" to doubleArrayOf(5.0, 3.0, 0.0, 2.0, 5.0),
80
- "Acoustic" to doubleArrayOf(4.0, 2.0, 1.0, 3.0, 3.0),
81
- "R&B" to doubleArrayOf(3.0, 6.0, 2.0, -1.0, 2.0),
82
- "Loudness" to doubleArrayOf(6.0, 3.0, -1.0, 3.0, 6.0),
61
+ "Flat" to doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
62
+ "Rock" to doubleArrayOf(4.8, 2.88, -3.36, -4.8, -1.92, 2.4, 5.28, 6.72, 6.72, 6.72),
63
+ "Pop" to doubleArrayOf(0.96, 2.88, 4.32, 4.8, 3.36, 0.0, -1.44, -1.44, 0.96, 0.96),
64
+ "Classical" to doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.32, -4.32, -4.32, -5.76),
65
+ "Dance" to doubleArrayOf(5.76, 4.32, 1.44, 0.0, 0.0, -3.36, -4.32, -4.32, 0.0, 0.0),
66
+ "Techno" to doubleArrayOf(4.8, 3.36, 0.0, -3.36, -2.88, 0.0, 4.8, 5.76, 5.76, 5.28),
67
+ "Club" to doubleArrayOf(0.0, 0.0, 4.8, 3.36, 3.36, 3.36, 1.92, 0.0, 0.0, 0.0),
68
+ "Live" to doubleArrayOf(-2.88, 0.0, 2.4, 3.36, 3.36, 3.36, 2.4, 1.44, 1.44, 1.44),
69
+ "Reggae" to doubleArrayOf(0.0, 0.0, 0.0, -3.36, 0.0, 3.84, 3.84, 0.0, 0.0, 0.0),
70
+ "Full Bass" to doubleArrayOf(4.8, 5.76, 5.76, 3.36, 0.96, -2.4, -4.8, -6.24, -6.72, -6.72),
71
+ "Full Treble" to doubleArrayOf(-5.76, -5.76, -5.76, -2.4, 1.44, 6.72, 9.6, 9.6, 9.6, 10.08),
72
+ "Full Bass & Treble" to doubleArrayOf(4.32, 3.36, 0.0, -4.32, -2.88, 0.96, 4.8, 6.72, 7.2, 7.2),
73
+ "Large Hall" to doubleArrayOf(6.24, 6.24, 3.36, 3.36, 0.0, -2.88, -2.88, -2.88, 0.0, 0.0),
74
+ "Party" to doubleArrayOf(4.32, 4.32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.32, 4.32),
75
+ "Ska" to doubleArrayOf(-1.44, -2.88, -2.4, 0.0, 2.4, 3.36, 5.28, 5.76, 6.72, 5.76),
76
+ "Soft" to doubleArrayOf(2.88, 0.96, 0.0, -1.44, 0.0, 2.4, 4.8, 5.76, 6.72, 7.2),
77
+ "Soft Rock" to doubleArrayOf(2.4, 2.4, 1.44, 0.0, -2.4, -3.36, -1.92, 0.0, 1.44, 5.28),
78
+ "Headphones" to doubleArrayOf(2.88, 6.72, 3.36, -1.92, -1.44, 0.96, 2.88, 5.76, 7.68, 8.64),
79
+ "Laptop Speakers" to doubleArrayOf(2.88, 6.72, 3.36, -1.92, -1.44, 0.96, 2.88, 5.76, 7.68, 8.64),
83
80
  )
84
81
  }
85
82
 
@@ -92,24 +89,47 @@ class EqualizerCore private constructor(
92
89
  this.isUsingFallbackSession = (audioSessionId == 0)
93
90
 
94
91
  try {
95
- equalizer?.release()
96
- equalizer =
97
- Equalizer(0, audioSessionId).apply {
92
+ releaseAudioEffects()
93
+
94
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
95
+ initDynamicsProcessing(audioSessionId)
96
+ usingDynamicsProcessing = true
97
+ } else {
98
+ equalizer = Equalizer(0, audioSessionId).apply {
98
99
  enabled = false
99
100
  }
100
- setupBandMapping()
101
+ usingDynamicsProcessing = false
102
+ setupBandMapping()
103
+ }
101
104
  restoreSettings()
102
105
  } catch (e: Exception) {
103
106
  NitroPlayerLogger.log("EqualizerCore", "Failed to initialize equalizer: ${e.message}")
104
107
  }
105
108
  }
106
109
 
110
+ @RequiresApi(Build.VERSION_CODES.P)
111
+ private fun initDynamicsProcessing(sessionId: Int) {
112
+ val config = DynamicsProcessing.Config.Builder(
113
+ DynamicsProcessing.VARIANT_FAVOR_FREQUENCY_RESOLUTION,
114
+ 1, // channelCount (stereo handled internally)
115
+ true, 10, // Pre-EQ enabled, 10 bands
116
+ false, 0, // MBC disabled
117
+ false, 0, // Post-EQ disabled
118
+ false // Limiter disabled
119
+ ).build()
120
+ dynamicsProcessing = DynamicsProcessing(0, sessionId, config).apply { enabled = false }
121
+ for (i in 0 until 10) {
122
+ val band = DynamicsProcessing.EqBand(true, frequencies[i].toFloat(), 0f)
123
+ dynamicsProcessing!!.setPreEqBandAllChannelsTo(i, band)
124
+ }
125
+ }
126
+
107
127
  /**
108
128
  * Ensure equalizer is initialized, using audio session 0 (global output mix) if needed
109
129
  * This allows the equalizer to work even before TrackPlayer is used
110
130
  */
111
131
  fun ensureInitialized() {
112
- if (equalizer == null) {
132
+ if (equalizer == null && dynamicsProcessing == null) {
113
133
  initialize(0)
114
134
  }
115
135
  }
@@ -136,10 +156,12 @@ class EqualizerCore private constructor(
136
156
  }
137
157
 
138
158
  fun setEnabled(enabled: Boolean): Boolean {
139
- val eq = equalizer ?: return false
140
-
141
159
  return try {
142
- eq.enabled = enabled
160
+ if (usingDynamicsProcessing && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
161
+ dynamicsProcessing?.enabled = enabled
162
+ } else {
163
+ equalizer?.enabled = enabled
164
+ }
143
165
  isEqualizerEnabled = enabled
144
166
  notifyEnabledChange(enabled)
145
167
  saveEnabled(enabled)
@@ -153,19 +175,9 @@ class EqualizerCore private constructor(
153
175
  fun isEnabled(): Boolean = isEqualizerEnabled
154
176
 
155
177
  fun getBands(): Array<EqualizerBand> {
156
- val eq = equalizer ?: return emptyArray()
157
-
158
- return (0 until 5)
178
+ return (0 until 10)
159
179
  .map { i ->
160
- val actualBand = bandMapping[i].toShort()
161
- val gainMb =
162
- try {
163
- eq.getBandLevel(actualBand)
164
- } catch (e: Exception) {
165
- 0.toShort()
166
- }
167
- val gainDb = gainMb / 100.0 // convert millibels to dB
168
-
180
+ val gainDb = getCurrentBandGain(i)
169
181
  EqualizerBand(
170
182
  index = i.toDouble(),
171
183
  centerFrequency = frequencies[i].toDouble(),
@@ -175,19 +187,26 @@ class EqualizerCore private constructor(
175
187
  }.toTypedArray()
176
188
  }
177
189
 
190
+ private fun getCurrentBandGain(bandIndex: Int): Double = currentGainsArray[bandIndex]
191
+
178
192
  fun setBandGain(
179
193
  bandIndex: Int,
180
194
  gainDb: Double,
181
195
  ): Boolean {
182
- if (bandIndex !in 0..4) return false
196
+ if (bandIndex !in 0..9) return false
183
197
 
184
- val eq = equalizer ?: return false
185
198
  val clampedGain = gainDb.coerceIn(-12.0, 12.0)
186
- val gainMb = (clampedGain * 100).toInt().toShort() // convert dB to millibels
187
199
 
188
200
  return try {
189
- eq.setBandLevel(bandMapping[bandIndex].toShort(), gainMb)
190
- currentPresetName = null // Custom settings
201
+ currentGainsArray[bandIndex] = clampedGain
202
+ if (usingDynamicsProcessing && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
203
+ setDPBandGain(bandIndex, clampedGain.toFloat())
204
+ } else {
205
+ val eq = equalizer ?: return false
206
+ val gainMb = (clampedGain * 100).toInt().toShort()
207
+ eq.setBandLevel(bandMapping[bandIndex].toShort(), gainMb)
208
+ }
209
+ currentPresetName = null
191
210
  notifyBandChange(getBands())
192
211
  notifyPresetChange(null)
193
212
  saveBandGainsAndPreset(getAllGains(), null)
@@ -197,16 +216,30 @@ class EqualizerCore private constructor(
197
216
  }
198
217
  }
199
218
 
200
- fun setAllBandGains(gains: DoubleArray): Boolean {
201
- if (gains.size != 5) return false
219
+ @RequiresApi(Build.VERSION_CODES.P)
220
+ private fun setDPBandGain(bandIndex: Int, gainDb: Float) {
221
+ val band = DynamicsProcessing.EqBand(true, frequencies[bandIndex].toFloat(), gainDb)
222
+ dynamicsProcessing?.setPreEqBandAllChannelsTo(bandIndex, band)
223
+ }
202
224
 
203
- val eq = equalizer ?: return false
225
+ fun setAllBandGains(gains: DoubleArray): Boolean {
226
+ if (gains.size != 10) return false
204
227
 
205
228
  return try {
206
- gains.forEachIndexed { i, gain ->
207
- val clampedGain = gain.coerceIn(-12.0, 12.0)
208
- val gainMb = (clampedGain * 100).toInt().toShort()
209
- eq.setBandLevel(bandMapping[i].toShort(), gainMb)
229
+ if (usingDynamicsProcessing && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
230
+ gains.forEachIndexed { i, gain ->
231
+ val clampedGain = gain.coerceIn(-12.0, 12.0)
232
+ currentGainsArray[i] = clampedGain
233
+ setDPBandGain(i, clampedGain.toFloat())
234
+ }
235
+ } else {
236
+ val eq = equalizer ?: return false
237
+ gains.forEachIndexed { i, gain ->
238
+ val clampedGain = gain.coerceIn(-12.0, 12.0)
239
+ currentGainsArray[i] = clampedGain
240
+ val gainMb = (clampedGain * 100).toInt().toShort()
241
+ eq.setBandLevel(bandMapping[i].toShort(), gainMb)
242
+ }
210
243
  }
211
244
  notifyBandChange(getBands())
212
245
  saveBandGains(gains.toList())
@@ -217,26 +250,23 @@ class EqualizerCore private constructor(
217
250
  }
218
251
 
219
252
  private fun getAllGains(): List<Double> {
220
- val eq = equalizer ?: return listOf(0.0, 0.0, 0.0, 0.0, 0.0)
221
- return (0 until 5).map { i ->
222
- try {
223
- eq.getBandLevel(bandMapping[i].toShort()) / 100.0
224
- } catch (e: Exception) {
225
- 0.0
226
- }
227
- }
253
+ return (0 until 10).map { i -> getCurrentBandGain(i) }
228
254
  }
229
255
 
230
256
  fun getBandRange(): GainRange {
231
- val eq = equalizer
232
- return if (eq != null) {
233
- val range = eq.bandLevelRange
234
- GainRange(
235
- min = (range[0] / 100.0).coerceAtLeast(-12.0),
236
- max = (range[1] / 100.0).coerceAtMost(12.0),
237
- )
238
- } else {
257
+ return if (usingDynamicsProcessing) {
239
258
  GainRange(min = -12.0, max = 12.0)
259
+ } else {
260
+ val eq = equalizer
261
+ if (eq != null) {
262
+ val range = eq.bandLevelRange
263
+ GainRange(
264
+ min = (range[0] / 100.0).coerceAtLeast(-12.0),
265
+ max = (range[1] / 100.0).coerceAtMost(12.0),
266
+ )
267
+ } else {
268
+ GainRange(min = -12.0, max = 12.0)
269
+ }
240
270
  }
241
271
  }
242
272
 
@@ -265,7 +295,7 @@ class EqualizerCore private constructor(
265
295
  .asSequence()
266
296
  .map { name ->
267
297
  val gainsArray = json.getJSONArray(name)
268
- val gains = DoubleArray(5) { gainsArray.getDouble(it) }
298
+ val gains = DoubleArray(gainsArray.length()) { gainsArray.getDouble(it) }
269
299
  EqualizerPreset(
270
300
  name = name,
271
301
  gains = gains,
@@ -300,7 +330,7 @@ class EqualizerCore private constructor(
300
330
  val json = JSONObject(customPresetsJson)
301
331
  if (json.has(name)) {
302
332
  val gainsArray = json.getJSONArray(name)
303
- DoubleArray(5) { gainsArray.getDouble(it) }
333
+ DoubleArray(gainsArray.length()) { gainsArray.getDouble(it) }
304
334
  } else {
305
335
  null
306
336
  }
@@ -362,7 +392,7 @@ class EqualizerCore private constructor(
362
392
  )
363
393
 
364
394
  fun reset() {
365
- setAllBandGains(doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0))
395
+ setAllBandGains(DoubleArray(10) { 0.0 })
366
396
  currentPresetName = "Flat"
367
397
  notifyPresetChange("Flat")
368
398
  saveCurrentPreset("Flat")
@@ -409,8 +439,12 @@ class EqualizerCore private constructor(
409
439
  if (gainsJson != null) {
410
440
  try {
411
441
  val arr = JSONArray(gainsJson)
412
- val gains = DoubleArray(5) { arr.getDouble(it) }
413
- setAllBandGains(gains)
442
+ // Migration: if saved array length != 10, skip (5-band data incompatible)
443
+ if (arr.length() == 10) {
444
+ val gains = DoubleArray(10) { arr.getDouble(it) }
445
+ setAllBandGains(gains)
446
+ }
447
+ // else: start at flat (migration from 5-band)
414
448
  } catch (e: Exception) {
415
449
  // Ignore
416
450
  }
@@ -420,7 +454,11 @@ class EqualizerCore private constructor(
420
454
  isEqualizerEnabled = enabled
421
455
 
422
456
  try {
423
- equalizer?.enabled = enabled
457
+ if (usingDynamicsProcessing && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
458
+ dynamicsProcessing?.enabled = enabled
459
+ } else {
460
+ equalizer?.enabled = enabled
461
+ }
424
462
  } catch (e: Exception) {
425
463
  // Ignore
426
464
  }
@@ -429,71 +467,48 @@ class EqualizerCore private constructor(
429
467
  // === Callback management ===
430
468
 
431
469
  fun addOnEnabledChangeListener(callback: (Boolean) -> Unit) {
432
- val box = WeakCallbackBox(WeakReference(callback as Any), callback)
433
- onEnabledChangeListeners.add(box)
470
+ onEnabledChangeListeners.add(callback)
434
471
  }
435
472
 
436
473
  fun addOnBandChangeListener(callback: (Array<EqualizerBand>) -> Unit) {
437
- val box = WeakCallbackBox(WeakReference(callback as Any), callback)
438
- synchronized(onBandChangeListeners) {
439
- @Suppress("UNCHECKED_CAST")
440
- (onBandChangeListeners as MutableList<WeakCallbackBox<(Array<EqualizerBand>) -> Unit>>).add(box)
441
- }
474
+ onBandChangeListeners.add(callback)
442
475
  }
443
476
 
444
477
  fun addOnPresetChangeListener(callback: (Variant_NullType_String?) -> Unit) {
445
- val box = WeakCallbackBox(WeakReference(callback as Any), callback)
446
- onPresetChangeListeners.add(box)
478
+ onPresetChangeListeners.add(callback)
447
479
  }
448
480
 
449
481
  private fun notifyEnabledChange(enabled: Boolean) {
450
- synchronized(onEnabledChangeListeners) {
451
- onEnabledChangeListeners.removeAll { !it.isAlive }
452
- onEnabledChangeListeners.forEach { box ->
453
- try {
454
- box.callback(enabled)
455
- } catch (e: Exception) {
456
- // Ignore callback errors
457
- }
458
- }
459
- }
482
+ onEnabledChangeListeners.forEach { it(enabled) }
460
483
  }
461
484
 
462
485
  private fun notifyBandChange(bands: Array<EqualizerBand>) {
463
- synchronized(onBandChangeListeners) {
464
- @Suppress("UNCHECKED_CAST")
465
- val listeners = onBandChangeListeners as MutableList<WeakCallbackBox<(Array<EqualizerBand>) -> Unit>>
466
- listeners.removeAll { !it.isAlive }
467
- listeners.forEach { box ->
468
- try {
469
- box.callback(bands)
470
- } catch (e: Exception) {
471
- // Ignore callback errors
472
- }
473
- }
474
- }
486
+ onBandChangeListeners.forEach { it(bands) }
475
487
  }
476
488
 
477
489
  private fun notifyPresetChange(presetName: String?) {
478
- synchronized(onPresetChangeListeners) {
479
- onPresetChangeListeners.removeAll { !it.isAlive }
480
- onPresetChangeListeners.forEach { box ->
481
- try {
482
- val variant = presetName?.let { Variant_NullType_String.create(it) }
483
- box.callback(variant)
484
- } catch (e: Exception) {
485
- // Ignore callback errors
486
- }
487
- }
488
- }
490
+ val variant = presetName?.let { Variant_NullType_String.create(it) }
491
+ onPresetChangeListeners.forEach { it(variant) }
489
492
  }
490
493
 
491
- fun release() {
494
+ private fun releaseAudioEffects() {
492
495
  try {
493
496
  equalizer?.release()
494
497
  equalizer = null
495
498
  } catch (e: Exception) {
496
499
  // Ignore
497
500
  }
501
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
502
+ try {
503
+ dynamicsProcessing?.release()
504
+ dynamicsProcessing = null
505
+ } catch (e: Exception) {
506
+ // Ignore
507
+ }
508
+ }
509
+ }
510
+
511
+ fun release() {
512
+ releaseAudioEffects()
498
513
  }
499
514
  }
@@ -26,6 +26,7 @@ import com.google.common.util.concurrent.ListenableFuture
26
26
  import com.margelo.nitro.nitroplayer.TrackItem
27
27
  import com.margelo.nitro.nitroplayer.core.NitroPlayerLogger
28
28
  import com.margelo.nitro.nitroplayer.core.TrackPlayerCore
29
+ import com.margelo.nitro.nitroplayer.core.loadPlaylist
29
30
  import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
30
31
  import com.margelo.nitro.nitroplayer.playlist.PlaylistManager
31
32
  import kotlinx.coroutines.CoroutineScope
@@ -50,6 +51,8 @@ class MediaSessionManager(
50
51
  private set
51
52
  private var notificationManager: NotificationManager? = null
52
53
  private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
54
+ @Volatile private var currentTrack: TrackItem? = null
55
+ @Volatile private var isPlaying: Boolean = false
53
56
  private val artworkCache = object : LruCache<String, Bitmap>(20) {
54
57
  override fun sizeOf(key: String, value: Bitmap): Int = 1
55
58
  }
@@ -216,7 +219,7 @@ class MediaSessionManager(
216
219
 
217
220
  if (trackIndex >= 0) {
218
221
  // Load the entire playlist into TrackPlayerCore
219
- trackPlayerCore?.loadPlaylist(playlistId)
222
+ trackPlayerCore?.let { core -> scope.launch { core.loadPlaylist(playlistId) } }
220
223
 
221
224
  // Create MediaItems for the entire playlist
222
225
  val playlistMediaItems =
@@ -324,28 +327,7 @@ class MediaSessionManager(
324
327
  }
325
328
  }
326
329
 
327
- private fun getCurrentTrack(): TrackItem? {
328
- val currentMediaItem = player.currentMediaItem ?: return null
329
- val mediaId = currentMediaItem.mediaId
330
-
331
- // Parse mediaId format: "playlistId:trackId" or just "trackId"
332
- val trackId =
333
- if (mediaId.contains(':')) {
334
- mediaId.substring(mediaId.indexOf(':') + 1)
335
- } else {
336
- mediaId
337
- }
338
-
339
- // Find track in current playlist or all playlists
340
- return trackPlayerCore?.getCurrentPlaylistId()?.let { playlistId ->
341
- playlistManager.getPlaylist(playlistId)?.tracks?.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
- }
348
- }
330
+ private fun getCurrentTrack(): TrackItem? = currentTrack
349
331
 
350
332
  private fun updateNotification() {
351
333
  if (!showInNotification) return
@@ -376,7 +358,7 @@ class MediaSessionManager(
376
358
  .setSmallIcon(android.R.drawable.ic_media_play)
377
359
  .setContentIntent(contentIntent)
378
360
  .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
379
- .setOngoing(player.isPlaying)
361
+ .setOngoing(isPlaying)
380
362
  .setShowWhen(false)
381
363
  .setPriority(NotificationCompat.PRIORITY_LOW)
382
364
  .setCategory(NotificationCompat.CATEGORY_TRANSPORT)
@@ -401,7 +383,7 @@ class MediaSessionManager(
401
383
  createMediaAction(ACTION_PREVIOUS),
402
384
  )
403
385
 
404
- if (player.isPlaying) {
386
+ if (isPlaying) {
405
387
  builder.addAction(
406
388
  android.R.drawable.ic_media_pause,
407
389
  "Pause",
@@ -459,12 +441,12 @@ class MediaSessionManager(
459
441
  notificationManager?.cancel(NOTIFICATION_ID)
460
442
  }
461
443
 
462
- fun onTrackChanged() {
444
+ fun onTrackChanged(track: TrackItem?) {
445
+ currentTrack = track
463
446
  // Preload artwork for better notification display
464
- val currentTrack = getCurrentTrack()
465
- if (currentTrack != null) {
447
+ if (track != null) {
466
448
  scope.launch {
467
- currentTrack.artwork?.asSecondOrNull()?.let { artworkUrl ->
449
+ track.artwork?.asSecondOrNull()?.let { artworkUrl ->
468
450
  loadArtworkBitmap(artworkUrl)
469
451
  }
470
452
  updateNotification()
@@ -474,7 +456,8 @@ class MediaSessionManager(
474
456
  }
475
457
  }
476
458
 
477
- fun onPlaybackStateChanged() {
459
+ fun onPlaybackStateChanged(playing: Boolean) {
460
+ isPlaying = playing
478
461
  updateNotification()
479
462
  }
480
463