fork(fix): Clone AMLL 并修复 BUG

- 将AMLL Clone到本以地进行修复和优化(emm虽然这很不优雅但是暂时无时间做子模块和Fork)
- 修复在当前播放歌词行不可见的视口Seek会出现滚动偏移的问题
This commit is contained in:
lqtmcstudio
2026-06-07 00:02:14 +08:00
parent 783d2c3dee
commit 72f4510dc8
458 changed files with 86075 additions and 1665 deletions

View File

@@ -93,9 +93,44 @@
/>
</div>
</div>
<div class="lyric">
<div class="lyric" :class="{ 'show-playlist': showPlaylistPanel }">
<!-- Playlist Panel -->
<Transition name="playlist-fade">
<div v-if="showPlaylistPanel" class="playlist-panel">
<div class="playlist-header">
<span class="playlist-title">播放队列</span>
<span class="playlist-count">{{ playerStore.playlist.length }} 首</span>
</div>
<div class="playlist-scroll">
<div
v-for="(song, index) in playerStore.playlist"
:key="song.id"
class="playlist-item"
:class="{ active: index === playerStore.playlist.findIndex(s => s.id === playerStore.currentSong?.id) }"
@click="playFromPlaylist(index)"
>
<div class="item-index">
<span v-if="index === playerStore.playlist.findIndex(s => s.id === playerStore.currentSong?.id)" class="playing-indicator">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</span>
<span v-else>{{ index + 1 }}</span>
</div>
<img v-if="song.picUrl" :src="song.picUrl" class="item-cover" />
<div v-else class="item-cover item-cover-placeholder"></div>
<div class="item-info">
<div class="item-name">{{ song.name }}</div>
<div class="item-artist">{{ song.artist }}</div>
</div>
<div class="item-duration">{{ song.duration }}</div>
</div>
</div>
</div>
</Transition>
<!-- Lyric Player -->
<LyricPlayer
v-if="isPlayerFullScreen"
v-if="isPlayerFullScreen && !showPlaylistPanel"
ref="lyricPlayerRef"
:lyric-lines="toRaw(playerStore.lyrics.lines)"
:current-time="playerStore.currentTime"
@@ -113,6 +148,8 @@
<div class="bottomControls">
<ToggleIconButton
type="playlist"
:checked="showPlaylistPanel"
@click="togglePlaylistPanel"
/>
<ToggleIconButton
type="lyrics"
@@ -160,6 +197,8 @@ const lyricPlayerRef = ref<LyricPlayerRef>()
const bgRef = ref<BackgroundRenderRef>();
const showRemaining = ref(false);
const showPlaylistPanel = ref(false);
const lyricState = ref(false);
const formatTime = (miliseconds: number) => {
const seconds = miliseconds / 1000;
@@ -168,15 +207,20 @@ const formatTime = (miliseconds: number) => {
return `${min}:${sec.toString().padStart(2, '0')}`;
};
const syncLyricPlayerSeek = (time: number) => {
const lyricPlayer = lyricPlayerRef.value?.lyricPlayer.value;
lyricPlayer?.resetScroll();
lyricPlayer?.setCurrentTime(time, true);
};
const handleSeek = (val: number) => {
playerStore.seek(val);
lyricPlayerRef.value?.lyricPlayer.value?.setCurrentTime(val,true);
syncLyricPlayerSeek(val);
};
const playerStore = usePlayerStore();
const isPlayerFullScreen = computed(() => playerStore.isPlayerFullScreen);
//const currentSong = computed(() => playerStore.currentSong);
const isPlaying = computed(() => playerStore.isPlaying);
const playMode = computed(() => playerStore.playMode);
@@ -300,10 +344,26 @@ const toggleFullScreen = () => {
};
const jumpTime = (e: LyricLineMouseEvent) => {
playerStore.seek(e.line.getLine().startTime)
lyricPlayerRef.value?.lyricPlayer.value?.setCurrentTime(e.line.getLine().startTime,true);
const time = e.line.getLine().startTime;
playerStore.seek(time);
syncLyricPlayerSeek(time);
}
const togglePlaylistPanel = () => {
const opening = !showPlaylistPanel.value;
opening
? (lyricState.value = playerStore.hideLyricView, playerStore.hideLyricView = false)
: (playerStore.hideLyricView = lyricState.value);
showPlaylistPanel.value = opening;
};
const playFromPlaylist = (index: number) => {
const song = playerStore.playlist[index];
if (song) {
playerStore.playSong(song);
}
};
// watch(()=>playerStore.currentTime,(t)=>{
// console.log(toRaw(t))
// })
@@ -539,6 +599,10 @@ const jumpTime = (e: LyricLineMouseEvent) => {
}
}
.lyric.show-playlist {
mask-image: linear-gradient(black 0%, black 90%, transparent);
}
.bottomControls {
grid-area: buttom-controls / 1 / buttom-controls / 4;
gap: 2em;
@@ -617,4 +681,199 @@ const jumpTime = (e: LyricLineMouseEvent) => {
display: flex;
}
/* Playlist Panel Styles */
.playlist-panel {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
border-radius: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
pointer-events: auto;
margin-right: 15%;
}
@media screen and (max-width: 1600px), (max-height: 1000px) {
.playlist-panel {
margin-right: 8%;
}
}
.playlist-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.playlist-title {
font-size: 1.2em;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
}
.playlist-count {
font-size: 0.9em;
color: rgba(255, 255, 255, 0.5);
}
.playlist-scroll {
flex: 1;
overflow-y: auto;
padding: 8px 12px;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}
.playlist-scroll::-webkit-scrollbar {
width: 6px;
}
.playlist-scroll::-webkit-scrollbar-track {
background: transparent;
}
.playlist-scroll::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
.playlist-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
.playlist-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 10px;
cursor: pointer;
transition: background 0.2s ease;
}
.playlist-item:hover {
background: rgba(255, 255, 255, 0.1);
}
.playlist-item.active {
background: rgba(255, 255, 255, 0.15);
}
.playlist-item.active .item-name {
color: rgba(255, 255, 255, 1);
}
.item-index {
width: 28px;
text-align: center;
font-size: 0.85em;
color: rgba(255, 255, 255, 0.4);
flex-shrink: 0;
}
.playlist-item.active .item-index {
color: rgba(255, 255, 255, 0.9);
}
.playing-indicator {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 2px;
height: 14px;
}
.playing-indicator .bar {
width: 3px;
background: rgba(255, 255, 255, 0.9);
border-radius: 2px;
animation: soundBars 0.8s ease-in-out infinite;
}
.playing-indicator .bar:nth-child(1) {
height: 60%;
animation-delay: 0s;
}
.playing-indicator .bar:nth-child(2) {
height: 100%;
animation-delay: 0.2s;
}
.playing-indicator .bar:nth-child(3) {
height: 40%;
animation-delay: 0.4s;
}
@keyframes soundBars {
0%, 100% {
transform: scaleY(0.5);
}
50% {
transform: scaleY(1);
}
}
.item-cover {
width: 40px;
height: 40px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
}
.item-cover-placeholder {
background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0.05));
}
.item-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.item-name {
font-size: 0.95em;
color: rgba(255, 255, 255, 0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-artist {
font-size: 0.8em;
color: rgba(255, 255, 255, 0.45);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-duration {
font-size: 0.85em;
color: rgba(255, 255, 255, 0.4);
flex-shrink: 0;
font-variant-numeric: tabular-nums;
}
/* Playlist Panel Transition */
.playlist-fade-enter-active,
.playlist-fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.playlist-fade-enter-from,
.playlist-fade-leave-to {
opacity: 0;
transform: translateX(20px);
}
</style>

View File

@@ -251,7 +251,7 @@
<script setup lang="ts">
import { ref, reactive, onBeforeMount, nextTick, watch } from 'vue';
import { Icon } from '@iconify/vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { ElMessage } from 'element-plus';
import { usePlayerStore } from '../stores/player';
const playerStore = usePlayerStore();
@@ -323,17 +323,17 @@ const installPluginFromFile = async () => {
if (window.electronAPI?.plugin?.install) {
const result = await window.electronAPI.plugin.install();
if (result.success) {
MessagePlugin.success(result.message || '安装成功');
ElMessage.success(result.message || '安装成功');
await loadPlugins();
} else {
if (result.message !== 'canceled') { // Assuming 'canceled' might be a thing, or just show whatever message comes back
MessagePlugin.error(result.message || '安装失败');
ElMessage.error(result.message || '安装失败');
}
}
}
} catch (e) {
console.error('Failed to install plugin', e);
MessagePlugin.error('安装过程中发生错误');
ElMessage.error('安装过程中发生错误');
}
}
@@ -433,14 +433,14 @@ const changeCacheLocation = async () => {
isChangingCache.value = true;
const result = await window.electronAPI.changeCacheLocation(path);
if (result.success) {
MessagePlugin.success('缓存位置已修改');
ElMessage.success('缓存位置已修改');
await loadCacheInfo();
} else {
MessagePlugin.error(result.message || '修改失败');
ElMessage.error(result.message || '修改失败');
}
}
} catch (e) {
MessagePlugin.error('操作失败');
ElMessage.error('操作失败');
console.error(e);
} finally {
isChangingCache.value = false;

View File

@@ -4,7 +4,7 @@ import { createPinia } from 'pinia'
import { createRouter, createWebHashHistory } from 'vue-router'
import App from './App.vue'
import 'tdesign-vue-next/es/style/index.css'
import 'element-plus/dist/index.css'
const pinia = createPinia()
const router = createRouter({
@@ -41,4 +41,4 @@ const router = createRouter({
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')
app.mount('#app')

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { ref, shallowRef, watch } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { ElMessage } from 'element-plus';
import type { Song } from '../types/song';
import { parseLyric } from '../utils/lyricUtil'
export enum PlayMode {
@@ -191,7 +191,7 @@ export const usePlayerStore = defineStore('player', () => {
lyrics.value = { lines: parseLyric(rawLyric) }
console.log(lyrics.value)
} else {
MessagePlugin.warning("当前插件不支持歌词获取").then()
ElMessage.warning("当前插件不支持歌词获取")
}
} catch (e) {
console.error('Failed to fetch lyrics:', e);
@@ -263,11 +263,11 @@ export const usePlayerStore = defineStore('player', () => {
if (playErrorCount.value >= MAX_RETRY_COUNT) {
window.electronAPI.qzplayer.pause().then();
isPlaying.value = false;
MessagePlugin.error('连续多次播放失败,已停止播放').then();
ElMessage.error('连续多次播放失败,已停止播放');
playErrorCount.value = 0;
syncDummyAudioState(false);
} else {
MessagePlugin.warning(`播放失败,尝试播放下一首 (${playErrorCount.value}/${MAX_RETRY_COUNT})`).then();
ElMessage.warning(`播放失败,尝试播放下一首 (${playErrorCount.value}/${MAX_RETRY_COUNT})`);
next(false)
}
};
@@ -369,4 +369,4 @@ export const usePlayerStore = defineStore('player', () => {
playFromList,
hideLyricView
};
});
});