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:
Hermes Agent
2026-08-19 21:45:23 +03:00
parent 8072d0c95a
commit e29bc5032d
7 changed files with 178 additions and 14 deletions
@@ -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) {