feat(player): живой эквалайзер по реальному звуку (FFT-спектр)
Эквалайзер на плеере больше не декоративная синус-волна — реагирует на реальный звук. Через TeeAudioProcessor подключаемся к декодированному PCM в аудио-конвейере ExoPlayer (без разрешений/микрофона), считаем FFT → лог-полосы (AudioSpectrumAnalyzer), PlayerController отдаёт спектр StateFlow'ом, LiveEqualizer рисует столбики по уровням (с быстрым ростом/плавным спадом). Когда звука нет (пауза/float-выход) — фолбэк на прежнюю синус-волну. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,9 +13,14 @@ import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Metadata
|
||||
import androidx.media3.common.Player
|
||||
import android.util.Log
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.audio.AudioSink
|
||||
import androidx.media3.exoplayer.audio.DefaultAudioSink
|
||||
import androidx.media3.exoplayer.audio.TeeAudioProcessor
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.extractor.metadata.icy.IcyInfo
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -25,10 +30,30 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@UnstableApi
|
||||
@Singleton
|
||||
class PlayerController @Inject constructor(
|
||||
@ApplicationContext context: Context
|
||||
) {
|
||||
// Анализатор спектра реального звука — для «живого» эквалайзера.
|
||||
private val spectrumAnalyzer = AudioSpectrumAnalyzer()
|
||||
val spectrum: StateFlow<FloatArray> = spectrumAnalyzer.spectrum
|
||||
|
||||
// RenderersFactory, который вставляет наш tee-процессор в аудио-конвейер
|
||||
// (читает декодированный PCM, не меняя звук).
|
||||
private val renderersFactory = object : DefaultRenderersFactory(context) {
|
||||
override fun buildAudioSink(
|
||||
context: Context,
|
||||
enableFloatOutput: Boolean,
|
||||
enableAudioTrackPlaybackParams: Boolean,
|
||||
): AudioSink {
|
||||
return DefaultAudioSink.Builder(context)
|
||||
.setEnableFloatOutput(enableFloatOutput)
|
||||
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
|
||||
.setAudioProcessors(arrayOf(TeeAudioProcessor(spectrumAnalyzer)))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
private val _isPlaying = MutableStateFlow(false)
|
||||
val isPlaying: StateFlow<Boolean> = _isPlaying
|
||||
|
||||
@@ -88,6 +113,7 @@ class PlayerController @Inject constructor(
|
||||
)
|
||||
|
||||
private val exoPlayer: ExoPlayer = ExoPlayer.Builder(context)
|
||||
.setRenderersFactory(renderersFactory)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
|
||||
Reference in New Issue
Block a user