Skip to content

Commit 7b96544

Browse files
author
Taois
committed
feat:增加循环播放功能
1 parent c354cac commit 7b96544

File tree

4 files changed

+161
-30
lines changed

4 files changed

+161
-30
lines changed

dashboard/src/components/players/ArtVideoPlayer.vue

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:player-type="playerType"
66
:episodes="episodes"
77
:auto-next-enabled="autoNextEnabled"
8+
:loop-enabled="loopEnabled"
89
:countdown-enabled="countdownEnabled"
910
:skip-enabled="skipEnabled"
1011
:show-debug-button="showDebugButton"
@@ -14,6 +15,7 @@
1415
:needs-parsing="needsParsing"
1516
:parse-data="parseData"
1617
@toggle-auto-next="toggleAutoNext"
18+
@toggle-loop="toggleLoop"
1719
@toggle-countdown="toggleCountdown"
1820
@player-change="handlePlayerTypeChange"
1921
@open-skip-settings="openSkipSettingsDialog"
@@ -170,6 +172,7 @@ const dynamicHeight = ref(450) // 动态计算的高度
170172
171173
// 自动下一集功能相关数据
172174
const autoNextEnabled = ref(true) // 自动下一集开关,默认关闭
175+
const loopEnabled = ref(JSON.parse(localStorage.getItem('loopEnabled') || 'false')) // 循环播放开关,从本地存储读取
173176
const autoNextCountdown = ref(0) // 自动下一集倒计时
174177
const autoNextTimer = ref(null) // 自动下一集定时器
175178
const showAutoNextDialog = ref(false) // 显示自动下一集对话框
@@ -659,12 +662,34 @@ const initArtPlayer = async (url) => {
659662
})
660663
661664
art.on('video:ended', () => {
662-
console.log('视频播放结束')
663-
// 视频结束时启动自动下一集
664-
if (autoNextEnabled.value && hasNextEpisode()) {
665-
startAutoNextCountdown()
666-
} else if (!hasNextEpisode()) {
667-
Message.info('全部播放完毕')
665+
try {
666+
console.log('视频播放结束')
667+
668+
// 优先处理循环播放
669+
if (loopEnabled.value) {
670+
console.log('循环播放:重新播放当前选集')
671+
// 重新执行当前选集的播放逻辑
672+
setTimeout(() => {
673+
try {
674+
// 触发重新选择当前选集,这会重新获取播放链接
675+
emit('episode-selected', props.currentEpisodeIndex)
676+
} catch (error) {
677+
console.error('循环播放触发选集事件失败:', error)
678+
Message.error('循环播放失败,请重试')
679+
}
680+
}, 1000)
681+
return
682+
}
683+
684+
// 视频结束时启动自动下一集
685+
if (autoNextEnabled.value && hasNextEpisode()) {
686+
startAutoNextCountdown()
687+
} else if (!hasNextEpisode()) {
688+
Message.info('全部播放完毕')
689+
}
690+
} catch (error) {
691+
console.error('视频结束事件处理失败:', error)
692+
Message.error('视频结束处理失败')
668693
}
669694
})
670695
@@ -1022,11 +1047,33 @@ const playNextEpisode = () => {
10221047
const toggleAutoNext = () => {
10231048
autoNextEnabled.value = !autoNextEnabled.value
10241049
1050+
// 如果开启自动连播,则关闭循环播放
1051+
if (autoNextEnabled.value) {
1052+
loopEnabled.value = false
1053+
localStorage.setItem('loopEnabled', 'false')
1054+
}
1055+
10251056
if (!autoNextEnabled.value) {
10261057
cancelAutoNext()
10271058
}
10281059
}
10291060
1061+
// 切换循环播放开关
1062+
const toggleLoop = () => {
1063+
loopEnabled.value = !loopEnabled.value
1064+
1065+
// 保存到本地存储
1066+
localStorage.setItem('loopEnabled', JSON.stringify(loopEnabled.value))
1067+
1068+
// 如果开启循环播放,则关闭自动连播
1069+
if (loopEnabled.value) {
1070+
autoNextEnabled.value = false
1071+
cancelAutoNext()
1072+
}
1073+
1074+
console.log('循环播放开关:', loopEnabled.value ? '开启' : '关闭')
1075+
}
1076+
10301077
// 切换倒计时开关
10311078
const toggleCountdown = () => {
10321079
countdownEnabled.value = !countdownEnabled.value

dashboard/src/components/players/PlayerHeader.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@
2929
</svg>
3030
<span class="btn-text">自动连播</span>
3131
</div>
32+
33+
<!-- 循环播放按钮 -->
34+
<div
35+
class="compact-btn"
36+
@click="$emit('toggle-loop')"
37+
:class="{ active: loopEnabled }"
38+
title="循环播放当前选集"
39+
>
40+
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
41+
<path d="M17 1l4 4-4 4" stroke="currentColor" stroke-width="2" fill="none"/>
42+
<path d="M3 11V9a4 4 0 0 1 4-4h14" stroke="currentColor" stroke-width="2" fill="none"/>
43+
<path d="M7 23l-4-4 4-4" stroke="currentColor" stroke-width="2" fill="none"/>
44+
<path d="M21 13v2a4 4 0 0 1-4 4H3" stroke="currentColor" stroke-width="2" fill="none"/>
45+
</svg>
46+
<span class="btn-text">循环播放</span>
47+
</div>
3248

3349
<!-- 倒计时按钮 -->
3450
<div
@@ -199,6 +215,10 @@ const props = defineProps({
199215
type: Boolean,
200216
default: false
201217
},
218+
loopEnabled: {
219+
type: Boolean,
220+
default: false
221+
},
202222
countdownEnabled: {
203223
type: Boolean,
204224
default: false
@@ -246,6 +266,7 @@ const props = defineProps({
246266
// Emits
247267
const emit = defineEmits([
248268
'toggle-auto-next',
269+
'toggle-loop',
249270
'toggle-countdown',
250271
'player-change',
251272
'open-skip-settings',

dashboard/src/components/players/VideoPlayer.vue

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:player-type="playerType"
77
:episodes="episodes"
88
:auto-next-enabled="autoNext"
9+
:loop-enabled="loopEnabled"
910
:countdown-enabled="showCountdown"
1011
:skip-enabled="skipEnabled"
1112
:show-debug-button="showDebugButton"
@@ -15,6 +16,7 @@
1516
:needs-parsing="needsParsing"
1617
:parse-data="parseData"
1718
@toggle-auto-next="toggleAutoNext"
19+
@toggle-loop="toggleLoop"
1820
@toggle-countdown="toggleCountdown"
1921
@player-change="handlePlayerTypeChange"
2022
@open-skip-settings="openSkipSettingsDialog"
@@ -176,12 +178,13 @@ const props = defineProps({
176178
})
177179
178180
// Emits
179-
const emit = defineEmits(['close', 'error', 'player-change', 'next-episode', 'quality-change', 'parser-change'])
181+
const emit = defineEmits(['close', 'error', 'player-change', 'next-episode', 'episode-selected', 'quality-change', 'parser-change'])
180182
181183
// 响应式数据
182184
const videoPlayer = ref(null)
183185
const mediaPlayerManager = ref(null)
184186
const autoNext = ref(true) // 默认开启自动连播
187+
const loopEnabled = ref(JSON.parse(localStorage.getItem('loopEnabled') || 'false')) // 循环播放开关,从本地存储读取
185188
const showCountdown = ref(false)
186189
const showAutoNextDialog = ref(false)
187190
const autoNextCountdown = ref(10)
@@ -386,6 +389,26 @@ const {
386389
// 切换自动连播
387390
const toggleAutoNext = () => {
388391
autoNext.value = !autoNext.value
392+
// 如果开启自动连播,则关闭循环播放
393+
if (autoNext.value) {
394+
loopEnabled.value = false
395+
localStorage.setItem('loopEnabled', 'false')
396+
}
397+
}
398+
399+
// 切换循环播放
400+
const toggleLoop = () => {
401+
loopEnabled.value = !loopEnabled.value
402+
403+
// 保存到本地存储
404+
localStorage.setItem('loopEnabled', JSON.stringify(loopEnabled.value))
405+
406+
// 如果开启循环播放,则关闭自动连播
407+
if (loopEnabled.value) {
408+
autoNext.value = false
409+
}
410+
411+
console.log('循环播放开关:', loopEnabled.value ? '开启' : '关闭')
389412
}
390413
391414
// 切换倒计时显示
@@ -515,22 +538,43 @@ const initVideoPlayer = (url) => {
515538
516539
// 视频结束事件处理函数
517540
const handleVideoEnded = () => {
518-
// 防抖:如果正在处理自动连播,则忽略
519-
if (isProcessingAutoNext.value) {
520-
return
521-
}
522-
523-
if (autoNext.value && hasNextEpisode()) {
524-
isProcessingAutoNext.value = true
541+
try {
542+
// 防抖:如果正在处理自动连播,则忽略
543+
if (isProcessingAutoNext.value) {
544+
return
545+
}
525546
526-
if (showCountdown.value) {
527-
showAutoNextDialogFunc()
528-
} else {
529-
// 如果不显示倒计时,直接播放下一集
547+
// 优先处理循环播放
548+
if (loopEnabled.value) {
549+
console.log('循环播放:重新播放当前选集')
550+
// 重新执行当前选集的播放逻辑
530551
setTimeout(() => {
531-
playNextEpisode()
552+
try {
553+
// 触发重新选择当前选集,这会重新获取播放链接
554+
emit('episode-selected', props.currentEpisodeIndex)
555+
} catch (error) {
556+
console.error('循环播放触发选集事件失败:', error)
557+
Message.error('循环播放失败,请重试')
558+
}
532559
}, 1000)
560+
return
561+
}
562+
563+
if (autoNext.value && hasNextEpisode()) {
564+
isProcessingAutoNext.value = true
565+
566+
if (showCountdown.value) {
567+
showAutoNextDialogFunc()
568+
} else {
569+
// 如果不显示倒计时,直接播放下一集
570+
setTimeout(() => {
571+
playNextEpisode()
572+
}, 1000)
573+
}
533574
}
575+
} catch (error) {
576+
console.error('视频结束事件处理失败:', error)
577+
Message.error('视频结束处理失败')
534578
}
535579
}
536580

dashboard/src/views/VideoDetail.vue

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
@player-change="handlePlayerTypeChange"
6969
@parser-change="handleParserChange"
7070
@next-episode="handleNextEpisode"
71+
@episode-selected="handleEpisodeSelected"
72+
@quality-change="handleQualityChange"
7173
/>
7274

7375
<!-- ArtPlayer 播放器组件 -->
@@ -1017,19 +1019,36 @@ const handleNextEpisode = (nextEpisodeIndex) => {
10171019
}
10181020
10191021
// 处理选集选择事件
1020-
const handleEpisodeSelected = (episode) => {
1021-
console.log('从播放器选择剧集:', episode)
1022+
const handleEpisodeSelected = (episodeParam) => {
1023+
console.log('从播放器选择剧集:', episodeParam)
10221024
1023-
// 查找选集在当前路线中的索引
1024-
const episodeIndex = currentRouteEpisodes.value.findIndex(ep =>
1025-
ep.name === episode.name && ep.url === episode.url
1026-
)
1027-
1028-
if (episodeIndex !== -1) {
1029-
selectEpisode(episodeIndex)
1025+
// 判断传入的参数类型
1026+
if (typeof episodeParam === 'number') {
1027+
// 如果是数字,直接作为索引使用(循环播放场景)
1028+
const episodeIndex = episodeParam
1029+
if (episodeIndex >= 0 && episodeIndex < currentRouteEpisodes.value.length) {
1030+
console.log('使用索引选择选集:', episodeIndex)
1031+
selectEpisode(episodeIndex)
1032+
} else {
1033+
console.warn('无效的选集索引:', episodeIndex)
1034+
Message.warning('选集切换失败:无效的选集索引')
1035+
}
1036+
} else if (episodeParam && typeof episodeParam === 'object') {
1037+
// 如果是对象,按原来的逻辑查找索引
1038+
const episodeIndex = currentRouteEpisodes.value.findIndex(ep =>
1039+
ep.name === episodeParam.name && ep.url === episodeParam.url
1040+
)
1041+
1042+
if (episodeIndex !== -1) {
1043+
console.log('通过对象查找到选集索引:', episodeIndex)
1044+
selectEpisode(episodeIndex)
1045+
} else {
1046+
console.warn('未找到选集:', episodeParam)
1047+
Message.warning('选集切换失败:未找到匹配的选集')
1048+
}
10301049
} else {
1031-
console.warn('未找到选集:', episode)
1032-
Message.warning('选集切换失败')
1050+
console.warn('无效的选集参数:', episodeParam)
1051+
Message.warning('选集切换失败:参数格式错误')
10331052
}
10341053
}
10351054

0 commit comments

Comments
 (0)