feat(Player): implement audio preloading functionality with state management
This commit is contained in:
parent
096e74a9bd
commit
7a0c638d2c
|
@ -1,7 +1,8 @@
|
||||||
<!-- Player.vue - 添加调试信息 -->
|
<!-- Player.vue - 添加预加载功能 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import { usePlayQueueStore } from '../stores/usePlayQueueStore'
|
import { usePlayQueueStore } from '../stores/usePlayQueueStore'
|
||||||
import { useTemplateRef, watch, nextTick } from 'vue'
|
import { useTemplateRef, watch, nextTick, computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import PlayIcon from '../assets/icons/play.vue'
|
import PlayIcon from '../assets/icons/play.vue'
|
||||||
|
@ -12,6 +13,28 @@ const playQueueStore = usePlayQueueStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const player = useTemplateRef('playerRef')
|
const player = useTemplateRef('playerRef')
|
||||||
|
|
||||||
|
// [调试] 检查 store 方法类型
|
||||||
|
console.log('[Player] 检查 store 方法:', {
|
||||||
|
preloadNext: typeof playQueueStore.preloadNext,
|
||||||
|
getPreloadedAudio: typeof playQueueStore.getPreloadedAudio,
|
||||||
|
clearPreloadedAudio: typeof playQueueStore.clearPreloadedAudio
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取当前歌曲的计算属性
|
||||||
|
const currentTrack = computed(() => {
|
||||||
|
if (playQueueStore.playMode.shuffle && playQueueStore.shuffleList.length > 0) {
|
||||||
|
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
|
||||||
|
} else {
|
||||||
|
return playQueueStore.list[playQueueStore.currentIndex]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取当前歌曲的音频源
|
||||||
|
const currentAudioSrc = computed(() => {
|
||||||
|
const track = currentTrack.value
|
||||||
|
return track ? track.song.sourceUrl : ''
|
||||||
|
})
|
||||||
|
|
||||||
watch(() => playQueueStore.isPlaying, (newValue) => {
|
watch(() => playQueueStore.isPlaying, (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
player.value?.play()
|
player.value?.play()
|
||||||
|
@ -20,10 +43,66 @@ watch(() => playQueueStore.isPlaying, (newValue) => {
|
||||||
else { player.value?.pause() }
|
else { player.value?.pause() }
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => playQueueStore.currentIndex, () => {
|
// 监听当前索引变化,处理预加载逻辑
|
||||||
|
watch(() => playQueueStore.currentIndex, async () => {
|
||||||
console.log('[Player] 当前索引变化:', playQueueStore.currentIndex)
|
console.log('[Player] 当前索引变化:', playQueueStore.currentIndex)
|
||||||
|
|
||||||
|
// 检查是否可以使用预加载的音频
|
||||||
|
const track = currentTrack.value
|
||||||
|
if (track) {
|
||||||
|
const songId = track.song.cid
|
||||||
|
|
||||||
|
try {
|
||||||
|
const preloadedAudio = playQueueStore.getPreloadedAudio(songId)
|
||||||
|
|
||||||
|
if (preloadedAudio) {
|
||||||
|
console.log(`[Player] 使用预加载的音频: ${track.song.name}`)
|
||||||
|
|
||||||
|
// 直接使用预加载的音频数据
|
||||||
|
if (player.value) {
|
||||||
|
// 复制预加载音频的状态到主播放器
|
||||||
|
player.value.src = preloadedAudio.src
|
||||||
|
player.value.currentTime = 0
|
||||||
|
|
||||||
|
// 清理使用过的预加载音频
|
||||||
|
playQueueStore.clearPreloadedAudio(songId)
|
||||||
|
|
||||||
|
// 如果正在播放状态,立即播放
|
||||||
|
if (playQueueStore.isPlaying) {
|
||||||
|
await nextTick()
|
||||||
|
player.value.play().catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
playQueueStore.isBuffering = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`[Player] 正常加载音频: ${track.song.name}`)
|
||||||
|
playQueueStore.isBuffering = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Player] 处理预加载音频时出错:', error)
|
||||||
|
playQueueStore.isBuffering = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setMetadata()
|
setMetadata()
|
||||||
playQueueStore.isBuffering = true
|
|
||||||
|
// 延迟预加载下一首歌,避免影响当前歌曲加载
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
console.log('[Player] 尝试预加载下一首歌')
|
||||||
|
|
||||||
|
// 检查函数是否存在
|
||||||
|
if (typeof playQueueStore.preloadNext === 'function') {
|
||||||
|
playQueueStore.preloadNext()
|
||||||
|
playQueueStore.limitPreloadCache()
|
||||||
|
} else {
|
||||||
|
console.error('[Player] preloadNext 不是一个函数')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Player] 预加载失败:', error)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
function artistsOrganize(list: string[]) {
|
function artistsOrganize(list: string[]) {
|
||||||
|
@ -35,13 +114,9 @@ function artistsOrganize(list: string[]) {
|
||||||
|
|
||||||
function setMetadata() {
|
function setMetadata() {
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
let current = (() => {
|
let current = currentTrack.value
|
||||||
if (playQueueStore.playMode.shuffle) {
|
if (!current) return
|
||||||
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
|
|
||||||
} else {
|
|
||||||
return playQueueStore.list[playQueueStore.currentIndex]
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: current.song.name,
|
title: current.song.name,
|
||||||
artist: artistsOrganize(current.song.artists ?? []),
|
artist: artistsOrganize(current.song.artists ?? []),
|
||||||
|
@ -54,8 +129,8 @@ function setMetadata() {
|
||||||
navigator.mediaSession.setActionHandler('previoustrack', playPrevious)
|
navigator.mediaSession.setActionHandler('previoustrack', playPrevious)
|
||||||
navigator.mediaSession.setActionHandler('nexttrack', playNext)
|
navigator.mediaSession.setActionHandler('nexttrack', playNext)
|
||||||
|
|
||||||
playQueueStore.duration = player.value?.duration?? 0
|
playQueueStore.duration = player.value?.duration ?? 0
|
||||||
playQueueStore.currentTime = player.value?.currentTime?? 0
|
playQueueStore.currentTime = player.value?.currentTime ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => playQueueStore.updatedCurrentTime, (newValue) => {
|
watch(() => playQueueStore.updatedCurrentTime, (newValue) => {
|
||||||
|
@ -69,7 +144,7 @@ function playNext() {
|
||||||
if (playQueueStore.currentIndex === playQueueStore.list.length - 1) {
|
if (playQueueStore.currentIndex === playQueueStore.list.length - 1) {
|
||||||
console.log("at the bottom, pause")
|
console.log("at the bottom, pause")
|
||||||
playQueueStore.currentIndex = 0
|
playQueueStore.currentIndex = 0
|
||||||
if(playQueueStore.playMode.repeat === 'all') {
|
if (playQueueStore.playMode.repeat === 'all') {
|
||||||
playQueueStore.currentIndex = 0
|
playQueueStore.currentIndex = 0
|
||||||
playQueueStore.isPlaying = true
|
playQueueStore.isPlaying = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,18 +167,43 @@ function playPrevious() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCurrentTime() {
|
function updateCurrentTime() {
|
||||||
playQueueStore.currentTime = player.value?.currentTime?? 0
|
playQueueStore.currentTime = player.value?.currentTime ?? 0
|
||||||
|
|
||||||
|
// 智能预加载策略:支持动态配置
|
||||||
|
if (playQueueStore.duration > 0) {
|
||||||
|
const progress = playQueueStore.currentTime / playQueueStore.duration
|
||||||
|
const remainingTime = playQueueStore.duration - playQueueStore.currentTime
|
||||||
|
|
||||||
|
// 从 localStorage 获取配置,如果没有则使用默认值
|
||||||
|
const config = JSON.parse(localStorage.getItem('preloadConfig') || '{}')
|
||||||
|
const preloadTrigger = (config.preloadTrigger || 50) / 100 // 转换为小数
|
||||||
|
const remainingTimeThreshold = config.remainingTimeThreshold || 30
|
||||||
|
|
||||||
|
if ((progress > preloadTrigger || remainingTime < remainingTimeThreshold) && !playQueueStore.isPreloading) {
|
||||||
|
console.log(`[Player] 触发预加载 - 进度: ${Math.round(progress * 100)}%, 剩余: ${Math.round(remainingTime)}s`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof playQueueStore.preloadNext === 'function') {
|
||||||
|
playQueueStore.preloadNext()
|
||||||
|
} else {
|
||||||
|
console.error('[Player] preloadNext 不是一个函数')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Player] 智能预加载失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Player] 初始化 audioVisualizer')
|
console.log('[Player] 初始化 audioVisualizer')
|
||||||
const { barHeights, connectAudio, isAnalyzing, error } = audioVisualizer({
|
const { barHeights, connectAudio, isAnalyzing, error } = audioVisualizer({
|
||||||
sensitivity: 1.5,
|
sensitivity: 1.5,
|
||||||
barCount: 6,
|
barCount: 6,
|
||||||
maxDecibels: -10,
|
maxDecibels: -10,
|
||||||
bassBoost: 0.8,
|
bassBoost: 0.8,
|
||||||
midBoost: 1.2,
|
midBoost: 1.2,
|
||||||
trebleBoost: 1.4,
|
trebleBoost: 1.4,
|
||||||
threshold: 0
|
threshold: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('[Player] audioVisualizer 返回值:', { barHeights: barHeights.value, isAnalyzing: isAnalyzing.value })
|
console.log('[Player] audioVisualizer 返回值:', { barHeights: barHeights.value, isAnalyzing: isAnalyzing.value })
|
||||||
|
@ -111,15 +211,15 @@ console.log('[Player] audioVisualizer 返回值:', { barHeights: barHeights.valu
|
||||||
// 监听播放列表变化
|
// 监听播放列表变化
|
||||||
watch(() => playQueueStore.list.length, async (newLength) => {
|
watch(() => playQueueStore.list.length, async (newLength) => {
|
||||||
console.log('[Player] 播放列表长度变化:', newLength)
|
console.log('[Player] 播放列表长度变化:', newLength)
|
||||||
if (newLength === 0) {
|
if (newLength === 0) {
|
||||||
console.log('[Player] 播放列表为空,跳过连接')
|
console.log('[Player] 播放列表为空,跳过连接')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待下一帧,确保 audio 元素已经渲染
|
// 等待下一帧,确保 audio 元素已经渲染
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
if (player.value) {
|
if (player.value) {
|
||||||
console.log('[Player] 连接音频元素到可视化器')
|
console.log('[Player] 连接音频元素到可视化器')
|
||||||
console.log('[Player] 音频元素状态:', {
|
console.log('[Player] 音频元素状态:', {
|
||||||
src: player.value.src?.substring(0, 50) + '...',
|
src: player.value.src?.substring(0, 50) + '...',
|
||||||
|
@ -130,8 +230,13 @@ watch(() => playQueueStore.list.length, async (newLength) => {
|
||||||
} else {
|
} else {
|
||||||
console.log('[Player] ❌ 音频元素不存在')
|
console.log('[Player] ❌ 音频元素不存在')
|
||||||
}
|
}
|
||||||
|
|
||||||
playQueueStore.visualizer = barHeights.value
|
playQueueStore.visualizer = barHeights.value
|
||||||
|
|
||||||
|
// 开始预加载第一首歌的下一首
|
||||||
|
setTimeout(() => {
|
||||||
|
playQueueStore.preloadNext()
|
||||||
|
}, 2000)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听音频元素变化
|
// 监听音频元素变化
|
||||||
|
@ -185,60 +290,62 @@ watch(() => playQueueStore.playMode.shuffle, (isShuffle) => {
|
||||||
// 将当前播放的歌曲的原来的索引赋给 currentIndex
|
// 将当前播放的歌曲的原来的索引赋给 currentIndex
|
||||||
playQueueStore.currentIndex = playQueueStore.shuffleList[playQueueStore.currentIndex]
|
playQueueStore.currentIndex = playQueueStore.shuffleList[playQueueStore.currentIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换播放模式后重新预加载
|
||||||
|
setTimeout(() => {
|
||||||
|
playQueueStore.clearAllPreloadedAudio()
|
||||||
|
playQueueStore.preloadNext()
|
||||||
|
}, 500)
|
||||||
})
|
})
|
||||||
|
|
||||||
function getCurrentTrack() {
|
function getCurrentTrack() {
|
||||||
if (playQueueStore.playMode.shuffle) {
|
return currentTrack.value
|
||||||
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
|
|
||||||
} else {
|
|
||||||
return playQueueStore.list[playQueueStore.currentIndex]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 组件卸载时清理预加载
|
||||||
|
// onUnmounted(() => {
|
||||||
|
// playQueueStore.clearAllPreloadedAudio()
|
||||||
|
// })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<audio
|
<audio :src="currentAudioSrc" ref="playerRef" :autoplay="playQueueStore.isPlaying"
|
||||||
:src="(() => {
|
v-if="playQueueStore.list.length !== 0" @ended="() => {
|
||||||
if (playQueueStore.playMode.shuffle) {
|
|
||||||
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]] ? playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]].song.sourceUrl : ''
|
|
||||||
} else {
|
|
||||||
return playQueueStore.list[playQueueStore.currentIndex] ? playQueueStore.list[playQueueStore.currentIndex].song.sourceUrl : ''
|
|
||||||
}
|
|
||||||
})()"
|
|
||||||
ref="playerRef"
|
|
||||||
:autoplay="playQueueStore.isPlaying"
|
|
||||||
v-if="playQueueStore.list.length !== 0"
|
|
||||||
@ended="() => {
|
|
||||||
if (playQueueStore.playMode.repeat === 'single') { playQueueStore.isPlaying = true }
|
if (playQueueStore.playMode.repeat === 'single') { playQueueStore.isPlaying = true }
|
||||||
else { playNext() }
|
else { playNext() }
|
||||||
}"
|
}" @pause="playQueueStore.isPlaying = false" @play="playQueueStore.isPlaying = true" @playing="() => {
|
||||||
@pause="playQueueStore.isPlaying = false"
|
|
||||||
@play="playQueueStore.isPlaying = true"
|
|
||||||
@playing="() => {
|
|
||||||
console.log('[Player] 音频开始播放事件')
|
console.log('[Player] 音频开始播放事件')
|
||||||
playQueueStore.isBuffering = false
|
playQueueStore.isBuffering = false
|
||||||
setMetadata()
|
setMetadata()
|
||||||
}"
|
}" @waiting="playQueueStore.isBuffering = true" @loadeddata="() => {
|
||||||
@waiting="playQueueStore.isBuffering = true"
|
console.log('[Player] 音频数据加载完成')
|
||||||
@loadeddata="() => console.log('[Player] 音频数据加载完成')"
|
playQueueStore.isBuffering = false
|
||||||
@canplay="() => console.log('[Player] 音频可以播放')"
|
}" @canplay="() => {
|
||||||
@error="(e) => console.error('[Player] 音频错误:', e)"
|
console.log('[Player] 音频可以播放')
|
||||||
crossorigin="anonymous"
|
playQueueStore.isBuffering = false
|
||||||
@timeupdate="updateCurrentTime">
|
}" @error="(e) => {
|
||||||
|
console.error('[Player] 音频错误:', e)
|
||||||
|
playQueueStore.isBuffering = false
|
||||||
|
}" crossorigin="anonymous" @timeupdate="updateCurrentTime">
|
||||||
</audio>
|
</audio>
|
||||||
|
|
||||||
|
<!-- 预加载进度指示器(可选显示) -->
|
||||||
|
<div v-if="playQueueStore.isPreloading"
|
||||||
|
class="fixed top-4 right-4 bg-black/80 text-white px-3 py-1 rounded text-xs z-50">
|
||||||
|
预加载中... {{ Math.round(playQueueStore.preloadProgress) }}%
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="text-white h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex gap-2 overflow-hidden select-none"
|
class="text-white h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex gap-2 overflow-hidden select-none"
|
||||||
v-if="playQueueStore.list.length !== 0 && route.path !== '/playroom'"
|
v-if="playQueueStore.list.length !== 0 && route.path !== '/playroom'">
|
||||||
>
|
|
||||||
<RouterLink to="/playroom">
|
<RouterLink to="/playroom">
|
||||||
<img :src="getCurrentTrack().album?.coverUrl ?? ''" class="rounded-full h-9 w-9" />
|
<img :src="getCurrentTrack()?.album?.coverUrl ?? ''" class="rounded-full h-9 w-9" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
<RouterLink to="/playroom">
|
<RouterLink to="/playroom">
|
||||||
<div class="flex items-center w-32 h-9">
|
<div class="flex items-center w-32 h-9">
|
||||||
<span class="truncate">{{ getCurrentTrack().song.name }}</span>
|
<span class="truncate">{{ getCurrentTrack()?.song.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
|
@ -248,9 +355,10 @@ function getCurrentTrack() {
|
||||||
<div v-if="playQueueStore.isPlaying">
|
<div v-if="playQueueStore.isPlaying">
|
||||||
<LoadingIndicator v-if="playQueueStore.isBuffering === true" :size="4" />
|
<LoadingIndicator v-if="playQueueStore.isBuffering === true" :size="4" />
|
||||||
<div v-else class="h-4 flex justify-center items-center gap-[.125rem]">
|
<div v-else class="h-4 flex justify-center items-center gap-[.125rem]">
|
||||||
<div class="bg-white/75 w-[.125rem] rounded-full" v-for="(bar, index) in playQueueStore.visualizer" :key="index" :style="{
|
<div class="bg-white/75 w-[.125rem] rounded-full" v-for="(bar, index) in playQueueStore.visualizer"
|
||||||
height: `${Math.max(10, bar)}%`
|
:key="index" :style="{
|
||||||
}" />
|
height: `${Math.max(10, bar)}%`
|
||||||
|
}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PlayIcon v-else :size="4" />
|
<PlayIcon v-else :size="4" />
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { ref } from "vue"
|
import { ref, computed } from "vue"
|
||||||
|
|
||||||
export const usePlayQueueStore = defineStore('queue', () =>{
|
export const usePlayQueueStore = defineStore('queue', () => {
|
||||||
const list = ref<QueueItem[]>([])
|
const list = ref<QueueItem[]>([])
|
||||||
const currentIndex = ref<number>(0)
|
const currentIndex = ref<number>(0)
|
||||||
const isPlaying = ref<boolean>(false)
|
const isPlaying = ref<boolean>(false)
|
||||||
const queueReplaceLock = ref<boolean>(false)
|
const queueReplaceLock = ref<boolean>(false)
|
||||||
const isBuffering = ref<boolean>(false)
|
const isBuffering = ref<boolean>(false)
|
||||||
|
@ -21,5 +21,183 @@ export const usePlayQueueStore = defineStore('queue', () =>{
|
||||||
})
|
})
|
||||||
const shuffleCurrent = ref<boolean | undefined>(undefined)
|
const shuffleCurrent = ref<boolean | undefined>(undefined)
|
||||||
|
|
||||||
return { list, currentIndex, isPlaying, queueReplaceLock, isBuffering, currentTime, duration, updatedCurrentTime, visualizer, shuffleList, playMode, shuffleCurrent }
|
// 预加载相关状态
|
||||||
|
const preloadedAudio = ref<Map<string, HTMLAudioElement>>(new Map())
|
||||||
|
const isPreloading = ref<boolean>(false)
|
||||||
|
const preloadProgress = ref<number>(0)
|
||||||
|
|
||||||
|
// 获取下一首歌的索引
|
||||||
|
const getNextIndex = computed(() => {
|
||||||
|
if (list.value.length === 0) return -1
|
||||||
|
|
||||||
|
if (playMode.value.repeat === 'single') {
|
||||||
|
return currentIndex.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playMode.value.shuffle && shuffleList.value.length > 0) {
|
||||||
|
const currentShuffleIndex = shuffleList.value.indexOf(currentIndex.value)
|
||||||
|
if (currentShuffleIndex < shuffleList.value.length - 1) {
|
||||||
|
return shuffleList.value[currentShuffleIndex + 1]
|
||||||
|
} else if (playMode.value.repeat === 'all') {
|
||||||
|
return shuffleList.value[0]
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex.value < list.value.length - 1) {
|
||||||
|
return currentIndex.value + 1
|
||||||
|
} else if (playMode.value.repeat === 'all') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
})
|
||||||
|
|
||||||
|
// 预加载下一首歌
|
||||||
|
const preloadNext = async () => {
|
||||||
|
console.log('[Store] preloadNext 被调用')
|
||||||
|
|
||||||
|
const nextIndex = getNextIndex.value
|
||||||
|
if (nextIndex === -1) {
|
||||||
|
console.log('[Store] 没有下一首歌,跳过预加载')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取下一首歌曲对象
|
||||||
|
let nextSong
|
||||||
|
if (playMode.value.shuffle && shuffleList.value.length > 0) {
|
||||||
|
nextSong = list.value[shuffleList.value[nextIndex]]
|
||||||
|
} else {
|
||||||
|
nextSong = list.value[nextIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextSong || !nextSong.song) {
|
||||||
|
console.log('[Store] 下一首歌曲不存在,跳过预加载')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const songId = nextSong.song.cid
|
||||||
|
|
||||||
|
// 如果已经预加载过,跳过
|
||||||
|
if (preloadedAudio.value.has(songId)) {
|
||||||
|
console.log(`[Store] 歌曲 ${songId} 已预加载`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有有效的音频源
|
||||||
|
if (!nextSong.song.sourceUrl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
isPreloading.value = true
|
||||||
|
preloadProgress.value = 0
|
||||||
|
|
||||||
|
const audio = new Audio()
|
||||||
|
audio.preload = 'auto'
|
||||||
|
audio.crossOrigin = 'anonymous'
|
||||||
|
|
||||||
|
// 监听加载进度
|
||||||
|
audio.addEventListener('progress', () => {
|
||||||
|
if (audio.buffered.length > 0) {
|
||||||
|
const buffered = audio.buffered.end(0)
|
||||||
|
const total = audio.duration || 1
|
||||||
|
preloadProgress.value = (buffered / total) * 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听加载完成
|
||||||
|
audio.addEventListener('canplaythrough', () => {
|
||||||
|
preloadedAudio.value.set(songId, audio)
|
||||||
|
isPreloading.value = false
|
||||||
|
preloadProgress.value = 100
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听加载错误
|
||||||
|
audio.addEventListener('error', (e) => {
|
||||||
|
console.error(`[Store] 预加载音频失败: ${e}`)
|
||||||
|
isPreloading.value = false
|
||||||
|
preloadProgress.value = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置音频源并开始加载
|
||||||
|
audio.src = nextSong.song.sourceUrl
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
isPreloading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取预加载的音频对象
|
||||||
|
const getPreloadedAudio = (songId: string): HTMLAudioElement | null => {
|
||||||
|
const audio = preloadedAudio.value.get(songId) || null
|
||||||
|
return audio
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理预加载的音频
|
||||||
|
const clearPreloadedAudio = (songId: string) => {
|
||||||
|
const audio = preloadedAudio.value.get(songId)
|
||||||
|
if (audio) {
|
||||||
|
audio.pause()
|
||||||
|
audio.src = ''
|
||||||
|
preloadedAudio.value.delete(songId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理所有预加载的音频
|
||||||
|
const clearAllPreloadedAudio = () => {
|
||||||
|
preloadedAudio.value.forEach((_audio, songId) => {
|
||||||
|
clearPreloadedAudio(songId)
|
||||||
|
})
|
||||||
|
preloadedAudio.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制预加载缓存大小(最多保留3首歌)
|
||||||
|
const limitPreloadCache = () => {
|
||||||
|
while (preloadedAudio.value.size > 3) {
|
||||||
|
const oldestKey = preloadedAudio.value.keys().next().value
|
||||||
|
if (oldestKey) {
|
||||||
|
clearPreloadedAudio(oldestKey)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试函数:打印当前状态
|
||||||
|
const debugPreloadState = () => {
|
||||||
|
console.log('[Store] 预加载状态:', {
|
||||||
|
isPreloading: isPreloading.value,
|
||||||
|
progress: preloadProgress.value,
|
||||||
|
cacheSize: preloadedAudio.value.size,
|
||||||
|
cachedSongs: Array.from(preloadedAudio.value.keys()),
|
||||||
|
nextIndex: getNextIndex.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
currentIndex,
|
||||||
|
isPlaying,
|
||||||
|
queueReplaceLock,
|
||||||
|
isBuffering,
|
||||||
|
currentTime,
|
||||||
|
duration,
|
||||||
|
updatedCurrentTime,
|
||||||
|
visualizer,
|
||||||
|
shuffleList,
|
||||||
|
playMode,
|
||||||
|
shuffleCurrent,
|
||||||
|
// 预加载相关 - 确保所有函数都在返回对象中
|
||||||
|
preloadedAudio,
|
||||||
|
isPreloading,
|
||||||
|
preloadProgress,
|
||||||
|
getNextIndex,
|
||||||
|
preloadNext,
|
||||||
|
getPreloadedAudio,
|
||||||
|
clearPreloadedAudio,
|
||||||
|
clearAllPreloadedAudio,
|
||||||
|
limitPreloadCache,
|
||||||
|
debugPreloadState
|
||||||
|
}
|
||||||
})
|
})
|
Loading…
Reference in New Issue
Block a user