From 61a99975b27c4a9417c413bde978bc4411c49b6e Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Thu, 21 Aug 2025 11:30:35 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B7=B3=E8=BD=AC=E8=87=B3=E4=B8=8B?= =?UTF-8?q?=E4=B8=80=E9=A6=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Player.vue | 61 +++++++++++++++++++++++++++++++-- src/stores/usePlayQueueStore.ts | 42 +++++++++++++++++++++-- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/src/components/Player.vue b/src/components/Player.vue index e8d1bf0..ae0fd37 100644 --- a/src/components/Player.vue +++ b/src/components/Player.vue @@ -7,6 +7,7 @@ import apis from '../apis' const playQueue = usePlayQueueStore() const resourcesUrl = ref<{ [key: string]: string }>({}) +const audioRefs = ref<{ [key: string]: HTMLAudioElement }>({}) // audio 元素的引用 // 监听播放列表变化 watch(() => playQueue.queue, async () => { @@ -20,9 +21,10 @@ watch(() => playQueue.queue, async () => { resourcesUrl.value = newResourcesUrl }) -// 在播放曲目变动时,将元数据更新到浏览器和操作系统中 -watch(() => playQueue.currentTrack, async () => { +watch(() => playQueue.currentTrack, async (newTrack, oldTrack) => { if (!playQueue.currentTrack) return + + // 更新元数据 navigator.mediaSession.metadata = new MediaMetadata({ title: playQueue.currentTrack.song.name, artist: artistsOrganize(playQueue.currentTrack.song.artists ?? []), @@ -35,6 +37,30 @@ watch(() => playQueue.currentTrack, async () => { }, ], }) + navigator.mediaSession.setActionHandler('previoustrack', () => {}) + navigator.mediaSession.setActionHandler('nexttrack', playQueue.skipToNext) + + // 如果目前歌曲变动时正在播放,则激活对应的 audio 组件,并将播放时间进度重置为零 + if (!playQueue.isPlaying) return + debugPlayer("正在播放,变更至下一首歌") + if (oldTrack) { + const oldAudio = getAudioElement(oldTrack.song.cid) + if (oldAudio && !oldAudio.paused) { + oldAudio.pause() + } + } + + const newAudio = getAudioElement(newTrack.song.cid) + if (newAudio) { + try { + await newAudio.play() + debugPlayer(`开始播放: audio-${newTrack.song.cid}`) + } catch (error) { + console.error(`播放失败: audio-${newTrack.song.cid}`, error) + } + } else { + console.warn(`找不到音频元素: audio-${newTrack.song.cid}`) + } }) // 优化音乐人字符串显示 @@ -65,6 +91,32 @@ function isAutoPlay(cid: string) { return true } + +// 获取 audio 元素的 ref +function getAudioElement(cid: string): HTMLAudioElement | null { + debugPlayer('Getting audio element for:', cid, audioRefs.value) + return audioRefs.value[cid] || null +} + +// audio 元素结束播放事件 +function endOfPlay() { + debugPlayer("结束播放") + if (playQueue.loopingMode !== "single") { + const next = playQueue.queue[playQueue.currentIndex + 1] + debugPlayer(next.song.cid) + debugPlayer(audioRefs.value[next.song.cid]) + audioRefs.value[next.song.cid].play() + } +} + +function setAudioRef(cid: string, el: HTMLAudioElement | null) { + if (el) { + audioRefs.value[cid] = el + debugPlayer(`Audio element for ${cid} registered`, el) + } else { + delete audioRefs.value[cid] + } +} diff --git a/src/stores/usePlayQueueStore.ts b/src/stores/usePlayQueueStore.ts index 176f943..010b570 100644 --- a/src/stores/usePlayQueueStore.ts +++ b/src/stores/usePlayQueueStore.ts @@ -1,5 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' +import { debugStore } from '../utils/debug' export const usePlayQueueStore = defineStore('queue', () => { // 内部状态 @@ -10,6 +11,7 @@ export const usePlayQueueStore = defineStore('queue', () => { const currentPlaying = ref(0) // 当前播放指针,指针在 queueOrder 中寻址(无论是否开启了随机播放) const queueOrder = ref([]) // 播放队列顺序 const isPlaying = ref(false) + const playProgress = ref(0) // 当前曲目的播放时间指针 // 暴露给外部的响应式只读引用 const queueState = computed(() => @@ -27,6 +29,9 @@ export const usePlayQueueStore = defineStore('queue', () => { return queue.value[actualIndex] || null }) + // 获取当前播放时间 + const playProgressState = computed(() => playProgress.value) + // 获取当前是否正在播放 const playingState = computed(() => isPlaying.value) @@ -62,7 +67,6 @@ export const usePlayQueueStore = defineStore('queue', () => { /*********** * 播放控制相关 - * **********/ // 控制播放 const togglePlay = (turnTo?: boolean) => { @@ -71,6 +75,33 @@ export const usePlayQueueStore = defineStore('queue', () => { isPlaying.value = newPlayState } + // 跳转至队列的某首歌曲 + const toggleQueuePlay = (turnTo: number) => { + if (turnTo < 0 || turnTo >= queue.value.length) return + currentPlaying.value = turnTo + } + + // 跳至下一首(通常为用户点按下一首按钮) + const skipToNext = () => { + currentPlaying.value = currentPlaying.value + 1 + } + + // 继续播放接下来的曲目 + // 通常为当前曲目播放完毕,需要通过循环模式判断应该重置进度或队列指针 +1 + const continueToNext = () => { + debugStore(loopingMode.value) + // TODO: 需要留意 progress seeking 相关 + if (loopingMode.value === 'single') playProgress.value = 0 + else currentPlaying.value = currentPlaying.value + 1 + } + + // 回报播放进度 + const reportPlayProgress = (progress: number) => { + debugStore(`进度更新回报: ${progress}`) + playProgress.value = progress + } + + /************ * 播放模式相关 **********/ @@ -81,7 +112,7 @@ export const usePlayQueueStore = defineStore('queue', () => { if (newShuffleState === isShuffle.value) return // 状态未改变 - // TODO: 进行洗牌 + // TODO: 进行洗牌(以下代码是 AI 写的,需要人工复查) /* if (newShuffleState) { // 开启随机播放:保存当前顺序并打乱 const originalOrder = [...queueOrder.value] @@ -149,11 +180,16 @@ export const usePlayQueueStore = defineStore('queue', () => { currentTrack, currentIndex: currentPlaying, isPlaying: playingState, + playProgress: playProgressState, // 修改方法 replaceQueue, toggleShuffle, toggleLoop, - togglePlay + togglePlay, + toggleQueuePlay, + skipToNext, + continueToNext, + reportPlayProgress } })