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