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" >
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// 响应式数据
134150const artPlayerContainer = ref (null )
@@ -150,6 +166,11 @@ const countdownEnabled = ref(false) // 倒计时开关,默认关闭
150166const showDebugDialog = ref (false )
151167const detectedFormat = ref (' ' )
152168
169+ // 画质相关
170+ const currentQuality = ref (' 默认' )
171+ const availableQualities = ref ([])
172+ const currentPlayingUrl = ref (' ' )
173+
153174// 计算属性:是否显示调试按钮
154175const 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// 关闭播放器
600759const 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// 窗口大小变化处理
10701241const 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