fix(now-playing): мгновенный рефреш эфира при возврате из фона + восстановление сессии
Опрос now-playing был привязан к play()/жизни ViewModel — при заморозке ColorOS в фоне или пересоздании ViewModel трек «застывал». Теперь: startNowPlaying() с мгновенным refresh, восстановление привязки к играющей станции из PlayerController.currentStationId, и onAppForeground() на ON_RESUME.
This commit is contained in:
@@ -109,6 +109,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
val isRecording by playerViewModel.isRecording.collectAsState()
|
val isRecording by playerViewModel.isRecording.collectAsState()
|
||||||
val isLoggedIn by tokenDataStore.isLoggedIn.collectAsState(initial = false)
|
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<com.radiola.update.VersionInfo?>(null) }
|
var pendingUpdate by remember { mutableStateOf<com.radiola.update.VersionInfo?>(null) }
|
||||||
var updateState by remember { mutableStateOf(com.radiola.ui.update.UpdateState.IDLE) }
|
var updateState by remember { mutableStateOf(com.radiola.ui.update.UpdateState.IDLE) }
|
||||||
|
|||||||
@@ -118,6 +118,20 @@ class PlayerViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsRepository.getPreferredBitrate().collect { preferredBitrate = it }
|
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 {
|
viewModelScope.launch {
|
||||||
_currentTrack
|
_currentTrack
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
@@ -150,8 +164,20 @@ class PlayerViewModel @Inject constructor(
|
|||||||
playerController.play(url, station.prefix, station.name, station.id)
|
playerController.play(url, station.prefix, station.name, station.id)
|
||||||
}
|
}
|
||||||
viewModelScope.launch { pushHistoryUseCase(station.id) }
|
viewModelScope.launch { pushHistoryUseCase(station.id) }
|
||||||
|
startNowPlaying(station)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает опрос now-playing для станции: мгновенный рефреш + цикл раз в 5с
|
||||||
|
* (пока играем) + сбор трека из API (приоритет) и ICY (фолбэк). Вынесено из
|
||||||
|
* play(), чтобы переиспользовать при восстановлении сессии (возврат из фона /
|
||||||
|
* пересоздание ViewModel) — иначе эфир «застывает» на последнем значении.
|
||||||
|
*/
|
||||||
|
private fun startNowPlaying(station: Station) {
|
||||||
nowPlayingJob?.cancel()
|
nowPlayingJob?.cancel()
|
||||||
nowPlayingJob = viewModelScope.launch {
|
nowPlayingJob = viewModelScope.launch {
|
||||||
|
// Сразу тянем свежий эфир — не ждём первые 5с цикла.
|
||||||
|
launch { nowPlayingRepository.refreshNowPlaying() }
|
||||||
// Поллинг now-playing — ТОЛЬКО пока играем. collectLatest отменяет
|
// Поллинг now-playing — ТОЛЬКО пока играем. collectLatest отменяет
|
||||||
// внутренний цикл при паузе (иначе на паузе радио зря дёргали сеть
|
// внутренний цикл при паузе (иначе на паузе радио зря дёргали сеть
|
||||||
// каждые 5с → батарея + лишняя нагрузка на бэкенд).
|
// каждые 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? {
|
private fun pickInitialQuality(station: Station): StreamQuality? {
|
||||||
val list = station.qualities
|
val list = station.qualities
|
||||||
|
|||||||
Reference in New Issue
Block a user