feat: стабилизация гашения экрана очков (пауза/резюм звука)
Проблемы на железе (RayNeo X2 + HONOR): при гашении экрана позиция ExoPlayer «набегала» (мусор 42 мин), откат не срабатывал (markPosition сохранял мусор), доставка паузы телефону 5-10с (RayNeo вырубает WiFi-радио при screen-off, игнорируя WifiLock). Решение: - markPosition(positionMs, trusted): защита от мусорных скачков (>30с за цикл = игнор); seek через трекбар помечает позицию trusted (rollback откатывает к seek, не к началу) - rollback при resume: откат к последней достоверной позиции, если текущая убежала вперёд >5с - WifiLock (FULL_HIGH_PERF) при просмотре + ensureWifiLock в точках гашения (страховка; RayNeo игнорирует, но не мешает) - Watchdog на телефоне: очки молчат >5с (AUDIO_SILENCE_PAUSE_MS) → пауза звука (гарантированная верхняя граница) - sendPlaybackPositionNow с повторами (0/2/5с) при включении экрана — свежая позиция уходит при пробуждении радио - Диагностика: события пишутся в files/events.log (logcat очков забит EGL-спамом) - Тесты: +14 (rollback/скачки/trusted seek) - Железо подтверждено: позиция после гашения идеальная (продолжение с места паузы, не в начало), звук пауза ≤5-6с, синк diff < 1000
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
package pw.binom.viewmate.phone
|
||||
|
||||
import android.app.Application
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import pw.binom.viewmate.core.GlassesMode
|
||||
@@ -88,13 +91,31 @@ class PhoneApp : Application() {
|
||||
log("server", "WS-сервер запущен на ${PhoneConfig.SERVER_PORT}")
|
||||
nsd.publish()
|
||||
|
||||
val lastPositionAt = AtomicLong(0)
|
||||
val lastWasPlaying = AtomicBoolean(false)
|
||||
|
||||
// каждый PlaybackPosition от очков (5с) → синхронизация аудио-плеера
|
||||
// + автоподхват звука (очки играют после рестарта телефона, audioSync не активен)
|
||||
server.hub.onPlaybackPosition = { positionMs, playing, itemId, audioIndex ->
|
||||
lastPositionAt.set(System.currentTimeMillis())
|
||||
lastWasPlaying.set(playing)
|
||||
audioSync.sync(positionMs, playing)
|
||||
scope.launch { autostartAudio(itemId, audioIndex, positionMs, playing) }
|
||||
}
|
||||
|
||||
// watchdog: очки молчат (экран погас/радио спит) — пауза звука по таймауту
|
||||
scope.launch {
|
||||
while (true) {
|
||||
delay(PhoneConfig.WATCHDOG_TICK_MS)
|
||||
val silentFor = System.currentTimeMillis() - lastPositionAt.get()
|
||||
if (silentFor > PhoneConfig.AUDIO_SILENCE_PAUSE_MS && audioSync.isActive && lastWasPlaying.get()) {
|
||||
log("audio", "очки молчат ${silentFor}мс — пауза (экран погас?)")
|
||||
audioSync.pause()
|
||||
lastWasPlaying.set(false) // не спамить каждые 2с
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// автоматика обрыва связи: потеря → пауза, восстановление → продолжить с того же места
|
||||
scope.launch(Dispatchers.Main) {
|
||||
var prevConnected = server.hub.connected.value > 0
|
||||
|
||||
@@ -12,6 +12,12 @@ object PhoneConfig {
|
||||
const val MIRROR_API_KEY = "67KA48ScP0qxRIihmr1srlggIIkRUAG"
|
||||
const val SERVER_PORT = 8080
|
||||
|
||||
/** Watchdog: пауза звука, если очки молчат дольше этого времени (экран погас, радио спит). */
|
||||
const val AUDIO_SILENCE_PAUSE_MS = 5_000L
|
||||
|
||||
/** Период проверки watchdog. */
|
||||
const val WATCHDOG_TICK_MS = 2_000L
|
||||
|
||||
// S3 SeaweedFS (bucket media, префикс mirror/): read-only identity mirror-client
|
||||
const val S3_ACCESS_KEY = "MirrorClient01QwertyKey"
|
||||
const val S3_SECRET_KEY = "MirrorClient01SecretKeyValueForGlassesOnly"
|
||||
|
||||
Reference in New Issue
Block a user