feat: implement seeking (FFmpeg -ss restart), fix lyrics sync with seekOffset

- AudioPlayer.seek() restarts FFmpeg with -ss parameter for actual seeking
- New POST /api/player/:botId/seek endpoint
- BotStatus includes seekOffset and playStartTime from server
- Frontend elapsed = seekOffset + (now - playStartedAt)
- Progress bar click now seeks to clicked position
- All timing actions use _resetTiming() helper with seekOffset
- Lyrics sync uses store.elapsed which includes seekOffset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 03:16:37 +08:00
1 parent 080c2836c9
commit 1cdface33c
5 files changed
+101 -50

No files matched your search

+14 -8
View File
@@ -74,6 +74,7 @@
<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted } from 'vue';
import { Icon } from '@iconify/vue';
import axios from 'axios';
import { usePlayerStore } from '../stores/player.js';
import CoverArt from './CoverArt.vue';
import Queue from './Queue.vue';
@@ -101,10 +102,10 @@ function formatTime(seconds: number): string {
}
function updateProgress() {
// Directly compute elapsed from raw values (bypass Vue reactivity)
const bot = store.activeBot;
const seekOff = store.seekOffset;
if (bot?.playing && !bot.paused && store.playStartedAt > 0) {
currentElapsed.value = (Date.now() - store.playStartedAt) / 1000;
currentElapsed.value = seekOff + (Date.now() - store.playStartedAt) / 1000;
} else if (bot?.paused) {
currentElapsed.value = store.pausedElapsed;
}
@@ -117,17 +118,22 @@ function updateProgress() {
rafId = requestAnimationFrame(updateProgress);
}
function onProgressClick(e: MouseEvent) {
async function onProgressClick(e: MouseEvent) {
const bar = progressBarRef.value;
if (!bar) return;
if (!bar || !store.activeBotId) return;
const rect = bar.getBoundingClientRect();
const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
const duration = currentSong.value?.duration ?? 0;
const seekTime = ratio * duration;
// Can't seek server-side FFmpeg stream — show tooltip with target time
progressTooltipTime.value = `跳转不可用 (${formatTime(seekTime)})`;
progressTooltipVisible.value = true;
setTimeout(() => { progressTooltipVisible.value = false; }, 1500);
try {
await axios.post(`/api/player/${store.activeBotId}/seek`, { position: seekTime });
store.seekOffset = seekTime;
store.playStartedAt = Date.now();
store.pausedElapsed = 0;
} catch {
// ignore
}
}
function onProgressHover(e: MouseEvent) {