fix: dangling audio elements when user ends the call#161
Open
adrivelasco wants to merge 1 commit into
Open
Conversation
The SDK creates an <audio data-participant-id="..."> element per remote participant via buildAudioPlayer and only removes it when Daily's participant-left event fires (destroyAudioPlayer). When the user ends the call via vapi.stop() -> call.destroy(), Daily tears down the local client before the server delivers participant-left for the bot, so destroyAudioPlayer never runs for it. The element stays in the DOM with a dead MediaStream attached on srcObject, and a new one is appended on the next call. Safari degrades audibly once enough accumulate. Sweeps every audio[data-participant-id] element on teardown and explicitly releases the MediaStream reference (srcObject = null) so the playback graph is freed immediately instead of waiting on GC. Runs in both cleanup() and stop() so it covers the bot-ends-call path (left-meeting -> cleanup) and the user-ends-call path (stop).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
I've been experiencing issues in my app where after having many Vapi-powered conversations in a row, the bot goes to silence. I was able to consistently reproduce this on Safari only.
Root cause: The SDK creates an
<audio data-participant-id="...">element per remote participant viabuildAudioPlayerand only removes it when Daily'sparticipant-leftevent fires (destroyAudioPlayer).When the user ends the call via
vapi.stop()→call.destroy(), Daily tears down the local client before the server deliversparticipant-leftfor the bot, sodestroyAudioPlayernever runs for it. The element stays in the DOM with a deadMediaStreamattached onsrcObject, and a new one is appended on the next call. Safari degrades audibly once enough accumulate.Reproduce steps
vapi.start(...).vapi.stop()). Do not let the bot end it.<audio data-participant-id="...">element remains in<body>with a deadMediaStreamonsrcObject.Fix
teardownAudioPlayer(player)that explicitly nullssrcObject(releasing theMediaStreamreference so the playback graph is freed immediately instead of waiting on GC) and removes the element from the DOM.destroyAllAudioPlayers()that sweeps everyaudio[data-participant-id]element through the same teardown.cleanup()(covers the bot-ends-call path vialeft-meeting→cleanup) andstop()(covers the user-ends-call path).destroyAudioPlayer(participantId)now routes throughteardownAudioPlayertoo, so cleanup behavior is identical across all three call sites.The sweep is idempotent — if
participant-leftalready removed an element, the selector finds nothing.