msr-mod/src/stores/usePlayQueueStore.ts

160 lines
4.6 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const usePlayQueueStore = defineStore('queue', () => {
// 内部状态
const queue = ref<QueueItem[]>([])
const isShuffle = ref(false)
const loopingMode = ref<'single' | 'all' | 'off'>('off')
const queueReplaceLock = ref(false)
const currentPlaying = ref(0) // 当前播放指针,指针在 queueOrder 中寻址(无论是否开启了随机播放)
const queueOrder = ref<number[]>([]) // 播放队列顺序
const isPlaying = ref(false)
// 暴露给外部的响应式只读引用
const queueState = computed(() =>
// 按 queueOrder 的顺序排序输出队列
queueOrder.value
.map((index) => queue.value[index])
.filter(Boolean),
)
const shuffleState = computed(() => isShuffle.value)
const loopModeState = computed(() => loopingMode.value)
// 获取当前播放项
const currentTrack = computed(() => {
const actualIndex = queueOrder.value[currentPlaying.value]
return queue.value[actualIndex] || null
})
// 获取当前是否正在播放
const playingState = computed(() => isPlaying.value)
/************
* 播放队列相关
***********/
// 使用新队列替换老队列
// 队列替换锁开启时启用确认,确认后重置该锁
async function replaceQueue(newQueue: QueueItem[]) {
if (queueReplaceLock.value) {
if (
!confirm(
'当前操作会将你的播放队列清空、放入这张专辑所有曲目,并从头播放。继续吗?',
)
) {
return
}
// 重置队列替换锁
queueReplaceLock.value = false
}
// 将新队列替换已有队列
queue.value = newQueue
// 初始化播放顺序
queueOrder.value = Array.from({ length: newQueue.length }, (_, i) => i)
currentPlaying.value = 0
// 关闭随机播放和循环(外部可在此方法执行完毕后再更新播放模式)
isShuffle.value = false
loopingMode.value = 'off'
}
/***********
* 播放控制相关
*
**********/
// 控制播放
const togglePlay = (turnTo?: boolean) => {
const newPlayState = turnTo ?? !isPlaying.value
if (newPlayState === isPlaying.value) return
isPlaying.value = newPlayState
}
/************
* 播放模式相关
**********/
// 切换随机播放模式
const toggleShuffle = (turnTo?: boolean) => {
// 未指定随机状态时自动开关
const newShuffleState = turnTo ?? !isShuffle.value
if (newShuffleState === isShuffle.value) return // 状态未改变
// TODO: 进行洗牌
/* 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 {
// 响应式状态(只读)
queue: queueState,
isShuffle: shuffleState,
loopMode: loopModeState,
currentTrack,
currentIndex: currentPlaying,
isPlaying: playingState,
// 修改方法
replaceQueue,
toggleShuffle,
toggleLoop,
togglePlay
}
})