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 +47 -46
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueue.kt +24 -10
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueueBuild.kt +16 -7
- package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +139 -77
- package/ios/core/TrackPlayerQueue.swift +27 -18
- package/ios/core/TrackPlayerQueueBuild.swift +16 -5
- package/ios/equalizer/EqualizerCore.swift +39 -34
- package/lib/hooks/useEqualizer.js +10 -5
- package/lib/specs/Equalizer.nitro.d.ts +1 -1
- package/lib/types/EqualizerTypes.d.ts +3 -3
- package/package.json +5 -5
- package/src/hooks/useEqualizer.ts +10 -5
- package/src/specs/Equalizer.nitro.ts +1 -1
- package/src/types/EqualizerTypes.ts +17 -13
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**.
|
|
60
|
-
| `getState()` | Both | **Async**.
|
|
61
|
-
| `skipToIndex(index)` | Both | **Async**. Skips to
|
|
62
|
-
| `configure(config)` | Both |
|
|
63
|
-
| `isAndroidAutoConnected()` | Both |
|
|
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
|
|
70
|
-
| `deletePlaylist(id)` | Both | Deletes a playlist by ID.
|
|
71
|
-
| `updatePlaylist(id, ...)` | Both | Updates
|
|
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
|
|
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
|
|
102
|
-
| `getAllDownloadedTracks()` | Both |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
TrackPlayer.
|
|
174
|
-
TrackPlayer.
|
|
175
|
-
TrackPlayer.
|
|
176
|
-
TrackPlayer.
|
|
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
|
-
|
|
180
|
-
TrackPlayer.setRepeatMode('
|
|
181
|
-
TrackPlayer.setRepeatMode('
|
|
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
|
-
|
|
185
|
-
TrackPlayer.setVolume(
|
|
186
|
-
TrackPlayer.setVolume(
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
91
|
-
if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT &&
|
|
92
|
-
|
|
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
|
|
128
|
-
|
|
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
|
|
147
|
+
val targetTrack = actualQueue[index]
|
|
136
148
|
playNextStack.clear()
|
|
137
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
63
|
-
if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT &&
|
|
64
|
-
|
|
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.
|
|
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
|
|
29
|
-
private val targetFrequencies = intArrayOf(
|
|
30
|
-
private val frequencyLabels = arrayOf("
|
|
31
|
-
private val frequencies = intArrayOf(
|
|
32
|
-
private var bandMapping = IntArray(
|
|
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 -> [
|
|
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
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
return (0 until 5)
|
|
178
|
+
return (0 until 10)
|
|
147
179
|
.map { i ->
|
|
148
|
-
val
|
|
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..
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
225
|
+
fun setAllBandGains(gains: DoubleArray): Boolean {
|
|
226
|
+
if (gains.size != 10) return false
|
|
192
227
|
|
|
193
228
|
return try {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
401
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
97
|
-
if currentTemporaryType == .upNext
|
|
98
|
-
|
|
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
|
|
157
|
-
//
|
|
158
|
-
let
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
244
|
-
|
|
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
|
|
26
|
-
let frequencies: [Float] = [
|
|
27
|
-
private let frequencyLabels = ["
|
|
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":
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
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..<
|
|
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 <
|
|
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 ==
|
|
227
|
+
guard gains.count == 10 else { return false }
|
|
224
228
|
|
|
225
|
-
for i in 0..<
|
|
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 ==
|
|
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
|
|
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
|
|
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
|
|
470
|
-
for _ in 0..<
|
|
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] = [
|
|
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..<
|
|
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..<
|
|
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
|
|
617
|
-
for bandIndex in 0..<
|
|
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:
|
|
6
|
-
{ index: 1, centerFrequency:
|
|
7
|
-
{ index: 2, centerFrequency:
|
|
8
|
-
{ index: 3, centerFrequency:
|
|
9
|
-
{ index: 4, centerFrequency:
|
|
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
|
|
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-
|
|
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
|
|
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' | '
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
71
|
-
"react-native": "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:
|
|
29
|
-
{ index: 1, centerFrequency:
|
|
30
|
-
{ index: 2, centerFrequency:
|
|
31
|
-
{ index: 3, centerFrequency:
|
|
32
|
-
{ index: 4, centerFrequency:
|
|
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
|
|
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-
|
|
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
|
|
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
|
-
| '
|
|
69
|
-
| '
|
|
70
|
-
| '
|
|
71
|
-
| '
|
|
72
|
-
| '
|
|
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'
|