feat: 暂停与续播
This commit is contained in:
parent
dae6210239
commit
f42ba2662b
|
@ -126,6 +126,7 @@ watch(
|
|||
album.value = undefined // Reset album when cid changes
|
||||
try {
|
||||
let res = await apis.getAlbum(props.albumCid)
|
||||
debugUI(res.cid)
|
||||
for (const track in res.songs) {
|
||||
res.songs[parseInt(track)] = await apis.getSong(
|
||||
res.songs[parseInt(track)].cid,
|
||||
|
|
|
@ -16,6 +16,7 @@ class WebAudioPlayer {
|
|||
currentTrackStartTime: number
|
||||
currentSource: AudioBufferSourceNode | null
|
||||
nextSource: AudioBufferSourceNode | null
|
||||
reportInterval: ReturnType<typeof setTimeout> | null
|
||||
|
||||
constructor() {
|
||||
this.context = new window.AudioContext()
|
||||
|
@ -23,6 +24,7 @@ class WebAudioPlayer {
|
|||
this.currentTrackStartTime = 0
|
||||
this.currentSource = null
|
||||
this.nextSource = null
|
||||
this.reportInterval = null
|
||||
|
||||
// 创建一个隐藏的 HTML Audio 元素来帮助同步媒体会话状态
|
||||
this.dummyAudio = new Audio()
|
||||
|
@ -93,7 +95,7 @@ class WebAudioPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
// 缓存歌曲
|
||||
// 缓存歌曲并播放
|
||||
async loadResourceAndPlay() {
|
||||
try {
|
||||
debugPlayer("从播放器实例内部获取播放项目:")
|
||||
|
@ -103,6 +105,9 @@ class WebAudioPlayer {
|
|||
|
||||
if (playQueue.queue.length === 0) {
|
||||
// TODO: 如果当前正在播放,则可能需要停止播放
|
||||
playState.reportPlayProgress(0)
|
||||
playState.reportActualPlaying(false)
|
||||
this.currentSource?.stop()
|
||||
}
|
||||
|
||||
// 将音频 buffer 加载到缓存空间
|
||||
|
@ -121,7 +126,7 @@ class WebAudioPlayer {
|
|||
|
||||
if (playQueue.nextTrack) {
|
||||
await loadBuffer(playQueue.nextTrack)
|
||||
this.preloadNextTrack()
|
||||
if (playState.isPlaying) this.scheduleNextTrack()
|
||||
} else {
|
||||
this.nextSource = null
|
||||
}
|
||||
|
@ -135,23 +140,28 @@ class WebAudioPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
// 从头播放
|
||||
// 播放
|
||||
play() {
|
||||
if (!playQueue.currentTrack) return
|
||||
debugPlayer("开始播放")
|
||||
if (playState.playProgress !== 0) debugPlayer(`已经有所进度!${playState.playProgress}`)
|
||||
this.currentSource = this.context.createBufferSource()
|
||||
this.currentSource.buffer = this.audioBuffer[playQueue.currentTrack.song.cid]
|
||||
this.currentSource.connect(this.context.destination)
|
||||
this.currentSource.start()
|
||||
if (!playQueue.nextTrack) return
|
||||
this.currentSource.start(this.context.currentTime, playState.playProgress)
|
||||
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
|
||||
const nextTrackStartTime = this.currentTrackStartTime + this.audioBuffer[playQueue.currentTrack.song.cid].duration
|
||||
const nextTrackStartTime = this.currentTrackStartTime
|
||||
+ this.audioBuffer[playQueue.currentTrack.song.cid].duration
|
||||
debugPlayer(`下一首歌将在 ${nextTrackStartTime} 时间点接入`)
|
||||
|
||||
this.nextSource = this.context.createBufferSource()
|
||||
|
@ -161,9 +171,26 @@ class WebAudioPlayer {
|
|||
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() {
|
||||
|
||||
|
@ -201,6 +228,16 @@ watch(() => playQueue.currentTrack, () => {
|
|||
debugPlayer(`检测到当前播放曲目更新`)
|
||||
playerInstance.value?.loadResourceAndPlay()
|
||||
})
|
||||
|
||||
watch(() => playState.isPlaying, () => {
|
||||
if (!playState.isPlaying) {
|
||||
// 触发暂停
|
||||
playerInstance.value?.pause()
|
||||
} else {
|
||||
// 恢复音频
|
||||
playerInstance.value?.play()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -10,12 +10,14 @@ import HomePage from './pages/Home.vue'
|
|||
import AlbumDetailView from './pages/AlbumDetail.vue'
|
||||
import Playroom from './pages/Playroom.vue'
|
||||
import Library from './pages/Library.vue'
|
||||
import Debug from './pages/Debug.vue'
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: HomePage },
|
||||
{ path: '/albums/:albumId', component: AlbumDetailView },
|
||||
{ path: '/playroom', component: Playroom },
|
||||
{ path: '/library', component: Library },
|
||||
{ path: '/debug', component: Debug}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
|
35
src/pages/Debug.vue
Normal file
35
src/pages/Debug.vue
Normal 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>
|
|
@ -5,16 +5,18 @@ import artistsOrganize from '../utils/artistsOrganize'
|
|||
|
||||
export const usePlayState = defineStore('playState', () => {
|
||||
// 播放状态
|
||||
const isPlaying = ref(false)
|
||||
const isPlaying = ref(false) // 用户控制的播放与暂停
|
||||
const playProgress = ref(0) // 播放进度
|
||||
const currentTrackDuration = ref(0) // 曲目总时长
|
||||
const currentTrack = ref<QueueItem | null>(null) // 当前播放的曲目
|
||||
const mediaSessionInitialized = ref(false)
|
||||
const actualPlaying = ref(false) // 实际音频的播放与暂停
|
||||
|
||||
// 外显播放状态方法
|
||||
const playingState = computed(() => isPlaying.value)
|
||||
const playProgressState = computed(() => playProgress.value)
|
||||
const trackDurationState = computed(() => currentTrackDuration.value)
|
||||
const actualPlayingState = computed(() => actualPlaying.value)
|
||||
|
||||
// 回报目前播放进度百分比
|
||||
const playProgressPercent = computed(() => {
|
||||
|
@ -40,13 +42,11 @@ export const usePlayState = defineStore('playState', () => {
|
|||
|
||||
// 回报播放位置
|
||||
const reportPlayProgress = (progress: number) => {
|
||||
debugStore(`播放位置回报: ${progress}`)
|
||||
playProgress.value = progress
|
||||
}
|
||||
|
||||
// 回报曲目进度
|
||||
const setCurrentTrackDuration = (duration: number) => {
|
||||
debugStore(`曲目进度回报: ${duration}`)
|
||||
// 回报曲目长度
|
||||
const reportCurrentTrackDuration = (duration: number) => {
|
||||
currentTrackDuration.value = duration
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,11 @@ export const usePlayState = defineStore('playState', () => {
|
|||
playProgress.value = clampedTime
|
||||
}
|
||||
|
||||
// 回报 Web Audio API 正在播放
|
||||
const reportActualPlaying = (playing: boolean) => {
|
||||
actualPlaying.value = playing
|
||||
}
|
||||
|
||||
/***********
|
||||
* 媒体会话管理
|
||||
**********/
|
||||
|
@ -184,13 +189,15 @@ export const usePlayState = defineStore('playState', () => {
|
|||
playProgressPercent,
|
||||
remainingTime,
|
||||
currentTrack: computed(() => currentTrack.value),
|
||||
actualPlaying: actualPlayingState,
|
||||
|
||||
// 修改方法
|
||||
togglePlay,
|
||||
reportPlayProgress,
|
||||
setCurrentTrackDuration,
|
||||
reportCurrentTrackDuration,
|
||||
resetProgress,
|
||||
seekTo,
|
||||
reportActualPlaying,
|
||||
|
||||
// 媒体会话方法
|
||||
setCurrentTrack,
|
||||
|
|
Loading…
Reference in New Issue
Block a user