feat: автоподхват звука после рестарта телефона (очки играют — звук молчит)
Сценарий (железо): телефон перезапущен, очки продолжают играть — звук не создавался (плеер живёт только по watch). Решение: - PlaybackPosition теперь несёт itemId+audioIndex (дефолты — совместимость) - очки: MovieController хранит itemId/audioIndex (playVideo/close), шлёт в позиции - телефон: onPlaybackPosition → autostartAudio: если itemId есть, аудио не активно и не было неудачи — mirrorByItem → resolveAudioSource → play(source, positionMs); lastFailed защита от спама каждые 5с - Тесты: 13 lib-core (сериализация + старый формат), 4 glasses (contentId/close), 70/0 phone; железо: рестарт телефона → «audio sync» через ~5с (diff 483), подтверждено пользователем
This commit is contained in:
@@ -86,8 +86,12 @@ class GlassesHub(
|
||||
private val _positionMs = MutableStateFlow<Long?>(null)
|
||||
val positionMs: StateFlow<Long?> = _positionMs.asStateFlow()
|
||||
|
||||
/** Хук на позицию очков: вызывается на каждый PlaybackPosition (и GlassesOff с playing=false). */
|
||||
var onPlaybackPosition: ((positionMs: Long, playing: Boolean) -> Unit)? = null
|
||||
/**
|
||||
* Хук на позицию очков: вызывается на каждый PlaybackPosition (и GlassesOff
|
||||
* с playing=false). [itemId]+[audioIndex] — для автоподхвата звука после
|
||||
* рестарта телефона (см. PhoneApp.autostartAudio).
|
||||
*/
|
||||
var onPlaybackPosition: ((positionMs: Long, playing: Boolean, itemId: String?, audioIndex: Int) -> Unit)? = null
|
||||
|
||||
fun add(session: DefaultWebSocketServerSession) {
|
||||
sessions[session] = Unit
|
||||
@@ -155,14 +159,14 @@ class GlassesHub(
|
||||
_positionMs.value = msg.positionMs
|
||||
this.state.playing = msg.playing
|
||||
val state = if (msg.playing) "играет" else "пауза"
|
||||
log("glasses", "позиция: ${msg.positionMs} мс, $state")
|
||||
onPlaybackPosition?.invoke(msg.positionMs, msg.playing)
|
||||
log("glasses", "позиция: ${msg.positionMs} мс, $state, item=${msg.itemId} idx=${msg.audioIndex}")
|
||||
onPlaybackPosition?.invoke(msg.positionMs, msg.playing, msg.itemId, msg.audioIndex)
|
||||
}
|
||||
|
||||
is GlassesOff -> {
|
||||
log("glasses", "очки выключились → пауза (${msg.reason})")
|
||||
this.state.playing = false
|
||||
onPlaybackPosition?.invoke(_positionMs.value ?: 0L, false)
|
||||
onPlaybackPosition?.invoke(_positionMs.value ?: 0L, false, null, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,28 @@ class PhoneApp : Application() {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
/** itemId, у которого автоподхват звука уже не удался — не дёргать повторно каждые 5с. */
|
||||
private var lastFailedItemId: String? = null
|
||||
|
||||
/**
|
||||
* Автоподхват звука после рестарта телефона: очки продолжают играть и шлют
|
||||
* PlaybackPosition с itemId+audioIndex, а audioSync не активен (телефон «не в курсе»).
|
||||
* Создаём аудио-плеер с позиции очков — как в DetailsScreen.onWatch.
|
||||
*/
|
||||
private suspend fun autostartAudio(itemId: String?, audioIndex: Int, positionMs: Long) {
|
||||
if (itemId == null || audioSync.isActive || itemId == lastFailedItemId) return
|
||||
val mirrorFiles = runCatching { mirror.mirrorByItem(itemId) }.getOrNull()?.files
|
||||
val source = resolveAudioSource(downloadManager, itemId, mirrorFiles, audioIndex)
|
||||
if (source != null) {
|
||||
lastFailedItemId = null
|
||||
audioSync.play(source, positionMs)
|
||||
log("audio", "автоподхват звука: itemId=$itemId idx=$audioIndex pos=$positionMs")
|
||||
} else {
|
||||
log("audio", "автоподхват звука не удался: itemId=$itemId idx=$audioIndex (нет источника)")
|
||||
lastFailedItemId = itemId
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
@@ -59,7 +81,11 @@ class PhoneApp : Application() {
|
||||
nsd.publish()
|
||||
|
||||
// каждый PlaybackPosition от очков (5с) → синхронизация аудио-плеера
|
||||
server.hub.onPlaybackPosition = { positionMs, playing -> audioSync.sync(positionMs, playing) }
|
||||
// + автоподхват звука (очки играют после рестарта телефона, audioSync не активен)
|
||||
server.hub.onPlaybackPosition = { positionMs, playing, itemId, audioIndex ->
|
||||
audioSync.sync(positionMs, playing)
|
||||
scope.launch { autostartAudio(itemId, audioIndex, positionMs) }
|
||||
}
|
||||
|
||||
// автоматика обрыва связи: потеря → пауза, восстановление → продолжить с того же места
|
||||
scope.launch(Dispatchers.Main) {
|
||||
|
||||
Reference in New Issue
Block a user