up
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import type { Command, MediaKind, State } from '../types/screen'
|
||||
|
||||
const audio = ref<HTMLAudioElement | null>(null)
|
||||
const video = ref<HTMLVideoElement | null>(null)
|
||||
const kind = ref<MediaKind>('audio')
|
||||
const bridge = window.screenBridge!
|
||||
let unsubscribe: (() => void) | undefined
|
||||
let generation = 0
|
||||
let lastRevision = -1
|
||||
let commandId = ''
|
||||
let lastCommand: Command | null = null
|
||||
|
||||
async function metadata(media: HTMLMediaElement) {
|
||||
if (media.readyState >= 1) return
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const finish = (error?: Error) => {
|
||||
clearTimeout(timeout)
|
||||
media.removeEventListener('loadedmetadata', ready)
|
||||
media.removeEventListener('error', failed)
|
||||
if (error) reject(error)
|
||||
else resolve()
|
||||
}
|
||||
const ready = () => finish()
|
||||
const failed = () => finish(new Error('Не удалось прочитать тестовое медиа'))
|
||||
const timeout = setTimeout(() => finish(new Error('Медиа не подготовлено за 10 секунд')), 10000)
|
||||
media.addEventListener('loadedmetadata', ready, { once: true })
|
||||
media.addEventListener('error', failed, { once: true })
|
||||
if (media.error) failed()
|
||||
})
|
||||
}
|
||||
async function apply(state: State) {
|
||||
if (state.revision <= lastRevision) return
|
||||
lastRevision = state.revision
|
||||
const command = state.command
|
||||
if (state.status !== 'pending' || !command || command.id === commandId) return
|
||||
commandId = command.id
|
||||
lastCommand = command
|
||||
const current = ++generation
|
||||
const media = command.media === 'audio' ? audio.value : video.value
|
||||
const other = command.media === 'audio' ? video.value : audio.value
|
||||
other?.pause()
|
||||
kind.value = command.media
|
||||
if (!media) return
|
||||
try {
|
||||
if (command.action === 'pause' || command.action === 'stop') media.pause()
|
||||
await metadata(media)
|
||||
if (current !== generation) return
|
||||
if (command.action === 'play' || command.action === 'stop') media.currentTime = 0
|
||||
if (command.action === 'seek') media.currentTime = Math.min(command.position, media.duration)
|
||||
if (command.action === 'play') {
|
||||
await media.play()
|
||||
if (current !== generation) return
|
||||
}
|
||||
const status = command.action === 'stop' ? 'stopped' : media.paused ? 'paused' : 'playing'
|
||||
bridge.report({ id: command.id, status })
|
||||
} catch (error) {
|
||||
if (current === generation)
|
||||
bridge.report({
|
||||
id: command.id,
|
||||
status: 'error',
|
||||
error: error instanceof Error ? error.message : 'Ошибка воспроизведения',
|
||||
})
|
||||
}
|
||||
}
|
||||
function activeMedia() {
|
||||
return lastCommand?.media === 'audio' ? audio.value : video.value
|
||||
}
|
||||
function ended(event: Event) {
|
||||
const media = activeMedia()
|
||||
if (lastCommand && event.target === media && media?.ended)
|
||||
bridge.report({ id: lastCommand.id, status: 'ended' })
|
||||
}
|
||||
function failed(event: Event) {
|
||||
if (lastCommand && event.target === activeMedia())
|
||||
bridge.report({
|
||||
id: lastCommand.id,
|
||||
status: 'error',
|
||||
error: 'Ошибка локального медиа; проверьте наличие fixtures',
|
||||
})
|
||||
}
|
||||
onMounted(async () => {
|
||||
for (const media of [audio.value, video.value]) if (media) media.volume = 0.35
|
||||
unsubscribe = bridge.subscribe((state) => {
|
||||
void apply(state)
|
||||
})
|
||||
await apply(await bridge.state())
|
||||
})
|
||||
onUnmounted(() => {
|
||||
generation++
|
||||
unsubscribe?.()
|
||||
audio.value?.pause()
|
||||
video.value?.pause()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="output">
|
||||
<audio
|
||||
ref="audio"
|
||||
:src="'screen-app://app/media/audio'"
|
||||
preload="auto"
|
||||
@ended="ended"
|
||||
@error="failed"
|
||||
/>
|
||||
<video
|
||||
v-show="kind === 'video'"
|
||||
ref="video"
|
||||
:src="'screen-app://app/media/video'"
|
||||
preload="auto"
|
||||
@ended="ended"
|
||||
@error="failed"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.output {
|
||||
background: var(--brand-background);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user