diff --git a/package-lock.json b/package-lock.json index 5db5feb..5751edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "msr-mod", "version": "0.0.0", "dependencies": { + "@astrian/music-surge-revolution": "^0.0.0-20250831052313", "@tailwindcss/vite": "^4.1.7", "axios": "^1.9.0", "gsap": "^3.13.0", @@ -42,6 +43,11 @@ "node": ">=6.0.0" } }, + "node_modules/@astrian/music-surge-revolution": { + "version": "0.0.0-20250831055015", + "resolved": "https://registry.npmjs.org/@astrian/music-surge-revolution/-/music-surge-revolution-0.0.0-20250831055015.tgz", + "integrity": "sha512-joXpUDjez+5M90C4RoGsfHZifXdUBhqSHH+kW3v6TDQJQZwh/sdof1ro4qYXG3/8D8AkfWdhFV3O1C8nxG6syw==" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", diff --git a/package.json b/package.json index c215a13..97b461b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "prebuild:safari": "node scripts/prebuild-safari.js" }, "dependencies": { + "@astrian/music-surge-revolution": "^0.0.0-20250831052313", "@tailwindcss/vite": "^4.1.7", "axios": "^1.9.0", "gsap": "^3.13.0", @@ -40,4 +41,4 @@ "vite": "^6.0.1", "vue-tsc": "^2.1.10" } -} \ No newline at end of file +} diff --git a/src/App.vue b/src/App.vue index 927471c..3763765 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,5 @@ diff --git a/src/components/MiniPlayer.vue b/src/components/MiniPlayer.vue new file mode 100644 index 0000000..0c8d10e --- /dev/null +++ b/src/components/MiniPlayer.vue @@ -0,0 +1,20 @@ + + + + + + + + + + {{playQueue.currentTrack.metadata?.title ?? "未知歌曲"}} + + + + diff --git a/src/components/PlayListItem.vue b/src/components/PlayListItem.vue index 6090b0e..97ac672 100644 --- a/src/components/PlayListItem.vue +++ b/src/components/PlayListItem.vue @@ -5,18 +5,19 @@ import { useFavourites } from '../stores/useFavourites' import StarSlashIcon from '../assets/icons/starslash.vue' +// biome-ignore lint/correctness/noUnusedVariables: used in const favourites = useFavourites() +// biome-ignore lint/correctness/noUnusedVariables: used in const hover = ref(false) defineProps<{ - item: QueueItem + item: InternalQueueItem index: number }>() -const emit = defineEmits<{ - (e: 'play', index: number): void -}>() +// biome-ignore lint/correctness/noUnusedVariables: used in +const emit = defineEmits<(e: 'play', index: number) => void>() diff --git a/src/components/PlayQueueItem.vue b/src/components/PlayQueueItem.vue index eb3d284..6e4ffdd 100644 --- a/src/components/PlayQueueItem.vue +++ b/src/components/PlayQueueItem.vue @@ -1,5 +1,5 @@ - - - - { - if (playQueueStore.playMode.repeat === 'single') { playQueueStore.isPlaying = true } - else { playNext() } - }" @pause="playQueueStore.isPlaying = false" @play="playQueueStore.isPlaying = true" @playing="() => { - console.log('[Player] 音频开始播放事件') - playQueueStore.isBuffering = false - setMetadata() - initializeVolume() - }" @waiting="playQueueStore.isBuffering = true" @loadeddata="() => { - console.log('[Player] 音频数据加载完成') - playQueueStore.isBuffering = false - initializeVolume() - }" @canplay="() => { - console.log('[Player] 音频可以播放') - playQueueStore.isBuffering = false - }" @error="(e) => { - console.error('[Player] 音频错误:', e) - playQueueStore.isBuffering = false - }" crossorigin="anonymous" @timeupdate="updateCurrentTime"> - - - - - - - - - - - - - {{ getCurrentTrack()?.song.name }} - - - - { - playQueueStore.isPlaying = !playQueueStore.isPlaying - }"> - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/components/ScrollingLyrics.vue b/src/components/ScrollingLyrics.vue index 05f762e..093bf31 100644 --- a/src/components/ScrollingLyrics.vue +++ b/src/components/ScrollingLyrics.vue @@ -89,7 +89,7 @@ import { onMounted, ref, watch, nextTick, computed, onUnmounted } from 'vue' import axios from 'axios' import gsap from 'gsap' -import { usePlayQueueStore } from '../stores/usePlayQueueStore' +import { usePlayStore } from '../stores/usePlayStore' // 类型定义 interface LyricsLine { @@ -106,7 +106,7 @@ interface GapLine { duration?: number } -const playQueueStore = usePlayQueueStore() +const playStore = usePlayStore() // 响应式数据 const parsedLyrics = ref<(LyricsLine | GapLine)[]>([]) @@ -121,7 +121,7 @@ const lyricsWrapper = ref() const lineRefs = ref<(HTMLElement | null)[]>([]) const controlPanel = ref() const loadingIndicator = ref() -const noLyricsIndicator = ref() +//const noLyricsIndicator = ref() // GSAP 动画实例 let scrollTween: gsap.core.Tween | null = null @@ -135,12 +135,13 @@ const props = defineProps<{ // 滚动指示器相关计算 const scrollIndicatorHeight = computed(() => { - if (parsedLyrics.value.length === 0) return 0 + if (parsedLyrics.value.length === 0) {return 0} return Math.max(10, 100 / parsedLyrics.value.length * 5) // 显示大约5行的比例 }) +// biome-ignore lint/correctness/noUnusedVariables: used in const scrollIndicatorPosition = computed(() => { - if (parsedLyrics.value.length === 0 || currentLineIndex.value < 0) return 0 + if (parsedLyrics.value.length === 0 || currentLineIndex.value < 0) {return 0} const progress = currentLineIndex.value / (parsedLyrics.value.length - 1) const containerHeight = lyricsContainer.value?.clientHeight || 400 const indicatorTrackHeight = containerHeight / 2 // 指示器轨道高度 @@ -148,6 +149,7 @@ const scrollIndicatorPosition = computed(() => { }) // 设置行引用 +// biome-ignore lint/correctness/noUnusedVariables: used in function setLineRef(el: HTMLElement | null, index: number) { if (el) { lineRefs.value[index] = el @@ -155,15 +157,17 @@ function setLineRef(el: HTMLElement | null, index: number) { } // 歌词解析函数 -function parseLyrics(lrcText: string, minGapDuration: number = 5): (LyricsLine | GapLine)[] { - if (!lrcText) return [ - { - type: 'lyric', - time: 0, - text: '', - originalTime: '[00:00]' - } - ] +function parseLyrics(lrcText: string, minGapDuration = 5): (LyricsLine | GapLine)[] { + if (!lrcText) { + return [ + { + type: 'lyric', + time: 0, + text: '', + originalTime: '[00:00]' + } + ] + } const lines = lrcText.split('\n') const tempParsedLines: (LyricsLine | GapLine)[] = [] @@ -172,14 +176,14 @@ function parseLyrics(lrcText: string, minGapDuration: number = 5): (LyricsLine | for (const line of lines) { const matches = [...line.matchAll(timeRegex)] - if (matches.length === 0) continue + if (matches.length === 0) {continue} const text = line.replace(/\[\d{1,2}:\d{2}(?:\.\d{1,3})?\]/g, '').trim() for (const match of matches) { - const minutes = parseInt(match[1]) - const seconds = parseInt(match[2]) - const milliseconds = match[3] ? parseInt(match[3].padEnd(3, '0')) : 0 + const minutes = Number.parseInt(match[1]) + const seconds = Number.parseInt(match[2]) + const milliseconds = match[3] ? Number.parseInt(match[3].padEnd(3, '0')) : 0 const totalSeconds = minutes * 60 + seconds + milliseconds / 1000 @@ -206,7 +210,7 @@ function parseLyrics(lrcText: string, minGapDuration: number = 5): (LyricsLine | const lyricLines = tempParsedLines.filter(line => line.type === 'lyric') as LyricsLine[] const gapLines = tempParsedLines.filter(line => line.type === 'gap') as GapLine[] - if (lyricLines.length === 0) return tempParsedLines + if (lyricLines.length === 0) {return tempParsedLines} for (let i = 0; i < gapLines.length; i++) { const gapLine = gapLines[i] @@ -236,9 +240,9 @@ function parseLyrics(lrcText: string, minGapDuration: number = 5): (LyricsLine | // 查找当前行索引 function findCurrentLineIndex(time: number): number { - if (parsedLyrics.value.length === 0) return -1 + if (parsedLyrics.value.length === 0) {return -1} // 如果时间小于第一句歌词,则返回0(空行) - if (time < parsedLyrics.value[1]?.time) return 0 + if (time < parsedLyrics.value[1]?.time) {return 0} let index = 0 for (let i = 1; i < parsedLyrics.value.length; i++) { if (time >= parsedLyrics.value[i].time) { @@ -252,7 +256,7 @@ function findCurrentLineIndex(time: number): number { // 使用 GSAP 滚动到指定行 function scrollToLine(lineIndex: number, smooth = true) { - if (!lyricsContainer.value || !lyricsWrapper.value || !lineRefs.value[lineIndex]) return + if (!lyricsContainer.value || !lyricsWrapper.value || !lineRefs.value[lineIndex]) {return} const container = lyricsContainer.value const wrapper = lyricsWrapper.value @@ -288,7 +292,7 @@ function scrollToLine(lineIndex: number, smooth = true) { // 高亮当前行动画 function highlightCurrentLine(lineIndex: number) { - if (!lineRefs.value[lineIndex]) return + if (!lineRefs.value[lineIndex]) {return} const lineElement = lineRefs.value[lineIndex] @@ -322,10 +326,11 @@ function highlightCurrentLine(lineIndex: number) { } // 处理鼠标滚轮 +// biome-ignore lint/correctness/noUnusedVariables: used in function handleWheel(event: WheelEvent) { event.preventDefault() - if (!lyricsWrapper.value || !lyricsContainer.value) return + if (!lyricsWrapper.value || !lyricsContainer.value) {return} userScrolling.value = true autoScroll.value = false @@ -365,6 +370,7 @@ function handleWheel(event: WheelEvent) { } // 处理歌词行点击 +// biome-ignore lint/correctness/noUnusedVariables: used in function handleLineClick(line: LyricsLine | GapLine, index: number) { if (line.type === 'lyric') { console.log('Jump to time:', line.time) @@ -418,11 +424,11 @@ function toggleAutoScroll() { // 重置滚动 function resetScroll() { - if (!lyricsWrapper.value) return + if (!lyricsWrapper.value) {return} // 停止所有动画 - if (scrollTween) scrollTween.kill() - if (highlightTween) highlightTween.kill() + if (scrollTween) {scrollTween.kill()} + if (highlightTween) {highlightTween.kill()} // 重置位置 gsap.to(lyricsWrapper.value, { @@ -456,12 +462,13 @@ function resetScroll() { } // gap 圆点透明度计算 +// biome-ignore lint/correctness/noUnusedVariables: used in function getGapDotOpacities(line: GapLine) { // 获取 gap 的持续时间 const duration = line.duration ?? 0 - if (duration <= 0) return [0.3, 0.3, 0.3] + if (duration <= 0) {return [0.3, 0.3, 0.3]} // 当前播放时间 - const now = playQueueStore.currentTime + const now = playStore.progress.currentTime // gap 起止时间 const start = line.time // 计算进度 @@ -474,7 +481,7 @@ function getGapDotOpacities(line: GapLine) { } // 监听播放时间变化 -watch(() => playQueueStore.currentTime, (time) => { +watch(() => playStore.progress.currentTime, (time) => { const newIndex = findCurrentLineIndex(time) if (newIndex !== currentLineIndex.value && newIndex >= 0) { @@ -500,8 +507,8 @@ watch(() => props.lrcSrc, async (newSrc) => { lineRefs.value = [] // 停止所有动画 - if (scrollTween) scrollTween.kill() - if (highlightTween) highlightTween.kill() + if (scrollTween) {scrollTween.kill()} + if (highlightTween) {highlightTween.kill()} if (newSrc) { loading.value = true @@ -552,12 +559,12 @@ function setupPageFocusHandlers() { handleVisibilityChange = () => { if (document.hidden) { // 页面失去焦点时暂停动画 - if (scrollTween) scrollTween.pause() - if (highlightTween) highlightTween.pause() + if (scrollTween) {scrollTween.pause()} + if (highlightTween) {highlightTween.pause()} } else { // 页面重新获得焦点时恢复并重新同步 - if (scrollTween && scrollTween.paused()) scrollTween.resume() - if (highlightTween && highlightTween.paused()) highlightTween.resume() + if (scrollTween?.paused()) {scrollTween.resume()} + if (highlightTween?.paused()) {highlightTween.resume()} // 重新同步歌词位置 nextTick(() => { @@ -605,9 +612,9 @@ onMounted(() => { // 组件卸载时清理 onUnmounted(() => { - if (scrollTween) scrollTween.kill() - if (highlightTween) highlightTween.kill() - if (userScrollTimeout) clearTimeout(userScrollTimeout) + if (scrollTween) {scrollTween.kill()} + if (highlightTween) {highlightTween.kill()} + if (userScrollTimeout) {clearTimeout(userScrollTimeout)} // 清理页面焦点事件监听器 if (handleVisibilityChange) { diff --git a/src/components/TrackItem.vue b/src/components/TrackItem.vue index f43ae90..6c448c6 100644 --- a/src/components/TrackItem.vue +++ b/src/components/TrackItem.vue @@ -1,8 +1,7 @@ diff --git a/src/main.ts b/src/main.ts index f086584..dfcb1d0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,13 +7,11 @@ import 'vue-toast-notification/dist/theme-default.css' import App from './App.vue' import HomePage from './pages/Home.vue' -import AlbumDetailView from './pages/AlbumDetail.vue' import Playroom from './pages/Playroom.vue' import Library from './pages/Library.vue' const routes = [ { path: '/', component: HomePage }, - { path: '/albums/:albumId', component: AlbumDetailView }, { path: '/playroom', component: Playroom }, { path: '/library', component: Library } ] diff --git a/src/pages/AlbumDetail.vue b/src/pages/AlbumDetail.vue deleted file mode 100644 index b3c24da..0000000 --- a/src/pages/AlbumDetail.vue +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - {{ album?.name }} - {{ artistsOrganize(album?.artistes ?? []) }} - {{ album?.intro }} - - - - - - - - - - 播放专辑 - - - { - playTheAlbum() - playQueue.shuffleCurrent = true - playQueue.playMode.shuffle = true - }"> - - - - - - - - - - 共 {{ album?.songs?.length ?? '?' }} 首曲目 - - - - - - - - \ No newline at end of file diff --git a/src/pages/Library.vue b/src/pages/Library.vue index 82d66d2..0522fe2 100644 --- a/src/pages/Library.vue +++ b/src/pages/Library.vue @@ -6,21 +6,21 @@ import ShuffleIcon from '../assets/icons/shuffle.vue' import { useFavourites } from '../stores/useFavourites' import { ref } from 'vue' import PlayListItem from '../components/PlayListItem.vue' -import { usePlayQueueStore } from '../stores/usePlayQueueStore' +import { usePlayStore } from '../stores/usePlayStore' const favourites = useFavourites() -const playQueueStore = usePlayQueueStore() +const playQueueStore = usePlayStore() const currentList = ref<'favourites' | number>('favourites') -function playTheList(list: 'favourites' | number, playFrom: number = 0) { - if (playFrom < 0 || playFrom >= favourites.favouritesCount) { playFrom = 0 } +function playTheList(list: 'favourites' | number, playFrom = 0) { + let actualPlayFrom = playFrom + if (playFrom < 0 || playFrom >= favourites.favouritesCount) { actualPlayFrom = 0 } - if (usePlayQueueStore().queueReplaceLock) { + if (playQueueStore.queueReplaceLock) { if (!confirm("当前操作会将你的播放队列清空、放入这张歌单所有曲目,并从头播放。继续吗?")) { return } - usePlayQueueStore().queueReplaceLock = false + playQueueStore.queueReplaceLock = false } - playQueueStore.list = [] if (list === 'favourites') { if (favourites.favouritesCount === 0) return @@ -29,25 +29,25 @@ function playTheList(list: 'favourites' | number, playFrom: number = 0) { song: item.song, album: item.album })) - playQueueStore.list = newPlayQueue.slice().reverse() - playQueueStore.currentIndex = playFrom - playQueueStore.playMode.shuffle = false - playQueueStore.isPlaying = true - playQueueStore.isBuffering = true + // playQueueStore.list = newPlayQueue.slice().reverse() + // playQueueStore.currentIndex = playFrom + // playQueueStore.playMode.shuffle = false + // playQueueStore.isPlaying = true + // playQueueStore.isBuffering = true } else { // Handle other lists if needed } } function shuffle(list: 'favourites' | number) { - playTheList(list) - playQueueStore.shuffleCurrent = true - playQueueStore.playMode.shuffle = false - setTimeout(() => { - playQueueStore.playMode.shuffle = true - playQueueStore.isPlaying = true - playQueueStore.isBuffering = true - }, 100) + // playTheList(list) + // playQueueStore.shuffleCurrent = true + // playQueueStore.playMode.shuffle = false + // setTimeout(() => { + // playQueueStore.playMode.shuffle = true + // playQueueStore.isPlaying = true + // playQueueStore.isBuffering = true + // }, 100) } diff --git a/src/pages/Playroom.vue b/src/pages/Playroom.vue index 4aa4c43..31e9ccd 100644 --- a/src/pages/Playroom.vue +++ b/src/pages/Playroom.vue @@ -1,5 +1,5 @@