feat(app-glasses): жесты RayNeo SDK (TempleAction) + отключение звуков трекпада

Перенос из pair-view (проверено на железе): RayNeoARSDK V0.1.3 (aar в
libs/, MercurySDK.init в GlassesApp), MainActivity → BaseEventActivity,
templeActionViewModel.state → команды (Click=пауза/плей, SlideForward/
Backward=±30с), dispatchTouchEvent→onTouchEvent + toggleSoundEffect(0..3,
false) — системные щелчки трекпада отключены.

Фиксы по ходу (железо):
- applyMediaCommand(msg, onDone) — sendPlaybackPositionNow после seekTo
  (иначе телефон получал позицию ДО seek)
- догонка seekTo через 1с на телефоне (первый seekTo в ogg неточен)
- playbackPosition: isPlaying → playWhenReady — ложное playing=false при
  буферизации после seekTo паузило звук на телефоне (~5с «пропажи»);
  pauseIfPlaying тоже по playWhenReady

Тесты: glasses 59+5, phone 71. Железо: клик пауза/плей ✓, свайпы ±30с ✓,
звук при seek не пропадает ✓ (подтверждено пользователем)
This commit is contained in:
Hermes Agent
2026-08-19 23:38:45 +03:00
parent 654a5f9e26
commit 345e7b74fa
7 changed files with 196 additions and 9 deletions
@@ -115,6 +115,11 @@ class AudioSyncPlayer(private val context: Context) {
private var player: ExoPlayer? = null
private var ticker: Job? = null
/** Позиция очков из последнего sync (для догонки после неточного seekTo). */
private var lastVideoPositionMs = 0L
/** IsPlaying очков из последнего sync. */
private var lastVideoPlaying = false
private val _positionMs = MutableStateFlow(0L)
/** Текущая позиция аудио (для индикатора «🔊 звук: N мс»). Обновляется каждые 500 мс. */
val positionMs: StateFlow<Long> = _positionMs.asStateFlow()
@@ -151,9 +156,24 @@ class AudioSyncPlayer(private val context: Context) {
fun sync(positionMs: Long, isPlaying: Boolean) {
main.post {
val p = player ?: return@post
lastVideoPositionMs = positionMs
lastVideoPlaying = isPlaying
val audioPos = p.currentPosition
val decision = decideSync(audioPos, positionMs, p.isPlaying, isPlaying)
decision.seekToMs?.let { p.seekTo(it) }
decision.seekToMs?.let {
p.seekTo(it)
lastVideoPositionMs = it
// Догонка через 1с: первый seekTo в ogg приблизительный (недолёт ~2с),
// второй seekTo точен — делаем его сразу, не дожидаясь следующего sync (5с).
main.postDelayed({
val pp = player ?: return@postDelayed
val target = lastVideoPositionMs + 1_000 // очки ушли вперёд за секунду
if (abs(pp.currentPosition - target) > 500) {
pp.seekTo(target)
log("audio sync:", "догонка seekTo → $target (было ${pp.currentPosition})")
}
}, 1_000)
}
decision.playing?.let { if (it) p.play() else p.pause() }
log("audio sync:", "video=$positionMs мс audio=$audioPos мс diff=${abs(audioPos - positionMs)}")
}