fix(YouTube): destroy player on unmount to fix memory leak in v2 - #2048
Open
jv8-alt wants to merge 1 commit into
Open
fix(YouTube): destroy player on unmount to fix memory leak in v2#2048jv8-alt wants to merge 1 commit into
jv8-alt wants to merge 1 commit into
Conversation
The YouTube iframe API keeps an internal reference to every player it creates, so removing the container from the DOM does not release the iframe. `stop()` only calls `stopVideo`, which halts playback but leaves the player registered, so unmounting a YouTube player leaves a detached HTMLIFrameElement retained by `window.YT` along with everything its event handlers close over. Add `componentWillUnmount` to call `player.destroy()`, which removes the iframe and deregisters it from the API. The call is guarded and wrapped in try/catch because `destroy()` throws when the iframe has already been removed, and the player may not exist yet if the component unmounts before the SDK resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JM3M1hmNuwrvKiEcvpHPWV
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.
Adds the missing
componentWillUnmountsoplayer.destroy()runs when a YouTube player unmounts.The bug
YouTubehas no unmount hook.Player.jscallsstop()on unmount, but that maps tostopVideo:stopVideohalts playback without deregistering the player, so every mount/unmount cycle leaks one — a detached<iframe>retained bywindow.YT, along with the player's event handlers and, throughthis.props, the surrounding component tree.The API keys every player by element id and clears it only in
destroy():this.gis the iframe (n.getIframe=function(){return this.g}). The docs describestopVideo()as stopping playback while keeping the player interface intact, anddestroy()as the call that "Removes the<iframe>containing the player."Found via heap snapshots on a production app:
Detached HTMLIFrameElementnodes accumulating across route changes.Previously reported, closed without a fix: #531 (
"The YouTube player is not attached to the DOM"on unmount; asks for a destroy hook) and #377 (players not cleaned up when loaded in quick succession).Full
destroy(), verbatim fromwww-widgetapi.js(buildea6f527e)Zis a second registry, used by a globalmessagelistener to route postMessages to players. It is cleared in the same method and nowhere else.The fix
Both guards matter: the component can unmount before
getSDK()resolves, sothis.playermay never exist; anddestroy()throws when the iframe is already gone — the exact error in #531, so the catch fixes that console error too.this.player = nulldrops the closure chain holdingthis.props.Testing
Three cases in
test/players/YouTube.jsusing the existing zora/sinon/react-test-rendererharness:destroy()called once and reference nulled; unmount before the player exists doesn't throw; a throwingdestroy()doesn't break unmount.Reverting
src/players/YouTube.jsalone makes them fail, so they aren't passing vacuously:Full suite green and
npm run lintclean on Node 18 (matching CI).Note on v3
v3 has the same gap — it delegates to
youtube-video-element, which defines nodisconnectedCallback. Opened separately as muxinc/media-elements#256.