feat: 跳转至下一首功能

This commit is contained in:
Astrian Zheng 2025-08-21 11:30:35 +10:00
parent 024a84b2eb
commit 61a99975b2
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
2 changed files with 97 additions and 6 deletions

View File

@ -7,6 +7,7 @@ import apis from '../apis'
const playQueue = usePlayQueueStore() const playQueue = usePlayQueueStore()
const resourcesUrl = ref<{ [key: string]: string }>({}) const resourcesUrl = ref<{ [key: string]: string }>({})
const audioRefs = ref<{ [key: string]: HTMLAudioElement }>({}) // audio
// //
watch(() => playQueue.queue, async () => { watch(() => playQueue.queue, async () => {
@ -20,9 +21,10 @@ watch(() => playQueue.queue, async () => {
resourcesUrl.value = newResourcesUrl resourcesUrl.value = newResourcesUrl
}) })
// watch(() => playQueue.currentTrack, async (newTrack, oldTrack) => {
watch(() => playQueue.currentTrack, async () => {
if (!playQueue.currentTrack) return if (!playQueue.currentTrack) return
//
navigator.mediaSession.metadata = new MediaMetadata({ navigator.mediaSession.metadata = new MediaMetadata({
title: playQueue.currentTrack.song.name, title: playQueue.currentTrack.song.name,
artist: artistsOrganize(playQueue.currentTrack.song.artists ?? []), 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 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]
}
}
</script> </script>
<template> <template>
@ -74,9 +126,12 @@ function isAutoPlay(cid: string) {
v-if="resourcesUrl[track.song.cid]" v-if="resourcesUrl[track.song.cid]"
:src="resourcesUrl[track.song.cid]" :src="resourcesUrl[track.song.cid]"
preload="auto" preload="auto"
:ref="`audio-${track.song.cid}`" :ref="el => setAudioRef(track.song.cid, el as HTMLAudioElement)"
:autoplay="isAutoPlay(track.song.cid)" :autoplay="isAutoPlay(track.song.cid)"
@ended="endOfPlay"
@timeupdate=""
/> />
{{track.song.cid}}
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,5 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { debugStore } from '../utils/debug'
export const usePlayQueueStore = defineStore('queue', () => { export const usePlayQueueStore = defineStore('queue', () => {
// 内部状态 // 内部状态
@ -10,6 +11,7 @@ export const usePlayQueueStore = defineStore('queue', () => {
const currentPlaying = ref(0) // 当前播放指针,指针在 queueOrder 中寻址(无论是否开启了随机播放) const currentPlaying = ref(0) // 当前播放指针,指针在 queueOrder 中寻址(无论是否开启了随机播放)
const queueOrder = ref<number[]>([]) // 播放队列顺序 const queueOrder = ref<number[]>([]) // 播放队列顺序
const isPlaying = ref(false) const isPlaying = ref(false)
const playProgress = ref(0) // 当前曲目的播放时间指针
// 暴露给外部的响应式只读引用 // 暴露给外部的响应式只读引用
const queueState = computed(() => const queueState = computed(() =>
@ -27,6 +29,9 @@ export const usePlayQueueStore = defineStore('queue', () => {
return queue.value[actualIndex] || null return queue.value[actualIndex] || null
}) })
// 获取当前播放时间
const playProgressState = computed(() => playProgress.value)
// 获取当前是否正在播放 // 获取当前是否正在播放
const playingState = computed(() => isPlaying.value) const playingState = computed(() => isPlaying.value)
@ -62,7 +67,6 @@ export const usePlayQueueStore = defineStore('queue', () => {
/*********** /***********
* *
*
**********/ **********/
// 控制播放 // 控制播放
const togglePlay = (turnTo?: boolean) => { const togglePlay = (turnTo?: boolean) => {
@ -71,6 +75,33 @@ export const usePlayQueueStore = defineStore('queue', () => {
isPlaying.value = newPlayState 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 // 状态未改变 if (newShuffleState === isShuffle.value) return // 状态未改变
// TODO: 进行洗牌 // TODO: 进行洗牌(以下代码是 AI 写的,需要人工复查)
/* if (newShuffleState) { /* if (newShuffleState) {
// 开启随机播放:保存当前顺序并打乱 // 开启随机播放:保存当前顺序并打乱
const originalOrder = [...queueOrder.value] const originalOrder = [...queueOrder.value]
@ -149,11 +180,16 @@ export const usePlayQueueStore = defineStore('queue', () => {
currentTrack, currentTrack,
currentIndex: currentPlaying, currentIndex: currentPlaying,
isPlaying: playingState, isPlaying: playingState,
playProgress: playProgressState,
// 修改方法 // 修改方法
replaceQueue, replaceQueue,
toggleShuffle, toggleShuffle,
toggleLoop, toggleLoop,
togglePlay togglePlay,
toggleQueuePlay,
skipToNext,
continueToNext,
reportPlayProgress
} }
}) })