react-native-nitro-player 0.7.1-alpha.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.
package/README.md CHANGED
@@ -42,41 +42,46 @@ npm install react-native-nitro-modules
42
42
 
43
43
  ### TrackPlayer Methods
44
44
 
45
+ Command-style methods return **`Promise<void>`** (or another **`Promise`**) and **reject** on failure; **`getRepeatMode()`** and **`isAndroidAutoConnected()`** are synchronous reads.
46
+
45
47
  | Name | Platform | Description |
46
48
  | --------------------------- | -------- | ------------------------------------------------------------------- |
47
- | `play()` | Both | Resumes playback. |
48
- | `pause()` | Both | Pauses playback. |
49
+ | `play()` | Both | **Async**. Resumes playback. |
50
+ | `pause()` | Both | **Async**. Pauses playback. |
49
51
  | `playSong(id, playlistId?)` | Both | **Async**. Plays a specific song, optionally from a playlist. |
50
- | `skipToNext()` | Both | Skips to the next track in the queue. |
51
- | `skipToPrevious()` | Both | Skips to the previous track. |
52
- | `seek(position)` | Both | Seeks to a specific time position in seconds. |
52
+ | `skipToNext()` | Both | **Async**. Skips to the next track in the queue. |
53
+ | `skipToPrevious()` | Both | **Async**. Skips to the previous track. |
54
+ | `seek(position)` | Both | **Async**. Seeks to a specific time position in seconds. |
53
55
  | `setPlaybackSpeed(speed)` | Both | **Async**. Sets playback speed (e.g. 0.5x, 1x, 1.5x, 2x). |
54
56
  | `getPlaybackSpeed()` | Both | **Async**. Gets the current playback speed. |
55
- | `setVolume(0-100)` | Both | Sets playback volume (0-100). |
56
- | `setRepeatMode(mode)` | Both | Sets repeat mode (`off`, `track`, `Playlist`). |
57
+ | `setVolume(0-100)` | Both | **Async**. Sets playback volume (0-100). |
58
+ | `setRepeatMode(mode)` | Both | **Async**. Sets repeat mode (`off`, `track`, `Playlist`). |
59
+ | `getRepeatMode()` | Both | **Sync**. Current repeat mode. |
57
60
  | `addToUpNext(id)` | Both | **Async**. Adds a track to the "up next" queue (FIFO). |
58
61
  | `playNext(id)` | Both | **Async**. Adds a track to the "play next" stack (LIFO). |
59
- | `getActualQueue()` | Both | **Async**. Gets the full playback queue including temporary tracks. |
60
- | `getState()` | Both | **Async**. Gets the current player state immediately. |
61
- | `skipToIndex(index)` | Both | **Async**. Skips to a specific index in the actual queue. |
62
- | `configure(config)` | Both | Configures player settings (Android Auto, etc.). |
63
- | `isAndroidAutoConnected()` | Both | Checks if Android Auto is currently connected. |
62
+ | `getActualQueue()` | Both | **Async**. Full playback queue including temporary tracks. |
63
+ | `getState()` | Both | **Async**. Snapshot of player state. |
64
+ | `skipToIndex(index)` | Both | **Async**. Skips to an index in the actual queue (`Promise<boolean>`). |
65
+ | `configure(config)` | Both | **Async**. Player settings (Android Auto, CarPlay, notification). |
66
+ | `isAndroidAutoConnected()` | Both | **Sync**. Android Auto connection. |
64
67
 
65
68
  ### PlayerQueue Methods
66
69
 
70
+ Mutations return **`Promise<void>`** (or **`Promise<string>`** for `createPlaylist`). **`getPlaylist`** / **`getAllPlaylists`** / **`getCurrentPlaylistId`** are synchronous reads.
71
+
67
72
  | Name | Platform | Description |
68
73
  | --------------------------------------- | -------- | ------------------------------------------------------- |
69
- | `createPlaylist(name, ...)` | Both | Creates a new playlist. Returns ID. |
70
- | `deletePlaylist(id)` | Both | Deletes a playlist by ID. |
71
- | `updatePlaylist(id, ...)` | Both | Updates playlist metadata (name, description, artwork). |
74
+ | `createPlaylist(name, ...)` | Both | **Async**. Creates a playlist; resolves to playlist ID. |
75
+ | `deletePlaylist(id)` | Both | **Async**. Deletes a playlist by ID. |
76
+ | `updatePlaylist(id, ...)` | Both | **Async**. Updates metadata (name, description, artwork). |
72
77
  | `getPlaylist(id)` | Both | Gets a specific playlist object. |
73
78
  | `getAllPlaylists()` | Both | Gets all available playlists. |
74
- | `loadPlaylist(id)` | Both | Loads a playlist for playback. |
79
+ | `loadPlaylist(id)` | Both | **Async**. Loads a playlist for playback. |
75
80
  | `getCurrentPlaylistId()` | Both | Gets the ID of the currently playing playlist. |
76
- | `addTrackToPlaylist(pid, track)` | Both | Adds a track to a playlist. |
77
- | `addTracksToPlaylist(pid, tracks)` | Both | Adds multiple tracks to a playlist. |
78
- | `removeTrackFromPlaylist(pid, tid)` | Both | Removes a track from a playlist. |
79
- | `reorderTrackInPlaylist(pid, tid, idx)` | Both | Moves a track to a new position in the playlist. |
81
+ | `addTrackToPlaylist(pid, track)` | Both | **Async**. Adds a track to a playlist. |
82
+ | `addTracksToPlaylist(pid, tracks)` | Both | **Async**. Adds multiple tracks to a playlist. |
83
+ | `removeTrackFromPlaylist(pid, tid)` | Both | **Async**. Removes a track from a playlist. |
84
+ | `reorderTrackInPlaylist(pid, tid, idx)` | Both | **Async**. Moves a track to a new position. |
80
85
 
81
86
  ### Platform-Specific APIs
82
87
 
@@ -98,8 +103,10 @@ npm install react-native-nitro-modules
98
103
  | `pauseDownload(downloadId)` | Both | **Async**. Pauses an active download. |
99
104
  | `resumeDownload(downloadId)` | Both | **Async**. Resumes a paused download. |
100
105
  | `cancelDownload(downloadId)` | Both | **Async**. Cancels a download. |
101
- | `isTrackDownloaded(trackId)` | Both | Checks if a track is downloaded. |
102
- | `getAllDownloadedTracks()` | Both | Gets all downloaded tracks. |
106
+ | `isTrackDownloaded(trackId)` | Both | **Async**. Checks on-disk download state. |
107
+ | `getAllDownloadedTracks()` | Both | **Async**. Lists persisted downloaded tracks. |
108
+ | `getEffectiveUrl(track)` | Both | **Async**. Local or remote URL from preference + downloads. |
109
+ | `syncDownloads()` | Both | **Async**. Reconcile DB with files; returns cleanup count. |
103
110
  | `deleteDownloadedTrack(trackId)` | Both | **Async**. Deletes a downloaded track. |
104
111
  | `getStorageInfo()` | Both | **Async**. Gets download storage usage information. |
105
112
  | `setPlaybackSourcePreference(pref)` | Both | Sets playback source: `'auto'`, `'download'`, or `'network'`. |
@@ -109,6 +116,8 @@ npm install react-native-nitro-modules
109
116
 
110
117
  ## Quick Start
111
118
 
119
+ `TrackPlayer` / `PlayerQueue` commands return **Promises**. The snippets use `await`—run them inside an `async` function, or use `.then()` / `void` / `.catch()` as appropriate.
120
+
112
121
  ### 1. Configure the Player
113
122
 
114
123
  Configure the player before using it in your app:
@@ -116,7 +125,7 @@ Configure the player before using it in your app:
116
125
  ```typescript
117
126
  import { TrackPlayer } from 'react-native-nitro-player'
118
127
 
119
- TrackPlayer.configure({
128
+ await TrackPlayer.configure({
120
129
  androidAutoEnabled: true,
121
130
  carPlayEnabled: false,
122
131
  showInNotification: true,
@@ -147,15 +156,13 @@ const tracks: TrackItem[] = [
147
156
  },
148
157
  ]
149
158
 
150
- // Create a playlist
151
- const playlistId = PlayerQueue.createPlaylist(
159
+ const playlistId = await PlayerQueue.createPlaylist(
152
160
  'My Playlist',
153
161
  'Playlist description',
154
162
  'https://example.com/playlist-artwork.jpg'
155
163
  )
156
164
 
157
- // Add tracks to the playlist
158
- PlayerQueue.addTracksToPlaylist(playlistId, tracks)
165
+ await PlayerQueue.addTracksToPlaylist(playlistId, tracks)
159
166
  ```
160
167
 
161
168
  ### 3. Play Music
@@ -163,28 +170,23 @@ PlayerQueue.addTracksToPlaylist(playlistId, tracks)
163
170
  ```typescript
164
171
  import { TrackPlayer, PlayerQueue } from 'react-native-nitro-player'
165
172
 
166
- // Load and play a playlist
167
- PlayerQueue.loadPlaylist(playlistId)
173
+ await PlayerQueue.loadPlaylist(playlistId)
168
174
 
169
- // Or play a specific song
170
175
  await TrackPlayer.playSong('song-id', playlistId)
171
176
 
172
- // Basic controls
173
- TrackPlayer.play()
174
- TrackPlayer.pause()
175
- TrackPlayer.skipToNext()
176
- TrackPlayer.skipToPrevious()
177
- TrackPlayer.seek(30) // Seek to 30 seconds
177
+ await TrackPlayer.play()
178
+ await TrackPlayer.pause()
179
+ await TrackPlayer.skipToNext()
180
+ await TrackPlayer.skipToPrevious()
181
+ await TrackPlayer.seek(30)
178
182
 
179
- // Set repeat mode
180
- TrackPlayer.setRepeatMode('off') // No repeat
181
- TrackPlayer.setRepeatMode('Playlist') // Repeat entire playlist
182
- TrackPlayer.setRepeatMode('track') // Repeat current track
183
+ await TrackPlayer.setRepeatMode('off')
184
+ await TrackPlayer.setRepeatMode('Playlist')
185
+ await TrackPlayer.setRepeatMode('track')
183
186
 
184
- // Set volume (0-100)
185
- TrackPlayer.setVolume(50) // Set volume to 50%
186
- TrackPlayer.setVolume(0) // Mute
187
- TrackPlayer.setVolume(100) // Maximum volume
187
+ await TrackPlayer.setVolume(50)
188
+ await TrackPlayer.setVolume(0)
189
+ await TrackPlayer.setVolume(100)
188
190
 
189
191
  // Add temporary tracks to queue
190
192
  await TrackPlayer.addToUpNext('song-id') // Add to up-next queue (FIFO)
@@ -280,8 +282,7 @@ The actual playback order is:
280
282
  Temporary tracks are automatically cleared when:
281
283
 
282
284
  - `await TrackPlayer.playSong()` is called
283
- - `PlayerQueue.loadPlaylist()` is called
284
- - `TrackPlayer.playFromIndex()` is called
285
+ - `await PlayerQueue.loadPlaylist()` is called
285
286
 
286
287
  ### `skipToIndex(index: number): Promise<boolean>`
287
288
 
@@ -80,16 +80,26 @@ internal fun TrackPlayerCore.getActualQueueInternal(): List<TrackItem> {
80
80
  // Current track
81
81
  getCurrentTrack()?.let { queue.add(it) }
82
82
 
83
- // playNext skip index 0 if it is the current item
84
- if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT && playNextStack.size > 1) {
85
- queue.addAll(playNextStack.subList(1, playNextStack.size))
83
+ val currentId = exo.currentMediaItem?.mediaId?.let { extractTrackId(it) }
84
+
85
+ // playNext — skip the currently playing track by ID (not position)
86
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT && currentId != null) {
87
+ var skipped = false
88
+ for (track in playNextStack) {
89
+ if (!skipped && track.id == currentId) { skipped = true; continue }
90
+ queue.add(track)
91
+ }
86
92
  } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.PLAY_NEXT) {
87
93
  queue.addAll(playNextStack)
88
94
  }
89
95
 
90
- // upNext — skip index 0 if it is the current item
91
- if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT && upNextQueue.size > 1) {
92
- queue.addAll(upNextQueue.subList(1, upNextQueue.size))
96
+ // upNext — skip the currently playing track by ID (not position)
97
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT && currentId != null) {
98
+ var skipped = false
99
+ for (track in upNextQueue) {
100
+ if (!skipped && track.id == currentId) { skipped = true; continue }
101
+ queue.add(track)
102
+ }
93
103
  } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.UP_NEXT) {
94
104
  queue.addAll(upNextQueue)
95
105
  }
@@ -124,17 +134,21 @@ private fun TrackPlayerCore.skipToIndexInternal(index: Int): Boolean {
124
134
  if (index == currentPos) { exo.seekTo(0); return true }
125
135
 
126
136
  if (index in playNextStart until playNextEnd) {
127
- val listIndex = (index - playNextStart) + if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT) 1 else 0
128
- if (listIndex > 0) playNextStack.subList(0, listIndex).clear()
137
+ val targetTrack = actualQueue[index]
138
+ // Remove all playNext tracks before the target (by ID lookup, not position)
139
+ val targetIdx = playNextStack.indexOfFirst { it.id == targetTrack.id }
140
+ if (targetIdx > 0) playNextStack.subList(0, targetIdx).clear()
129
141
  rebuildQueueFromCurrentPosition()
130
142
  exo.seekToNext()
131
143
  return true
132
144
  }
133
145
 
134
146
  if (index in upNextStart until upNextEnd) {
135
- val listIndex = (index - upNextStart) + if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT) 1 else 0
147
+ val targetTrack = actualQueue[index]
136
148
  playNextStack.clear()
137
- if (listIndex > 0) upNextQueue.subList(0, listIndex).clear()
149
+ // Remove all upNext tracks before the target (by ID lookup, not position)
150
+ val targetIdx = upNextQueue.indexOfFirst { it.id == targetTrack.id }
151
+ if (targetIdx > 0) upNextQueue.subList(0, targetIdx).clear()
138
152
  rebuildQueueFromCurrentPosition()
139
153
  exo.seekToNext()
140
154
  return true
@@ -51,17 +51,26 @@ internal fun TrackPlayerCore.rebuildQueueFromCurrentPosition() {
51
51
  }
52
52
 
53
53
  val newQueueTracks = ArrayList<TrackItem>(playNextStack.size + upNextQueue.size + currentTracks.size)
54
-
55
- // playNext stack — skip index 0 if it is the currently playing item
56
- if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT && playNextStack.size > 1) {
57
- newQueueTracks.addAll(playNextStack.subList(1, playNextStack.size))
54
+ val currentId = exo.currentMediaItem?.mediaId?.let { extractTrackId(it) }
55
+
56
+ // playNext stack skip the currently playing track by ID (not position)
57
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT && currentId != null) {
58
+ var skipped = false
59
+ for (track in playNextStack) {
60
+ if (!skipped && track.id == currentId) { skipped = true; continue }
61
+ newQueueTracks.add(track)
62
+ }
58
63
  } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.PLAY_NEXT) {
59
64
  newQueueTracks.addAll(playNextStack)
60
65
  }
61
66
 
62
- // upNext queue — skip index 0 if it is the currently playing item
63
- if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT && upNextQueue.size > 1) {
64
- newQueueTracks.addAll(upNextQueue.subList(1, upNextQueue.size))
67
+ // upNext queue — skip the currently playing track by ID (not position)
68
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT && currentId != null) {
69
+ var skipped = false
70
+ for (track in upNextQueue) {
71
+ if (!skipped && track.id == currentId) { skipped = true; continue }
72
+ newQueueTracks.add(track)
73
+ }
65
74
  } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.UP_NEXT) {
66
75
  newQueueTracks.addAll(upNextQueue)
67
76
  }
@@ -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
@@ -20,16 +22,19 @@ class EqualizerCore private constructor(
20
22
  private val context: Context,
21
23
  ) {
22
24
  private var equalizer: Equalizer? = null
25
+ private var dynamicsProcessing: DynamicsProcessing? = null
26
+ private var usingDynamicsProcessing: Boolean = false
23
27
  private var audioSessionId: Int = 0
24
28
  private var isUsingFallbackSession: Boolean = false // Track if using fallback session 0
25
29
  private var isEqualizerEnabled: Boolean = false
26
30
  private var currentPresetName: String? = null
27
31
 
28
- // Standard 5-band frequencies: 60Hz, 230Hz, 910Hz, 3.6kHz, 14kHz
29
- private val targetFrequencies = intArrayOf(60000, 230000, 910000, 3600000, 14000000) // milliHz
30
- private val frequencyLabels = arrayOf("60 Hz", "230 Hz", "910 Hz", "3.6 kHz", "14 kHz")
31
- private val frequencies = intArrayOf(60, 230, 910, 3600, 14000)
32
- 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)
33
38
 
34
39
  private val prefs: SharedPreferences =
35
40
  context.getSharedPreferences("equalizer_settings", Context.MODE_PRIVATE)
@@ -50,24 +55,28 @@ class EqualizerCore private constructor(
50
55
  INSTANCE ?: EqualizerCore(context).also { INSTANCE = it }
51
56
  }
52
57
 
53
- // 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
54
59
  private val BUILT_IN_PRESETS =
55
60
  mapOf(
56
- "Flat" to doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0),
57
- "Bass Boost" to doubleArrayOf(6.0, 4.0, 0.0, 0.0, 0.0),
58
- "Bass Reducer" to doubleArrayOf(-6.0, -4.0, 0.0, 0.0, 0.0),
59
- "Treble Boost" to doubleArrayOf(0.0, 0.0, 0.0, 4.0, 6.0),
60
- "Treble Reducer" to doubleArrayOf(0.0, 0.0, 0.0, -4.0, -6.0),
61
- "Vocal Boost" to doubleArrayOf(-2.0, 0.0, 4.0, 2.0, 0.0),
62
- "Rock" to doubleArrayOf(5.0, 3.0, -1.0, 3.0, 5.0),
63
- "Pop" to doubleArrayOf(-1.0, 2.0, 4.0, 2.0, -1.0),
64
- "Jazz" to doubleArrayOf(3.0, 1.0, -2.0, 2.0, 4.0),
65
- "Classical" to doubleArrayOf(4.0, 2.0, -1.0, 2.0, 3.0),
66
- "Hip Hop" to doubleArrayOf(6.0, 4.0, 0.0, 1.0, 3.0),
67
- "Electronic" to doubleArrayOf(5.0, 3.0, 0.0, 2.0, 5.0),
68
- "Acoustic" to doubleArrayOf(4.0, 2.0, 1.0, 3.0, 3.0),
69
- "R&B" to doubleArrayOf(3.0, 6.0, 2.0, -1.0, 2.0),
70
- "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),
71
80
  )
72
81
  }
73
82
 
@@ -80,24 +89,47 @@ class EqualizerCore private constructor(
80
89
  this.isUsingFallbackSession = (audioSessionId == 0)
81
90
 
82
91
  try {
83
- equalizer?.release()
84
- equalizer =
85
- 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 {
86
99
  enabled = false
87
100
  }
88
- setupBandMapping()
101
+ usingDynamicsProcessing = false
102
+ setupBandMapping()
103
+ }
89
104
  restoreSettings()
90
105
  } catch (e: Exception) {
91
106
  NitroPlayerLogger.log("EqualizerCore", "Failed to initialize equalizer: ${e.message}")
92
107
  }
93
108
  }
94
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
+
95
127
  /**
96
128
  * Ensure equalizer is initialized, using audio session 0 (global output mix) if needed
97
129
  * This allows the equalizer to work even before TrackPlayer is used
98
130
  */
99
131
  fun ensureInitialized() {
100
- if (equalizer == null) {
132
+ if (equalizer == null && dynamicsProcessing == null) {
101
133
  initialize(0)
102
134
  }
103
135
  }
@@ -124,10 +156,12 @@ class EqualizerCore private constructor(
124
156
  }
125
157
 
126
158
  fun setEnabled(enabled: Boolean): Boolean {
127
- val eq = equalizer ?: return false
128
-
129
159
  return try {
130
- 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
+ }
131
165
  isEqualizerEnabled = enabled
132
166
  notifyEnabledChange(enabled)
133
167
  saveEnabled(enabled)
@@ -141,19 +175,9 @@ class EqualizerCore private constructor(
141
175
  fun isEnabled(): Boolean = isEqualizerEnabled
142
176
 
143
177
  fun getBands(): Array<EqualizerBand> {
144
- val eq = equalizer ?: return emptyArray()
145
-
146
- return (0 until 5)
178
+ return (0 until 10)
147
179
  .map { i ->
148
- val actualBand = bandMapping[i].toShort()
149
- val gainMb =
150
- try {
151
- eq.getBandLevel(actualBand)
152
- } catch (e: Exception) {
153
- 0.toShort()
154
- }
155
- val gainDb = gainMb / 100.0 // convert millibels to dB
156
-
180
+ val gainDb = getCurrentBandGain(i)
157
181
  EqualizerBand(
158
182
  index = i.toDouble(),
159
183
  centerFrequency = frequencies[i].toDouble(),
@@ -163,19 +187,26 @@ class EqualizerCore private constructor(
163
187
  }.toTypedArray()
164
188
  }
165
189
 
190
+ private fun getCurrentBandGain(bandIndex: Int): Double = currentGainsArray[bandIndex]
191
+
166
192
  fun setBandGain(
167
193
  bandIndex: Int,
168
194
  gainDb: Double,
169
195
  ): Boolean {
170
- if (bandIndex !in 0..4) return false
196
+ if (bandIndex !in 0..9) return false
171
197
 
172
- val eq = equalizer ?: return false
173
198
  val clampedGain = gainDb.coerceIn(-12.0, 12.0)
174
- val gainMb = (clampedGain * 100).toInt().toShort() // convert dB to millibels
175
199
 
176
200
  return try {
177
- eq.setBandLevel(bandMapping[bandIndex].toShort(), gainMb)
178
- 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
179
210
  notifyBandChange(getBands())
180
211
  notifyPresetChange(null)
181
212
  saveBandGainsAndPreset(getAllGains(), null)
@@ -185,16 +216,30 @@ class EqualizerCore private constructor(
185
216
  }
186
217
  }
187
218
 
188
- fun setAllBandGains(gains: DoubleArray): Boolean {
189
- 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
+ }
190
224
 
191
- val eq = equalizer ?: return false
225
+ fun setAllBandGains(gains: DoubleArray): Boolean {
226
+ if (gains.size != 10) return false
192
227
 
193
228
  return try {
194
- gains.forEachIndexed { i, gain ->
195
- val clampedGain = gain.coerceIn(-12.0, 12.0)
196
- val gainMb = (clampedGain * 100).toInt().toShort()
197
- 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
+ }
198
243
  }
199
244
  notifyBandChange(getBands())
200
245
  saveBandGains(gains.toList())
@@ -205,26 +250,23 @@ class EqualizerCore private constructor(
205
250
  }
206
251
 
207
252
  private fun getAllGains(): List<Double> {
208
- val eq = equalizer ?: return listOf(0.0, 0.0, 0.0, 0.0, 0.0)
209
- return (0 until 5).map { i ->
210
- try {
211
- eq.getBandLevel(bandMapping[i].toShort()) / 100.0
212
- } catch (e: Exception) {
213
- 0.0
214
- }
215
- }
253
+ return (0 until 10).map { i -> getCurrentBandGain(i) }
216
254
  }
217
255
 
218
256
  fun getBandRange(): GainRange {
219
- val eq = equalizer
220
- return if (eq != null) {
221
- val range = eq.bandLevelRange
222
- GainRange(
223
- min = (range[0] / 100.0).coerceAtLeast(-12.0),
224
- max = (range[1] / 100.0).coerceAtMost(12.0),
225
- )
226
- } else {
257
+ return if (usingDynamicsProcessing) {
227
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
+ }
228
270
  }
229
271
  }
230
272
 
@@ -253,7 +295,7 @@ class EqualizerCore private constructor(
253
295
  .asSequence()
254
296
  .map { name ->
255
297
  val gainsArray = json.getJSONArray(name)
256
- val gains = DoubleArray(5) { gainsArray.getDouble(it) }
298
+ val gains = DoubleArray(gainsArray.length()) { gainsArray.getDouble(it) }
257
299
  EqualizerPreset(
258
300
  name = name,
259
301
  gains = gains,
@@ -288,7 +330,7 @@ class EqualizerCore private constructor(
288
330
  val json = JSONObject(customPresetsJson)
289
331
  if (json.has(name)) {
290
332
  val gainsArray = json.getJSONArray(name)
291
- DoubleArray(5) { gainsArray.getDouble(it) }
333
+ DoubleArray(gainsArray.length()) { gainsArray.getDouble(it) }
292
334
  } else {
293
335
  null
294
336
  }
@@ -350,7 +392,7 @@ class EqualizerCore private constructor(
350
392
  )
351
393
 
352
394
  fun reset() {
353
- setAllBandGains(doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0))
395
+ setAllBandGains(DoubleArray(10) { 0.0 })
354
396
  currentPresetName = "Flat"
355
397
  notifyPresetChange("Flat")
356
398
  saveCurrentPreset("Flat")
@@ -397,8 +439,12 @@ class EqualizerCore private constructor(
397
439
  if (gainsJson != null) {
398
440
  try {
399
441
  val arr = JSONArray(gainsJson)
400
- val gains = DoubleArray(5) { arr.getDouble(it) }
401
- 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)
402
448
  } catch (e: Exception) {
403
449
  // Ignore
404
450
  }
@@ -408,7 +454,11 @@ class EqualizerCore private constructor(
408
454
  isEqualizerEnabled = enabled
409
455
 
410
456
  try {
411
- 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
+ }
412
462
  } catch (e: Exception) {
413
463
  // Ignore
414
464
  }
@@ -441,12 +491,24 @@ class EqualizerCore private constructor(
441
491
  onPresetChangeListeners.forEach { it(variant) }
442
492
  }
443
493
 
444
- fun release() {
494
+ private fun releaseAudioEffects() {
445
495
  try {
446
496
  equalizer?.release()
447
497
  equalizer = null
448
498
  } catch (e: Exception) {
449
499
  // Ignore
450
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()
451
513
  }
452
514
  }
@@ -86,16 +86,25 @@ extension TrackPlayerCore {
86
86
  // Add current track (temp or original)
87
87
  if let current = getCurrentTrack() { queue.append(current) }
88
88
 
89
- // Add playNext stack — skip index 0 if current is from playNext (already added as current)
90
- if currentTemporaryType == .playNext && playNextStack.count > 1 {
91
- queue.append(contentsOf: playNextStack.dropFirst())
89
+ // Add playNext stack — skip the currently playing track by ID (already added as current)
90
+ let currentId = player?.currentItem?.trackId
91
+ if currentTemporaryType == .playNext, let currentId = currentId {
92
+ var skipped = false
93
+ for track in playNextStack {
94
+ if !skipped && track.id == currentId { skipped = true; continue }
95
+ queue.append(track)
96
+ }
92
97
  } else if currentTemporaryType != .playNext {
93
98
  queue.append(contentsOf: playNextStack)
94
99
  }
95
100
 
96
- // Add upNext queue — skip index 0 if current is from upNext (already added as current)
97
- if currentTemporaryType == .upNext && upNextQueue.count > 1 {
98
- queue.append(contentsOf: upNextQueue.dropFirst())
101
+ // Add upNext queue — skip the currently playing track by ID (already added as current)
102
+ if currentTemporaryType == .upNext, let currentId = currentId {
103
+ var skipped = false
104
+ for track in upNextQueue {
105
+ if !skipped && track.id == currentId { skipped = true; continue }
106
+ queue.append(track)
107
+ }
99
108
  } else if currentTemporaryType != .upNext {
100
109
  queue.append(contentsOf: upNextQueue)
101
110
  }
@@ -153,12 +162,13 @@ extension TrackPlayerCore {
153
162
 
154
163
  // Case 3: Target is in playNext section
155
164
  if index >= playNextStart && index < playNextEnd {
156
- let playNextIndex = index - playNextStart
157
- // Offset by 1 if current is from playNext (index 0 is already playing)
158
- let actualListIndex = currentTemporaryType == .playNext
159
- ? playNextIndex + 1 : playNextIndex
160
-
161
- if actualListIndex > 0 { playNextStack.removeFirst(actualListIndex) }
165
+ let targetTrack = actualQueue[index]
166
+ // Remove all playNext tracks before the target (by ID lookup, not position)
167
+ if let targetIdx = playNextStack.firstIndex(where: { $0.id == targetTrack.id }), targetIdx > 0 {
168
+ // Remove tracks before target, but keep the currently playing track
169
+ // (rebuildAVQueueFromCurrentPosition will skip it by ID)
170
+ playNextStack.removeSubrange(0..<targetIdx)
171
+ }
162
172
  rebuildAVQueueFromCurrentPosition()
163
173
  player?.advanceToNextItem()
164
174
  return true
@@ -166,13 +176,12 @@ extension TrackPlayerCore {
166
176
 
167
177
  // Case 4: Target is in upNext section
168
178
  if index >= upNextStart && index < upNextEnd {
169
- let upNextIndex = index - upNextStart
170
- // Offset by 1 if current is from upNext (index 0 is already playing)
171
- let actualListIndex = currentTemporaryType == .upNext
172
- ? upNextIndex + 1 : upNextIndex
173
-
179
+ let targetTrack = actualQueue[index]
174
180
  playNextStack.removeAll()
175
- if actualListIndex > 0 { upNextQueue.removeFirst(actualListIndex) }
181
+ // Remove all upNext tracks before the target (by ID lookup, not position)
182
+ if let targetIdx = upNextQueue.firstIndex(where: { $0.id == targetTrack.id }), targetIdx > 0 {
183
+ upNextQueue.removeSubrange(0..<targetIdx)
184
+ }
176
185
  rebuildAVQueueFromCurrentPosition()
177
186
  player?.advanceToNextItem()
178
187
  return true
@@ -233,15 +233,26 @@ extension TrackPlayerCore {
233
233
 
234
234
  // Build the desired upcoming track list
235
235
  var newQueueTracks: [TrackItem] = []
236
-
237
- if currentTemporaryType == .playNext && playNextStack.count > 1 {
238
- newQueueTracks.append(contentsOf: playNextStack.dropFirst())
236
+ let currentId = currentItem?.trackId
237
+
238
+ // PlayNext stack: skip the currently playing track by ID (not position)
239
+ if currentTemporaryType == .playNext, let currentId = currentId {
240
+ var skipped = false
241
+ for track in playNextStack {
242
+ if !skipped && track.id == currentId { skipped = true; continue }
243
+ newQueueTracks.append(track)
244
+ }
239
245
  } else if currentTemporaryType != .playNext {
240
246
  newQueueTracks.append(contentsOf: playNextStack)
241
247
  }
242
248
 
243
- if currentTemporaryType == .upNext && upNextQueue.count > 1 {
244
- newQueueTracks.append(contentsOf: upNextQueue.dropFirst())
249
+ // UpNext queue: skip the currently playing track by ID (not position)
250
+ if currentTemporaryType == .upNext, let currentId = currentId {
251
+ var skipped = false
252
+ for track in upNextQueue {
253
+ if !skipped && track.id == currentId { skipped = true; continue }
254
+ newQueueTracks.append(track)
255
+ }
245
256
  } else if currentTemporaryType != .upNext {
246
257
  newQueueTracks.append(contentsOf: upNextQueue)
247
258
  }
@@ -22,12 +22,12 @@ class EqualizerCore {
22
22
  private(set) var isEqualizerEnabled: Bool = false
23
23
  private var currentPresetName: String?
24
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"]
25
+ // Standard 10-band frequencies: 31Hz, 63Hz, 125Hz, 250Hz, 500Hz, 1kHz, 2kHz, 4kHz, 8kHz, 16kHz
26
+ let frequencies: [Float] = [31, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000]
27
+ private let frequencyLabels = ["31 Hz", "63 Hz", "125 Hz", "250 Hz", "500 Hz", "1 kHz", "2 kHz", "4 kHz", "8 kHz", "16 kHz"]
28
28
 
29
29
  // Current gains storage - internal so TapContext can access
30
- private(set) var currentGains: [Double] = [0, 0, 0, 0, 0]
30
+ private(set) var currentGains: [Double] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
31
31
  private var cachedBands: [EqualizerBand]?
32
32
  private var cachedCustomPresets: [EqualizerPreset]?
33
33
 
@@ -48,21 +48,25 @@ class EqualizerCore {
48
48
  // MARK: - Built-in Presets
49
49
 
50
50
  private static let builtInPresets: [String: [Double]] = [
51
- "Flat": [0, 0, 0, 0, 0],
52
- "Bass Boost": [6, 4, 0, 0, 0],
53
- "Bass Reducer": [-6, -4, 0, 0, 0],
54
- "Treble Boost": [0, 0, 0, 4, 6],
55
- "Treble Reducer": [0, 0, 0, -4, -6],
56
- "Vocal Boost": [-2, 0, 4, 2, 0],
57
- "Rock": [5, 3, -1, 3, 5],
58
- "Pop": [-1, 2, 4, 2, -1],
59
- "Jazz": [3, 1, -2, 2, 4],
60
- "Classical": [4, 2, -1, 2, 3],
61
- "Hip Hop": [6, 4, 0, 1, 3],
62
- "Electronic": [5, 3, 0, 2, 5],
63
- "Acoustic": [4, 2, 1, 3, 3],
64
- "R&B": [3, 6, 2, -1, 2],
65
- "Loudness": [6, 3, -1, 3, 6],
51
+ "Flat": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
52
+ "Rock": [4.8, 2.88, -3.36, -4.8, -1.92, 2.4, 5.28, 6.72, 6.72, 6.72],
53
+ "Pop": [0.96, 2.88, 4.32, 4.8, 3.36, 0.0, -1.44, -1.44, 0.96, 0.96],
54
+ "Classical": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.32, -4.32, -4.32, -5.76],
55
+ "Dance": [5.76, 4.32, 1.44, 0.0, 0.0, -3.36, -4.32, -4.32, 0.0, 0.0],
56
+ "Techno": [4.8, 3.36, 0.0, -3.36, -2.88, 0.0, 4.8, 5.76, 5.76, 5.28],
57
+ "Club": [0.0, 0.0, 4.8, 3.36, 3.36, 3.36, 1.92, 0.0, 0.0, 0.0],
58
+ "Live": [-2.88, 0.0, 2.4, 3.36, 3.36, 3.36, 2.4, 1.44, 1.44, 1.44],
59
+ "Reggae": [0.0, 0.0, 0.0, -3.36, 0.0, 3.84, 3.84, 0.0, 0.0, 0.0],
60
+ "Full Bass": [4.8, 5.76, 5.76, 3.36, 0.96, -2.4, -4.8, -6.24, -6.72, -6.72],
61
+ "Full Treble": [-5.76, -5.76, -5.76, -2.4, 1.44, 6.72, 9.6, 9.6, 9.6, 10.08],
62
+ "Full Bass & Treble": [4.32, 3.36, 0.0, -4.32, -2.88, 0.96, 4.8, 6.72, 7.2, 7.2],
63
+ "Large Hall": [6.24, 6.24, 3.36, 3.36, 0.0, -2.88, -2.88, -2.88, 0.0, 0.0],
64
+ "Party": [4.32, 4.32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.32, 4.32],
65
+ "Ska": [-1.44, -2.88, -2.4, 0.0, 2.4, 3.36, 5.28, 5.76, 6.72, 5.76],
66
+ "Soft": [2.88, 0.96, 0.0, -1.44, 0.0, 2.4, 4.8, 5.76, 6.72, 7.2],
67
+ "Soft Rock": [2.4, 2.4, 1.44, 0.0, -2.4, -3.36, -1.92, 0.0, 1.44, 5.28],
68
+ "Headphones": [2.88, 6.72, 3.36, -1.92, -1.44, 0.96, 2.88, 5.76, 7.68, 8.64],
69
+ "Laptop Speakers": [2.88, 6.72, 3.36, -1.92, -1.44, 0.96, 2.88, 5.76, 7.68, 8.64],
66
70
  ]
67
71
 
68
72
  // MARK: - Initialization
@@ -189,7 +193,7 @@ class EqualizerCore {
189
193
 
190
194
  func getBands() -> [EqualizerBand] {
191
195
  if let cached = cachedBands { return cached }
192
- let bands = (0..<5).map { i in
196
+ let bands = (0..<10).map { i in
193
197
  EqualizerBand(
194
198
  index: Double(i),
195
199
  centerFrequency: Double(frequencies[i]),
@@ -202,7 +206,7 @@ class EqualizerCore {
202
206
  }
203
207
 
204
208
  func setBandGain(bandIndex: Int, gainDb: Double) -> Bool {
205
- guard bandIndex >= 0 && bandIndex < 5 else { return false }
209
+ guard bandIndex >= 0 && bandIndex < 10 else { return false }
206
210
 
207
211
  let clampedGain = max(-12.0, min(12.0, gainDb))
208
212
  currentGains[bandIndex] = clampedGain
@@ -220,9 +224,9 @@ class EqualizerCore {
220
224
  }
221
225
 
222
226
  func setAllBandGains(_ gains: [Double]) -> Bool {
223
- guard gains.count == 5 else { return false }
227
+ guard gains.count == 10 else { return false }
224
228
 
225
- for i in 0..<5 {
229
+ for i in 0..<10 {
226
230
  currentGains[i] = max(-12.0, min(12.0, gains[i]))
227
231
  }
228
232
  cachedBands = nil
@@ -373,7 +377,7 @@ class EqualizerCore {
373
377
  }
374
378
 
375
379
  func reset() {
376
- _ = setAllBandGains([0, 0, 0, 0, 0])
380
+ _ = setAllBandGains([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
377
381
  currentPresetName = "Flat"
378
382
  notifyPresetChange("Flat")
379
383
  saveCurrentPreset("Flat")
@@ -400,10 +404,11 @@ class EqualizerCore {
400
404
 
401
405
  if let data = UserDefaults.standard.data(forKey: bandGainsKey),
402
406
  let gains = try? JSONDecoder().decode([Double].self, from: data),
403
- gains.count == 5
407
+ gains.count == 10
404
408
  {
405
409
  currentGains = gains
406
410
  }
411
+ // else: migration from 5-band or fresh install — start at flat (currentGains already zeroed)
407
412
 
408
413
  currentPresetName = UserDefaults.standard.string(forKey: currentPresetKey)
409
414
  isEqualizerEnabled = enabled
@@ -456,28 +461,28 @@ private class TapContext {
456
461
  var sampleRate: Float = 44100.0
457
462
  var channelCount: Int = 2
458
463
 
459
- // Biquad filter states for 5 bands
464
+ // Biquad filter states for 10 bands
460
465
  // Each band needs 4 delay elements per channel (x[n-1], x[n-2], y[n-1], y[n-2])
461
466
  var filterStates: [[Float]] = []
462
467
 
463
- // Biquad coefficients for 5 bands
468
+ // Biquad coefficients for 10 bands
464
469
  // Each band: [b0, b1, b2, a1, a2] (normalized, a0 = 1)
465
470
  var filterCoeffs: [[Double]] = []
466
471
 
467
472
  init(eqCore: EqualizerCore) {
468
473
  self.eqCore = eqCore
469
- // Initialize 5 bands with flat coefficients
470
- for _ in 0..<5 {
474
+ // Initialize 10 bands with flat coefficients
475
+ for _ in 0..<10 {
471
476
  filterCoeffs.append([1.0, 0.0, 0.0, 0.0, 0.0]) // Flat/bypass
472
477
  }
473
478
  }
474
479
 
475
480
  func updateCoefficients() {
476
481
  guard let eqCore = eqCore else { return }
477
- let frequencies: [Float] = [60, 230, 910, 3600, 14000]
482
+ let frequencies: [Float] = [31, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000]
478
483
  let gains = eqCore.currentGains
479
484
 
480
- for i in 0..<5 {
485
+ for i in 0..<10 {
481
486
  filterCoeffs[i] = calculatePeakingEQCoefficients(
482
487
  frequency: Double(frequencies[i]),
483
488
  gain: gains[i],
@@ -517,7 +522,7 @@ private class TapContext {
517
522
 
518
523
  func resetFilterStates() {
519
524
  filterStates = []
520
- for _ in 0..<5 {
525
+ for _ in 0..<10 {
521
526
  // 4 delay elements per channel (2 for input history, 2 for output history)
522
527
  filterStates.append(Array(repeating: Float(0.0), count: channelCount * 4))
523
528
  }
@@ -613,8 +618,8 @@ private func tapProcessCallback(
613
618
  let frameCount = Int(numberFramesOut.pointee)
614
619
  let samples = data.assumingMemoryBound(to: Float.self)
615
620
 
616
- // Apply all 5 EQ bands in series
617
- for bandIndex in 0..<5 {
621
+ // Apply all 10 EQ bands in series
622
+ for bandIndex in 0..<10 {
618
623
  let coeffs: [Double] = context.filterCoeffs[bandIndex]
619
624
 
620
625
  // Skip if essentially flat
@@ -2,11 +2,16 @@ import { useEffect, useState, useCallback, useRef } from 'react';
2
2
  import { Equalizer } from '../index';
3
3
  import { equalizerCallbackManager } from './equalizerCallbackManager';
4
4
  const DEFAULT_BANDS = [
5
- { index: 0, centerFrequency: 60, gainDb: 0, frequencyLabel: '60 Hz' },
6
- { index: 1, centerFrequency: 230, gainDb: 0, frequencyLabel: '230 Hz' },
7
- { index: 2, centerFrequency: 910, gainDb: 0, frequencyLabel: '910 Hz' },
8
- { index: 3, centerFrequency: 3600, gainDb: 0, frequencyLabel: '3.6 kHz' },
9
- { index: 4, centerFrequency: 14000, gainDb: 0, frequencyLabel: '14 kHz' },
5
+ { index: 0, centerFrequency: 31, gainDb: 0, frequencyLabel: '31 Hz' },
6
+ { index: 1, centerFrequency: 63, gainDb: 0, frequencyLabel: '63 Hz' },
7
+ { index: 2, centerFrequency: 125, gainDb: 0, frequencyLabel: '125 Hz' },
8
+ { index: 3, centerFrequency: 250, gainDb: 0, frequencyLabel: '250 Hz' },
9
+ { index: 4, centerFrequency: 500, gainDb: 0, frequencyLabel: '500 Hz' },
10
+ { index: 5, centerFrequency: 1000, gainDb: 0, frequencyLabel: '1 kHz' },
11
+ { index: 6, centerFrequency: 2000, gainDb: 0, frequencyLabel: '2 kHz' },
12
+ { index: 7, centerFrequency: 4000, gainDb: 0, frequencyLabel: '4 kHz' },
13
+ { index: 8, centerFrequency: 8000, gainDb: 0, frequencyLabel: '8 kHz' },
14
+ { index: 9, centerFrequency: 16000, gainDb: 0, frequencyLabel: '16 kHz' },
10
15
  ];
11
16
  export function useEqualizer() {
12
17
  const [isEnabled, setIsEnabledState] = useState(false);
@@ -12,7 +12,7 @@ export interface Equalizer extends HybridObject<{
12
12
  getBands(): Promise<EqualizerBand[]>;
13
13
  /** Set gain for a specific band index (-12 to +12 dB) */
14
14
  setBandGain(bandIndex: number, gainDb: number): Promise<void>;
15
- /** Set gains for all bands at once (array of 5 values) */
15
+ /** Set gains for all bands at once (array of 10 values) */
16
16
  setAllBandGains(gains: number[]): Promise<void>;
17
17
  /** Get the valid gain range for bands */
18
18
  getBandRange(): GainRange;
@@ -2,7 +2,7 @@
2
2
  * Represents a single equalizer frequency band
3
3
  */
4
4
  export interface EqualizerBand {
5
- /** Band index (0-4) */
5
+ /** Band index (0-9) */
6
6
  index: number;
7
7
  /** Center frequency in Hz */
8
8
  centerFrequency: number;
@@ -21,7 +21,7 @@ export type PresetType = 'built-in' | 'custom';
21
21
  export interface EqualizerPreset {
22
22
  /** Unique preset name */
23
23
  name: string;
24
- /** Array of 5 gain values in dB for each band */
24
+ /** Array of 10 gain values in dB for each band */
25
25
  gains: number[];
26
26
  /** Whether this is a built-in or custom preset */
27
27
  type: PresetType;
@@ -49,4 +49,4 @@ export interface GainRange {
49
49
  /**
50
50
  * Built-in preset names
51
51
  */
52
- export type BuiltInPresetName = 'Flat' | 'Bass Boost' | 'Bass Reducer' | 'Treble Boost' | 'Treble Reducer' | 'Vocal Boost' | 'Rock' | 'Pop' | 'Jazz' | 'Classical' | 'Hip Hop' | 'Electronic' | 'Acoustic' | 'R&B' | 'Loudness';
52
+ export type BuiltInPresetName = 'Flat' | 'Rock' | 'Pop' | 'Classical' | 'Dance' | 'Techno' | 'Club' | 'Live' | 'Reggae' | 'Full Bass' | 'Full Treble' | 'Full Bass & Treble' | 'Large Hall' | 'Party' | 'Ska' | 'Soft' | 'Soft Rock' | 'Headphones' | 'Laptop Speakers';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "0.7.1-alpha.0",
3
+ "version": "0.7.1-alpha.1",
4
4
  "description": "A powerful audio player library for React Native with playlist management, playback controls, and support for Android Auto and CarPlay",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -57,18 +57,18 @@
57
57
  "registry": "https://registry.npmjs.org/"
58
58
  },
59
59
  "devDependencies": {
60
- "@react-native/eslint-config": "0.82.0",
60
+ "@react-native/eslint-config": "0.85.0-rc.6",
61
61
  "@release-it-plugins/workspaces": "^5.0.3",
62
62
  "@release-it/bumper": "^7.0.5",
63
63
  "@release-it/conventional-changelog": "^10.0.1",
64
- "@types/react": "^19.1.03",
64
+ "@types/react": "^19.2.0",
65
65
  "eslint": "^8.57.0",
66
66
  "eslint-config-prettier": "^9.1.0",
67
67
  "eslint-plugin-prettier": "^5.2.1",
68
68
  "nitrogen": "^0.35.2",
69
69
  "prettier": "^3.3.3",
70
- "react": "19.1.1",
71
- "react-native": "0.82.0",
70
+ "react": "19.2.3",
71
+ "react-native": "0.85.0-rc.6",
72
72
  "react-native-nitro-modules": "^0.35.2",
73
73
  "release-it": "^19.0.4",
74
74
  "typescript": "^5.8.3"
@@ -25,11 +25,16 @@ export interface UseEqualizerResult {
25
25
  }
26
26
 
27
27
  const DEFAULT_BANDS: EqualizerBand[] = [
28
- { index: 0, centerFrequency: 60, gainDb: 0, frequencyLabel: '60 Hz' },
29
- { index: 1, centerFrequency: 230, gainDb: 0, frequencyLabel: '230 Hz' },
30
- { index: 2, centerFrequency: 910, gainDb: 0, frequencyLabel: '910 Hz' },
31
- { index: 3, centerFrequency: 3600, gainDb: 0, frequencyLabel: '3.6 kHz' },
32
- { index: 4, centerFrequency: 14000, gainDb: 0, frequencyLabel: '14 kHz' },
28
+ { index: 0, centerFrequency: 31, gainDb: 0, frequencyLabel: '31 Hz' },
29
+ { index: 1, centerFrequency: 63, gainDb: 0, frequencyLabel: '63 Hz' },
30
+ { index: 2, centerFrequency: 125, gainDb: 0, frequencyLabel: '125 Hz' },
31
+ { index: 3, centerFrequency: 250, gainDb: 0, frequencyLabel: '250 Hz' },
32
+ { index: 4, centerFrequency: 500, gainDb: 0, frequencyLabel: '500 Hz' },
33
+ { index: 5, centerFrequency: 1000, gainDb: 0, frequencyLabel: '1 kHz' },
34
+ { index: 6, centerFrequency: 2000, gainDb: 0, frequencyLabel: '2 kHz' },
35
+ { index: 7, centerFrequency: 4000, gainDb: 0, frequencyLabel: '4 kHz' },
36
+ { index: 8, centerFrequency: 8000, gainDb: 0, frequencyLabel: '8 kHz' },
37
+ { index: 9, centerFrequency: 16000, gainDb: 0, frequencyLabel: '16 kHz' },
33
38
  ]
34
39
 
35
40
  export function useEqualizer(): UseEqualizerResult {
@@ -22,7 +22,7 @@ export interface Equalizer
22
22
  /** Set gain for a specific band index (-12 to +12 dB) */
23
23
  setBandGain(bandIndex: number, gainDb: number): Promise<void>
24
24
 
25
- /** Set gains for all bands at once (array of 5 values) */
25
+ /** Set gains for all bands at once (array of 10 values) */
26
26
  setAllBandGains(gains: number[]): Promise<void>
27
27
 
28
28
  /** Get the valid gain range for bands */
@@ -2,7 +2,7 @@
2
2
  * Represents a single equalizer frequency band
3
3
  */
4
4
  export interface EqualizerBand {
5
- /** Band index (0-4) */
5
+ /** Band index (0-9) */
6
6
  index: number
7
7
  /** Center frequency in Hz */
8
8
  centerFrequency: number
@@ -23,7 +23,7 @@ export type PresetType = 'built-in' | 'custom'
23
23
  export interface EqualizerPreset {
24
24
  /** Unique preset name */
25
25
  name: string
26
- /** Array of 5 gain values in dB for each band */
26
+ /** Array of 10 gain values in dB for each band */
27
27
  gains: number[]
28
28
  /** Whether this is a built-in or custom preset */
29
29
  type: PresetType
@@ -56,17 +56,21 @@ export interface GainRange {
56
56
  */
57
57
  export type BuiltInPresetName =
58
58
  | 'Flat'
59
- | 'Bass Boost'
60
- | 'Bass Reducer'
61
- | 'Treble Boost'
62
- | 'Treble Reducer'
63
- | 'Vocal Boost'
64
59
  | 'Rock'
65
60
  | 'Pop'
66
- | 'Jazz'
67
61
  | 'Classical'
68
- | 'Hip Hop'
69
- | 'Electronic'
70
- | 'Acoustic'
71
- | 'R&B'
72
- | 'Loudness'
62
+ | 'Dance'
63
+ | 'Techno'
64
+ | 'Club'
65
+ | 'Live'
66
+ | 'Reggae'
67
+ | 'Full Bass'
68
+ | 'Full Treble'
69
+ | 'Full Bass & Treble'
70
+ | 'Large Hall'
71
+ | 'Party'
72
+ | 'Ska'
73
+ | 'Soft'
74
+ | 'Soft Rock'
75
+ | 'Headphones'
76
+ | 'Laptop Speakers'