3 changed files with 104 additions and 17 deletions
@ -1,27 +1,82 @@ |
|||
<template> |
|||
<div class="live-player"> |
|||
<video v-if="playable" ref="video" controls autoplay playsinline :src="playUrl" @error="$emit('error')"></video> |
|||
<div v-else class="video-empty"> |
|||
<span>{{ message }}</span> |
|||
<div |
|||
ref="frameEl" |
|||
class="video-frame" |
|||
:class="{ streaming: isLiveShell, playing: playable }" |
|||
> |
|||
<video |
|||
v-if="playable" |
|||
ref="videoEl" |
|||
controls |
|||
autoplay |
|||
playsinline |
|||
:src="playUrl" |
|||
@error="$emit('error')" |
|||
/> |
|||
<div v-else class="camera-scene"> |
|||
<div class="horizon" /> |
|||
<div class="dock-model"><span /><span /><i /></div> |
|||
</div> |
|||
|
|||
<div class="video-overlay"> |
|||
<span>{{ cameraLabel }}</span> |
|||
<small>{{ qualityText }}</small> |
|||
</div> |
|||
|
|||
<div v-if="statusMessage" class="video-status">{{ statusMessage }}</div> |
|||
|
|||
<button |
|||
class="play-control" |
|||
type="button" |
|||
:aria-label="active ? '关闭实时画面' : '打开实时画面'" |
|||
@click="$emit('toggle')" |
|||
> |
|||
<svg><use :href="active ? '#i-stop' : '#i-play'" /></svg> |
|||
</button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { computed } from 'vue' |
|||
import { computed, ref } from 'vue' |
|||
|
|||
const props = defineProps({ |
|||
playUrl: { type: String, default: '' }, |
|||
phase: { type: String, default: '' } |
|||
phase: { type: String, default: '' }, |
|||
active: { type: Boolean, default: false }, |
|||
cameraLabel: { type: String, default: '机巢摄像头' }, |
|||
}) |
|||
|
|||
defineEmits(['error']) |
|||
defineEmits(['toggle', 'error']) |
|||
|
|||
const frameEl = ref(null) |
|||
const videoEl = ref(null) |
|||
|
|||
const playable = computed(() => props.playUrl && !props.playUrl.startsWith('fake://')) |
|||
const message = computed(() => { |
|||
const isLiveShell = computed(() => props.active && !playable.value) |
|||
|
|||
const qualityText = computed(() => { |
|||
if (playable.value) return 'LIVE · 播放中' |
|||
if (props.playUrl?.startsWith('fake://')) return '控制面已验证' |
|||
if (props.phase === 'starting' || props.phase === 'reconnecting') return '启动中' |
|||
if (props.phase === 'failed') return '启动失败' |
|||
return 'H.264 · 720P' |
|||
}) |
|||
|
|||
const statusMessage = computed(() => { |
|||
if (playable.value) return '' |
|||
if (props.phase === 'starting' || props.phase === 'reconnecting') return '正在等待直播流就绪' |
|||
if (props.phase === 'failed') return '直播启动失败' |
|||
if (props.playUrl?.startsWith('fake://')) return '控制面已验证,未配置本地媒体流' |
|||
return '暂无实时视频数据' |
|||
if (props.active) return '暂无实时视频数据' |
|||
return '' |
|||
}) |
|||
|
|||
function requestFullscreen() { |
|||
const el = playable.value ? videoEl.value : frameEl.value |
|||
if (!el?.requestFullscreen) return false |
|||
el.requestFullscreen() |
|||
return true |
|||
} |
|||
|
|||
defineExpose({ requestFullscreen, frameEl, videoEl }) |
|||
</script> |
|||
|
|||
Loading…
Reference in new issue