Skip to content

Commit 0730783

Browse files
author
Taois
committed
feat:画质切换
1 parent d3cdcf9 commit 0730783

File tree

6 files changed

+517
-12
lines changed

6 files changed

+517
-12
lines changed

dashboard/src/api/modules/module.js

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,35 +263,87 @@ export const parsePlayUrl = async (module, params) => {
263263
if (playData.parse === 0) {
264264
// 直链播放
265265
result.playType = 'direct'
266-
result.url = playData.url || playData.play_url || ''
266+
267+
// 处理URL字段 - 支持数组格式的多画质
268+
const urlData = playData.url || playData.play_url || ''
269+
if (Array.isArray(urlData)) {
270+
// URL是数组格式,包含多画质信息
271+
console.log('检测到多画质URL数组:', urlData)
272+
273+
// 解析画质数组:奇数索引是画质名称,偶数索引是对应链接
274+
const qualities = []
275+
for (let i = 0; i < urlData.length; i += 2) {
276+
if (i + 1 < urlData.length) {
277+
const qualityName = urlData[i]?.toString().trim()
278+
const qualityUrl = urlData[i + 1]?.toString().trim()
279+
if (qualityName && qualityUrl) {
280+
qualities.push({
281+
name: qualityName,
282+
url: qualityUrl
283+
})
284+
}
285+
}
286+
}
287+
288+
console.log('解析出的画质列表:', qualities)
289+
290+
// 设置多画质数据
291+
result.qualities = qualities
292+
result.hasMultipleQualities = qualities.length > 1
293+
294+
// 默认使用第一个画质
295+
if (qualities.length > 0) {
296+
result.url = qualities[0].url
297+
result.currentQuality = qualities[0].name
298+
result.message = `多画质播放 (当前: ${qualities[0].name})`
299+
} else {
300+
result.url = ''
301+
result.message = '多画质数据解析失败'
302+
}
303+
} else {
304+
// URL是字符串格式,单一画质
305+
result.url = urlData
306+
result.qualities = []
307+
result.hasMultipleQualities = false
308+
result.currentQuality = '默认'
309+
result.message = '直链播放'
310+
}
311+
267312
result.headers = parseHeaders(playData.headers || playData.header)
268313
result.needParse = false
269314
result.needSniff = false
270-
result.message = '直链播放'
271315
} else if (playData.parse === 1) {
272316
// 需要嗅探
273317
result.playType = 'sniff'
274318
result.url = playData.url || playData.play_url || ''
275319
result.headers = parseHeaders(playData.headers || playData.header)
276320
result.needSniff = true
321+
result.qualities = []
322+
result.hasMultipleQualities = false
277323
result.message = '需要嗅探才能播放,尽情期待'
278324
} else if (playData.jx === 1) {
279325
// 需要解析
280326
result.playType = 'parse'
281327
result.url = playData.url || playData.play_url || ''
282328
result.headers = parseHeaders(playData.headers || playData.header)
283329
result.needParse = true
330+
result.qualities = []
331+
result.hasMultipleQualities = false
284332
result.message = '需要解析才能播放,尽情期待'
285333
} else {
286334
// 默认处理为直链
287335
result.url = playData.url || playData.play_url || playData
288336
result.headers = parseHeaders(playData.headers || playData.header)
337+
result.qualities = []
338+
result.hasMultipleQualities = false
289339
result.message = '直链播放'
290340
}
291341
} else if (typeof playData === 'string') {
292342
// 如果返回的是字符串,直接作为播放地址
293343
result.url = playData
294344
result.headers = {}
345+
result.qualities = []
346+
result.hasMultipleQualities = false
295347
result.message = '直链播放'
296348
}
297349

dashboard/src/components/players/ArtVideoPlayer.vue

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
:countdown-enabled="countdownEnabled"
99
:skip-enabled="skipEnabled"
1010
:show-debug-button="showDebugButton"
11+
:qualities="convertQualitiesToHeaderFormat"
12+
:current-quality="getCurrentQualityLabel"
1113
@toggle-auto-next="toggleAutoNext"
1214
@toggle-countdown="toggleCountdown"
1315
@player-change="handlePlayerTypeChange"
1416
@open-skip-settings="openSkipSettingsDialog"
1517
@toggle-debug="toggleDebugDialog"
1618
@proxy-change="handleProxyChange"
19+
@quality-change="handleHeaderQualityChange"
1720
@close="closePlayer"
1821
/>
1922
<div class="art-player-wrapper" v-show="props.visible">
@@ -57,7 +60,7 @@
5760
<!-- 调试信息弹窗组件 -->
5861
<DebugInfoDialog
5962
:visible="showDebugDialog"
60-
:video-url="videoUrl"
63+
:video-url="currentPlayingUrl || videoUrl"
6164
:headers="headers"
6265
:player-type="'artplayer'"
6366
:detected-format="detectedFormat"
@@ -124,11 +127,24 @@ const props = defineProps({
124127
headers: {
125128
type: Object,
126129
default: () => ({})
130+
},
131+
// 画质相关属性
132+
qualities: {
133+
type: Array,
134+
default: () => []
135+
},
136+
hasMultipleQualities: {
137+
type: Boolean,
138+
default: false
139+
},
140+
initialQuality: {
141+
type: String,
142+
default: '默认'
127143
}
128144
})
129145
130146
// Emits
131-
const emit = defineEmits(['close', 'error', 'player-change', 'next-episode', 'episode-selected'])
147+
const emit = defineEmits(['close', 'error', 'player-change', 'next-episode', 'episode-selected', 'quality-change'])
132148
133149
// 响应式数据
134150
const artPlayerContainer = ref(null)
@@ -150,6 +166,11 @@ const countdownEnabled = ref(false) // 倒计时开关,默认关闭
150166
const showDebugDialog = ref(false)
151167
const detectedFormat = ref('')
152168
169+
// 画质相关
170+
const currentQuality = ref('默认')
171+
const availableQualities = ref([])
172+
const currentPlayingUrl = ref('')
173+
153174
// 计算属性:是否显示调试按钮
154175
const showDebugButton = computed(() => {
155176
return !!props.videoUrl
@@ -163,6 +184,26 @@ const proxyVideoUrl = computed(() => {
163184
return processVideoUrl(props.videoUrl, headers)
164185
})
165186
187+
// 计算属性:获取当前画质的标签
188+
const getCurrentQualityLabel = computed(() => {
189+
if (!currentQuality.value || availableQualities.value.length === 0) {
190+
return '默认'
191+
}
192+
193+
// 按照T4格式处理:使用name字段
194+
const currentQualityData = availableQualities.value.find(q => q.name === currentQuality.value)
195+
return currentQualityData?.name || currentQuality.value || '默认'
196+
})
197+
198+
// 计算属性:转换画质数据格式以适配PlayerHeader组件
199+
const convertQualitiesToHeaderFormat = computed(() => {
200+
return availableQualities.value.map(q => ({
201+
name: q.name || '未知',
202+
value: q.name,
203+
url: q.url
204+
}))
205+
})
206+
166207
// 选集弹窗相关数据已移除,现在使用ArtPlayer的layer功能
167208
168209
// 使用片头片尾设置组合式函数
@@ -428,6 +469,21 @@ const initArtPlayer = async (url) => {
428469
playNextEpisode()
429470
},
430471
},
472+
{
473+
position: 'right',
474+
html: availableQualities.value.length > 1 ? `画质: ${getCurrentQualityLabel.value}` : '',
475+
tooltip: availableQualities.value.length > 1 ? '选择画质' : '',
476+
style: availableQualities.value.length > 1 ? {} : { display: 'none' },
477+
selector: availableQualities.value.length > 1 ? availableQualities.value.map(q => ({
478+
html: q.name || '未知',
479+
value: q.name,
480+
default: q.name === currentQuality.value
481+
})) : [],
482+
onSelect: function (item) {
483+
handleQualityChange(item.value)
484+
return item.html
485+
},
486+
},
431487
{
432488
position: 'right',
433489
html: props.episodes.length > 1 ? '选集' : '',
@@ -596,6 +652,109 @@ const initArtPlayer = async (url) => {
596652
}
597653
}
598654
655+
// 初始化画质数据
656+
const initQualityData = () => {
657+
if (props.qualities && props.qualities.length > 0) {
658+
availableQualities.value = [...props.qualities]
659+
currentQuality.value = props.initialQuality || props.qualities[0]?.name || '默认'
660+
661+
// 设置当前播放URL
662+
const currentQualityData = availableQualities.value.find(q => q.name === currentQuality.value)
663+
currentPlayingUrl.value = currentQualityData?.url || props.videoUrl
664+
} else {
665+
availableQualities.value = []
666+
currentQuality.value = '默认'
667+
currentPlayingUrl.value = props.videoUrl
668+
}
669+
670+
console.log('画质数据初始化完成:', {
671+
available: availableQualities.value,
672+
current: currentQuality.value,
673+
currentPlayingUrl: currentPlayingUrl.value
674+
})
675+
}
676+
677+
// 处理画质切换
678+
const handleQualityChange = (qualityName) => {
679+
const quality = availableQualities.value.find(q => q.name === qualityName)
680+
if (!quality) {
681+
console.warn('未找到指定画质:', qualityName)
682+
return
683+
}
684+
685+
console.log('切换画质:', qualityName, quality)
686+
687+
// 保存当前播放状态
688+
let currentTime = 0
689+
let isPaused = true
690+
691+
if (artPlayerInstance.value) {
692+
currentTime = artPlayerInstance.value.currentTime || 0
693+
isPaused = artPlayerInstance.value.paused
694+
}
695+
696+
// 更新当前画质和播放URL
697+
currentQuality.value = qualityName
698+
currentPlayingUrl.value = quality.url
699+
700+
// 触发画质切换事件,让父组件更新videoUrl
701+
emit('quality-change', quality)
702+
703+
// 等待父组件更新videoUrl后重新初始化播放器
704+
nextTick(() => {
705+
if (quality.url && artPlayerInstance.value) {
706+
// 更新播放器URL
707+
artPlayerInstance.value.switchUrl(quality.url)
708+
709+
// 恢复播放位置和状态
710+
setTimeout(() => {
711+
if (artPlayerInstance.value) {
712+
artPlayerInstance.value.currentTime = currentTime
713+
if (!isPaused) {
714+
artPlayerInstance.value.play()
715+
}
716+
}
717+
}, 100)
718+
}
719+
})
720+
}
721+
722+
723+
724+
// 处理来自PlayerHeader的画质切换事件
725+
const handleHeaderQualityChange = (qualityName) => {
726+
// 根据name找到对应的画质
727+
const quality = availableQualities.value.find(q => q.name === qualityName)
728+
729+
if (quality) {
730+
handleQualityChange(quality.name)
731+
}
732+
}
733+
734+
// 切换视频源(用于画质切换)
735+
const switchVideoSource = (newUrl) => {
736+
if (!artPlayerInstance.value || !newUrl) return
737+
738+
console.log('切换视频源:', newUrl)
739+
740+
// 保存当前播放状态
741+
const currentTime = artPlayerInstance.value.currentTime || 0
742+
const isPaused = artPlayerInstance.value.paused
743+
744+
// 切换URL
745+
artPlayerInstance.value.switchUrl(newUrl)
746+
747+
// 恢复播放位置和状态
748+
setTimeout(() => {
749+
if (artPlayerInstance.value) {
750+
artPlayerInstance.value.currentTime = currentTime
751+
if (!isPaused) {
752+
artPlayerInstance.value.play()
753+
}
754+
}
755+
}, 100)
756+
}
757+
599758
// 关闭播放器
600759
const closePlayer = () => {
601760
console.log('关闭 ArtPlayer 播放器')
@@ -1066,6 +1225,18 @@ watch(() => props.visible, async (newVisible) => {
10661225
}
10671226
})
10681227
1228+
// 监听画质数据变化
1229+
watch(() => props.qualities, () => {
1230+
initQualityData()
1231+
}, { immediate: true, deep: true })
1232+
1233+
// 监听初始画质变化
1234+
watch(() => props.initialQuality, (newQuality) => {
1235+
if (newQuality && newQuality !== currentQuality.value) {
1236+
currentQuality.value = newQuality
1237+
}
1238+
})
1239+
10691240
// 窗口大小变化处理
10701241
const handleResize = () => {
10711242
if (artPlayerContainer.value && artPlayerInstance.value) {
@@ -1086,6 +1257,8 @@ onMounted(() => {
10861257
window.addEventListener('resize', handleResize)
10871258
// 初始化片头片尾设置
10881259
initSkipSettings()
1260+
// 初始化画质数据
1261+
initQualityData()
10891262
})
10901263
10911264
// 组件卸载时清理资源

0 commit comments

Comments
 (0)