perf(android): батарея и плавность — gate FFT, изоляция рекомпозиции, поллинг на паузе
- AudioSpectrumAnalyzer: FFT считается ТОЛЬКО когда открыт плеер (флаг active); раньше ~86 FFT/с молотили всегда при проигрывании (даже экран выкл) — главный пожиратель батареи. Включается из VisualizerHost через DisposableEffect. - Спектр (45/с) собирается в leaf VisualizerHost, а не на верху PlayerBottomSheet — весь плеер больше не рекомпозится 45 раз/сек. - now-playing поллинг (5с) останавливается на паузе (isPlaying.collectLatest) — раньше на паузе зря дёргали сеть каждые 5с. - PlayerService.onDestroy отменяет serviceScope (singleton-плеер НЕ релизим). - refreshStations (парс ~700 станций + сеть + Room) уведён на Dispatchers.IO с главного потока (jank/ANR на старте). - Coil ImageLoader: память 25% + диск 100МБ (обложки не перекачиваются каждую сессию). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -87,7 +87,6 @@ fun PlayerBottomSheet(
|
||||
var selectedSound by remember { mutableStateOf<com.radiola.service.SleepSound?>(null) }
|
||||
val currentQuality by viewModel.currentQuality.collectAsState()
|
||||
val sleepRemainingMs by viewModel.sleepRemainingMs.collectAsState()
|
||||
val spectrum by viewModel.spectrum.collectAsState()
|
||||
val vizStyle by viewModel.visualizerStyle.collectAsState()
|
||||
|
||||
val landscape = com.radiola.ui.util.isLandscape()
|
||||
@@ -188,10 +187,11 @@ fun PlayerBottomSheet(
|
||||
}
|
||||
|
||||
val visualizerSection: @Composable () -> Unit = {
|
||||
// Живой эквалайзер — вместо прогресс-бара (эфир нельзя перематывать)
|
||||
com.radiola.ui.components.Visualizer(
|
||||
style = com.radiola.ui.components.VisualizerStyle.fromKey(vizStyle),
|
||||
levels = spectrum,
|
||||
// Живой эквалайзер. Спектр (45/с) собирается ВНУТРИ VisualizerHost —
|
||||
// чтобы 45/с рекомпозиции не задевали весь плеер, только этот leaf.
|
||||
VisualizerHost(
|
||||
viewModel = viewModel,
|
||||
vizStyle = vizStyle,
|
||||
playing = isPlaying,
|
||||
color = colors.accent,
|
||||
modifier = Modifier
|
||||
@@ -614,6 +614,33 @@ private fun SleepRow(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaf-обёртка эквалайзера: сама собирает спектр (обновляется ~45/с) и включает
|
||||
* расчёт FFT только пока скомпонована (открыт плеер) — это и изолирует частые
|
||||
* рекомпозиции от остального плеера, и гасит FFT в фоне (батарея).
|
||||
*/
|
||||
@Composable
|
||||
private fun VisualizerHost(
|
||||
viewModel: PlayerViewModel,
|
||||
vizStyle: String,
|
||||
playing: Boolean,
|
||||
color: Color,
|
||||
modifier: Modifier
|
||||
) {
|
||||
val spectrum by viewModel.spectrum.collectAsState()
|
||||
DisposableEffect(Unit) {
|
||||
viewModel.setSpectrumActive(true)
|
||||
onDispose { viewModel.setSpectrumActive(false) }
|
||||
}
|
||||
com.radiola.ui.components.Visualizer(
|
||||
style = com.radiola.ui.components.VisualizerStyle.fromKey(vizStyle),
|
||||
levels = spectrum,
|
||||
playing = playing,
|
||||
color = color,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
/** Чип выбора звука для сна. */
|
||||
@Composable
|
||||
private fun SoundChip(label: String, selected: Boolean, onClick: () -> Unit) {
|
||||
|
||||
@@ -79,6 +79,9 @@ class PlayerViewModel @Inject constructor(
|
||||
playerController.startSleepTimer(minutes * 60_000L, sound)
|
||||
fun cancelSleepTimer() = playerController.cancelSleepTimer()
|
||||
|
||||
// Спектр (FFT) считаем только пока открыт плеер — экономия батареи в фоне.
|
||||
fun setSpectrumActive(active: Boolean) = playerController.setSpectrumActive(active)
|
||||
|
||||
private var nowPlayingJob: Job? = null
|
||||
|
||||
init {
|
||||
@@ -126,11 +129,16 @@ class PlayerViewModel @Inject constructor(
|
||||
viewModelScope.launch { pushHistoryUseCase(station.id) }
|
||||
nowPlayingJob?.cancel()
|
||||
nowPlayingJob = viewModelScope.launch {
|
||||
// Polling loop for Record API now playing
|
||||
// Поллинг now-playing — ТОЛЬКО пока играем. collectLatest отменяет
|
||||
// внутренний цикл при паузе (иначе на паузе радио зря дёргали сеть
|
||||
// каждые 5с → батарея + лишняя нагрузка на бэкенд).
|
||||
launch {
|
||||
while (true) {
|
||||
nowPlayingRepository.refreshNowPlaying()
|
||||
delay(5_000) // чаще — трек/обложка на плеере обновляются быстрее
|
||||
playerController.isPlaying.collectLatest { playing ->
|
||||
if (!playing) return@collectLatest
|
||||
while (true) {
|
||||
nowPlayingRepository.refreshNowPlaying()
|
||||
delay(5_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect now playing for this station (API has priority: covers + accurate metadata)
|
||||
|
||||
Reference in New Issue
Block a user