5.7 KiB
Design: FM Bug Fix + Artist Loop + Playlist Fuzzy Search
Date: 2026-04-27
Overview
Three features for the TeamSpeak Music Bot:
- New
!artist <name>command — loop playback filtered by artist - Fuzzy playlist name search in existing
!playlistcommand - Fix
!fmaudio dropout bug (no sound after a few songs but status shows playing)
Feature 1: !artist Command
Behavior
!artist <歌手名> [-q|-b|-y] searches for songs by the artist, loads them into the queue, sets the queue mode to Loop, and starts playing.
Flow
- Parse command with optional platform flags (
-q,-b,-y) - Call
provider.search(歌手名, 50)to get up to 50 results - Filter results: only keep songs where
song.artistcontains the search query (case-insensitive) - If filtered list is empty, fall back to unfiltered search results (up to 20)
- Clear current queue, add filtered songs, set mode to
Loop - Play first song via
resolveAndPlay
Key Decisions
- Why Loop mode? The user said "循环播放" (loop playback). After the artist's songs are exhausted, they should restart.
- Why filter client-side? The search API doesn't support artist-only filtering. We search broadly then narrow down.
- Why 50 results? The default limit is 20, but for prolific artists we want more coverage. 50 balances API response size with coverage.
Files Changed
src/bot/commands.ts: Registerartistin PUBLIC_COMMANDS, update help textsrc/bot/instance.ts: NewcmdArtist()method
Feature 2: Playlist Fuzzy Search
Behavior
!playlist <name or ID> now accepts both playlist IDs and playlist names. When the input is not a pure numeric ID, it searches for matching playlists and uses the top result.
Flow
- Parse input — if it's a pure numeric ID or contains a URL with an ID, use existing logic
- Otherwise, call
provider.search(input)which already returnsplaylists[]in the result - Also call
provider.getUserPlaylists()if the provider supports it (logged-in state) - Client-side fuzzy match user playlists:
playlist.namecontains input (case-insensitive) - Merge results: public search results first (sorted by API relevance), then user matches
- Take the first playlist, load its songs, play
Key Decisions
- Why public search first? It's already sorted by relevance from the API. User playlists are a secondary source.
- Why client-side matching for user playlists? The
getUserPlaylists()API returns all user playlists without a search parameter, so we must filter locally. - Backward compatibility: Numeric IDs and URL parsing are unchanged.
Files Changed
src/bot/instance.ts: ModifycmdPlaylist()to add search fallbacksrc/bot/commands.ts: Update help text
Feature 3: FM Bug Fix
Root Cause Analysis
The !fm bug manifests as: audio stops after a few songs, but !now shows a playing song and the song name keeps changing.
getPersonalFm() returns only ~3 songs per API call. After those are consumed:
- In
Sequentialmode:queue.next()returns null →player.stop()is called → playback stops entirely. This does NOT match "歌还在轮播" (songs still rotating). - In
Loopmode (if user changed mode): the same 3 songs loop, but URLs may expire, causing silent playback failures.
The most likely scenario for "no audio but status shows playing + song names changing":
- FM songs have URLs that resolve but don't produce playable audio (copyright/region restrictions)
- ffmpeg spawns, connects to the URL, gets an HTTP error or silent stream
- ffmpeg exits quickly (clean exit or error)
- The frame loop detects ffmpeg gone + buffer empty → emits
trackEnd playNext()advances to the next song- This rapid cycle (spawn → fail → advance) makes it appear that songs are "playing and rotating" but with no audio
- After 3 consecutive ffmpeg spawn failures,
consecutiveFailures >= MAX_CONSECUTIVE_FAILURES→ player refuses to spawn new ffmpeg processes - After that,
resolveAndPlaystill sets state viaplayer.play()which immediately emits "error" →playNext()skips to next → cycle continues with no ffmpeg at all
Fix Strategy
Fix 1 — FM auto-refill (primary fix):
- In
cmdFm(), set queue mode toRandomLoopso the queue never "runs out" - Add a
refillFm()method that fetches more FM songs and appends to queue - Hook into the
trackEndflow: when queue has ≤ 2 songs remaining and we're in FM mode, trigger a refill - Track FM state with a boolean flag
isFmModeon the instance
Fix 2 — Reset consecutive failures on successful playback (safety net):
- Reset
consecutiveFailureswhen a track plays successfully for at least N frames (e.g., 50 frames = 1 second) - This prevents transient URL failures from accumulating toward the hard limit
Fix 3 — FM refill before queue exhaustion:
- After
playNext()successfully starts a song, check ifisFmModeandqueue.size() - currentIndex <= 2 - If so, fire an async refill (don't block playback)
Files Changed
src/bot/instance.ts: ModifycmdFm(), addrefillFm(), add FM state tracking, modifyplayNext()to check for FM refillsrc/audio/player.ts: AddframesPlayedthreshold check to resetconsecutiveFailures
Implementation Order
- FM bug fix first — it's a bug fix affecting current users
- Playlist fuzzy search — small change, quick win
- Artist loop — new feature, depends on queue/player being stable
Testing
- FM: Verify songs keep playing beyond the initial 3-song batch, verify auto-refill works
- Playlist: Test with numeric ID (backward compat), test with playlist name (fuzzy search)
- Artist: Test with known artist names, test edge case (no results), test with platform flags