Store queue and timing state per-bot instead of globally, move bot
selector from Home page into Navbar dropdown, show bot name badge
in the player bar, and route WebSocket stateChange queues per-bot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add isAdvancing guard to playNext() to prevent concurrent calls from
skipping songs (trackEnd + user !next race condition)
- Replace removeAllListeners in WebSocket setup with tracked named
listeners, attach only once per bot instead of every 5 seconds
- Add .catch() to unhandled playNext() in cmdVote
- Add sessionId counter to AudioPlayer to discard stale setTimeout
callbacks after stop()+play() transitions
- Defer nulling TS3 client until disconnect() promise resolves
- Add null checks for providers in play-by-id and add-by-id endpoints
- Add PCM buffer backpressure (pause FFmpeg at 960KB, resume at 480KB)
- Change volume slider from @input to @change to avoid flooding server
- Wrap error-reporting sendTextMessage in try/catch to prevent
unhandled rejection in error handler
- Clamp elapsed getter to song duration
- Log FFmpeg stderr at debug level instead of swallowing
- Return null from prev() in Sequential mode instead of wrapping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Progress/lyrics sync:
- AudioPlayer tracks framesPlayed (ground truth: frames * 20ms + seekOffset)
- BotStatus.elapsed is the real elapsed from server
- Frontend polls GET /elapsed every 3s for ground truth
- Client interpolates between syncs (serverElapsed + timeSinceSync)
- Pause freezes at interpolated value, resume resyncs from server
- No more client-side timestamp drift
Playlist play all:
- Stops current playback before loading
- Respects play mode: random/rloop picks random first song, seq plays first
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
- New endpoints: play-by-id, add-by-id, play-playlist (load all songs at once)
- Playlist 'Play All' now uses server-side bulk load (no N sequential searches)
- Search/Home/Playlist views use playById instead of search-by-name
- Fixed updateBotStatus timing: capture prev state before mutation
- Early return on song change prevents stale pause/resume logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Start @sansenjian/qq-music-api server via dynamic import in api-server.ts
- Rewrite QQ Music provider to use real API endpoints (getSearchByKey,
getMusicPlay, getSongListDetail, getAlbumInfo, getLyric, etc.)
- Cache home page data (recommend playlists, daily songs, user playlists)
in Pinia store with 5-minute TTL to avoid refetching on every mount
- Add unified /api/music/search/all endpoint that queries both Netease
and QQ providers in parallel and returns merged results
- Remove platform toggle from Search.vue; search both platforms at once
- Add platform badge (网易云 / QQ) to SongCard component
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Player store tracks playStartedAt/pausedElapsed for elapsed time
- Queue component fetches from server when opened
- Lyrics page syncs every 200ms using elapsed time
- Lyrics auto-scroll to active line with smooth animation
- Translation lines shown below main lyrics
- Fade mask on lyrics scroll area
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>