Files
butterfliu/web/src/stores/player.js
lzw-723 aa45b1cd8d
All checks were successful
Go CI / test-and-build (push) Successful in 14s
Web CI / lint-test-build (push) Successful in 23s
修复前端错误
2026-04-07 14:15:43 +08:00

247 lines
5.8 KiB
JavaScript

import { ref, computed, watch } from 'vue'
import { defineStore } from 'pinia'
const STORAGE_KEY = 'butterfliu_player_state'
function saveState(state) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
} catch (e) {
console.error('Failed to save player state:', e)
}
}
function loadState() {
try {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
return JSON.parse(saved)
}
} catch (e) {
console.error('Failed to load player state:', e)
}
return null
}
export const usePlayerStore = defineStore('player', () => {
const audio = ref(null)
const currentTime = ref(0)
const duration = ref(0)
const volume = ref(80)
const isMuted = ref(false)
const isPlaying = ref(false)
const song = ref(null)
const playlist = ref([])
const cover = computed(() => `/api/songs/${song.value?.id || 0}/cover`)
const progress = computed(() => {
if (duration.value === 0) return 0
return (currentTime.value / duration.value) * 100
})
const currentTrack = computed(() => ({
title: song.value?.title || '未播放',
artist: song.value?.artist || '-',
}))
function setupAudioListeners() {
if (!audio.value) return
audio.value.addEventListener('timeupdate', () => {
currentTime.value = audio.value.currentTime
savePlaybackProgress()
})
audio.value.addEventListener('loadedmetadata', () => {
duration.value = audio.value.duration
})
audio.value.addEventListener('ended', () => {
isPlaying.value = false
currentTime.value = 0
savePlaybackProgress()
playNext()
})
audio.value.addEventListener('play', () => {
isPlaying.value = true
savePlaybackProgress()
})
audio.value.addEventListener('pause', () => {
isPlaying.value = false
savePlaybackProgress()
})
audio.value.volume = volume.value / 100
}
function savePlaybackProgress() {
if (!song.value) return
const state = {
song: song.value,
currentTime: currentTime.value,
volume: volume.value,
isMuted: isMuted.value,
playlist: playlist.value,
}
saveState(state)
}
function restorePlaybackState() {
const saved = loadState()
if (!saved || !saved.song) return
song.value = saved.song
volume.value = saved.volume ?? 80
isMuted.value = saved.isMuted ?? false
playlist.value = saved.playlist || []
const src = `/api/songs/${saved.song.id}/stream`
if (!audio.value) {
audio.value = new Audio(src)
setupAudioListeners()
} else {
audio.value.src = src
}
audio.value.volume = isMuted.value ? 0 : volume.value / 100
audio.value.addEventListener('loadedmetadata', () => {
duration.value = audio.value.duration
if (saved.currentTime > 0 && saved.currentTime < duration.value) {
audio.value.currentTime = saved.currentTime
currentTime.value = saved.currentTime
}
}, { once: true })
}
function playSong(newSong, newPlaylist = null) {
if (newPlaylist) {
playlist.value = newPlaylist
}
song.value = newSong
const src = `/api/songs/${newSong.id}/stream`
if (!audio.value) {
audio.value = new Audio(src)
setupAudioListeners()
} else {
audio.value.src = src
}
currentTime.value = 0
audio.value.play()
savePlaybackProgress()
}
function togglePlay() {
if (!audio.value) return
if (isPlaying.value) {
audio.value.pause()
} else {
audio.value.play()
}
}
function seekTo(percent) {
if (!audio.value) return
const time = (percent / 100) * duration.value
audio.value.currentTime = time
savePlaybackProgress()
}
function seekForward(seconds = 10) {
if (!audio.value) return
audio.value.currentTime = Math.min(audio.value.currentTime + seconds, duration.value)
savePlaybackProgress()
}
function seekBackward(seconds = 10) {
if (!audio.value) return
audio.value.currentTime = Math.max(audio.value.currentTime - seconds, 0)
savePlaybackProgress()
}
function setVolume(newVolume) {
volume.value = newVolume
if (audio.value) {
audio.value.volume = isMuted.value ? 0 : newVolume / 100
}
if (newVolume > 0 && isMuted.value) {
isMuted.value = false
}
savePlaybackProgress()
}
function toggleMute() {
isMuted.value = !isMuted.value
if (audio.value) {
audio.value.volume = isMuted.value ? 0 : volume.value / 100
}
savePlaybackProgress()
}
function playNext() {
if (playlist.value.length === 0) return
const currentIndex = playlist.value.findIndex(s => s.id === song.value?.id)
if (currentIndex === -1) return
const nextIndex = (currentIndex + 1) % playlist.value.length
playSong(playlist.value[nextIndex], playlist.value)
}
function playPrevious() {
if (playlist.value.length === 0) return
const currentIndex = playlist.value.findIndex(s => s.id === song.value?.id)
if (currentIndex === -1) return
const prevIndex = currentIndex === 0 ? playlist.value.length - 1 : currentIndex - 1
playSong(playlist.value[prevIndex], playlist.value)
}
function formatTime(seconds) {
if (!seconds || isNaN(seconds)) return '0:00'
const mins = Math.floor(seconds / 60)
const secs = Math.floor(seconds % 60)
return `${mins}:${secs.toString().padStart(2, '0')}`
}
watch(volume, (newVolume) => {
if (audio.value && !isMuted.value) {
audio.value.volume = newVolume / 100
}
})
return {
audio,
currentTime,
duration,
volume,
isMuted,
isPlaying,
song,
playlist,
progress,
currentTrack,
cover,
playSong,
togglePlay,
seekTo,
seekForward,
seekBackward,
setVolume,
toggleMute,
playNext,
playPrevious,
formatTime,
restorePlaybackState,
}
})