package pw.binom.viewmate.glasses import android.content.Context import android.os.Handler import android.os.Looper import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.SimpleExoPlayer 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.PlayVideo /** * Держатель плеера фильма: создаёт/освобождает SimpleExoPlayer, * выполняет команды из WS-протокола. Сам плеер создаётся только по PlayVideo. */ class MovieController(private val context: Context) { private val _movieActive = MutableStateFlow(false) val movieActive: StateFlow = _movieActive.asStateFlow() private val _title = MutableStateFlow(null) val title: StateFlow = _title.asStateFlow() private var _player: SimpleExoPlayer? = null val player: SimpleExoPlayer? get() = _player private val mainHandler = Handler(Looper.getMainLooper()) fun playVideo(msg: PlayVideo) { mainHandler.post { val player = _player ?: SimpleExoPlayer.Builder(context).build().also { _player = it log("glasses", "SimpleExoPlayer создан") } _title.value = msg.title log("glasses", "PlayVideo: «${msg.title}» — ${msg.videoUrl}") player.setMediaItem(MediaItem.fromUri(msg.videoUrl)) player.prepare() player.seekTo(msg.startPositionMs) player.playWhenReady = true _movieActive.value = true } } fun applyMediaCommand(command: MediaCommand, seekMs: Long) { mainHandler.post { val player = _player ?: run { log("glasses", "MediaCommand $command проигнорирован: плеер не активен") return@post } when (command) { MediaCommand.PLAY -> player.play() MediaCommand.PAUSE -> player.pause() MediaCommand.TOGGLE -> { if (player.isPlaying) player.pause() else player.play() } MediaCommand.SEEK_FORWARD -> { val target = player.currentPosition + seekMs player.seekTo(target) } MediaCommand.SEEK_BACKWARD -> { val target = (player.currentPosition - seekMs).coerceAtLeast(0L) player.seekTo(target) } // Звук — на телефоне, переключение аудио-дорожек очкам не нужно. MediaCommand.NEXT_AUDIO, MediaCommand.PREV_AUDIO -> { log("glasses", "MediaCommand $command игнорируется (звук на телефоне)") } MediaCommand.CLOSE -> close() } } } fun close() { mainHandler.post { val player = _player _player = null if (player != null) { log("glasses", "CLOSE: плеер остановлен и освобождён") player.playWhenReady = false player.stop() player.release() } _movieActive.value = false _title.value = null } } /** Читать ТОЛЬКО с главного потока. */ fun playbackPosition(): Pair { val player = _player ?: return 0L to false return player.currentPosition to player.isPlaying } }