feat(播放器): 添加随机播放功能
实现播放队列的随机播放模式,包括: 1. 在 store 中添加 shuffleList 和 playMode 状态 2. 修改播放器组件以支持随机播放时的歌曲切换 3. 更新播放界面以显示随机播放队列 4. 添加随机播放按钮交互逻辑 当开启随机播放时,会生成随机播放列表并保持当前播放歌曲不变,关闭时可恢复原播放顺序。
This commit is contained in:
parent
b6574d8093
commit
d99ae28f8c
|
@ -35,12 +35,19 @@ function artistsOrganize(list: string[]) {
|
||||||
|
|
||||||
function setMetadata() {
|
function setMetadata() {
|
||||||
if ('mediaSession' in navigator) {
|
if ('mediaSession' in navigator) {
|
||||||
|
let current = (() => {
|
||||||
|
if (playQueueStore.playMode.shuffle) {
|
||||||
|
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
|
||||||
|
} else {
|
||||||
|
return playQueueStore.list[playQueueStore.currentIndex]
|
||||||
|
}
|
||||||
|
})()
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
title: playQueueStore.list[playQueueStore.currentIndex].song.name,
|
title: current.song.name,
|
||||||
artist: artistsOrganize(playQueueStore.list[playQueueStore.currentIndex].song.artists ?? []),
|
artist: artistsOrganize(current.song.artists ?? []),
|
||||||
album: playQueueStore.list[playQueueStore.currentIndex].album?.name,
|
album: current.album?.name,
|
||||||
artwork: [
|
artwork: [
|
||||||
{ src: playQueueStore.list[playQueueStore.currentIndex].album?.coverUrl ?? '', sizes: '500x500', type: 'image/png' },
|
{ src: current.album?.coverUrl ?? '', sizes: '500x500', type: 'image/png' },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -140,12 +147,50 @@ watch(() => error.value, (newError) => {
|
||||||
console.error('[Player] 可视化器错误:', newError)
|
console.error('[Player] 可视化器错误:', newError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 切换播放模式
|
||||||
|
watch(() => playQueueStore.playMode.shuffle, (isShuffle) => {
|
||||||
|
if (isShuffle) {
|
||||||
|
// 提取当前歌曲的索引和队列中总项目数
|
||||||
|
const currentIndex = playQueueStore.currentIndex
|
||||||
|
const trackCount = playQueueStore.list.length
|
||||||
|
// 生成新的随机播放列表,该列表是原来列表的下标数组(保持原有的顺序不变,以便用户关闭随机播放时恢复原有队列)
|
||||||
|
// 将队列中剩余的项目随机排列,队列中更早的歌曲保持不变
|
||||||
|
let shuffledList = [...Array(currentIndex).keys()]
|
||||||
|
// 如果 shuffleCurrent 被指定为 false 或 undefined,那么将当前歌曲放在新列表的开头
|
||||||
|
if (!playQueueStore.shuffleCurrent) {
|
||||||
|
shuffledList.push(currentIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将剩余的项目列出来
|
||||||
|
let shuffleSpace = [...Array(trackCount).keys()]
|
||||||
|
shuffleSpace = shuffleSpace.filter((item) => item > currentIndex)
|
||||||
|
console.log(shuffleSpace)
|
||||||
|
// 随机打乱剩余的项目
|
||||||
|
shuffleSpace.sort(() => Math.random() - 0.5)
|
||||||
|
|
||||||
|
// 拼接新队列
|
||||||
|
shuffledList = shuffledList.concat(shuffleSpace)
|
||||||
|
|
||||||
|
// 应用新的随机播放列表
|
||||||
|
playQueueStore.shuffleList = shuffledList
|
||||||
|
} else {
|
||||||
|
// 将当前播放的歌曲的原来的索引赋给 currentIndex
|
||||||
|
playQueueStore.currentIndex = playQueueStore.shuffleList[playQueueStore.currentIndex]
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<audio
|
<audio
|
||||||
:src="playQueueStore.list[playQueueStore.currentIndex] ? playQueueStore.list[playQueueStore.currentIndex].song.sourceUrl : ''"
|
:src="(() => {
|
||||||
|
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"
|
ref="playerRef"
|
||||||
:autoplay="playQueueStore.isPlaying"
|
:autoplay="playQueueStore.isPlaying"
|
||||||
v-if="playQueueStore.list.length !== 0"
|
v-if="playQueueStore.list.length !== 0"
|
||||||
|
|
|
@ -134,39 +134,47 @@ function makePlayQueueListDismiss() {
|
||||||
ease: 'power2.out'
|
ease: 'power2.out'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCurrentTrack() {
|
||||||
|
if (playQueueStore.playMode.shuffle) {
|
||||||
|
return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
|
||||||
|
} else {
|
||||||
|
return playQueueStore.list[playQueueStore.currentIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="z-0 absolute top-0 left-0 w-screen h-screen"
|
<div class="z-0 absolute top-0 left-0 w-screen h-screen"
|
||||||
v-if="playQueueStore.list[playQueueStore.currentIndex].album?.coverDeUrl">
|
v-if="getCurrentTrack().album?.coverDeUrl">
|
||||||
<img class="w-full h-full blur-2xl object-cover"
|
<img class="w-full h-full blur-2xl object-cover"
|
||||||
:src="playQueueStore.list[playQueueStore.currentIndex].album?.coverDeUrl" />
|
:src="getCurrentTrack().album?.coverDeUrl" />
|
||||||
<div class="bg-transparent w-full h-full absolute top-0 left-0" />
|
<div class="bg-transparent w-full h-full absolute top-0 left-0" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex justify-center items-center my-auto gap-16 z-10 select-none">
|
<div class="w-full flex justify-center items-center my-auto gap-16 z-10 select-none">
|
||||||
<div class="flex flex-col w-96 gap-4">
|
<div class="flex flex-col w-96 gap-4">
|
||||||
<img :src="playQueueStore.list[playQueueStore.currentIndex].album?.coverUrl"
|
<img :src="getCurrentTrack().album?.coverUrl"
|
||||||
class="rounded-2xl shadow-2xl border border-white/20 w-96 h-96" />
|
class="rounded-2xl shadow-2xl border border-white/20 w-96 h-96" />
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="relative flex-auto w-0">
|
<div class="relative flex-auto w-0">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="text-black/90 blur-lg text-lg font-medium truncate">
|
<div class="text-black/90 blur-lg text-lg font-medium truncate">
|
||||||
{{ playQueueStore.list[playQueueStore.currentIndex].song.name }}
|
{{ getCurrentTrack().song.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-black/90 blur-lg text-base truncate">
|
<div class="text-black/90 blur-lg text-base truncate">
|
||||||
{{ artistsOrganize(playQueueStore.list[playQueueStore.currentIndex].song.artists ?? []) }} —
|
{{ getCurrentTrack().song.artists ?? [] }} —
|
||||||
{{ playQueueStore.list[playQueueStore.currentIndex].album?.name ?? '未知专辑' }}
|
{{ getCurrentTrack().album?.name ?? '未知专辑' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute top-0">
|
<div class="absolute top-0">
|
||||||
<div class="text-white text-lg font-medium truncate">
|
<div class="text-white text-lg font-medium truncate">
|
||||||
{{ playQueueStore.list[playQueueStore.currentIndex].song.name }}
|
{{ getCurrentTrack().song.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-white/75 text-base truncate">
|
<div class="text-white/75 text-base truncate">
|
||||||
{{ artistsOrganize(playQueueStore.list[playQueueStore.currentIndex].song.artists ?? []) }} —
|
{{ artistsOrganize(getCurrentTrack().song.artists ?? []) }} —
|
||||||
{{ playQueueStore.list[playQueueStore.currentIndex].album?.name ?? '未知专辑' }}
|
{{ getCurrentTrack().album?.name ?? '未知专辑' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -337,8 +345,12 @@ function makePlayQueueListDismiss() {
|
||||||
|
|
||||||
<div class="flex gap-2 mx-8 mb-4">
|
<div class="flex gap-2 mx-8 mb-4">
|
||||||
<button
|
<button
|
||||||
class="text-white flex-1 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center"
|
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center"
|
||||||
@click="">
|
:class="playQueueStore.playMode.shuffle ? 'bg-[#ffffffaa] text-neutral-700' : 'text-white bg-neutral-800/80'"
|
||||||
|
@click="() => {
|
||||||
|
playQueueStore.playMode.shuffle = !playQueueStore.playMode.shuffle
|
||||||
|
playQueueStore.shuffleCurrent = false
|
||||||
|
}">
|
||||||
<ShuffleIcon :size="4" />
|
<ShuffleIcon :size="4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
@ -350,7 +362,36 @@ function makePlayQueueListDismiss() {
|
||||||
|
|
||||||
<hr class="border-[#ffffff39]" />
|
<hr class="border-[#ffffff39]" />
|
||||||
|
|
||||||
<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2">
|
<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-if="playQueueStore.playMode.shuffle">
|
||||||
|
<button v-for="(oriIndex, shuffledIndex) in playQueueStore.shuffleList" class="p-4 w-full rounded-md hover:bg-white/5 first:mt-2"
|
||||||
|
:key="oriIndex" @click="() => {
|
||||||
|
if (playQueueStore.currentIndex === shuffledIndex) { return }
|
||||||
|
playQueueStore.currentIndex = shuffledIndex
|
||||||
|
playQueueStore.isPlaying = true
|
||||||
|
}">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="relative w-12 h-12 rounded-md shadow-xl overflow-hidden">
|
||||||
|
<img :src="playQueueStore.list[oriIndex].album?.coverUrl" />
|
||||||
|
<div class="w-full h-full absolute top-0 left-0 bg-neutral-900/75 flex justify-center items-center"
|
||||||
|
v-if="shuffledIndex === playQueueStore.currentIndex">
|
||||||
|
<div style="height: 1rem;" class="flex justify-center items-center gap-[.125rem]">
|
||||||
|
<div class="bg-white w-[.125rem] rounded-full" v-for="(bar, index) in playQueueStore.visualizer"
|
||||||
|
:key="index" :style="{
|
||||||
|
height: `${Math.max(10, bar)}%`
|
||||||
|
}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col text-left flex-auto w-0">
|
||||||
|
<div class="text-white text-base font-medium truncate">{{ playQueueStore.list[oriIndex].song.name }}</div>
|
||||||
|
<div class="text-white/75 text-sm truncate">{{ artistsOrganize(playQueueStore.list[oriIndex].song.artists ?? []) }} —
|
||||||
|
{{ playQueueStore.list[oriIndex].album?.name ?? '未知专辑' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-else>
|
||||||
<button v-for="(track, index) in playQueueStore.list" class="p-4 w-full rounded-md hover:bg-white/5 first:mt-2"
|
<button v-for="(track, index) in playQueueStore.list" class="p-4 w-full rounded-md hover:bg-white/5 first:mt-2"
|
||||||
:key="track.song.cid" @click="() => {
|
:key="track.song.cid" @click="() => {
|
||||||
if (playQueueStore.currentIndex === index) { return }
|
if (playQueueStore.currentIndex === index) { return }
|
||||||
|
|
|
@ -10,7 +10,16 @@ export const usePlayQueueStore = defineStore('queue', () =>{
|
||||||
const currentTime = ref<number>(0)
|
const currentTime = ref<number>(0)
|
||||||
const duration = ref<number>(0)
|
const duration = ref<number>(0)
|
||||||
const updatedCurrentTime = ref<number | null>(null)
|
const updatedCurrentTime = ref<number | null>(null)
|
||||||
const visualizer = ref<number[]>([0, 0, 0, 0])
|
const visualizer = ref<number[]>([0, 0, 0, 0, 0, 0])
|
||||||
|
const shuffleList = ref<number[]>([])
|
||||||
return { list, currentIndex, isPlaying, queueReplaceLock, isBuffering, currentTime, duration, updatedCurrentTime, visualizer }
|
const playMode = ref<{
|
||||||
|
shuffle: boolean,
|
||||||
|
repeat: 'off' | 'single' | 'all'
|
||||||
|
}>({
|
||||||
|
shuffle: false,
|
||||||
|
repeat: 'off'
|
||||||
|
})
|
||||||
|
const shuffleCurrent = ref<boolean | undefined>(undefined)
|
||||||
|
|
||||||
|
return { list, currentIndex, isPlaying, queueReplaceLock, isBuffering, currentTime, duration, updatedCurrentTime, visualizer, shuffleList, playMode, shuffleCurrent }
|
||||||
})
|
})
|
Loading…
Reference in New Issue
Block a user