Очки штампуют sentTs в PlaybackPosition; телефон мерит one-way
латентность (receivedTs - sentTs, EWMA) и добавляет её к мастер-позиции
в sync(), снимая системный перекос, из-за которого аудио недокручивало
на величину транспортной задержки. Плюс правки тестов под
HARD_SEEK_THRESHOLD_MS=5000 (остались незакомиченными с прошлого фикса).
- Mercury (app-phone): управление яркостью (с авто-выкл. перед
применением) и WiFi (скан/подключение/список сетей с паролем);
добавлено право BLUETOOTH_ADVERTISE в манифест и runtime-запрос
(без него GATT-сервер не рекламируется → очки не подключаются)
- логирование (lib-core core.log): BatchingLogCollector + LogSink +
LogSpool + LokiLogSink; цепочка очки→телефон→Loki; lib-core log()
переадресован в коллектор
- STT: Qwen3-ASR через pw.binom.asr:asr-qwen3-android, убран in-house
whisper-фоллбек
- audio-sync: P-регулятор с насыщением вместо фиксированной скорости
- media-split: очки только video, телефон только audio (UI-чипы)
- Components: форматирование размера через Locale.ROOT
- AndroidManifest: три uses-native-library (libOpenCL/libvndksupport/libcdsprpc)
ВНУТРИ <application> — aapt2 8.9.2 отбрасывает тег на уровне <manifest>
- LocalLlm: enableSpeculativeDecoding=true, GPU/GPU/CPU бэкенды (как у автора)
- DebugHttpServer: HTTP-отладка ассистента без UI
- Log/PhoneApp: мелкие правки
Подтверждено: движок поднимается, локальный чат отвечает на HONOR AXGL024B05001337
Проверено на железе (HONOR FCP-N49): Gemma 4 E2B отвечает
«Здравствуйте! Чем я могу вам помочь с вашими очками Viewmate?»
(15 чанков, 61 символ, ~19с генерации).
- MEDIA_TOOLSET + MediaToolExecutor: search (поиск с лимитом), info (детали), play (проверки+запуск)
- JellyfinMediaSource: обёртка JellyfinClient (search/item) с защитой userId
- agentPlayMedia: проверки (очки на связи, видео скачано на очки, звук на телефоне) → watch с позицией
- PhoneActions.watch(startPositionMs), JellyfinItem.ProductionYear
- скилл app-phone/skills/media.md (frontmatter: name/description)
- MediaToolsTest: 22 кейса — всего 142 теста зелёные
Проблемы на железе (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
После рестарта телефона автоподхват создавал звук, но state.currentItem
оставался null — пульт не знал длительность: слайдер скрыт, «сколько
всего» не показывалось. Теперь при успешном подхвате восстанавливаются
currentItem (jellyfin.item по itemId), currentAudioIndex, mode=MOVIE,
playing. Железо: «Акира · 05:21 / 2:04:28» + слайдер на месте.
Сценарий (железо): телефон перезапущен, очки продолжают играть — звук
не создавался (плеер живёт только по 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), подтверждено пользователем
- AudioSyncPlayer (ExoPlayer media3): играет дорожку (локальный файл или presigned S3-URL), коррекция по PlaybackPosition очков (каждые 5с, порог 1000мс), мгновенная реакция на пульт, индикатор «🔊 звук: N мс»
- Вещдок-лог: audio sync: video=N audio=M diff=K (на стенде diff ~150мс)
- Смена озвучки во время просмотра — перезапуск с позиции очков
- README: секция «Статус реализации»
- Тесты 24/0 (8 новых AudioSyncPlayerTest)