feat(player): переключатель качества звука на экране воспроизведения
Перепроверены все 594 рабочие станции на наличие битрейт-вариантов потока (скрипт-пробер). У 71 станции найдено по 2–4 качества (Record-флагманы 96/64/32, zaycev 256/128/48, ВГТРК 192/128/64, НАШЕ/Орфей/Шансон HQ и др.) — записаны в поле qualities в stations.json. HLS (EMG) и Love (UID-привязка) корректно пропущены. Клиент: модель StreamQuality, хранение в Room (миграция v5), предпочтение битрейта в настройках. На экране плеера — чип текущего качества (виден только если вариантов ≥2) и шторка «Качество звука» со ступенями; переключение на лету без сброса now-playing, выбор запоминается между станциями. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.radiola.domain.model.DeeplinkService
|
||||
import com.radiola.domain.model.Station
|
||||
import com.radiola.domain.model.StreamQuality
|
||||
import com.radiola.domain.model.Track
|
||||
import com.radiola.domain.repository.SettingsRepository
|
||||
import com.radiola.domain.repository.StationRepository
|
||||
@@ -57,6 +58,13 @@ class PlayerViewModel @Inject constructor(
|
||||
private val _playlist = MutableStateFlow<List<Station>>(emptyList())
|
||||
val playlist: StateFlow<List<Station>> = _playlist.asStateFlow()
|
||||
|
||||
// Выбранное качество текущей станции (битрейт). null — у станции нет вариантов.
|
||||
private val _currentQuality = MutableStateFlow<StreamQuality?>(null)
|
||||
val currentQuality: StateFlow<StreamQuality?> = _currentQuality.asStateFlow()
|
||||
|
||||
// Предпочитаемый битрейт пользователя (0 = авто/по умолчанию станции).
|
||||
private var preferredBitrate: Int = 0
|
||||
|
||||
val isRecording: StateFlow<Boolean> = recordingRepository.isRecording
|
||||
|
||||
private var nowPlayingJob: Job? = null
|
||||
@@ -72,6 +80,9 @@ class PlayerViewModel @Inject constructor(
|
||||
_enabledServices.value = DeeplinkService.entries.filter { it.serviceId in ids }
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
settingsRepository.getPreferredBitrate().collect { preferredBitrate = it }
|
||||
}
|
||||
viewModelScope.launch {
|
||||
_currentTrack
|
||||
.filterNotNull()
|
||||
@@ -86,10 +97,15 @@ class PlayerViewModel @Inject constructor(
|
||||
_currentStation.value = station
|
||||
_currentTrack.value = null
|
||||
_playlist.value = playlist ?: _stations.value
|
||||
// Выбираем стартовое качество: предпочтение пользователя → совпадение с
|
||||
// потоком по умолчанию → высшее. Если вариантов нет — играем как есть.
|
||||
val quality = pickInitialQuality(station)
|
||||
_currentQuality.value = quality
|
||||
val streamUrl = quality?.url ?: station.streamUrl
|
||||
// Love Radio: подставляем сессионный UID (иначе поток отдаёт заглушку).
|
||||
// Для остальных resolve вернёт URL как есть.
|
||||
viewModelScope.launch {
|
||||
val url = loveStreamResolver.resolve(station.streamUrl)
|
||||
val url = loveStreamResolver.resolve(streamUrl)
|
||||
playerController.play(url, station.prefix, station.name)
|
||||
}
|
||||
viewModelScope.launch { pushHistoryUseCase(station.id) }
|
||||
@@ -142,6 +158,28 @@ class PlayerViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** Стартовое качество станции с учётом предпочтения пользователя. */
|
||||
private fun pickInitialQuality(station: Station): StreamQuality? {
|
||||
val list = station.qualities
|
||||
if (list.size < 2) return null
|
||||
return list.firstOrNull { it.bitrate == preferredBitrate }
|
||||
?: list.firstOrNull { it.url == station.streamUrl }
|
||||
?: list.first()
|
||||
}
|
||||
|
||||
/** Переключить качество текущей станции на лету (без сброса now-playing). */
|
||||
fun selectQuality(quality: StreamQuality) {
|
||||
val station = _currentStation.value ?: return
|
||||
if (_currentQuality.value?.bitrate == quality.bitrate) return
|
||||
_currentQuality.value = quality
|
||||
preferredBitrate = quality.bitrate
|
||||
viewModelScope.launch { settingsRepository.setPreferredBitrate(quality.bitrate) }
|
||||
viewModelScope.launch {
|
||||
val url = loveStreamResolver.resolve(quality.url)
|
||||
playerController.changeStream(url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseIcyTitle(title: String?): Track? {
|
||||
if (title.isNullOrBlank()) return null
|
||||
val separators = listOf(" - ", " — ", " – ")
|
||||
|
||||
Reference in New Issue
Block a user