Download src/stores/playqueue.js from ahutchen/music: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/spaces/ahutchen/music/resolve/main/src/stores/playqueue.js
- Command line
-
hf download hf://spaces/ahutchen/music/src/stores/playqueue.js
-
curl -L -o playqueue.js https://huggingface.co/spaces/ahutchen/music/resolve/main/src/stores/playqueue.js
10.6 kB
| import { defineStore } from 'pinia' | |
| import { ref, computed } from 'vue' | |
| /** | |
| * 播放列表 Store - 单一职责:管理当前播放器的临时队列 | |
| * 对应网易云音乐的 "Now Playing" 概念 | |
| */ | |
| export const usePlayQueueStore = defineStore('playqueue', () => { | |
| // 状态 | |
| const queue = ref([]) | |
| const currentIndex = ref(-1) | |
| const playMode = ref('list') // 'list', 'random', 'single' | |
| // 常量 | |
| const STORAGE_KEY = 'vue-music-play-queue' | |
| const MAX_QUEUE_SIZE = 2000 | |
| // 计算属性 | |
| const currentSong = computed(() => { | |
| return currentIndex.value >= 0 && currentIndex.value < queue.value.length | |
| ? queue.value[currentIndex.value] | |
| : null | |
| }) | |
| const queueLength = computed(() => queue.value.length) | |
| const isEmpty = computed(() => queue.value.length === 0) | |
| const hasPrevious = computed(() => currentIndex.value > 0) | |
| const hasNext = computed(() => currentIndex.value < queue.value.length - 1) | |
| // 队列管理方法 | |
| const setQueue = (songs, startIndex = 0) => { | |
| if (!Array.isArray(songs)) { | |
| console.error('队列必须是数组') | |
| return false | |
| } | |
| // 去重:移除重复的歌曲 | |
| const uniqueSongs = [] | |
| const seen = new Set() | |
| for (const song of songs) { | |
| if (song && song.id) { | |
| const key = `${song.source}-${song.id}` | |
| if (!seen.has(key)) { | |
| seen.add(key) | |
| uniqueSongs.push(song) | |
| } | |
| } | |
| } | |
| queue.value = uniqueSongs.slice(0, MAX_QUEUE_SIZE) | |
| currentIndex.value = Math.max(0, Math.min(startIndex, queue.value.length - 1)) | |
| saveQueue() | |
| return true | |
| } | |
| const addToQueue = (song, position = 'end') => { | |
| if (!song || !song.id) { | |
| return { success: false, message: '歌曲信息无效' } | |
| } | |
| // 防重复:检查歌曲是否已在队列中 | |
| const exists = queue.value.some(s => s.id === song.id && s.source === song.source) | |
| if (exists) { | |
| return { success: false, message: '歌曲已在播放列表中' } | |
| } | |
| if (queue.value.length >= MAX_QUEUE_SIZE) { | |
| return { success: false, message: `播放列表最多只能添加 ${MAX_QUEUE_SIZE} 首歌曲` } | |
| } | |
| switch (position) { | |
| case 'next': | |
| // 插入到当前播放歌曲的下一个位置 | |
| const insertIndex = Math.min(currentIndex.value + 1, queue.value.length) | |
| queue.value.splice(insertIndex, 0, { ...song }) | |
| break | |
| case 'start': | |
| queue.value.unshift({ ...song }) | |
| if (currentIndex.value >= 0) { | |
| currentIndex.value++ | |
| } | |
| break | |
| case 'end': | |
| default: | |
| queue.value.push({ ...song }) | |
| break | |
| } | |
| saveQueue() | |
| return { success: true, message: '已添加到播放列表' } | |
| } | |
| const removeFromQueue = (index) => { | |
| if (index < 0 || index >= queue.value.length) { | |
| return { success: false, message: '无效的索引' } | |
| } | |
| queue.value.splice(index, 1) | |
| // 调整当前播放索引 | |
| if (currentIndex.value > index) { | |
| currentIndex.value-- | |
| } else if (currentIndex.value === index) { | |
| // 如果删除的是当前播放的歌曲 | |
| if (queue.value.length === 0) { | |
| currentIndex.value = -1 | |
| } else { | |
| currentIndex.value = Math.min(currentIndex.value, queue.value.length - 1) | |
| } | |
| } | |
| saveQueue() | |
| return { success: true, message: '已从播放列表移除' } | |
| } | |
| const clearQueue = () => { | |
| queue.value = [] | |
| currentIndex.value = -1 | |
| saveQueue() | |
| } | |
| // 播放控制方法 | |
| const playAtIndex = (index) => { | |
| if (index < 0 || index >= queue.value.length) { | |
| return null | |
| } | |
| currentIndex.value = index | |
| saveQueue() | |
| return queue.value[index] | |
| } | |
| const playNext = () => { | |
| if (queue.value.length === 0) return null | |
| let nextIndex = currentIndex.value | |
| switch (playMode.value) { | |
| case 'random': | |
| // 随机播放:生成与当前不同的随机索引 | |
| if (queue.value.length > 1) { | |
| do { | |
| nextIndex = Math.floor(Math.random() * queue.value.length) | |
| } while (nextIndex === currentIndex.value) | |
| } | |
| break | |
| case 'single': | |
| // 单曲循环:保持当前索引 | |
| break | |
| case 'list': | |
| default: | |
| // 顺序播放:下一首,到末尾则循环到开头 | |
| nextIndex = hasNext.value ? currentIndex.value + 1 : 0 | |
| break | |
| } | |
| return playAtIndex(nextIndex) | |
| } | |
| const playPrevious = () => { | |
| if (queue.value.length === 0) return null | |
| let prevIndex = currentIndex.value | |
| switch (playMode.value) { | |
| case 'random': | |
| // 随机播放:生成与当前不同的随机索引 | |
| if (queue.value.length > 1) { | |
| do { | |
| prevIndex = Math.floor(Math.random() * queue.value.length) | |
| } while (prevIndex === currentIndex.value) | |
| } | |
| break | |
| case 'single': | |
| // 单曲循环:保持当前索引 | |
| break | |
| case 'list': | |
| default: | |
| // 顺序播放:上一首,到开头则循环到末尾 | |
| prevIndex = hasPrevious.value ? currentIndex.value - 1 : queue.value.length - 1 | |
| break | |
| } | |
| return playAtIndex(prevIndex) | |
| } | |
| const setPlayMode = (mode) => { | |
| const validModes = ['list', 'random', 'single'] | |
| if (validModes.includes(mode)) { | |
| playMode.value = mode | |
| saveQueue() | |
| return true | |
| } | |
| return false | |
| } | |
| const togglePlayMode = () => { | |
| const modes = ['list', 'random', 'single'] | |
| const currentIndex = modes.indexOf(playMode.value) | |
| const nextIndex = (currentIndex + 1) % modes.length | |
| setPlayMode(modes[nextIndex]) | |
| return playMode.value | |
| } | |
| // 队列操作方法 | |
| const moveInQueue = (fromIndex, toIndex) => { | |
| if (fromIndex < 0 || fromIndex >= queue.value.length || | |
| toIndex < 0 || toIndex >= queue.value.length) { | |
| return false | |
| } | |
| const item = queue.value.splice(fromIndex, 1)[0] | |
| queue.value.splice(toIndex, 0, item) | |
| // 调整当前播放索引 | |
| if (currentIndex.value === fromIndex) { | |
| currentIndex.value = toIndex | |
| } else if (fromIndex < currentIndex.value && toIndex >= currentIndex.value) { | |
| currentIndex.value-- | |
| } else if (fromIndex > currentIndex.value && toIndex <= currentIndex.value) { | |
| currentIndex.value++ | |
| } | |
| saveQueue() | |
| return true | |
| } | |
| const shuffleQueue = () => { | |
| if (queue.value.length <= 1) return | |
| const currentSong = currentSong.value | |
| // Fisher-Yates洗牌算法 | |
| for (let i = queue.value.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)) | |
| ;[queue.value[i], queue.value[j]] = [queue.value[j], queue.value[i]] | |
| } | |
| // 如果有当前播放的歌曲,找到它的新位置 | |
| if (currentSong) { | |
| const newIndex = queue.value.findIndex(song => | |
| song.id === currentSong.id && song.source === currentSong.source | |
| ) | |
| if (newIndex >= 0) { | |
| currentIndex.value = newIndex | |
| } | |
| } | |
| saveQueue() | |
| } | |
| // 批量操作 | |
| const addMultipleToQueue = (songs, position = 'end') => { | |
| let successCount = 0 | |
| const results = [] | |
| for (const song of songs) { | |
| const result = addToQueue(song, position) | |
| results.push(result) | |
| if (result.success) successCount++ | |
| } | |
| return { | |
| success: successCount > 0, | |
| successCount, | |
| totalCount: songs.length, | |
| results | |
| } | |
| } | |
| // 搜索功能 | |
| const searchInQueue = (keyword) => { | |
| if (!keyword || !keyword.trim()) { | |
| return queue.value.map((song, index) => ({ song, index })) | |
| } | |
| const lowerKeyword = keyword.toLowerCase().trim() | |
| return queue.value | |
| .map((song, index) => ({ song, index })) | |
| .filter(({ song }) => { | |
| // 安全的字符串比较函数 | |
| const safeIncludes = (text) => { | |
| if (!text) return false | |
| if (typeof text === 'string') { | |
| return text.toLowerCase().includes(lowerKeyword) | |
| } | |
| if (Array.isArray(text)) { | |
| // 如果是数组,检查每个元素 | |
| return text.some(item => { | |
| if (typeof item === 'string') { | |
| return item.toLowerCase().includes(lowerKeyword) | |
| } | |
| if (typeof item === 'object' && item?.name) { | |
| return item.name.toLowerCase().includes(lowerKeyword) | |
| } | |
| return false | |
| }) | |
| } | |
| if (typeof text === 'object' && text?.name) { | |
| return text.name.toLowerCase().includes(lowerKeyword) | |
| } | |
| return String(text).toLowerCase().includes(lowerKeyword) | |
| } | |
| return safeIncludes(song.name) || | |
| safeIncludes(song.artist) || | |
| safeIncludes(song.album) | |
| }) | |
| } | |
| // 存储管理 | |
| const saveQueue = () => { | |
| try { | |
| const data = { | |
| queue: queue.value, | |
| currentIndex: currentIndex.value, | |
| playMode: playMode.value, | |
| lastUpdated: Date.now(), | |
| version: '1.0' | |
| } | |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) | |
| } catch (error) { | |
| console.error('保存播放列表失败:', error) | |
| } | |
| } | |
| const loadQueue = () => { | |
| try { | |
| const saved = localStorage.getItem(STORAGE_KEY) | |
| if (saved) { | |
| const data = JSON.parse(saved) | |
| // 检查数据完整性 | |
| if (Array.isArray(data.queue)) { | |
| queue.value = data.queue | |
| currentIndex.value = data.currentIndex >= 0 ? data.currentIndex : -1 | |
| playMode.value = data.playMode || 'list' | |
| } | |
| } | |
| } catch (error) { | |
| console.error('加载播放列表失败:', error) | |
| queue.value = [] | |
| currentIndex.value = -1 | |
| playMode.value = 'list' | |
| } | |
| } | |
| return { | |
| // 状态 | |
| queue: computed(() => queue.value), | |
| currentIndex: computed(() => currentIndex.value), | |
| currentSong, | |
| playMode: computed(() => playMode.value), | |
| // 计算属性 | |
| queueLength, | |
| isEmpty, | |
| hasPrevious, | |
| hasNext, | |
| // 队列管理 | |
| setQueue, | |
| addToQueue, | |
| removeFromQueue, | |
| clearQueue, | |
| // 播放控制 | |
| playAtIndex, | |
| playNext, | |
| playPrevious, | |
| setPlayMode, | |
| togglePlayMode, | |
| // 队列操作 | |
| moveInQueue, | |
| shuffleQueue, | |
| // 批量操作 | |
| addMultipleToQueue, | |
| // 搜索功能 | |
| searchInQueue, | |
| // 存储管理 | |
| saveQueue, | |
| loadQueue | |
| } | |
| }) |