From 8961912530d06dde715250a35b5edc4577e54818 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Tue, 9 Jun 2026 22:42:26 +0100 Subject: [PATCH] fix: improve plyr render for youtube video --- .../custom/media-player/media-player.svelte | 20 +++++++ .../media-player/players/plyr-player.svelte | 58 ++++++++++++++----- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/custom/media-player/media-player.svelte b/packages/ui/src/custom/media-player/media-player.svelte index 62496d7b..d7d099fb 100644 --- a/packages/ui/src/custom/media-player/media-player.svelte +++ b/packages/ui/src/custom/media-player/media-player.svelte @@ -2,6 +2,7 @@ import type { MediaPlayerOptions, VideoSource } from './types'; import PlyrPlayer from './players/plyr-player.svelte'; import MusePlayer from './players/muse-player.svelte'; + import { formatYoutubeEmbedUrl } from './utils'; interface Props { source: VideoSource; @@ -26,11 +27,13 @@ const tracks = $derived(source.tracks ?? []); const isMuse = $derived.by(() => source.type === 'muse' && source.metadata?.svid); const isGoogleDrive = $derived(source.type === 'google_drive'); + const isYouTube = $derived(source.type === 'youtube'); const poster = $derived(source.type === 'upload' ? source.metadata?.thumbnailUrl : undefined); const iframeTitle = $derived(source.metadata?.title?.trim() || 'Video'); const iframeMaxHeight = $derived(options.maxHeight ?? '400px'); const iframeWidth = $derived(options.width ?? '100%'); + const youtubeEmbedUrl = $derived(isYouTube ? formatYoutubeEmbedUrl(source.url) : '');
@@ -51,6 +54,23 @@ allowfullscreen >
+ {:else if isYouTube} + +
+ +
{:else} {/if} diff --git a/packages/ui/src/custom/media-player/players/plyr-player.svelte b/packages/ui/src/custom/media-player/players/plyr-player.svelte index adb3ce8d..bf93725d 100644 --- a/packages/ui/src/custom/media-player/players/plyr-player.svelte +++ b/packages/ui/src/custom/media-player/players/plyr-player.svelte @@ -42,6 +42,8 @@ let hlsPlyrGeneration = 0; let plyrConstructInFlight = false; let hasFiredFirstPlay = false; + let isMounted = false; + let youtubeInitGeneration = 0; interface HlsQualityConfig { default: number; @@ -193,7 +195,7 @@ try { // Recheck after the dynamic import resolves — the component may have // unmounted (or the videoElement bind nulled out) while we awaited. - if (!videoElement || playerInstance) return; + if (!isMounted || !videoElement || playerInstance) return; mediaErrorCleanup = attachMediaErrorHandler(videoElement); @@ -257,23 +259,43 @@ }); } + async function constructPlyrForYouTubeEmbed(): Promise { + if (!isMounted || !containerElement || !youtubeVideoId || playerInstance || plyrConstructInFlight) return; + + const initGeneration = youtubeInitGeneration; + plyrConstructInFlight = true; + + const PlyrModule = await import('plyr'); + const PlyrConstructor = PlyrModule.default; + const controls = showControls ? PLYR_DEFAULT_CONTROLS : []; + + try { + if (!isMounted || initGeneration !== youtubeInitGeneration || !containerElement || playerInstance) { + return; + } + + playerInstance = new PlyrConstructor(containerElement, { + controls, + autoplay, + ratio: '16:9', + iconUrl: '/plyr.svg', + iconPrefix: 'plyr' + }); + timeupdateCleanup = attachTimeUpdates(playerInstance); + firstPlayCleanup = attachFirstPlay(playerInstance); + plyrErrorCleanup = attachPlyrErrorHandler(playerInstance); + options.onPlayerReady?.(playerInstance); + } finally { + plyrConstructInFlight = false; + } + } + onMount(() => { + isMounted = true; + void (async () => { if (isYouTube && youtubeVideoId && containerElement) { - const PlyrModule = await import('plyr'); - const PlyrConstructor = PlyrModule.default; - const controls = showControls ? PLYR_DEFAULT_CONTROLS : []; - playerInstance = new PlyrConstructor(containerElement, { - controls, - autoplay, - ratio: '16:9', - iconUrl: '/plyr.svg', - iconPrefix: 'plyr' - }); - timeupdateCleanup = attachTimeUpdates(playerInstance); - firstPlayCleanup = attachFirstPlay(playerInstance); - plyrErrorCleanup = attachPlyrErrorHandler(playerInstance); - options.onPlayerReady?.(playerInstance); + await constructPlyrForYouTubeEmbed(); return; } @@ -448,6 +470,8 @@ }); onDestroy(() => { + isMounted = false; + youtubeInitGeneration += 1; hlsLoadGeneration += 1; hlsPlyrGeneration = 0; destroyHlsInstance(); @@ -463,6 +487,10 @@ } teardownPlyrInstance(); + + if (isYouTube && containerElement) { + containerElement.replaceChildren(); + } });