From f8a58109b4a4095d8b1350f3f9b51bb73510016f Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Fri, 18 Sep 2026 21:08:57 +0200 Subject: [PATCH] fix(viewer): destroy only the current hls/flv instance on player teardown (#3139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit switchUrl() re-invokes the customType handler, which registered one 'destroy' listener per created player. On teardown every stale closure fired — mpegts null-derefs on an already-destroyed instance, crashing the page after FLV URL refreshes. Register a single hook per media type (WeakMap-guarded) that destroys the currently assigned player inside try/catch. --- .../src/component/Viewers/Video/Artplayer.tsx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/Viewers/Video/Artplayer.tsx b/frontend/src/component/Viewers/Video/Artplayer.tsx index 47cf497c..0ccafd4d 100644 --- a/frontend/src/component/Viewers/Video/Artplayer.tsx +++ b/frontend/src/component/Viewers/Video/Artplayer.tsx @@ -82,7 +82,7 @@ const playM3u8 = hls.loadSource(url); hls.attachMedia(video); art.hls = hls; - art.on("destroy", () => hls.destroy()); + hookMediaDestroy(art, "hls"); } else if (video.canPlayType("application/vnd.apple.mpegurl")) { video.src = url; } else { @@ -106,12 +106,35 @@ const playFlv = (video: HTMLVideoElement, url: string, art: Artplayer) => { flv.attachMediaElement(video); flv.load(); art.flv = flv; - art.on("destroy", () => flv.destroy()); + hookMediaDestroy(art, "flv"); } else { art.notice.show = "Unsupported playback format: flv"; } }; +// Registers a single destroy hook per media type. switchUrl() re-invokes the +// customType handler and would otherwise stack one handler per player — +// destroying a stale, already-destroyed instance (mpegts null-derefs, #3139). +const hookedPlayers = new WeakMap>(); +const hookMediaDestroy = (art: Artplayer, key: "hls" | "flv") => { + let flags = hookedPlayers.get(art); + if (!flags) { + flags = new Set(); + hookedPlayers.set(art, flags); + } + if (flags.has(key)) { + return; + } + flags.add(key); + art.on("destroy", () => { + try { + art[key]?.destroy(); + } catch (e) { + console.warn(`Failed to destroy ${key} player:`, e); + } + }); +}; + export default function Player({ option, chapters,