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]
+ }
+}
@@ -74,9 +126,12 @@ function isAutoPlay(cid: string) {
v-if="resourcesUrl[track.song.cid]"
:src="resourcesUrl[track.song.cid]"
preload="auto"
- :ref="`audio-${track.song.cid}`"
+ :ref="el => setAudioRef(track.song.cid, el as HTMLAudioElement)"
:autoplay="isAutoPlay(track.song.cid)"
+ @ended="endOfPlay"
+ @timeupdate=""
/>
+ {{track.song.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
}
})