feat: кнопка «⏹ Закрыть» в пульте + автоматика обрыва связи (README «Автоматика»)

- GlassesScreen: кнопка ⏹ Закрыть → sendCommand(CLOSE) (обработчики были: телефон audio.stop, очки MovieController.close)
- app-glasses: MovieController.pauseIfPlaying()/resume(); HostConnection — при обрыве WS пауза + флаг, при восстановлении — continue (лог «связь потеряна — пауза» / «связь восстановлена — продолжаю»); флаг сбрасывается на PlayVideo/CLOSE
- app-phone: ConnectionAutomatics (connectionEvent, авто-resume по флагу «играл ли») + коллектор на hub.connected
- Тесты: app-phone 67/0, app-glasses 15/0
- OFF7 3/3: A5 стоп + обрыв-пауза-продолжение подтверждены живым прогоном
This commit is contained in:
Hermes Agent
2026-08-19 19:20:04 +03:00
parent 535f4e2050
commit 35f462b34a
10 changed files with 226 additions and 1 deletions
@@ -7,6 +7,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import pw.binom.viewmate.core.media.JellyfinClient
import pw.binom.viewmate.core.media.MirrorClient
@@ -38,6 +39,9 @@ class PhoneApp : Application() {
/** Синхронизированный аудио-плеер (звук на телефоне при просмотре на очках). */
val audioSync: AudioSyncPlayer by lazy { AudioSyncPlayer(this) }
/** Автоматика паузы/продолжения звука при обрыве связи. */
private val connectionAutoResume = ConnectionAutoResume()
var actions: PhoneActions? = null
private set
@@ -53,6 +57,33 @@ class PhoneApp : Application() {
// каждый PlaybackPosition от очков (5с) → синхронизация аудио-плеера
server.hub.onPlaybackPosition = { positionMs, playing -> audioSync.sync(positionMs, playing) }
// автоматика обрыва связи: потеря → пауза, восстановление → продолжить с того же места
scope.launch(Dispatchers.Main) {
var prevConnected = server.hub.connected.value > 0
server.hub.connected.collect { count ->
val nowConnected = count > 0
when (connectionEvent(prevConnected, nowConnected)) {
ConnectionEvent.LOST -> {
connectionAutoResume.onLost(audioSync.isActive, state.playing)
if (audioSync.isActive) {
log("audio", "связь потеряна — пауза")
audioSync.pause()
}
}
ConnectionEvent.RESTORED -> {
if (connectionAutoResume.consumeResume()) {
log("audio", "связь восстановлена — продолжаю")
audioSync.play()
}
}
ConnectionEvent.NONE -> Unit
}
prevConnected = nowConnected
}
}
scope.launch {
val userId = runCatching { jellyfin.users().firstOrNull()?.Id }.getOrNull()
?: ""