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:
nk
2026-06-04 12:36:47 +03:00
parent 5b256a3421
commit 5ffaf9a924
13 changed files with 1473 additions and 89 deletions

View File

@@ -29,6 +29,7 @@ class SettingsRepositoryImpl @Inject constructor(
private val ENABLED_SERVICES = stringSetPreferencesKey("enabled_deeplink_services")
private val EQUALIZER_PRESET = stringPreferencesKey("equalizer_preset")
private val RECORDING_ENABLED = booleanPreferencesKey("recording_enabled")
private val PREFERRED_BITRATE = intPreferencesKey("preferred_bitrate")
}
override fun getLastStationId(): Flow<Int?> = dataStore.data.map { it[LAST_STATION_ID] }
@@ -49,4 +50,7 @@ class SettingsRepositoryImpl @Inject constructor(
override fun isRecordingEnabled(): Flow<Boolean> = dataStore.data.map { it[RECORDING_ENABLED] ?: false }
override suspend fun setRecordingEnabled(enabled: Boolean) { dataStore.edit { it[RECORDING_ENABLED] = enabled } }
override fun getPreferredBitrate(): Flow<Int> = dataStore.data.map { it[PREFERRED_BITRATE] ?: 0 }
override suspend fun setPreferredBitrate(bitrate: Int) { dataStore.edit { it[PREFERRED_BITRATE] = bitrate } }
}

View File

@@ -8,6 +8,7 @@ import com.radiola.data.remote.RecordApi
import com.radiola.data.remote.RadiolaApi
import com.radiola.data.remote.ApiMapper.toDomain
import com.radiola.domain.model.Station
import com.radiola.domain.model.StreamQuality
import com.radiola.domain.repository.StationRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -84,7 +85,8 @@ class StationRepositoryImpl @Inject constructor(
tags = station.tags.joinToString(","),
sortOrder = index,
source = station.source,
isFavorite = false
isFavorite = false,
qualities = encodeQualities(station.qualities)
)
}
db.stationDao().insertAll(entities)
@@ -133,6 +135,22 @@ class StationRepositoryImpl @Inject constructor(
genre = genre,
tags = tags.split(",").filter { it.isNotBlank() },
sortOrder = sortOrder,
source = source
source = source,
qualities = decodeQualities(qualities)
)
// Качества кодируем строкой "bitrate\ttype\turl" по строкам (URL может содержать ; и |,
// но не \t/\n — поэтому такие разделители безопасны).
private fun encodeQualities(list: List<StreamQuality>): String =
list.joinToString("\n") { "${it.bitrate}\t${it.type}\t${it.url}" }
private fun decodeQualities(raw: String): List<StreamQuality> {
if (raw.isBlank()) return emptyList()
return raw.split("\n").mapNotNull { line ->
val parts = line.split("\t")
if (parts.size != 3) return@mapNotNull null
val br = parts[0].toIntOrNull() ?: return@mapNotNull null
StreamQuality(br, parts[2], parts[1])
}
}
}