feat: 暂停与续播

This commit is contained in:
Astrian Zheng 2025-08-22 17:41:33 +10:00
parent dae6210239
commit f42ba2662b
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
5 changed files with 99 additions and 17 deletions

View File

@ -126,6 +126,7 @@ watch(
album.value = undefined // Reset album when cid changes album.value = undefined // Reset album when cid changes
try { try {
let res = await apis.getAlbum(props.albumCid) let res = await apis.getAlbum(props.albumCid)
debugUI(res.cid)
for (const track in res.songs) { for (const track in res.songs) {
res.songs[parseInt(track)] = await apis.getSong( res.songs[parseInt(track)] = await apis.getSong(
res.songs[parseInt(track)].cid, res.songs[parseInt(track)].cid,

View File

@ -16,6 +16,7 @@ class WebAudioPlayer {
currentTrackStartTime: number currentTrackStartTime: number
currentSource: AudioBufferSourceNode | null currentSource: AudioBufferSourceNode | null
nextSource: AudioBufferSourceNode | null nextSource: AudioBufferSourceNode | null
reportInterval: ReturnType<typeof setTimeout> | null
constructor() { constructor() {
this.context = new window.AudioContext() this.context = new window.AudioContext()
@ -23,6 +24,7 @@ class WebAudioPlayer {
this.currentTrackStartTime = 0 this.currentTrackStartTime = 0
this.currentSource = null this.currentSource = null
this.nextSource = null this.nextSource = null
this.reportInterval = null
// HTML Audio // HTML Audio
this.dummyAudio = new Audio() this.dummyAudio = new Audio()
@ -93,7 +95,7 @@ class WebAudioPlayer {
} }
} }
// //
async loadResourceAndPlay() { async loadResourceAndPlay() {
try { try {
debugPlayer("从播放器实例内部获取播放项目:") debugPlayer("从播放器实例内部获取播放项目:")
@ -103,6 +105,9 @@ class WebAudioPlayer {
if (playQueue.queue.length === 0) { if (playQueue.queue.length === 0) {
// TODO: // TODO:
playState.reportPlayProgress(0)
playState.reportActualPlaying(false)
this.currentSource?.stop()
} }
// buffer // buffer
@ -121,7 +126,7 @@ class WebAudioPlayer {
if (playQueue.nextTrack) { if (playQueue.nextTrack) {
await loadBuffer(playQueue.nextTrack) await loadBuffer(playQueue.nextTrack)
this.preloadNextTrack() if (playState.isPlaying) this.scheduleNextTrack()
} else { } else {
this.nextSource = null this.nextSource = null
} }
@ -135,23 +140,28 @@ class WebAudioPlayer {
} }
} }
// //
play() { play() {
if (!playQueue.currentTrack) return
debugPlayer("开始播放") debugPlayer("开始播放")
if (playState.playProgress !== 0) debugPlayer(`已经有所进度!${playState.playProgress}`)
this.currentSource = this.context.createBufferSource() this.currentSource = this.context.createBufferSource()
this.currentSource.buffer = this.audioBuffer[playQueue.currentTrack.song.cid] this.currentSource.buffer = this.audioBuffer[playQueue.currentTrack.song.cid]
this.currentSource.connect(this.context.destination) this.currentSource.connect(this.context.destination)
this.currentSource.start() this.currentSource.start(this.context.currentTime, playState.playProgress)
if (!playQueue.nextTrack) return this.reportProgress()
// //
// //
this.currentTrackStartTime = this.context.currentTime this.currentTrackStartTime = this.context.currentTime - playState.playProgress
if (this.audioBuffer[playQueue.nextTrack.song.cid]) this.scheduleNextTrack()
} }
// //
preloadNextTrack() { scheduleNextTrack() {
if (!playQueue.nextTrack) return
this.nextSource = null this.nextSource = null
const nextTrackStartTime = this.currentTrackStartTime + this.audioBuffer[playQueue.currentTrack.song.cid].duration const nextTrackStartTime = this.currentTrackStartTime
+ this.audioBuffer[playQueue.currentTrack.song.cid].duration
debugPlayer(`下一首歌将在 ${nextTrackStartTime} 时间点接入`) debugPlayer(`下一首歌将在 ${nextTrackStartTime} 时间点接入`)
this.nextSource = this.context.createBufferSource() this.nextSource = this.context.createBufferSource()
@ -161,9 +171,26 @@ class WebAudioPlayer {
this.nextSource.start(nextTrackStartTime) this.nextSource.start(nextTrackStartTime)
} }
pause() {} //
reportProgress() {
this.reportInterval = setInterval(() => {
const progress = this.context.currentTime - this.currentTrackStartTime
playState.reportPlayProgress(progress)
playState.reportCurrentTrackDuration(this.audioBuffer[playQueue.currentTrack.song.cid].duration)
}, 100)
}
resume() {} //
stopReportProgress() {
this.reportInterval = null
}
pause() {
debugPlayer("尝试暂停播放")
debugPlayer(this.currentSource)
this.currentSource?.stop()
this.nextSource?.stop()
}
stop() { stop() {
@ -201,6 +228,16 @@ watch(() => playQueue.currentTrack, () => {
debugPlayer(`检测到当前播放曲目更新`) debugPlayer(`检测到当前播放曲目更新`)
playerInstance.value?.loadResourceAndPlay() playerInstance.value?.loadResourceAndPlay()
}) })
watch(() => playState.isPlaying, () => {
if (!playState.isPlaying) {
//
playerInstance.value?.pause()
} else {
//
playerInstance.value?.play()
}
})
</script> </script>
<template> <template>

View File

@ -10,12 +10,14 @@ import HomePage from './pages/Home.vue'
import AlbumDetailView from './pages/AlbumDetail.vue' import AlbumDetailView from './pages/AlbumDetail.vue'
import Playroom from './pages/Playroom.vue' import Playroom from './pages/Playroom.vue'
import Library from './pages/Library.vue' import Library from './pages/Library.vue'
import Debug from './pages/Debug.vue'
const routes = [ const routes = [
{ path: '/', component: HomePage }, { path: '/', component: HomePage },
{ path: '/albums/:albumId', component: AlbumDetailView }, { path: '/albums/:albumId', component: AlbumDetailView },
{ path: '/playroom', component: Playroom }, { path: '/playroom', component: Playroom },
{ path: '/library', component: Library }, { path: '/library', component: Library },
{ path: '/debug', component: Debug}
] ]
const router = createRouter({ const router = createRouter({

35
src/pages/Debug.vue Normal file
View File

@ -0,0 +1,35 @@
<script lang="ts" setup>
import apis from '../apis'
import { debugUI } from '../utils/debug'
import { usePlayQueueStore } from '../stores/usePlayQueueStore'
import { usePlayState } from '../stores/usePlayState'
const playQueue = usePlayQueueStore()
const playState = usePlayState()
async function playTheList() {
debugUI("开始播放")
const res = await apis.getAlbum("8936")
let newQueue: QueueItem[] = []
for (const track of res.songs ?? []) {
newQueue[newQueue.length] = {
song: track,
album: res
}
}
playQueue.replaceQueue(newQueue)
playState.togglePlay(true)
}
async function pauseOrResume() {
playState.togglePlay()
}
</script>
<template>
<div class="text-white flex justify-center items-center min-h-screen flex-col">
<button class="bg-white/20 px-2 py-1" @click="playTheList">开始播放</button>
<div>当前播放队列里有 {{ playQueue.queue.length }} 首歌</div>
<button class="bg-white/20 px-2 py-1" @click="pauseOrResume">播放/暂停</button>
</div>
</template>

View File

@ -5,16 +5,18 @@ import artistsOrganize from '../utils/artistsOrganize'
export const usePlayState = defineStore('playState', () => { export const usePlayState = defineStore('playState', () => {
// 播放状态 // 播放状态
const isPlaying = ref(false) const isPlaying = ref(false) // 用户控制的播放与暂停
const playProgress = ref(0) // 播放进度 const playProgress = ref(0) // 播放进度
const currentTrackDuration = ref(0) // 曲目总时长 const currentTrackDuration = ref(0) // 曲目总时长
const currentTrack = ref<QueueItem | null>(null) // 当前播放的曲目 const currentTrack = ref<QueueItem | null>(null) // 当前播放的曲目
const mediaSessionInitialized = ref(false) const mediaSessionInitialized = ref(false)
const actualPlaying = ref(false) // 实际音频的播放与暂停
// 外显播放状态方法 // 外显播放状态方法
const playingState = computed(() => isPlaying.value) const playingState = computed(() => isPlaying.value)
const playProgressState = computed(() => playProgress.value) const playProgressState = computed(() => playProgress.value)
const trackDurationState = computed(() => currentTrackDuration.value) const trackDurationState = computed(() => currentTrackDuration.value)
const actualPlayingState = computed(() => actualPlaying.value)
// 回报目前播放进度百分比 // 回报目前播放进度百分比
const playProgressPercent = computed(() => { const playProgressPercent = computed(() => {
@ -40,13 +42,11 @@ export const usePlayState = defineStore('playState', () => {
// 回报播放位置 // 回报播放位置
const reportPlayProgress = (progress: number) => { const reportPlayProgress = (progress: number) => {
debugStore(`播放位置回报: ${progress}`)
playProgress.value = progress playProgress.value = progress
} }
// 回报曲目进度 // 回报曲目长度
const setCurrentTrackDuration = (duration: number) => { const reportCurrentTrackDuration = (duration: number) => {
debugStore(`曲目进度回报: ${duration}`)
currentTrackDuration.value = duration currentTrackDuration.value = duration
} }
@ -63,6 +63,11 @@ export const usePlayState = defineStore('playState', () => {
playProgress.value = clampedTime playProgress.value = clampedTime
} }
// 回报 Web Audio API 正在播放
const reportActualPlaying = (playing: boolean) => {
actualPlaying.value = playing
}
/*********** /***********
* *
**********/ **********/
@ -184,13 +189,15 @@ export const usePlayState = defineStore('playState', () => {
playProgressPercent, playProgressPercent,
remainingTime, remainingTime,
currentTrack: computed(() => currentTrack.value), currentTrack: computed(() => currentTrack.value),
actualPlaying: actualPlayingState,
// 修改方法 // 修改方法
togglePlay, togglePlay,
reportPlayProgress, reportPlayProgress,
setCurrentTrackDuration, reportCurrentTrackDuration,
resetProgress, resetProgress,
seekTo, seekTo,
reportActualPlaying,
// 媒体会话方法 // 媒体会话方法
setCurrentTrack, setCurrentTrack,