Follow-up to the auto-pause fix: resume never fired when someone came back.
Root cause (verified live against a TS3 server): the full-client library's
command/response channel is dead whenever >=2 clients are connected anywhere on
the server — clientlist, channellist and channelclientlist ALL time out
(confirmed even with the two clients in different channels). So the moment a
listener returns is exactly the moment occupancy can no longer be queried, and
the query-based refreshOccupancy() can never observe the return -> no resume.
Event channelID is also unusable (library reads notify `cid` but enter-view
carries `ctid`, so it's always 0), so per-channel membership can't be derived
from events either.
Fix (minimal, asymmetric): keep PAUSE on the authoritative clientlist path
(reliable precisely because it only succeeds when the bot is alone on the
server — the only state pause should fire), and arm RESUME directly from the
clientEnter push event. Because the bot only auto-pauses while alone, the sole
way occupancy can return while autoPaused is set is a fresh connection, which
arrives reliably as clientEnter. New pure predicate shouldResumeOnReturn() +
_resumeIfReturning() resume iff autoPaused && paused; the resume branch routes
through handleOccupancy(1) and NEVER pauses (userCount>0), so a spurious enter
can only harmlessly resume. The bot's own enter at connect is a no-op
(autoPaused is already false).
This deliberately does NOT adopt a full event-tracked peer set: events don't
reliably seed clients already present when the bot joins, so a count-from-events
==0 would reintroduce the false-pause bug we just fixed, and reconcile can't
heal it (clientlist only works when alone). Pause must trust only the
authoritative query; resume can trust the event.
Net semantics: pause when the server is empty (bot alone), resume when someone
connects. Channel granularity is impossible with this library. UI copy updated
to say "服务器" instead of "频道", and the Settings toggle default corrected to
false to match the backend default. cmdVote intentionally left as-is.
Verified live: auto-paused bot + a real client connecting -> resume fires with
no clientlist call in the path; bot's own enter and not-auto-paused enters do
not resume. 311 unit tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auto-pause within the first seconds of playback (and re-pause after a manual
play) whenever a listener is actually in the channel.
Root cause (confirmed live against a TS3 server): the full-client
`clientlist -uid -away -voice -groups` command TIMES OUT when other clients are
present in the bot's channel. `getClientsInChannel()` catches the error and
returns `[]`, so the occupancy callers computed `userCount = [].length - 1 = -1`,
which `decideOccupancyAction` reads as `-1 <= 0` → "channel empty" → pause. With
the bot alone, clientlist succeeds (returns just the bot), so the bug only
surfaced when someone was listening — exactly the report.
Fix: a connected bot is always a member of its own channel, so a valid query
returns >= 1 (itself). A length of 0 therefore means the query FAILED, not that
the channel is empty. New pure helper `occupancyFromClientList()` maps a
0-length result to `null` ("occupancy unknown"); `refreshOccupancy()` and the
30s idle poller skip the auto-pause / idle-disconnect decision when the count is
unknown instead of mis-reading it as empty. This also removes a latent
false-positive idle-disconnect on the same failed query.
Also default `autoPauseOnEmpty` to OFF (occupancy detection is unreliable on
some servers); users can opt in from Settings.
Verified live with two clients in one channel: clientlist returns 0 → helper
returns null → no false pause (control: bot alone returns 1 → 0 others, normal).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>