3 changed files with 104 additions and 17 deletions
@ -1,27 +1,82 @@ |
|||||
<template> |
<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> |
||||
|
|
||||
|
<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> |
</div> |
||||
</template> |
</template> |
||||
|
|
||||
<script setup> |
<script setup> |
||||
import { computed } from 'vue' |
|
||||
|
import { computed, ref } from 'vue' |
||||
|
|
||||
const props = defineProps({ |
const props = defineProps({ |
||||
playUrl: { type: String, default: '' }, |
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 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 === 'starting' || props.phase === 'reconnecting') return '正在等待直播流就绪' |
||||
if (props.phase === 'failed') return '直播启动失败' |
if (props.phase === 'failed') return '直播启动失败' |
||||
if (props.playUrl?.startsWith('fake://')) 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> |
</script> |
||||
|
|||||
Loading…
Reference in new issue