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 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]
}
}
</script>
<template>
@ -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}}
</div>
</div>
</template>

View File

@ -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<number[]>([]) // 播放队列顺序
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
}
})