feat(app): add music queue management with next/prev navigation
Add queue (ShallowRef<Song[]>), currentIndex, playNext, playPrevious, addToQueue, removeFromQueue to usePlayer. Auto-advance on song end. Add prev/next buttons and queue count to PlayerBar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
81ccd69aae
commit
79691c9a25
@@ -36,6 +36,19 @@
|
||||
|
||||
<div class="flex flex-col gap-1 flex-1 max-w-xl mx-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Previous button -->
|
||||
<button
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center shrink-0 transition-all hover:scale-105 active:scale-95"
|
||||
:class="hasPrevious
|
||||
? isDark ? 'text-white/70 hover:text-white/90' : 'text-gray-600 hover:text-gray-800'
|
||||
: isDark ? 'text-white/20' : 'text-gray-300'"
|
||||
:disabled="!hasPrevious"
|
||||
@click="playPrevious"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6 6h2v12H6V6zm3.5 6l8.5 6V6l-8.5 6z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="w-12 h-12 rounded-full flex items-center justify-center glass-button shrink-0 transition-all hover:scale-105 active:scale-95"
|
||||
@click="toggle"
|
||||
@@ -51,6 +64,19 @@
|
||||
<path d="M8 5v14l11-7L8 5z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Next button -->
|
||||
<button
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center shrink-0 transition-all hover:scale-105 active:scale-95"
|
||||
:class="hasNext
|
||||
? isDark ? 'text-white/70 hover:text-white/90' : 'text-gray-600 hover:text-gray-800'
|
||||
: isDark ? 'text-white/20' : 'text-gray-300'"
|
||||
:disabled="!hasNext"
|
||||
@click="playNext"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6 18l8.5-6L6 6v12zm10-12v12h2V6h-2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<span class="text-[10px] font-mono tabular-nums"
|
||||
:class="isDark ? 'text-white/40' : 'text-gray-400'">
|
||||
{{ formatTime(currentTime) }}
|
||||
@@ -73,6 +99,11 @@
|
||||
<p v-if="error" class="text-[10px] text-red-400">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="queue.length > 1"
|
||||
class="text-[10px] font-mono tabular-nums shrink-0"
|
||||
:class="isDark ? 'text-white/30' : 'text-gray-400'"
|
||||
>{{ queue.length }} songs</span>
|
||||
<button
|
||||
class="w-9 h-9 rounded-xl flex items-center justify-center path-glass-button path-glass-button-sm shrink-0 transition-all hover:scale-105"
|
||||
@click="clear"
|
||||
@@ -102,9 +133,14 @@ const {
|
||||
currentTime,
|
||||
duration,
|
||||
progress,
|
||||
queue,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
toggle,
|
||||
seek,
|
||||
clear,
|
||||
playNext,
|
||||
playPrevious,
|
||||
setContainer,
|
||||
} = usePlayer()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, shallowRef, computed } from 'vue'
|
||||
import type { Song } from '@aiui/core/types/content'
|
||||
import Plyr from 'plyr'
|
||||
import 'plyr/dist/plyr.css'
|
||||
@@ -19,6 +19,10 @@ const error = ref<string | null>(null)
|
||||
const currentTime = ref(0)
|
||||
const duration = ref(0)
|
||||
|
||||
// Queue management
|
||||
const queue = shallowRef<Song[]>([])
|
||||
const currentIndex = ref(-1)
|
||||
|
||||
let plyrInstance: Plyr | null = null
|
||||
let containerEl: HTMLDivElement | null = null
|
||||
|
||||
@@ -94,6 +98,10 @@ export function usePlayer() {
|
||||
})
|
||||
plyrInstance!.on('ended', () => {
|
||||
isPlaying.value = false
|
||||
// Auto-advance to next song in queue
|
||||
if (currentIndex.value >= 0 && currentIndex.value < queue.value.length - 1) {
|
||||
playNext()
|
||||
}
|
||||
})
|
||||
plyrInstance!.on('playing', () => {
|
||||
isPlaying.value = true
|
||||
@@ -155,6 +163,58 @@ export function usePlayer() {
|
||||
currentTime.value = time
|
||||
}
|
||||
|
||||
function addToQueue(song: Song) {
|
||||
const existing = queue.value.find(s => s.id === song.id)
|
||||
if (!existing) {
|
||||
queue.value = [...queue.value, song]
|
||||
}
|
||||
}
|
||||
|
||||
function playNext() {
|
||||
if (queue.value.length === 0) return
|
||||
const nextIdx = currentIndex.value + 1
|
||||
if (nextIdx < queue.value.length) {
|
||||
currentIndex.value = nextIdx
|
||||
play(queue.value[nextIdx])
|
||||
}
|
||||
}
|
||||
|
||||
function playPrevious() {
|
||||
if (queue.value.length === 0) return
|
||||
const prevIdx = currentIndex.value - 1
|
||||
if (prevIdx >= 0) {
|
||||
currentIndex.value = prevIdx
|
||||
play(queue.value[prevIdx])
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromQueue(index: number) {
|
||||
const newQueue = [...queue.value]
|
||||
newQueue.splice(index, 1)
|
||||
queue.value = newQueue
|
||||
// Adjust currentIndex if needed
|
||||
if (index < currentIndex.value) {
|
||||
currentIndex.value--
|
||||
} else if (index === currentIndex.value) {
|
||||
// Removed the current track — pause
|
||||
currentIndex.value = Math.min(currentIndex.value, newQueue.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Override play to update queue position
|
||||
const originalPlay = play
|
||||
async function playWithQueue(song: Song) {
|
||||
// If not in queue, add it
|
||||
const idx = queue.value.findIndex(s => s.id === song.id)
|
||||
if (idx === -1) {
|
||||
addToQueue(song)
|
||||
currentIndex.value = queue.value.length - 1
|
||||
} else {
|
||||
currentIndex.value = idx
|
||||
}
|
||||
await originalPlay(song)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
pause()
|
||||
destroyPlayer()
|
||||
@@ -165,6 +225,15 @@ export function usePlayer() {
|
||||
error.value = null
|
||||
}
|
||||
|
||||
function clearQueue() {
|
||||
clear()
|
||||
queue.value = []
|
||||
currentIndex.value = -1
|
||||
}
|
||||
|
||||
const hasNext = computed(() => currentIndex.value < queue.value.length - 1)
|
||||
const hasPrevious = computed(() => currentIndex.value > 0)
|
||||
|
||||
return {
|
||||
currentSong,
|
||||
playableSource,
|
||||
@@ -175,11 +244,20 @@ export function usePlayer() {
|
||||
duration,
|
||||
hasTrack,
|
||||
progress,
|
||||
play,
|
||||
queue,
|
||||
currentIndex,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
play: playWithQueue,
|
||||
pause,
|
||||
toggle,
|
||||
seek,
|
||||
clear,
|
||||
clearQueue,
|
||||
addToQueue,
|
||||
playNext,
|
||||
playPrevious,
|
||||
removeFromQueue,
|
||||
setContainer,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user