|
|
|
@ -10,8 +10,6 @@ |
|
|
|
controls |
|
|
|
autoplay |
|
|
|
playsinline |
|
|
|
:src="playUrl" |
|
|
|
@error="$emit('error')" |
|
|
|
/> |
|
|
|
<div v-else class="camera-scene"> |
|
|
|
<div class="horizon" /> |
|
|
|
@ -37,7 +35,7 @@ |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup> |
|
|
|
import { computed, ref } from 'vue' |
|
|
|
import { computed, onBeforeUnmount, ref, watch } from 'vue' |
|
|
|
|
|
|
|
const props = defineProps({ |
|
|
|
playUrl: { type: String, default: '' }, |
|
|
|
@ -46,10 +44,12 @@ const props = defineProps({ |
|
|
|
cameraLabel: { type: String, default: '机巢摄像头' }, |
|
|
|
}) |
|
|
|
|
|
|
|
defineEmits(['toggle', 'error']) |
|
|
|
const emit = defineEmits(['toggle', 'error']) |
|
|
|
|
|
|
|
const frameEl = ref(null) |
|
|
|
const videoEl = ref(null) |
|
|
|
let hls = null |
|
|
|
let attachSeq = 0 |
|
|
|
|
|
|
|
const playable = computed(() => props.playUrl && !props.playUrl.startsWith('fake://')) |
|
|
|
const isLiveShell = computed(() => props.active && !playable.value) |
|
|
|
@ -71,6 +71,72 @@ const statusMessage = computed(() => { |
|
|
|
return '' |
|
|
|
}) |
|
|
|
|
|
|
|
function destroyPlayer() { |
|
|
|
if (hls) { |
|
|
|
hls.destroy() |
|
|
|
hls = null |
|
|
|
} |
|
|
|
const video = videoEl.value |
|
|
|
if (video) { |
|
|
|
video.removeAttribute('src') |
|
|
|
video.load() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
async function attachPlayer(url) { |
|
|
|
const seq = ++attachSeq |
|
|
|
destroyPlayer() |
|
|
|
if (!url || url.startsWith('fake://')) return |
|
|
|
|
|
|
|
await Promise.resolve() |
|
|
|
const video = videoEl.value |
|
|
|
if (!video || seq !== attachSeq) return |
|
|
|
|
|
|
|
video.addEventListener('error', () => { |
|
|
|
if (seq !== attachSeq) return |
|
|
|
emit('error') |
|
|
|
}, { once: true }) |
|
|
|
|
|
|
|
if (video.canPlayType('application/vnd.apple.mpegurl')) { |
|
|
|
video.src = url |
|
|
|
try { await video.play() } catch (_) {} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
const { default: Hls } = await import('hls.js') |
|
|
|
if (seq !== attachSeq) return |
|
|
|
if (!Hls.isSupported()) { |
|
|
|
emit('error') |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
hls = new Hls({ enableWorker: true, lowLatencyMode: true }) |
|
|
|
hls.on(Hls.Events.ERROR, (_event, data) => { |
|
|
|
if (seq !== attachSeq) return |
|
|
|
if (data?.fatal) emit('error') |
|
|
|
}) |
|
|
|
hls.loadSource(url) |
|
|
|
hls.attachMedia(video) |
|
|
|
hls.on(Hls.Events.MANIFEST_PARSED, () => { |
|
|
|
if (seq !== attachSeq) return |
|
|
|
video.play().catch(() => {}) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
watch( |
|
|
|
() => props.playUrl, |
|
|
|
(url) => { |
|
|
|
if (playable.value) attachPlayer(url) |
|
|
|
else destroyPlayer() |
|
|
|
}, |
|
|
|
{ immediate: true } |
|
|
|
) |
|
|
|
|
|
|
|
onBeforeUnmount(() => { |
|
|
|
attachSeq += 1 |
|
|
|
destroyPlayer() |
|
|
|
}) |
|
|
|
|
|
|
|
function requestFullscreen() { |
|
|
|
const el = playable.value ? videoEl.value : frameEl.value |
|
|
|
if (!el?.requestFullscreen) return false |
|
|
|
|