Avoid repeated filesystem checks on every play() call by resolving
the ffmpeg binary path once at module load. Also verify execute
permission after chmod to handle noexec mounts.
https://claude.ai/code/session_01CqfKgV8GuCmWNpfx86H62X
The bundled ffmpeg-static binary may lose execute permission after npm
install on some platforms, causing EACCES errors during playback. Now
checks and fixes the permission automatically before spawning ffmpeg.
https://claude.ai/code/session_01CqfKgV8GuCmWNpfx86H62X
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>
Implement BiliBiliProvider for video-as-audio playback using direct
BiliBili API calls (search, video info, DASH audio URL extraction).
Add QR code login support and cookie persistence. Update FFmpeg to
send Referer header for BiliBili CDN URLs. Extend platform union type
to "netease" | "qq" | "bilibili" across all interfaces. Add -b flag
for chat commands and B站 badge in web UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bugs fixed:
- play-at: stop current playback before jumping to queue index
- playNext retry: use break instead of return to ensure stateChange emits
- sendVoiceData: skip if disconnecting to avoid errors during teardown
UI: player-left and lyrics button now toggle lyrics page (open/close)
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>
Two bugs prevented auto-advance:
1. FFmpeg close handler was empty — ChildProcess object persisted after
close, so frame loop check `!this.ffmpeg` never triggered trackEnd
2. playNext() is async (resolves URL) but trackEnd handler didn't catch
rejected promises, causing silent failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Multi-stage build: builder installs backend + frontend deps, compiles
- Production stage: npm ci --production rebuilds native modules (opus, sqlite3)
- FFmpeg bundled via ffmpeg-static (no apt install ffmpeg)
- .dockerignore prevents node_modules/dist from being copied
- docker-compose uses host network for TS3 UDP connectivity
- Named volume for persistent data
- Health check endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Users no longer need to install FFmpeg separately — it is now bundled as
an npm dependency (ffmpeg-static). player.ts resolves the bundled binary
automatically and falls back to system PATH. start.bat and a new
setup.bat script handle dependency installation and project building.
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>
Follows TS3AudioBot-NetEaseCloudmusic-plugin pattern:
- QueuedSong.url is now optional (metadata-only queue entries)
- resolveAndPlay() fetches URL on-demand right before playback
- Playlist/album/FM load instantly (only metadata stored)
- playNext() resolves URL lazily, skips up to 3 songs on failure
- Avoids expired URLs for songs deep in the queue
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>
- Progress bar reads playStartedAt directly in requestAnimationFrame loop
bypassing Vue reactivity caching for smooth 60fps updates
- Queue panel moved to sibling of player-bar (was trapped inside causing z-index issues)
- Added progress thumb dot on hover
- Click on progress shows 'seek not available' tooltip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents EADDRINUSE crash that killed the entire app when port 3200
was still occupied from a previous run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Playlist: add console.error in catch block so load failures are visible
- Player: add progress bar with requestAnimationFrame and time display
- Player: extract toggleQueue function with debug logging for queue panel
- WebSocket: fetch queue on stateChange when not included in event payload
- Lyrics: use CSS transform scrolling and reduce sync to 500ms to fix flicker
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>