From f81ab0dea14072cd89a198a13f2f7d184ffc97be Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Sat, 06 Jun 2026 17:30:12 +0800
Subject: [PATCH] feat: m3u8播放
---
applications/task-work-order/src/components/VideoPlayDialog.vue | 120 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 97 insertions(+), 23 deletions(-)
diff --git a/applications/task-work-order/src/components/VideoPlayDialog.vue b/applications/task-work-order/src/components/VideoPlayDialog.vue
index f6975d5..8e8e86b 100644
--- a/applications/task-work-order/src/components/VideoPlayDialog.vue
+++ b/applications/task-work-order/src/components/VideoPlayDialog.vue
@@ -1,56 +1,130 @@
<script setup>
+import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
+import videojs from 'video.js'
+
const props = defineProps({
width: {
type: String,
- default: '800'
+ default: '800',
},
modelValue: {
type: Boolean,
- default: false
+ default: false,
},
playUrl: {
type: String,
- default: ''
+ default: '',
},
- title:{
+ title: {
type: String,
- default: '视频播放'
- }
+ default: '视频播放',
+ },
})
const emit = defineEmits(['update:modelValue'])
-const handleClose = () => {
- emit('update:modelValue', false)
+const videoRefs = ref(null)
+let player = null
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: value => emit('update:modelValue', value),
+})
+
+function getVideoType(url) {
+ const path = (url || '').split('?')[0].toLowerCase()
+ if (path.endsWith('.m3u8')) return 'application/x-mpegURL'
+ if (path.endsWith('.mp4')) return 'video/mp4'
+ if (path.endsWith('.webm')) return 'video/webm'
+ if (path.endsWith('.ogg') || path.endsWith('.ogv')) return 'video/ogg'
+ if (path.endsWith('.mov')) return 'video/quicktime'
+ return ''
}
+
+function destroyPlayer() {
+ if (!player) return
+ player.dispose()
+ player = null
+}
+
+function getSource() {
+ const type = getVideoType(props.playUrl)
+ return {
+ src: props.playUrl,
+ ...(type ? { type } : {}),
+ }
+}
+
+async function initPlayer() {
+ if (!props.modelValue || !props.playUrl) return
+ await nextTick()
+ if (!videoRefs.value) return
+
+ if (player) {
+ player.src(getSource())
+ player.load()
+ return
+ }
+
+ player = videojs(videoRefs.value, {
+ controls: true,
+ preload: 'auto',
+ autoplay: false,
+ fluid: false,
+ width: '100%',
+ height: '600px',
+ sources: [getSource()],
+ })
+ player.on('error', () => {
+ console.error('视频播放失败:', player?.error(), props.playUrl)
+ })
+}
+
+watch(
+ () => props.playUrl,
+ () => {
+ if (!props.modelValue || !props.playUrl) return
+ initPlayer()
+ }
+)
+
+onBeforeUnmount(destroyPlayer)
</script>
<template>
<el-dialog
- class="gd-dialog"
+ class="gd-dialog video-dialog"
:title="props.title"
append-to-body
- v-model="props.modelValue"
+ v-model="dialogVisible"
width="60%"
destroy-on-close
:close-on-click-modal="false"
- @close="handleClose"
+ @opened="initPlayer"
+ @closed="destroyPlayer"
>
- <video ref="videoRefs" controls preload="auto" :src="props.playUrl">
- <source :src="props.playUrl" type="video/mp4" />
- </video>
- <!-- <el-carousel trigger="click" :autoplay="false" @change="handleCarouselChange">-->
- <!-- <el-carousel-item v-for="(item, index) in videoList" :key="index">-->
- <!-- <video ref="videoRefs" controls preload="auto" :src="currentVideoUrl" @play="handleVideoPlay" @ended="handleVideoEnded(index)">-->
- <!-- <source :src="item.smallUrl" type="video/mp4" />-->
- <!-- </video>-->
- <!-- </el-carousel-item>-->
- <!-- </el-carousel>-->
+ <video
+ ref="videoRefs"
+ id="videoPlayDialogPlayer"
+ class="video-js vjs-default-skin vjs-big-play-centered"
+ controls
+ preload="auto"
+ playsinline
+ ></video>
</el-dialog>
</template>
+<style lang="scss">
+.video-dialog {
+ .video-js {
+ height: 600px;
+ width: 100%;
+ }
+}
+</style>
+
<style scoped lang="scss">
-video {
- height: 600px;
+:deep(.video-js .vjs-tech) {
+ height: 100%;
width: 100%;
}
</style>
--
Gitblit v1.9.3