fix(app-glasses): scale на паузе — принудительная перерисовка кадра

На паузе ExoPlayer не шлёт кадры — поверхность меняла размер/позицию,
но кадр оставался старым (часть видео не видно). requestRedraw():
updateTexImage + drawFrame для всех outputs — вызывается при смене
scale (LaunchedEffect) и в surfaceChanged. Подтверждено на железе.
This commit is contained in:
Hermes Agent
2026-08-20 00:52:21 +03:00
parent 9f20093bbf
commit 896185244b
11 changed files with 201 additions and 27 deletions
@@ -16,6 +16,7 @@ import pw.binom.viewmate.core.protocol.DownloadFiles
import pw.binom.viewmate.core.protocol.MediaCommandMsg
import pw.binom.viewmate.core.protocol.PlayVideo
import pw.binom.viewmate.core.protocol.SetMode
import pw.binom.viewmate.core.protocol.SetScale
import pw.binom.viewmate.core.protocol.ShowText
/**
@@ -102,6 +103,8 @@ class PhoneActions(
val title = item.Name ?: itemId
sender.send(PlayVideo(itemId = itemId, title = title, videoUrl = localVideoPath, audioIndex = audioIndex))
// очки не помнят scale между запусками — при старте просмотра шлём текущий
if (state.scale != 1f) sender.send(SetScale(state.scale))
state.mode = GlassesMode.MOVIE
state.currentItem = item
state.currentAudioIndex = audioIndex
@@ -253,5 +256,12 @@ class PhoneActions(
return "команда медиа: $cmd${if (seekMs != 0L) " ($seekMs мс)" else ""}"
}
/** Масштаб экрана очков: SetScale (0.5..1.0). */
suspend fun applyScale(scale: Float): String {
val clamped = scale.coerceIn(0.5f, 1f)
sender.send(SetScale(clamped))
return "масштаб → $clamped"
}
private fun ticksToMinutes(ticks: Long): Long = ticks / 10_000_000 / 60
}
@@ -29,6 +29,16 @@ class PhoneState {
log("state", "режим → $value")
}
private val _scale = MutableStateFlow(1f)
val scaleFlow: StateFlow<Float> = _scale.asStateFlow()
/** Масштаб экрана очков (0.5..1.0); слайдер пульта пишет сюда же. */
var scale: Float
get() = _scale.value
set(value) {
_scale.value = value.coerceIn(0.5f, 1f)
log("state", "масштаб → $value")
}
private val _currentItem = MutableStateFlow<JellyfinItem?>(null)
val currentItemFlow: StateFlow<JellyfinItem?> = _currentItem.asStateFlow()
var currentItem: JellyfinItem?
@@ -75,3 +75,8 @@ data class ShowText(val text: String, val sessionId: String? = null) : HostToGla
@Serializable
@SerialName("chat_session")
data class ChatSessionMsg(val sessionId: String, val title: String) : HostToGlasses
/** Масштаб экрана очков (0.5..1.0); 1.0 — на весь экран. */
@Serializable
@SerialName("set_scale")
data class SetScale(val scale: Float) : HostToGlasses
@@ -45,6 +45,16 @@ class HostToGlassesTest {
assertEquals(GlassesMode.MOVIE, decoded.mode)
}
@Test
fun setScaleRoundTrip() {
val msg: HostToGlasses = SetScale(scale = 0.7f)
val text = json.encodeToString(HostToGlasses.serializer(), msg)
assertEquals("""{"type":"set_scale","scale":0.7}""", text)
val decoded = json.decodeFromString(HostToGlasses.serializer(), text)
assertIs<SetScale>(decoded)
assertEquals(0.7f, decoded.scale)
}
@Test
fun mediaCommandRoundTrip() {
val msg: HostToGlasses = MediaCommandMsg(command = MediaCommand.SEEK_FORWARD, seekMs = 30_000)