@javascriptcommon/react-native-track-player 4.1.23 → 4.1.25
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/android/src/main/java/com/doublesymmetry/trackplayer/model/Track.kt +1 -1
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +43 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +30 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioItem.kt +3 -2
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/AudioPlayer.kt +99 -114
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/APMRenderersFactory.kt +9 -5
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/processors/BalanceAudioProcessor.kt +66 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/processors/EqualizerAudioProcessor.kt +517 -0
- package/android/src/main/res/raw/silent_5_seconds.mp3 +0 -0
- package/ios/RNTrackPlayer/RNTrackPlayer.swift +56 -4
- package/ios/RNTrackPlayer/TrackPlayer.mm +30 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioTap.swift +21 -18
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/EqualizerAudioTap.swift +565 -232
- package/lib/specs/NativeTrackPlayer.d.ts +7 -0
- package/lib/src/trackPlayer.d.ts +35 -0
- package/lib/src/trackPlayer.js +50 -0
- package/package.json +1 -1
- package/specs/NativeTrackPlayer.ts +9 -0
- package/src/trackPlayer.ts +58 -0
|
@@ -62,7 +62,7 @@ class Track
|
|
|
62
62
|
headers!![header] = httpHeaders.getString(header)!!
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
notPlayable = bundle.getBoolean("notPlayable", false)
|
|
65
|
+
notPlayable = bundle.getBoolean("notPlayable", false) || uri == null || uri.toString().isBlank()
|
|
66
66
|
setMetadata(context, bundle, ratingType)
|
|
67
67
|
queueId = System.currentTimeMillis()
|
|
68
68
|
originalItem = bundle
|
|
@@ -389,6 +389,49 @@ class MusicModule(reactContext: ReactApplicationContext) : NativeTrackPlayerSpec
|
|
|
389
389
|
callback.resolve(null)
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
// Audio Effects (BassBoost, Loudness, Virtualizer)
|
|
393
|
+
|
|
394
|
+
override fun setBassBoostEnabled(enabled: Boolean, callback: Promise) = launchInScope {
|
|
395
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
396
|
+
musicService.setBassBoostEnabled(enabled)
|
|
397
|
+
callback.resolve(null)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
override fun setBassBoostLevel(level: Double, callback: Promise) = launchInScope {
|
|
401
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
402
|
+
musicService.setBassBoostLevel(level.toFloat())
|
|
403
|
+
callback.resolve(null)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
override fun setLoudnessEnabled(enabled: Boolean, callback: Promise) = launchInScope {
|
|
407
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
408
|
+
musicService.setLoudnessEnabled(enabled)
|
|
409
|
+
callback.resolve(null)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
override fun setLoudnessLevel(level: Double, callback: Promise) = launchInScope {
|
|
413
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
414
|
+
musicService.setLoudnessLevel(level.toFloat())
|
|
415
|
+
callback.resolve(null)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
override fun setVirtualizerEnabled(enabled: Boolean, callback: Promise) = launchInScope {
|
|
419
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
420
|
+
musicService.setVirtualizerEnabled(enabled)
|
|
421
|
+
callback.resolve(null)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
override fun setVirtualizerLevel(level: Double, callback: Promise) = launchInScope {
|
|
425
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
426
|
+
musicService.setVirtualizerLevel(level.toFloat())
|
|
427
|
+
callback.resolve(null)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
override fun setBalance(balance: Double, callback: Promise) = launchInScope {
|
|
431
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
432
|
+
musicService.setBalance(balance.toFloat())
|
|
433
|
+
callback.resolve(null)
|
|
434
|
+
}
|
|
392
435
|
|
|
393
436
|
override fun add(data: ReadableArray?, insertBeforeIndex: Double, callback: Promise) = launchInScope {
|
|
394
437
|
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
@@ -201,6 +201,36 @@ class MusicService : HeadlessJsMediaService() {
|
|
|
201
201
|
player.resetEqualizer()
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
// Audio Effects (BassBoost, Loudness, Virtualizer)
|
|
205
|
+
|
|
206
|
+
fun setBassBoostEnabled(enabled: Boolean) {
|
|
207
|
+
player.setBassBoostEnabled(enabled)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fun setBassBoostLevel(level: Float) {
|
|
211
|
+
player.setBassBoostLevel(level)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fun setLoudnessEnabled(enabled: Boolean) {
|
|
215
|
+
player.setLoudnessEnabled(enabled)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fun setLoudnessLevel(level: Float) {
|
|
219
|
+
player.setLoudnessLevel(level)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fun setVirtualizerEnabled(enabled: Boolean) {
|
|
223
|
+
player.setVirtualizerEnabled(enabled)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fun setVirtualizerLevel(level: Float) {
|
|
227
|
+
player.setVirtualizerLevel(level)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fun setBalance(balance: Float) {
|
|
231
|
+
player.setBalance(balance)
|
|
232
|
+
}
|
|
233
|
+
|
|
204
234
|
fun crossFadePrepare(previous: Boolean = false, seekTo: Double = 0.0) {
|
|
205
235
|
player.crossFadePrepare(previous, seekTo)
|
|
206
236
|
}
|
|
@@ -8,6 +8,8 @@ import androidx.media3.common.MediaItem
|
|
|
8
8
|
import androidx.media3.common.MediaMetadata
|
|
9
9
|
import com.lovegaoshi.kotlinaudio.utils.getEmbeddedBitmapArray
|
|
10
10
|
import com.lovegaoshi.kotlinaudio.utils.saveMediaCoverToPng
|
|
11
|
+
import androidx.media3.datasource.RawResourceDataSource
|
|
12
|
+
import com.doublesymmetry.trackplayer.R
|
|
11
13
|
import java.util.UUID
|
|
12
14
|
|
|
13
15
|
|
|
@@ -83,8 +85,7 @@ fun audioItem2MediaItem(audioItem: AudioItem, context: Context? = null): MediaIt
|
|
|
83
85
|
|
|
84
86
|
return MediaItem.Builder()
|
|
85
87
|
.setMediaId(audioItem.mediaId ?: UUID.randomUUID().toString())
|
|
86
|
-
|
|
87
|
-
.setUri(audioItem.audioUrl)
|
|
88
|
+
.setUri(if (hasValidUrl && !isNotPlayable) audioItem.audioUrl else RawResourceDataSource.buildRawResourceUri(R.raw.silent_5_seconds).toString())
|
|
88
89
|
.setMediaMetadata(
|
|
89
90
|
MediaMetadata.Builder()
|
|
90
91
|
.setTitle(audioItem.title)
|
|
@@ -38,6 +38,8 @@ import com.lovegaoshi.kotlinaudio.player.components.Cache
|
|
|
38
38
|
import com.lovegaoshi.kotlinaudio.player.components.FocusManager
|
|
39
39
|
import com.lovegaoshi.kotlinaudio.player.components.MediaFactory
|
|
40
40
|
import com.lovegaoshi.kotlinaudio.player.components.setupBuffer
|
|
41
|
+
import com.lovegaoshi.kotlinaudio.processors.BalanceAudioProcessor
|
|
42
|
+
import com.lovegaoshi.kotlinaudio.processors.EqualizerAudioProcessor
|
|
41
43
|
import com.lovegaoshi.kotlinaudio.processors.FFTEmitter
|
|
42
44
|
import kotlinx.coroutines.Deferred
|
|
43
45
|
import kotlinx.coroutines.MainScope
|
|
@@ -70,6 +72,8 @@ abstract class AudioPlayer internal constructor(
|
|
|
70
72
|
private val focusListener = APMFocusListener()
|
|
71
73
|
private val focusManager = FocusManager(context, listener=focusListener, options=options)
|
|
72
74
|
var fftEmitter: (DoubleArray) -> Unit = { v -> Timber.tag("APMFFT").d("FFT emitted $v") }
|
|
75
|
+
private val balanceProcessor = BalanceAudioProcessor()
|
|
76
|
+
private val equalizerProcessor = EqualizerAudioProcessor()
|
|
73
77
|
|
|
74
78
|
var alwaysPauseOnInterruption: Boolean
|
|
75
79
|
get() = focusManager.alwaysPauseOnInterruption
|
|
@@ -209,7 +213,9 @@ abstract class AudioPlayer internal constructor(
|
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
}) else
|
|
216
|
+
}, arrayOf(equalizerProcessor, balanceProcessor)) else APMRenderersFactory(
|
|
217
|
+
context, 0, null, arrayOf(equalizerProcessor, balanceProcessor)
|
|
218
|
+
)
|
|
213
219
|
renderer.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
|
|
214
220
|
val mPlayer = ExoPlayer
|
|
215
221
|
.Builder(context)
|
|
@@ -295,152 +301,122 @@ abstract class AudioPlayer internal constructor(
|
|
|
295
301
|
.map { i -> equalizers[0].getPresetName(i.toShort()) }
|
|
296
302
|
}
|
|
297
303
|
|
|
298
|
-
//
|
|
304
|
+
// 8-band Software Equalizer API (cross-platform, biquad with coefficient smoothing)
|
|
299
305
|
|
|
300
|
-
/**
|
|
301
|
-
* Enable or disable the equalizer
|
|
302
|
-
*/
|
|
303
306
|
fun setEqualizerEnabled(enabled: Boolean) {
|
|
304
|
-
|
|
305
|
-
equalizer.enabled = enabled
|
|
306
|
-
}
|
|
307
|
+
equalizerProcessor.isEnabled = enabled
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
|
|
310
|
-
* Check if the equalizer is enabled
|
|
311
|
-
*/
|
|
312
|
-
fun getEqualizerEnabled(): Boolean {
|
|
313
|
-
if (equalizers.isEmpty()) return false
|
|
314
|
-
return equalizers[0].enabled
|
|
315
|
-
}
|
|
310
|
+
fun getEqualizerEnabled(): Boolean = equalizerProcessor.isEnabled
|
|
316
311
|
|
|
317
|
-
|
|
318
|
-
* Get the number of equalizer bands available
|
|
319
|
-
*/
|
|
320
|
-
fun getEqualizerBandCount(): Int {
|
|
321
|
-
if (equalizers.isEmpty()) return 0
|
|
322
|
-
return equalizers[0].numberOfBands.toInt()
|
|
323
|
-
}
|
|
312
|
+
fun getEqualizerBandCount(): Int = EqualizerAudioProcessor.BAND_COUNT
|
|
324
313
|
|
|
325
|
-
/**
|
|
326
|
-
* Set the gain for a specific equalizer band
|
|
327
|
-
* @param band Band index (0 to bandCount-1)
|
|
328
|
-
* @param gainDB Gain in millibels (mB). 1 dB = 100 mB
|
|
329
|
-
*/
|
|
330
314
|
fun setEqualizerBand(band: Int, gainDB: Float) {
|
|
331
|
-
|
|
332
|
-
if (band >= 0 && band < equalizer.numberOfBands) {
|
|
333
|
-
// Convert dB to millibels (mB)
|
|
334
|
-
val millibels = (gainDB * 100).toInt().toShort()
|
|
335
|
-
val range = equalizer.bandLevelRange
|
|
336
|
-
val clampedLevel = millibels.coerceIn(range[0], range[1])
|
|
337
|
-
equalizer.setBandLevel(band.toShort(), clampedLevel)
|
|
338
|
-
equalizer.enabled = true
|
|
339
|
-
}
|
|
340
|
-
}
|
|
315
|
+
equalizerProcessor.setGain(band, gainDB)
|
|
341
316
|
}
|
|
342
317
|
|
|
343
|
-
/**
|
|
344
|
-
* Set gains for all equalizer bands at once
|
|
345
|
-
* @param gainsDB Array of gain values in dB
|
|
346
|
-
*/
|
|
347
318
|
fun setEqualizerBands(gainsDB: List<Float>) {
|
|
348
|
-
|
|
349
|
-
val bandCount = equalizer.numberOfBands.toInt()
|
|
350
|
-
val range = equalizer.bandLevelRange
|
|
351
|
-
for (i in 0 until minOf(gainsDB.size, bandCount)) {
|
|
352
|
-
val millibels = (gainsDB[i] * 100).toInt().toShort()
|
|
353
|
-
val clampedLevel = millibels.coerceIn(range[0], range[1])
|
|
354
|
-
equalizer.setBandLevel(i.toShort(), clampedLevel)
|
|
355
|
-
}
|
|
356
|
-
equalizer.enabled = true
|
|
357
|
-
}
|
|
319
|
+
equalizerProcessor.setAllGains(gainsDB)
|
|
358
320
|
}
|
|
359
321
|
|
|
360
|
-
/**
|
|
361
|
-
* Get all current equalizer band gains
|
|
362
|
-
* @return Array of gain values in dB
|
|
363
|
-
*/
|
|
364
322
|
fun getEqualizerBands(): List<Float> {
|
|
365
|
-
|
|
366
|
-
val equalizer = equalizers[0]
|
|
367
|
-
val bandCount = equalizer.numberOfBands.toInt()
|
|
368
|
-
return (0 until bandCount).map { band ->
|
|
369
|
-
// Convert millibels to dB
|
|
370
|
-
equalizer.getBandLevel(band.toShort()).toFloat() / 100f
|
|
371
|
-
}
|
|
323
|
+
return equalizerProcessor.getAllGains().toList()
|
|
372
324
|
}
|
|
373
325
|
|
|
374
|
-
/**
|
|
375
|
-
* Get the center frequencies for each equalizer band
|
|
376
|
-
* @return Array of frequency values in Hz
|
|
377
|
-
*/
|
|
378
326
|
fun getEqualizerFrequencies(): List<Int> {
|
|
379
|
-
|
|
380
|
-
val equalizer = equalizers[0]
|
|
381
|
-
val bandCount = equalizer.numberOfBands.toInt()
|
|
382
|
-
return (0 until bandCount).map { band ->
|
|
383
|
-
// getCenterFreq returns milliHz, convert to Hz
|
|
384
|
-
(equalizer.getCenterFreq(band.toShort()) / 1000)
|
|
385
|
-
}
|
|
327
|
+
return EqualizerAudioProcessor.FREQUENCIES.map { it.toInt() }
|
|
386
328
|
}
|
|
387
329
|
|
|
388
|
-
|
|
389
|
-
* Get the band level range in dB [min, max]
|
|
390
|
-
*/
|
|
391
|
-
fun getEqualizerBandLevelRange(): List<Float> {
|
|
392
|
-
if (equalizers.isEmpty()) return listOf(-12f, 12f)
|
|
393
|
-
val range = equalizers[0].bandLevelRange
|
|
394
|
-
// Convert millibels to dB
|
|
395
|
-
return listOf(range[0].toFloat() / 100f, range[1].toFloat() / 100f)
|
|
396
|
-
}
|
|
330
|
+
fun getEqualizerBandLevelRange(): List<Float> = listOf(-12f, 12f)
|
|
397
331
|
|
|
398
|
-
/**
|
|
399
|
-
* Reset all equalizer bands to 0 (flat response)
|
|
400
|
-
*/
|
|
401
332
|
fun resetEqualizer() {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
333
|
+
equalizerProcessor.resetGains()
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Bass Boost API (software DSP — matches iOS low shelf filter)
|
|
337
|
+
|
|
338
|
+
fun setBassBoostEnabled(enabled: Boolean) {
|
|
339
|
+
equalizerProcessor.isBassBoostEnabled = enabled
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fun setBassBoostLevel(level: Float) {
|
|
343
|
+
equalizerProcessor.updateBassBoostLevel(level)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Loudness Enhancer API (software DSP — matches iOS low+high shelf)
|
|
347
|
+
|
|
348
|
+
fun setLoudnessEnabled(enabled: Boolean) {
|
|
349
|
+
equalizerProcessor.isLoudnessEnabled = enabled
|
|
408
350
|
}
|
|
409
351
|
|
|
352
|
+
fun setLoudnessLevel(level: Float) {
|
|
353
|
+
equalizerProcessor.updateLoudnessLevel(level)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Virtualizer API (software DSP — matches iOS all-pass stereo widener)
|
|
357
|
+
|
|
358
|
+
fun setVirtualizerEnabled(enabled: Boolean) {
|
|
359
|
+
equalizerProcessor.isVirtualizerEnabled = enabled
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fun setVirtualizerLevel(level: Float) {
|
|
363
|
+
equalizerProcessor.updateVirtualizerLevel(level)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Balance API
|
|
367
|
+
|
|
368
|
+
fun setBalance(balance: Float) {
|
|
369
|
+
balanceProcessor.setBalance(balance)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
fun getBalance(): Float = balanceProcessor.getBalance()
|
|
373
|
+
|
|
410
374
|
/**
|
|
411
375
|
* Get preset names for iOS compatibility (custom presets mapped to Android system presets)
|
|
412
376
|
*/
|
|
413
377
|
fun getEqualizerPresetNames(): List<String> {
|
|
414
|
-
//
|
|
378
|
+
// Must match iOS EqualizerAudioTap.Preset order
|
|
415
379
|
return listOf(
|
|
416
|
-
"
|
|
417
|
-
"
|
|
418
|
-
"
|
|
380
|
+
"eqAcoustic", "eqBassBooster", "eqBassReducer", "eqClassical",
|
|
381
|
+
"eqDance", "eqDeep", "eqElectronic", "eqFlat",
|
|
382
|
+
"eqHipHop", "eqJazz", "eqLatin", "eqLoudness",
|
|
383
|
+
"eqLounge", "eqPiano", "eqPop", "eqRnb",
|
|
384
|
+
"eqRock", "eqSmallSpeakers", "eqSpokenWord",
|
|
385
|
+
"eqTrebleBooster", "eqTrebleReducer", "eqVocalBooster"
|
|
419
386
|
)
|
|
420
387
|
}
|
|
421
388
|
|
|
422
389
|
/**
|
|
423
|
-
* Apply a preset by index (iOS
|
|
424
|
-
*
|
|
390
|
+
* Apply a preset by index (matches iOS preset order).
|
|
391
|
+
* 8 bands: 60, 150, 400, 1K, 2.5K, 6K, 12K, 16K — same as software EQ.
|
|
425
392
|
*/
|
|
426
393
|
fun applyEqualizerPreset(presetIndex: Int) {
|
|
427
394
|
val presets = listOf(
|
|
428
|
-
listOf(
|
|
429
|
-
listOf(5f,
|
|
430
|
-
listOf(-
|
|
431
|
-
listOf(
|
|
432
|
-
listOf(
|
|
433
|
-
listOf(
|
|
434
|
-
listOf(
|
|
435
|
-
listOf(
|
|
436
|
-
listOf(
|
|
437
|
-
listOf(
|
|
438
|
-
listOf(
|
|
439
|
-
listOf(5f,
|
|
395
|
+
listOf( 3f, 2f, 1f, 0f, 1f, 2f, 2f, 2f), // Acoustic
|
|
396
|
+
listOf( 6f, 5f, 3f, 1f, 0f, 0f, 0f, 0f), // Bass Booster
|
|
397
|
+
listOf(-6f, -5f, -3f, -1f, 0f, 0f, 0f, 0f), // Bass Reducer
|
|
398
|
+
listOf( 4f, 2f, 0f, -1f, 0f, 2f, 3f, 3f), // Classical
|
|
399
|
+
listOf( 5f, 4f, 1f, 0f, 2f, 4f, 3f, 2f), // Dance
|
|
400
|
+
listOf( 5f, 4f, 2f, 1f, 0f, -1f, -2f, -3f), // Deep
|
|
401
|
+
listOf( 4f, 3f, 0f, -1f, 0f, 3f, 4f, 4f), // Electronic
|
|
402
|
+
listOf( 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f), // Flat
|
|
403
|
+
listOf( 5f, 4f, 2f, 0f, -1f, 1f, 2f, 3f), // Hip-Hop
|
|
404
|
+
listOf( 3f, 2f, 1f, 0f, -1f, 1f, 2f, 2f), // Jazz
|
|
405
|
+
listOf( 4f, 3f, 0f, -1f, 0f, 2f, 4f, 3f), // Latin
|
|
406
|
+
listOf( 5f, 3f, 0f, -2f, 0f, 2f, 4f, 4f), // Loudness
|
|
407
|
+
listOf(-1f, 1f, 2f, 1f, 0f, -1f, 1f, 1f), // Lounge
|
|
408
|
+
listOf( 1f, 0f, 1f, 2f, 3f, 2f, 2f, 1f), // Piano
|
|
409
|
+
listOf(-1f, 1f, 3f, 3f, 2f, 1f, 1f, 1f), // Pop
|
|
410
|
+
listOf( 5f, 4f, 2f, 0f, -1f, 1f, 2f, 2f), // R&B
|
|
411
|
+
listOf( 4f, 3f, 0f, -1f, 1f, 3f, 4f, 3f), // Rock
|
|
412
|
+
listOf( 5f, 4f, 3f, 1f, 0f, -1f, 2f, 3f), // Small Speakers
|
|
413
|
+
listOf(-2f, 0f, 1f, 3f, 4f, 3f, 1f, -1f), // Spoken Word
|
|
414
|
+
listOf( 0f, 0f, 0f, 0f, 1f, 3f, 5f, 6f), // Treble Booster
|
|
415
|
+
listOf( 0f, 0f, 0f, 0f, -1f, -3f, -5f, -6f), // Treble Reducer
|
|
416
|
+
listOf(-2f, -1f, 1f, 3f, 4f, 3f, 1f, 0f) // Vocal Booster
|
|
440
417
|
)
|
|
441
|
-
if (presetIndex
|
|
442
|
-
|
|
443
|
-
}
|
|
418
|
+
if (presetIndex < 0 || presetIndex >= presets.size) return
|
|
419
|
+
setEqualizerBands(presets[presetIndex])
|
|
444
420
|
}
|
|
445
421
|
|
|
446
422
|
fun togglePlaying() {
|
|
@@ -605,7 +581,13 @@ abstract class AudioPlayer internal constructor(
|
|
|
605
581
|
inner class AudioFxInitListener: AnalyticsListener {
|
|
606
582
|
@OptIn(UnstableApi::class)
|
|
607
583
|
override fun onAudioSessionIdChanged(eventTime: AnalyticsListener.EventTime, audioSessionId: Int) {
|
|
608
|
-
//
|
|
584
|
+
// Release old native effects before creating new ones
|
|
585
|
+
loudnessEnhancers.forEach { try { it.release() } catch (_: Exception) {} }
|
|
586
|
+
loudnessEnhancers.clear()
|
|
587
|
+
equalizers.forEach { try { it.release() } catch (_: Exception) {} }
|
|
588
|
+
equalizers.clear()
|
|
589
|
+
|
|
590
|
+
// Native LoudnessEnhancer (only for setLoudnessEnhance legacy API)
|
|
609
591
|
try {
|
|
610
592
|
val enhancer = LoudnessEnhancer(audioSessionId)
|
|
611
593
|
loudnessEnhancers.add(enhancer)
|
|
@@ -613,13 +595,16 @@ abstract class AudioPlayer internal constructor(
|
|
|
613
595
|
Timber.tag("APMAudioFx").e("[AudioFx] failed to load loudnessEnhancer. it's fine if in dev!")
|
|
614
596
|
}
|
|
615
597
|
|
|
616
|
-
//
|
|
598
|
+
// Native Equalizer (only for setEqualizerPreset legacy API)
|
|
617
599
|
try {
|
|
618
600
|
val equalizer = Equalizer(0, audioSessionId)
|
|
619
601
|
equalizers.add(equalizer)
|
|
620
602
|
} catch (e: RuntimeException) {
|
|
621
603
|
Timber.tag("APMAudioFx").e("[AudioFx] failed to load equalizer. it's fine if in dev!")
|
|
622
604
|
}
|
|
605
|
+
|
|
606
|
+
// Bass boost, loudness, virtualizer, EQ bands — all handled by
|
|
607
|
+
// software EqualizerAudioProcessor (no native effects needed)
|
|
623
608
|
}
|
|
624
609
|
}
|
|
625
610
|
|
package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/APMRenderersFactory.kt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.lovegaoshi.kotlinaudio.player.components
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import androidx.media3.common.audio.AudioProcessor
|
|
4
5
|
import androidx.media3.common.util.UnstableApi
|
|
5
6
|
import androidx.media3.exoplayer.DefaultRenderersFactory
|
|
6
7
|
import androidx.media3.exoplayer.audio.AudioSink
|
|
@@ -13,21 +14,24 @@ import com.lovegaoshi.kotlinaudio.processors.TeeListener
|
|
|
13
14
|
class APMRenderersFactory(
|
|
14
15
|
context: Context,
|
|
15
16
|
sampleRate: Int = 4096,
|
|
16
|
-
emitter: FFTEmitter
|
|
17
|
+
emitter: FFTEmitter?,
|
|
18
|
+
private val extraProcessors: Array<AudioProcessor> = emptyArray()
|
|
17
19
|
) : DefaultRenderersFactory(context) {
|
|
18
|
-
val teeProcessor =
|
|
19
|
-
|
|
20
|
+
val teeProcessor = if (sampleRate > 0 && emitter != null)
|
|
21
|
+
TeeAudioProcessor(TeeListener(sampleRate, emitter)) else null
|
|
20
22
|
|
|
21
23
|
override fun buildAudioSink(
|
|
22
24
|
context: Context,
|
|
23
25
|
enableFloatOutput: Boolean,
|
|
24
26
|
enableAudioTrackPlaybackParams: Boolean
|
|
25
27
|
): AudioSink? {
|
|
28
|
+
val processors = if (teeProcessor != null)
|
|
29
|
+
arrayOf(*extraProcessors, teeProcessor) else arrayOf(*extraProcessors)
|
|
26
30
|
return DefaultAudioSink.Builder(context)
|
|
27
31
|
.setEnableFloatOutput(enableFloatOutput)
|
|
28
32
|
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
|
|
29
|
-
.setAudioProcessors(
|
|
33
|
+
.setAudioProcessors(processors)
|
|
30
34
|
.build()
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
package com.lovegaoshi.kotlinaudio.processors
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.C
|
|
4
|
+
import androidx.media3.common.audio.AudioProcessor
|
|
5
|
+
import androidx.media3.common.audio.BaseAudioProcessor
|
|
6
|
+
import androidx.media3.common.util.UnstableApi
|
|
7
|
+
import java.nio.ByteBuffer
|
|
8
|
+
|
|
9
|
+
@UnstableApi
|
|
10
|
+
class BalanceAudioProcessor : BaseAudioProcessor() {
|
|
11
|
+
@Volatile
|
|
12
|
+
private var balance: Float = 0f
|
|
13
|
+
|
|
14
|
+
fun setBalance(value: Float) {
|
|
15
|
+
balance = value.coerceIn(-1f, 1f)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fun getBalance(): Float = balance
|
|
19
|
+
|
|
20
|
+
override fun onConfigure(inputAudioFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat {
|
|
21
|
+
if (inputAudioFormat.channelCount == 2 &&
|
|
22
|
+
(inputAudioFormat.encoding == C.ENCODING_PCM_16BIT || inputAudioFormat.encoding == C.ENCODING_PCM_FLOAT)
|
|
23
|
+
) {
|
|
24
|
+
return inputAudioFormat
|
|
25
|
+
}
|
|
26
|
+
return AudioProcessor.AudioFormat.NOT_SET
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun queueInput(inputBuffer: ByteBuffer) {
|
|
30
|
+
val bal = balance
|
|
31
|
+
if (bal == 0f) {
|
|
32
|
+
val output = replaceOutputBuffer(inputBuffer.remaining())
|
|
33
|
+
if (output !== inputBuffer) {
|
|
34
|
+
output.put(inputBuffer)
|
|
35
|
+
}
|
|
36
|
+
output.flip()
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
val leftGain = if (bal <= 0f) 1f else 1f - bal
|
|
41
|
+
val rightGain = if (bal >= 0f) 1f else 1f + bal
|
|
42
|
+
|
|
43
|
+
val size = inputBuffer.remaining()
|
|
44
|
+
val output = replaceOutputBuffer(size)
|
|
45
|
+
|
|
46
|
+
when (inputAudioFormat.encoding) {
|
|
47
|
+
C.ENCODING_PCM_16BIT -> {
|
|
48
|
+
while (inputBuffer.hasRemaining()) {
|
|
49
|
+
val left = inputBuffer.short
|
|
50
|
+
val right = inputBuffer.short
|
|
51
|
+
output.putShort((left * leftGain).toInt().coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort())
|
|
52
|
+
output.putShort((right * rightGain).toInt().coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort())
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
C.ENCODING_PCM_FLOAT -> {
|
|
56
|
+
while (inputBuffer.hasRemaining()) {
|
|
57
|
+
val left = inputBuffer.float
|
|
58
|
+
val right = inputBuffer.float
|
|
59
|
+
output.putFloat(left * leftGain)
|
|
60
|
+
output.putFloat(right * rightGain)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
output.flip()
|
|
65
|
+
}
|
|
66
|
+
}
|