Skip to content

Commit 2fc3727

Browse files
author
Taois
committed
feat: 完成默认播放器优化
1 parent 546c1d3 commit 2fc3727

File tree

2 files changed

+214
-2
lines changed

2 files changed

+214
-2
lines changed

dashboard/src/components/players/VideoPlayer.vue

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@
5858
>
5959
您的浏览器不支持视频播放
6060
</video>
61+
62+
<!-- 自动下一集倒计时弹窗 -->
63+
<div v-if="showAutoNextDialog" class="auto-next-dialog">
64+
<div class="auto-next-content">
65+
<div class="auto-next-title">
66+
<span>即将播放下一集</span>
67+
</div>
68+
<div class="auto-next-episode" v-if="getNextEpisode()">
69+
{{ getNextEpisode().name }}
70+
</div>
71+
<div class="auto-next-countdown">
72+
{{ autoNextCountdown }} 秒后自动播放
73+
</div>
74+
<div class="auto-next-buttons">
75+
<button @click="playNextEpisode" class="btn-play-now">立即播放</button>
76+
<button @click="cancelAutoNext" class="btn-cancel">取消</button>
77+
</div>
78+
</div>
79+
</div>
6180
</div>
6281
</a-card>
6382
</template>
@@ -89,17 +108,30 @@ const props = defineProps({
89108
playerType: {
90109
type: String,
91110
default: 'default'
111+
},
112+
// 自动下一集功能相关 props
113+
episodes: {
114+
type: Array,
115+
default: () => []
116+
},
117+
currentEpisodeIndex: {
118+
type: Number,
119+
default: 0
92120
}
93121
})
94122
95123
// Emits
96-
const emit = defineEmits(['close', 'error', 'player-change'])
124+
const emit = defineEmits(['close', 'error', 'player-change', 'next-episode'])
97125
98126
// 响应式数据
99127
const videoPlayer = ref(null)
100128
const hlsInstance = ref(null)
101-
const autoNext = ref(false)
129+
const autoNext = ref(true) // 默认开启自动连播
102130
const showCountdown = ref(false)
131+
const showAutoNextDialog = ref(false)
132+
const autoNextCountdown = ref(10)
133+
const countdownTimer = ref(null)
134+
const isProcessingAutoNext = ref(false) // 防止重复触发自动连播
103135
104136
// 切换自动连播
105137
const toggleAutoNext = () => {
@@ -111,6 +143,64 @@ const toggleCountdown = () => {
111143
showCountdown.value = !showCountdown.value
112144
}
113145
146+
// 检查是否有下一集
147+
const hasNextEpisode = () => {
148+
return props.episodes && props.episodes.length > 0 &&
149+
props.currentEpisodeIndex < props.episodes.length - 1
150+
}
151+
152+
// 获取下一集信息
153+
const getNextEpisode = () => {
154+
if (hasNextEpisode()) {
155+
return props.episodes[props.currentEpisodeIndex + 1]
156+
}
157+
return null
158+
}
159+
160+
// 播放下一集
161+
const playNextEpisode = () => {
162+
if (hasNextEpisode()) {
163+
const nextIndex = props.currentEpisodeIndex + 1
164+
emit('next-episode', nextIndex)
165+
hideAutoNextDialog()
166+
// 重置防抖标志
167+
setTimeout(() => {
168+
isProcessingAutoNext.value = false
169+
}, 2000) // 2秒后重置,给视频切换足够的时间
170+
}
171+
}
172+
173+
// 显示自动下一集对话框
174+
const showAutoNextDialogFunc = () => {
175+
if (!autoNext.value || !hasNextEpisode()) return
176+
177+
showAutoNextDialog.value = true
178+
autoNextCountdown.value = 10
179+
180+
countdownTimer.value = setInterval(() => {
181+
autoNextCountdown.value--
182+
if (autoNextCountdown.value <= 0) {
183+
playNextEpisode()
184+
}
185+
}, 1000)
186+
}
187+
188+
// 隐藏自动下一集对话框
189+
const hideAutoNextDialog = () => {
190+
showAutoNextDialog.value = false
191+
if (countdownTimer.value) {
192+
clearInterval(countdownTimer.value)
193+
countdownTimer.value = null
194+
}
195+
}
196+
197+
// 取消自动下一集
198+
const cancelAutoNext = () => {
199+
hideAutoNextDialog()
200+
// 重置防抖标志
201+
isProcessingAutoNext.value = false
202+
}
203+
114204
// 链接类型判断函数
115205
const isDirectVideoLink = (url) => {
116206
if (!url) return false
@@ -179,6 +269,30 @@ const initVideoPlayer = (url) => {
179269
180270
const video = videoPlayer.value
181271
272+
// 视频结束事件处理函数
273+
const handleVideoEnded = () => {
274+
console.log('视频播放结束')
275+
276+
// 防抖:如果正在处理自动连播,则忽略
277+
if (isProcessingAutoNext.value) {
278+
console.log('正在处理自动连播,忽略重复的ended事件')
279+
return
280+
}
281+
282+
if (autoNext.value && hasNextEpisode()) {
283+
isProcessingAutoNext.value = true
284+
285+
if (showCountdown.value) {
286+
showAutoNextDialogFunc()
287+
} else {
288+
// 如果不显示倒计时,直接播放下一集
289+
setTimeout(() => {
290+
playNextEpisode()
291+
}, 1000)
292+
}
293+
}
294+
}
295+
182296
// 检测视频格式
183297
const isM3u8 = url.toLowerCase().includes('.m3u8') || url.toLowerCase().includes('m3u8')
184298
@@ -243,6 +357,7 @@ const initVideoPlayer = (url) => {
243357
} else {
244358
Message.error('您的浏览器不支持HLS播放')
245359
emit('error', '浏览器不支持HLS播放')
360+
return // 如果不支持HLS,直接返回,不添加事件监听器
246361
}
247362
} else {
248363
// 处理其他格式的视频(mp4, webm, avi等)
@@ -290,6 +405,10 @@ const initVideoPlayer = (url) => {
290405
// 开始加载视频
291406
video.load()
292407
}
408+
409+
// 统一添加视频结束事件监听器(避免重复添加)
410+
video.removeEventListener('ended', handleVideoEnded)
411+
video.addEventListener('ended', handleVideoEnded)
293412
}
294413
295414
// 关闭播放器
@@ -343,10 +462,24 @@ watch(() => props.visible, (newVisible) => {
343462
// 组件卸载时清理资源
344463
onUnmounted(() => {
345464
console.log('VideoPlayer组件卸载,清理播放器资源')
465+
466+
// 清理视频播放器
467+
if (videoPlayer.value) {
468+
videoPlayer.value.pause()
469+
videoPlayer.value.src = ''
470+
videoPlayer.value.load() // 这会清理所有事件监听器
471+
}
472+
346473
if (hlsInstance.value) {
347474
hlsInstance.value.destroy()
348475
hlsInstance.value = null
349476
}
477+
478+
// 清理倒计时定时器
479+
if (countdownTimer.value) {
480+
clearInterval(countdownTimer.value)
481+
countdownTimer.value = null
482+
}
350483
})
351484
</script>
352485

@@ -486,6 +619,82 @@ onUnmounted(() => {
486619
color: #fff;
487620
}
488621
622+
/* 自动下一集倒计时弹窗样式 */
623+
.auto-next-dialog {
624+
position: absolute;
625+
top: 50%;
626+
left: 50%;
627+
transform: translate(-50%, -50%);
628+
background: rgba(0, 0, 0, 0.9);
629+
border-radius: 12px;
630+
padding: 24px;
631+
z-index: 1000;
632+
min-width: 300px;
633+
text-align: center;
634+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
635+
backdrop-filter: blur(10px);
636+
}
637+
638+
.auto-next-content {
639+
display: flex;
640+
flex-direction: column;
641+
gap: 12px;
642+
}
643+
644+
.auto-next-title {
645+
font-size: 16px;
646+
font-weight: 600;
647+
color: #fff;
648+
}
649+
650+
.auto-next-episode {
651+
font-size: 14px;
652+
color: #23ade5;
653+
font-weight: 500;
654+
}
655+
656+
.auto-next-countdown {
657+
font-size: 18px;
658+
font-weight: bold;
659+
color: #ff6b6b;
660+
}
661+
662+
.auto-next-buttons {
663+
display: flex;
664+
gap: 12px;
665+
justify-content: center;
666+
margin-top: 8px;
667+
}
668+
669+
.btn-play-now,
670+
.btn-cancel {
671+
padding: 8px 16px;
672+
border: none;
673+
border-radius: 4px;
674+
cursor: pointer;
675+
font-size: 14px;
676+
font-weight: 500;
677+
transition: all 0.2s ease;
678+
}
679+
680+
.btn-play-now {
681+
background: #23ade5;
682+
color: white;
683+
}
684+
685+
.btn-play-now:hover {
686+
background: #1890d5;
687+
}
688+
689+
.btn-cancel {
690+
background: #666;
691+
color: white;
692+
}
693+
694+
.btn-cancel:hover {
695+
background: #555;
696+
}
697+
489698
/* 响应式设计 */
490699
@media (max-width: 768px) {
491700
.player-header {

dashboard/src/views/VideoDetail.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@
5656
:poster="videoDetail?.vod_pic"
5757
:visible="showVideoPlayer"
5858
:player-type="playerType"
59+
:episodes="currentRouteEpisodes"
60+
:current-episode-index="currentEpisodeIndex"
5961
@close="handlePlayerClose"
6062
@player-change="handlePlayerTypeChange"
63+
@next-episode="handleNextEpisode"
6164
/>
6265

6366
<!-- ArtPlayer 播放器组件 -->

0 commit comments

Comments
 (0)