feat: 暂停与续播
This commit is contained in:
parent
dae6210239
commit
f42ba2662b
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
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', () => {
|
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,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user