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
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import pw.binom.viewmate.core.MediaCommand
import pw.binom.viewmate.core.protocol.MediaCommandMsg
import pw.binom.viewmate.core.protocol.PlayVideo
import java.io.File
@@ -172,10 +173,15 @@ class MovieController(private val context: Context) {
}
}
fun applyMediaCommand(command: MediaCommand, seekMs: Long) {
/** Команда от жеста очков: MediaCommandMsg из протокола (как из телефона). */
fun applyMediaCommand(msg: MediaCommandMsg, onDone: (() -> Unit)? = null) =
applyMediaCommand(msg.command, msg.seekMs, onDone)
fun applyMediaCommand(command: MediaCommand, seekMs: Long, onDone: (() -> Unit)? = null) {
mainHandler.post {
val player = _player ?: run {
log("glasses", "MediaCommand $command проигнорирован: плеер не активен")
onDone?.invoke()
return@post
}
when (command) {
@@ -212,6 +218,7 @@ class MovieController(private val context: Context) {
MediaCommand.CLOSE -> close()
}
onDone?.invoke()
}
}
@@ -235,13 +242,13 @@ class MovieController(private val context: Context) {
/** Снимок для PlaybackPosition (позиция, playing, itemId, audioIndex). Только с главного потока. */
internal fun playbackPosition(): PlaybackSnapshot {
val player = _player ?: return PlaybackSnapshot(0L, false, contentId.itemId, contentId.audioIndex)
return PlaybackSnapshot(player.currentPosition, player.isPlaying, contentId.itemId, contentId.audioIndex)
return PlaybackSnapshot(player.currentPosition, player.playWhenReady, contentId.itemId, contentId.audioIndex)
}
/** Пауза при потере связи, если плеер играл. Только с главного потока. Возвращает, играл ли. */
fun pauseIfPlaying(): Boolean {
val player = _player ?: return false
val wasPlaying = player.isPlaying
val wasPlaying = player.playWhenReady
if (wasPlaying) player.pause()
return wasPlaying
}