fix(recordings): не зависать плееру записи; меньше задержка обложки

Bug1: плеер записи (singleton ExoPlayer) не глушился при закрытии шторки и
уходе с экрана → аудио-сирота без управления, запуск радио конфликтовал.
Теперь воспроизведение записи останавливается на onDismiss и onDispose
экрана записей, а старт радио глушит плеер записи (взаимоисключение).

Bug2: обложка/трек на открытом плеере обновлялись с задержкой при записи.
Эмиссия спектра ограничена ~45/с (было ~86/с) — меньше перегруз перерисовки;
поллинг now-playing в захвате маркеров ускорен 15с→8с (точнее тайм-коды).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-04 19:03:44 +03:00
parent d9c83a83e9
commit 53cd1601dc
4 changed files with 24 additions and 6 deletions

View File

@@ -97,7 +97,7 @@ class RecordingRepositoryImpl @Inject constructor(
launch { launch {
while (isActive) { while (isActive) {
try { nowPlayingRepository.refreshNowPlaying() } catch (_: Exception) {} try { nowPlayingRepository.refreshNowPlaying() } catch (_: Exception) {}
delay(15_000) delay(8_000) // чаще — точнее тайм-коды треков в записи
} }
} }
nowPlayingRepository.getNowPlaying(station.id) nowPlayingRepository.getNowPlaying(station.id)

View File

@@ -86,6 +86,7 @@ class AudioSpectrumAnalyzer(
private var channelCount = 2 private var channelCount = 2
private var pcm16 = true private var pcm16 = true
private var sampleRate = 44100 private var sampleRate = 44100
private var lastEmit = 0L
// Автогейн: бегущий пик амплитуды — чтобы столбики всегда использовали всю // Автогейн: бегущий пик амплитуды — чтобы столбики всегда использовали всю
// высоту независимо от громкости трека. // высоту независимо от громкости трека.
private var agcPeak = 1e-4f private var agcPeak = 1e-4f
@@ -159,6 +160,12 @@ class AudioSpectrumAnalyzer(
smoothed[band] = if (v > prev) v else prev * 0.55f + v * 0.45f smoothed[band] = if (v > prev) v else prev * 0.55f + v * 0.45f
out[band] = smoothed[band] out[band] = smoothed[band]
} }
// Считаем сглаживание каждый хоп (плавность), но эмитим в UI ~45/с, чтобы
// не перегружать перерисовку плеера.
val now = System.nanoTime()
if (now - lastEmit >= 22_000_000L) {
lastEmit = now
_spectrum.value = out _spectrum.value = out
} }
} }
}

View File

@@ -37,7 +37,8 @@ class PlayerViewModel @Inject constructor(
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val recordingRepository: RecordingRepository, private val recordingRepository: RecordingRepository,
private val pushHistoryUseCase: PushHistoryUseCase, private val pushHistoryUseCase: PushHistoryUseCase,
private val loveStreamResolver: com.radiola.data.remote.LoveStreamResolver private val loveStreamResolver: com.radiola.data.remote.LoveStreamResolver,
private val recordingPlaybackController: com.radiola.service.RecordingPlaybackController
) : ViewModel() { ) : ViewModel() {
val isPlaying: StateFlow<Boolean> = playerController.isPlaying val isPlaying: StateFlow<Boolean> = playerController.isPlaying
@@ -98,6 +99,9 @@ class PlayerViewModel @Inject constructor(
} }
fun play(station: Station, playlist: List<Station>? = null) { fun play(station: Station, playlist: List<Station>? = null) {
// Глушим плеер записи, если он играл — иначе два ExoPlayer'а конфликтуют
// (радио не стартует, запись зависает без управления).
recordingPlaybackController.stop()
_currentStation.value = station _currentStation.value = station
_currentTrack.value = null _currentTrack.value = null
_playlist.value = playlist ?: _stations.value _playlist.value = playlist ?: _stations.value

View File

@@ -48,6 +48,13 @@ fun RecordingsScreen(
val isRecording by viewModel.isRecording.collectAsState() val isRecording by viewModel.isRecording.collectAsState()
val colors = RadiolaTheme.colors val colors = RadiolaTheme.colors
var playing by remember { mutableStateOf<Recording?>(null) } var playing by remember { mutableStateOf<Recording?>(null) }
// Плеер записи — singleton-контроллер; держим его VM здесь, чтобы корректно
// ОСТАНОВИТЬ воспроизведение при закрытии шторки и уходе с экрана (иначе
// аудио продолжает играть без UI и конфликтует с радио).
val recPlayerVm: RecordingPlayerViewModel = hiltViewModel()
DisposableEffect(Unit) {
onDispose { recPlayerVm.close() }
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -133,14 +140,14 @@ fun RecordingsScreen(
// налезает на системную навигацию. // налезает на системную навигацию.
playing?.let { rec -> playing?.let { rec ->
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { playing = null }, onDismissRequest = { recPlayerVm.close(); playing = null },
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
containerColor = colors.bgBase containerColor = colors.bgBase
) { ) {
RecordingPlayerSheet( RecordingPlayerSheet(
recording = rec, recording = rec,
onDismiss = { playing = null }, onDismiss = { recPlayerVm.close(); playing = null },
viewModel = hiltViewModel() viewModel = recPlayerVm
) )
} }
} }