From 7a00f53b2048f4eecb84a8b366701ed61cb68850 Mon Sep 17 00:00:00 2001 From: nk Date: Wed, 10 Jun 2026 18:05:32 +0300 Subject: [PATCH] =?UTF-8?q?fix(now-playing):=20=D0=BC=D0=B3=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B5=D1=84=D1=80?= =?UTF-8?q?=D0=B5=D1=88=20=D1=8D=D1=84=D0=B8=D1=80=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=82=D0=B5=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D1=84=D0=BE=D0=BD=D0=B0=20+=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=81=D0=B5=D1=81=D1=81=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Опрос now-playing был привязан к play()/жизни ViewModel — при заморозке ColorOS в фоне или пересоздании ViewModel трек «застывал». Теперь: startNowPlaying() с мгновенным refresh, восстановление привязки к играющей станции из PlayerController.currentStationId, и onAppForeground() на ON_RESUME. --- app/src/main/java/com/radiola/MainActivity.kt | 13 +++++++ .../com/radiola/ui/player/PlayerViewModel.kt | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/app/src/main/java/com/radiola/MainActivity.kt b/app/src/main/java/com/radiola/MainActivity.kt index 61773ee..28fc9dc 100644 --- a/app/src/main/java/com/radiola/MainActivity.kt +++ b/app/src/main/java/com/radiola/MainActivity.kt @@ -109,6 +109,19 @@ class MainActivity : ComponentActivity() { val isRecording by playerViewModel.isRecording.collectAsState() val isLoggedIn by tokenDataStore.isLoggedIn.collectAsState(initial = false) + // Возврат на передний план → мгновенно освежаем now-playing + // (фоновая заморозка ColorOS может останавливать опрос эфира). + val lifecycleOwner = androidx.compose.ui.platform.LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val obs = androidx.lifecycle.LifecycleEventObserver { _, event -> + if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) { + playerViewModel.onAppForeground() + } + } + lifecycleOwner.lifecycle.addObserver(obs) + onDispose { lifecycleOwner.lifecycle.removeObserver(obs) } + } + // --- Авто-обновление: проверяем версию на старте, показываем диалог --- var pendingUpdate by remember { mutableStateOf(null) } var updateState by remember { mutableStateOf(com.radiola.ui.update.UpdateState.IDLE) } diff --git a/app/src/main/java/com/radiola/ui/player/PlayerViewModel.kt b/app/src/main/java/com/radiola/ui/player/PlayerViewModel.kt index cabf2b8..65a5202 100644 --- a/app/src/main/java/com/radiola/ui/player/PlayerViewModel.kt +++ b/app/src/main/java/com/radiola/ui/player/PlayerViewModel.kt @@ -118,6 +118,20 @@ class PlayerViewModel @Inject constructor( viewModelScope.launch { settingsRepository.getPreferredBitrate().collect { preferredBitrate = it } } + // Восстановление сессии: если процесс/Activity пересоздались, а станция уже + // играет в фоновом сервисе (PlayerController помнит id) — заново привязываемся + // и запускаем опрос now-playing. Иначе мини-плеер/эфир «застывают». + viewModelScope.launch { + combine(playerController.currentStationId, _stations) { id, list -> + id?.let { sid -> list.firstOrNull { it.id == sid } } + }.collect { station -> + if (station != null && _currentStation.value == null) { + _currentStation.value = station + _playlist.value = _stations.value + startNowPlaying(station) + } + } + } viewModelScope.launch { _currentTrack .filterNotNull() @@ -150,8 +164,20 @@ class PlayerViewModel @Inject constructor( playerController.play(url, station.prefix, station.name, station.id) } viewModelScope.launch { pushHistoryUseCase(station.id) } + startNowPlaying(station) + } + + /** + * Запускает опрос now-playing для станции: мгновенный рефреш + цикл раз в 5с + * (пока играем) + сбор трека из API (приоритет) и ICY (фолбэк). Вынесено из + * play(), чтобы переиспользовать при восстановлении сессии (возврат из фона / + * пересоздание ViewModel) — иначе эфир «застывает» на последнем значении. + */ + private fun startNowPlaying(station: Station) { nowPlayingJob?.cancel() nowPlayingJob = viewModelScope.launch { + // Сразу тянем свежий эфир — не ждём первые 5с цикла. + launch { nowPlayingRepository.refreshNowPlaying() } // Поллинг now-playing — ТОЛЬКО пока играем. collectLatest отменяет // внутренний цикл при паузе (иначе на паузе радио зря дёргали сеть // каждые 5с → батарея + лишняя нагрузка на бэкенд). @@ -208,6 +234,17 @@ class PlayerViewModel @Inject constructor( } } + /** + * Возврат приложения на передний план: мгновенно освежаем эфир (чтобы юзер не + * видел залипший трек после фоновой заморозки) и, если опрос почему-то не идёт, + * перезапускаем его для текущей станции. + */ + fun onAppForeground() { + val station = _currentStation.value ?: return + viewModelScope.launch { nowPlayingRepository.refreshNowPlaying() } + if (nowPlayingJob?.isActive != true) startNowPlaying(station) + } + /** Стартовое качество станции с учётом предпочтения пользователя. */ private fun pickInitialQuality(station: Station): StreamQuality? { val list = station.qualities