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// 响应式数据
99127const videoPlayer = ref (null )
100128const hlsInstance = ref (null )
101- const autoNext = ref (false )
129+ const autoNext = ref (true ) // 默认开启自动连播
102130const 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// 切换自动连播
105137const 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// 链接类型判断函数
115205const 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// 组件卸载时清理资源
344463onUnmounted (() => {
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 {
0 commit comments