Skip to content

fix(voice): remove player from datastore during buffering - #11598

Open
Umit-Ulusoy wants to merge 3 commits into
discordjs:mainfrom
Umit-Ulusoy:fix/audio-player-buffer-hang
Open

fix(voice): remove player from datastore during buffering#11598
Umit-Ulusoy wants to merge 3 commits into
discordjs:mainfrom
Umit-Ulusoy:fix/audio-player-buffer-hang

Conversation

@Umit-Ulusoy

@Umit-Ulusoy Umit-Ulusoy commented Aug 8, 2026

Copy link
Copy Markdown

This fix updates the AudioPlayer stop method to allow direct transition to the Idle state when the player is currently in the Buffering status.

Previously, calling stop() during Buffering would hang or fail to clear the player due to silence padding constraints, leaving it lingering in the DataStore.

This ensures the player is properly cleaned up and removed from the store.

Fixes #11039

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
discord-js Skipped Skipped Aug 12, 2026 6:46pm
discord-js-guide Skipped Skipped Aug 12, 2026 6:46pm

Request Review

@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 8, 2026 22:51 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 8, 2026 22:51 Inactive
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

AudioPlayer.stop() now immediately cleans up players in the Buffering state. A test verifies the player enters Idle and is removed from the DataStore.

Changes

AudioPlayer cleanup

Layer / File(s) Summary
Buffering stop cleanup
packages/voice/src/audio/AudioPlayer.ts, packages/voice/__tests__/AudioPlayer.test.ts
stop() transitions buffering players to Idle without waiting for silence padding. The test verifies one deleteAudioPlayer call.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code directly addresses issue #11039 by removing buffering players from the DataStore, with a regression test covering the cleanup.
Out of Scope Changes check ✅ Passed The changes are limited to AudioPlayer buffering cleanup and its regression test, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The description accurately explains the buffering cleanup fix and its DataStore impact.
Title check ✅ Passed The title clearly identifies the voice fix that removes a buffering player from the DataStore.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/voice/__tests__/AudioPlayer.test.ts`:
- Line 430: Update the test around deleteAudioPlayer to assert that the
DataStore no longer contains player after deletion, while keeping the real
deletion path enabled and retaining the existing deleteAudioPlayer call-count
assertion.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2d62ca8d-f48d-4abc-bab8-cd015c647394

📥 Commits

Reviewing files that changed from the base of the PR and between b1a7704 and e63d296.

📒 Files selected for processing (2)
  • packages/voice/__tests__/AudioPlayer.test.ts
  • packages/voice/src/audio/AudioPlayer.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-05-18T13:40:11.014Z
Learnt from: almeidx
Repo: discordjs/discord.js PR: 11530
File: packages/core/src/api/user.ts:190-190
Timestamp: 2026-05-18T13:40:11.014Z
Learning: When reviewing JSDoc comments in the discord.js monorepo (e.g., discord.js and related packages), treat `see` links to Discord developer docs as consistent with the new documentation domain. Prefer `https://docs.discord.com/developers/...` over `https://discord.com/developers...`, and do not flag `see` links that already use `https://docs.discord.com/developers/...` as inconsistent.

Applied to files:

  • packages/voice/__tests__/AudioPlayer.test.ts
  • packages/voice/src/audio/AudioPlayer.ts
📚 Learning: 2026-05-18T13:40:11.014Z
Learnt from: almeidx
Repo: discordjs/discord.js PR: 11530
File: packages/core/src/api/user.ts:190-190
Timestamp: 2026-05-18T13:40:11.014Z
Learning: When reviewing discord.js (and related) source files, JSDoc `see` links that point to Discord’s documentation on `https://docs.discord.com/developers/...` are correct and should not be flagged as inconsistent. For new/updated links going forward, prefer `https://docs.discord.com/developers` over the legacy `https://discord.com/developers/docs` domain.

Applied to files:

  • packages/voice/__tests__/AudioPlayer.test.ts
  • packages/voice/src/audio/AudioPlayer.ts
🔇 Additional comments (1)
packages/voice/src/audio/AudioPlayer.ts (1)

494-495: LGTM!

player.stop();

expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the DataStore side effect.

Line [430] verifies only that deleteAudioPlayer was called. It does not verify that player was removed from the DataStore; a failed or early-returning deletion would still pass. Assert that the actual store no longer contains player, while keeping the real deletion path enabled.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/voice/__tests__/AudioPlayer.test.ts` at line 430, Update the test
around deleteAudioPlayer to assert that the DataStore no longer contains player
after deletion, while keeping the real deletion path enabled and retaining the
existing deleteAudioPlayer call-count assertion.

This fix updates the AudioPlayer stop method to allow direct transition to the Idle state when the player is currently in the Buffering status.

Previously, calling stop() during Buffering would hang or fail to clear the player due to silence padding constraints, leaving it lingering in the DataStore.

This ensures the player is properly cleaned up and removed from the store.

Fixes discordjs#11039
@Umit-Ulusoy
Umit-Ulusoy force-pushed the fix/audio-player-buffer-hang branch from e63d296 to 710869a Compare August 9, 2026 00:52
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 9, 2026 00:52 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 9, 2026 00:52 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 9, 2026 07:53 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js August 9, 2026 07:53 Inactive
@Qjuh

Qjuh commented Aug 12, 2026

Copy link
Copy Markdown
Member

(How) did you verify that the Player actually hangs/stays in DataStore before this fix? Because looking at the code the AudioResource should keep on playing 5 (by default) silence packets before the Player going to Idle state (and removing the Player from the DataStore in the process). Independent of it the resource had started or is still buffering. If users wanted to force the immediate stop then that parameter already exists.

@vercel
vercel Bot temporarily deployed to Preview – discord-js August 12, 2026 18:46 Inactive
@vercel
vercel Bot temporarily deployed to Preview – discord-js-guide August 12, 2026 18:46 Inactive
@Umit-Ulusoy

Copy link
Copy Markdown
Author

Hello there @Qjuh, thanks for taking a closer look.
Good question.

The silence-padding mechanism (silenceRemaining being decremented, _preparePacket being called, and the eventual transition to Idle) is entirely handled inside _stepPrepare().
That function starts with this guard:

private _stepPrepare() {
const state = this._state;
// Guard against the Idle state
if (state.status === AudioPlayerStatus.Idle || state.status === AudioPlayerStatus.Buffering) return;
...

So when the player is in Buffering, _stepPrepare() returns immediately and never processes the silence countdown.
In the original stop():

} else if (this.state.resource.silenceRemaining === -1) {
this.state.resource.silenceRemaining = this.state.resource.silencePaddingFrames;
}

this line does set resource.silenceRemaining, but nothing ever consumes it while the player stays in Buffering.
If the underlying stream never becomes readable (e.g. the scenario in the linked issue, where a voice connection is destroyed before the stream starts emitting data), the player has no path out of Buffering, it never reaches Idle, so deleteAudioPlayer is never called, and the player lingers in the global timer's list forever.

To confirm this isn't just a theoretical reading of the code, I ran the existing test suite against the unpatched stop():

FAIL should remove AudioPlayer from DataStore when stopped during Buffering state
AssertionError: expected 'buffering' to deeply equal 'idle'
Expected: "idle"
Received: "buffering"

and against the patched version, the same test (plus a second regression test I added for the "stream never becomes readable" case) passes cleanly, with no other test files affected.

On the force parameter point, I agree that's the mechanism for an intentional immediate stop.
The issue here is different, it's not that stop() is slow without force, it's that it can silently never complete at all when called during Buffering.
Silence padding doesn't really have meaning yet at that point anyway, since no packets have been played, so treating Buffering like force for the purposes of this transition seemed like the correct semantics rather than a workaround.

@Qjuh

Qjuh commented Aug 12, 2026

Copy link
Copy Markdown
Member

Okay, I see your point now. Your test however doesn't actually test for what it should. It would also fail if the 5 silence packets were sucessfully sent and then end the resource and thus the player, because your 2 wait() calls don't take long enough for five cycles of the audio loop (which takes ~100 ms). But I verified that the player does indeed keep staying stuck like you said even if waiting longer and thank you for pointing out the reason being the guard against Idle/Buffering state in _stepPrepare().

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 32.07%. Comparing base (4b3f1c1) to head (0deb3df).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #11598   +/-   ##
=======================================
  Coverage   32.07%   32.07%           
=======================================
  Files         390      390           
  Lines       14100    14100           
  Branches     1113     1113           
=======================================
  Hits         4522     4522           
  Misses       9440     9440           
  Partials      138      138           
Flag Coverage Δ
voice 55.49% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Umit-Ulusoy

Copy link
Copy Markdown
Author

Thanks for verifying it and for the feedback, @Qjuh!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

AudioPlayer does not get removed from DataStore in some situations

2 participants