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