chore: 重构播放列表
This commit is contained in:
parent
a139d1278a
commit
210700bc0d
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import Player from './components/Player.vue'
|
import MiniPlayer from './components/MiniPlayer.vue'
|
||||||
import PreferencePanel from './components/PreferencePanel.vue'
|
import PreferencePanel from './components/PreferencePanel.vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ watch(() => presentPreferencePanel, (value) => {
|
||||||
<CorgIcon :size="4" />
|
<CorgIcon :size="4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Player />
|
<MiniPlayer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -112,35 +112,19 @@ watch(() => props.albumCid, async () => {
|
||||||
|
|
||||||
const playQueue = usePlayQueueStore()
|
const playQueue = usePlayQueueStore()
|
||||||
|
|
||||||
function playTheAlbum(from: number = 0) {
|
async function playTheAlbum(from: number = 0) {
|
||||||
if (playQueue.queueReplaceLock) {
|
await playQueue.replaceQueue(album.value?.songs ?? [])
|
||||||
if (!confirm("当前操作会将你的播放队列清空、放入这张专辑所有曲目,并从头播放。继续吗?")) { return }
|
|
||||||
playQueue.queueReplaceLock = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let newPlayQueue = []
|
|
||||||
for (const track of album.value?.songs ?? []) {
|
|
||||||
console.log(track)
|
|
||||||
newPlayQueue.push({
|
|
||||||
song: track,
|
|
||||||
album: album.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
playQueue.list = newPlayQueue
|
|
||||||
playQueue.currentIndex = from
|
|
||||||
playQueue.isPlaying = true
|
|
||||||
playQueue.isBuffering = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffle() {
|
function shuffle() {
|
||||||
playTheAlbum()
|
// playTheAlbum()
|
||||||
playQueue.shuffleCurrent = true
|
// playQueue.shuffleCurrent = true
|
||||||
playQueue.playMode.shuffle = false
|
// playQueue.playMode.shuffle = false
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
playQueue.playMode.shuffle = true
|
// playQueue.playMode.shuffle = true
|
||||||
playQueue.isPlaying = true
|
// playQueue.isPlaying = true
|
||||||
playQueue.isBuffering = true
|
// playQueue.isBuffering = true
|
||||||
}, 100)
|
// }, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<RouterLink to="/playroom">
|
||||||
class="h-9 w-52 bg-neutral-800/80 border border-[#ffffff39] rounded-full backdrop-blur-3xl flex items-center justify-between select-none">
|
<div
|
||||||
<div class="flex items-center gap-2">
|
class="h-9 w-52 bg-neutral-800/80 border border-[#ffffff39] rounded-full backdrop-blur-3xl flex items-center justify-between select-none">
|
||||||
<div class="rounded-full w-9 h-9 bg-gray-600" />
|
<div class="flex items-center gap-2">
|
||||||
<div class="text-white">Song title</div>
|
<div class="rounded-full w-9 h-9 bg-gray-600" />
|
||||||
|
<div class="text-white">Song title</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</RouterLink>
|
||||||
</template>
|
</template>
|
0
src/composables/useMediaController.ts
Normal file
0
src/composables/useMediaController.ts
Normal file
|
@ -176,12 +176,12 @@ function updateAudioVolume() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDetector() {
|
function formatDetector() {
|
||||||
const format = playQueueStore.list[playQueueStore.currentIndex].song.sourceUrl?.split('.').pop()
|
/* const format = playQueueStore.list[playQueueStore.currentIndex].sourceUrl?.split('.').pop()
|
||||||
if (format === 'mp3') { return 'MP3' }
|
if (format === 'mp3') { return 'MP3' }
|
||||||
if (format === 'flac') { return 'FLAC' }
|
if (format === 'flac') { return 'FLAC' }
|
||||||
if (format === 'm4a') { return 'M4A' }
|
if (format === 'm4a') { return 'M4A' }
|
||||||
if (format === 'ape') { return 'APE' }
|
if (format === 'ape') { return 'APE' }
|
||||||
if (format === 'wav') { return 'WAV' }
|
if (format === 'wav') { return 'WAV' } */
|
||||||
return '未知格式'
|
return '未知格式'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,14 +305,11 @@ function makePlayQueueListDismiss() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentTrack() {
|
function getCurrentTrack() {
|
||||||
if (playQueueStore.list.length === 0) {
|
console.log(playQueueStore.queue)
|
||||||
|
if (playQueueStore.queue.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (playQueueStore.playMode.shuffle) {
|
return playQueueStore.currentTrack
|
||||||
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
|
|
||||||
} else {
|
|
||||||
return playQueueStore.list[playQueueStore.currentIndex]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMoreOptions() {
|
function toggleMoreOptions() {
|
||||||
|
@ -826,22 +823,22 @@ watch(() => playQueueStore.currentIndex, () => {
|
||||||
<div class="flex gap-2 mx-8 mb-4">
|
<div class="flex gap-2 mx-8 mb-4">
|
||||||
<button
|
<button
|
||||||
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
||||||
:class="playQueueStore.playMode.shuffle ? 'bg-[#ffffffaa] text-neutral-700' : 'text-white bg-neutral-800/80'"
|
:class="playQueueStore.isShuffle ? 'bg-[#ffffffaa] text-neutral-700' : 'text-white bg-neutral-800/80'"
|
||||||
@click="toggleShuffle">
|
@click="toggleShuffle">
|
||||||
<ShuffleIcon :size="4" />
|
<ShuffleIcon :size="4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
||||||
:class="playQueueStore.playMode.repeat === 'off' ? 'text-white bg-neutral-800/80' : 'bg-[#ffffffaa] text-neutral-700'"
|
:class="playQueueStore.loopMode === 'off' ? 'text-white bg-neutral-800/80' : 'bg-[#ffffffaa] text-neutral-700'"
|
||||||
@click="toggleRepeat">
|
@click="toggleRepeat">
|
||||||
<CycleTwoArrowsIcon :size="4" v-if="playQueueStore.playMode.repeat !== 'single'" />
|
<CycleTwoArrowsIcon :size="4" v-if="playQueueStore.loopMode !== 'single'" />
|
||||||
<CycleTwoArrowsWithNumOneIcon :size="4" v-else />
|
<CycleTwoArrowsWithNumOneIcon :size="4" v-else />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="border-[#ffffff39]" />
|
<hr class="border-[#ffffff39]" />
|
||||||
|
|
||||||
<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-if="playQueueStore.playMode.shuffle">
|
<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-if="playQueueStore.isShuffle">
|
||||||
<PlayQueueItem v-for="(oriIndex, shuffledIndex) in playQueueStore.shuffleList"
|
<PlayQueueItem v-for="(oriIndex, shuffledIndex) in playQueueStore.shuffleList"
|
||||||
:queueItem="playQueueStore.list[oriIndex]" :isCurrent="playQueueStore.currentIndex === shuffledIndex"
|
:queueItem="playQueueStore.list[oriIndex]" :isCurrent="playQueueStore.currentIndex === shuffledIndex"
|
||||||
:key="playQueueStore.list[oriIndex].song.cid" :index="shuffledIndex" />
|
:key="playQueueStore.list[oriIndex].song.cid" :index="shuffledIndex" />
|
||||||
|
|
|
@ -1,217 +1,154 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { computed, ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { checkAndRefreshSongResource } from '../utils'
|
import apis from '../apis'
|
||||||
|
|
||||||
export const usePlayQueueStore = defineStore('queue', () => {
|
export const usePlayQueueStore = defineStore('queue', () => {
|
||||||
const list = ref<QueueItem[]>([])
|
// 内部状态
|
||||||
const currentIndex = ref<number>(0)
|
const queue = ref<QueueItem[]>([])
|
||||||
const isPlaying = ref<boolean>(false)
|
const isShuffle = ref<boolean>(false)
|
||||||
|
const loopingMode = ref<'single' | 'all' | 'off'>('off')
|
||||||
const queueReplaceLock = ref<boolean>(false)
|
const queueReplaceLock = ref<boolean>(false)
|
||||||
const isBuffering = ref<boolean>(false)
|
const currentPlaying = ref<number>(0) // 当前播放指针,指针在 queueOrder 中寻址(无论是否开启了随机播放)
|
||||||
const currentTime = ref<number>(0)
|
const queueOrder = ref<number[]>([]) // 播放队列顺序
|
||||||
const duration = ref<number>(0)
|
|
||||||
const updatedCurrentTime = ref<number | null>(null)
|
|
||||||
const visualizer = ref<number[]>([0, 0, 0, 0, 0, 0])
|
|
||||||
const shuffleList = ref<number[]>([])
|
|
||||||
const playMode = ref<{
|
|
||||||
shuffle: boolean
|
|
||||||
repeat: 'off' | 'single' | 'all'
|
|
||||||
}>({
|
|
||||||
shuffle: false,
|
|
||||||
repeat: 'off',
|
|
||||||
})
|
|
||||||
const shuffleCurrent = ref<boolean | undefined>(undefined)
|
|
||||||
|
|
||||||
// 预加载相关状态
|
// 暴露给外部的响应式只读引用
|
||||||
const preloadedAudio = ref<Map<string, HTMLAudioElement>>(new Map())
|
const queueState = computed(() =>
|
||||||
const isPreloading = ref<boolean>(false)
|
// 按 queueOrder 的顺序排序输出队列
|
||||||
const preloadProgress = ref<number>(0)
|
queueOrder.value.map(index => queue.value[index]).filter(Boolean)
|
||||||
|
)
|
||||||
|
const shuffleState = computed(() => isShuffle.value)
|
||||||
|
const loopModeState = computed(() => loopingMode.value)
|
||||||
|
|
||||||
// 获取下一首歌的索引
|
// 获取当前播放项
|
||||||
const getNextIndex = computed(() => {
|
const currentTrack = computed(() => {
|
||||||
if (list.value.length === 0) return -1
|
const actualIndex = queueOrder.value[currentPlaying.value]
|
||||||
|
return queue.value[actualIndex] || null
|
||||||
if (playMode.value.repeat === 'single') {
|
|
||||||
return currentIndex.value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playMode.value.shuffle && shuffleList.value.length > 0) {
|
|
||||||
// 当前在 shuffleList 中的位置
|
|
||||||
const currentShuffleIndex = currentIndex.value
|
|
||||||
if (currentShuffleIndex < shuffleList.value.length - 1) {
|
|
||||||
// 返回下一个位置对应的原始 list 索引
|
|
||||||
return shuffleList.value[currentShuffleIndex + 1]
|
|
||||||
} else if (playMode.value.repeat === 'all') {
|
|
||||||
// 返回第一个位置对应的原始 list 索引
|
|
||||||
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 () => {
|
* 播放队列相关
|
||||||
const nextIndex = getNextIndex.value
|
***********/
|
||||||
if (nextIndex === -1) {
|
// 使用新队列替换老队列
|
||||||
return
|
// 队列替换锁开启时启用确认,确认后重置该锁
|
||||||
|
async function replaceQueue(songs: Song[]) {
|
||||||
|
if (queueReplaceLock.value) {
|
||||||
|
if (!confirm("当前操作会将你的播放队列清空、放入这张专辑所有曲目,并从头播放。继续吗?")) { return }
|
||||||
|
// 重置队列替换锁
|
||||||
|
queueReplaceLock.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取下一首歌曲对象
|
let newQueue: QueueItem[] = []
|
||||||
// nextIndex 已经是原始 list 中的索引
|
|
||||||
const nextSong = list.value[nextIndex]
|
|
||||||
|
|
||||||
if (!nextSong || !nextSong.song) {
|
// 专辑信息缓存空间
|
||||||
return
|
let albums: { [key: string]: Album } = {}
|
||||||
}
|
|
||||||
|
|
||||||
const songId = nextSong.song.cid
|
for (let i in songs) {
|
||||||
|
// 写入新队列
|
||||||
// 如果已经预加载过,跳过
|
newQueue[newQueue.length] = {
|
||||||
if (preloadedAudio.value.has(songId)) {
|
song: songs[i],
|
||||||
return
|
album: await (async () => {
|
||||||
}
|
if (albums[songs[i].albumCid ?? "0"]) return albums[songs[i].albumCid ?? "0"]
|
||||||
|
else {
|
||||||
// 检查是否有有效的音频源
|
const album = await apis.getAlbum(songs[i].albumCid ?? "0")
|
||||||
if (!nextSong.song.sourceUrl) {
|
albums[songs[i].albumCid ?? "0"] = album
|
||||||
return
|
return album
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
isPreloading.value = true
|
|
||||||
preloadProgress.value = 0
|
|
||||||
|
|
||||||
// 在预加载前检查和刷新资源
|
|
||||||
console.log('[Store] 预加载前检查资源:', nextSong.song.name)
|
|
||||||
const updatedSong = await checkAndRefreshSongResource(
|
|
||||||
nextSong.song,
|
|
||||||
(updated) => {
|
|
||||||
// 更新播放队列中的歌曲信息
|
|
||||||
// nextIndex 已经是原始 list 中的索引
|
|
||||||
if (list.value[nextIndex]) {
|
|
||||||
list.value[nextIndex].song = updated
|
|
||||||
}
|
}
|
||||||
|
})()
|
||||||
// 如果歌曲在收藏夹中,也更新收藏夹
|
|
||||||
// 注意:这里不直接导入 favourites store 以避免循环依赖
|
|
||||||
// 改为触发一个事件或者在调用方处理
|
|
||||||
console.log('[Store] 预加载时需要更新收藏夹:', updated.name)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
console.log('[Store] 预加载完成:', updatedSong.name)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听加载错误
|
|
||||||
audio.addEventListener('error', (e) => {
|
|
||||||
console.error(`[Store] 预加载音频失败: ${updatedSong.name}`, e)
|
|
||||||
isPreloading.value = false
|
|
||||||
preloadProgress.value = 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 使用更新后的音频源
|
|
||||||
audio.src = updatedSong.sourceUrl!
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Store] 预加载过程出错:', 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将新队列替换已有队列
|
||||||
|
queue.value = newQueue
|
||||||
|
|
||||||
|
// 初始化播放顺序
|
||||||
|
queueOrder.value = Array.from({ length: newQueue.length }, (_, i) => i)
|
||||||
|
currentPlaying.value = 0
|
||||||
|
|
||||||
|
// 关闭随机播放和循环(外部可在此方法执行完毕后再更新播放模式)
|
||||||
|
isShuffle.value = false
|
||||||
|
loopingMode.value = 'off'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调试函数:打印当前状态
|
/************
|
||||||
const debugPreloadState = () => {
|
* 播放模式相关
|
||||||
console.log('[Store] 预加载状态:', {
|
**********/
|
||||||
isPreloading: isPreloading.value,
|
// 切换随机播放模式
|
||||||
progress: preloadProgress.value,
|
const toggleShuffle = (turnTo?: boolean) => {
|
||||||
cacheSize: preloadedAudio.value.size,
|
// 未指定随机状态时自动开关
|
||||||
cachedSongs: Array.from(preloadedAudio.value.keys()),
|
const newShuffleState = turnTo ?? !isShuffle.value
|
||||||
nextIndex: getNextIndex.value,
|
|
||||||
})
|
if (newShuffleState === isShuffle.value) return // 状态未改变
|
||||||
|
|
||||||
|
/* if (newShuffleState) {
|
||||||
|
// 开启随机播放:保存当前顺序并打乱
|
||||||
|
const originalOrder = [...queueOrder.value]
|
||||||
|
const shuffled = [...queueOrder.value]
|
||||||
|
|
||||||
|
// Fisher-Yates 洗牌算法
|
||||||
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保当前播放的歌曲位置不变(可选)
|
||||||
|
const currentSongIndex = queueOrder.value[currentPlaying.value]
|
||||||
|
const newCurrentPos = shuffled.indexOf(currentSongIndex)
|
||||||
|
if (newCurrentPos !== -1 && newCurrentPos !== currentPlaying.value) {
|
||||||
|
[shuffled[currentPlaying.value], shuffled[newCurrentPos]] =
|
||||||
|
[shuffled[newCurrentPos], shuffled[currentPlaying.value]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存原始顺序以便恢复
|
||||||
|
queue.value.forEach((_, index) => {
|
||||||
|
queue.value[index]._originalOrderIndex = originalOrder.indexOf(index)
|
||||||
|
})
|
||||||
|
|
||||||
|
queueOrder.value = shuffled
|
||||||
|
} else {
|
||||||
|
// 关闭随机播放:恢复原始顺序
|
||||||
|
const restoredOrder = Array.from({ length: queue.value.length }, (_, i) => i)
|
||||||
|
|
||||||
|
// 找到当前播放歌曲在原始顺序中的位置
|
||||||
|
const currentSongIndex = queueOrder.value[currentPlaying.value]
|
||||||
|
const newCurrentPos = restoredOrder.indexOf(currentSongIndex)
|
||||||
|
|
||||||
|
queueOrder.value = restoredOrder
|
||||||
|
currentPlaying.value = newCurrentPos !== -1 ? newCurrentPos : 0
|
||||||
|
} */
|
||||||
|
|
||||||
|
isShuffle.value = newShuffleState
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换循环播放模式
|
||||||
|
const toggleLoop = (mode?: 'single' | 'all' | 'off') => {
|
||||||
|
// 如果指定了循环模式
|
||||||
|
if (mode) return loopingMode.value = mode
|
||||||
|
|
||||||
|
// 如果没有指定,那么按照「无 -> 列表循环 -> 单曲循环」的顺序轮换
|
||||||
|
switch (loopingMode.value) {
|
||||||
|
case 'off':
|
||||||
|
loopingMode.value = 'all'
|
||||||
|
break
|
||||||
|
case 'all':
|
||||||
|
loopingMode.value = 'single'
|
||||||
|
break
|
||||||
|
case 'single':
|
||||||
|
loopingMode.value = 'off'
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
list,
|
// 响应式状态(只读)
|
||||||
currentIndex,
|
queue: queueState,
|
||||||
isPlaying,
|
isShuffle: shuffleState,
|
||||||
queueReplaceLock,
|
loopMode: loopModeState,
|
||||||
isBuffering,
|
currentTrack,
|
||||||
currentTime,
|
currentIndex: currentPlaying,
|
||||||
duration,
|
|
||||||
updatedCurrentTime,
|
// 修改方法
|
||||||
visualizer,
|
replaceQueue,
|
||||||
shuffleList,
|
toggleShuffle,
|
||||||
playMode,
|
toggleLoop
|
||||||
shuffleCurrent,
|
|
||||||
// 预加载相关 - 确保所有函数都在返回对象中
|
|
||||||
preloadedAudio,
|
|
||||||
isPreloading,
|
|
||||||
preloadProgress,
|
|
||||||
getNextIndex,
|
|
||||||
preloadNext,
|
|
||||||
getPreloadedAudio,
|
|
||||||
clearPreloadedAudio,
|
|
||||||
clearAllPreloadedAudio,
|
|
||||||
limitPreloadCache,
|
|
||||||
debugPreloadState,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
6
src/vite-env.d.ts
vendored
6
src/vite-env.d.ts
vendored
|
@ -8,10 +8,10 @@ type Song = {
|
||||||
cid: string
|
cid: string
|
||||||
name: string
|
name: string
|
||||||
albumCid?: string
|
albumCid?: string
|
||||||
sourceUrl?: string
|
|
||||||
lyricUrl?: string | null
|
|
||||||
mvUrl?: string | null
|
mvUrl?: string | null
|
||||||
mvCoverUrl?: string | null
|
mvCoverUrl?: string | null
|
||||||
|
sourceUrl?: string | null
|
||||||
|
lyricUrl?: string | null
|
||||||
artistes?: string[]
|
artistes?: string[]
|
||||||
artists?: string[]
|
artists?: string[]
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,8 @@ interface ApiResponse {
|
||||||
interface QueueItem {
|
interface QueueItem {
|
||||||
song: Song
|
song: Song
|
||||||
album?: Album
|
album?: Album
|
||||||
|
sourceUrl?: string
|
||||||
|
lyricUrl?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LyricsLine {
|
interface LyricsLine {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user