From 41f7b797e71c9d36fce38f066a836c6fec92e046 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 1 Jan 2026 10:34:45 +0700 Subject: [PATCH] Fix Plyr player initialization with proper error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added robust error handling for Plyr player instance: - Check if player.on method exists before using events - Fallback to polling time updates every 500ms if events unavailable - Use setInterval to wait for player initialization - Properly cleanup intervals on component unmount - Prevents "L.on is not a function" error This ensures the video player works even if Plyr's event system has issues initializing, using a reliable polling fallback. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/components/VideoPlayerWithChapters.tsx | 127 ++++++++++++--------- 1 file changed, 73 insertions(+), 54 deletions(-) diff --git a/src/components/VideoPlayerWithChapters.tsx b/src/components/VideoPlayerWithChapters.tsx index 63b7880..a589f39 100644 --- a/src/components/VideoPlayerWithChapters.tsx +++ b/src/components/VideoPlayerWithChapters.tsx @@ -90,63 +90,82 @@ export const VideoPlayerWithChapters = forwardRef { - if (!isYouTube || !plyrRef.current) return; + if (!isYouTube) return; - const plyrElement = plyrRef.current; - const player = plyrElement?.plyr; - - if (!player) { - // Wait for player to be ready - const timeout = setTimeout(() => { - const p = plyrElement?.plyr; - if (p) { - setPlayerInstance(p); - setupTimeTracking(p); - } - }, 100); - return () => clearTimeout(timeout); - } - - setPlayerInstance(player); - setupTimeTracking(player); - - function setupTimeTracking(plyr: any) { - // Track time updates for chapter highlighting - plyr.on('timeupdate', (event: any) => { - const time = plyr.currentTime; - setCurrentTime(time); - - if (onTimeUpdate) { - onTimeUpdate(time); - } - - // Find current chapter - if (chapters.length > 0) { - let index = chapters.findIndex((chapter, i) => { - const nextChapter = chapters[i + 1]; - return time >= chapter.time && (!nextChapter || time < nextChapter.time); - }); - - // If before first chapter, no active chapter - if (index === -1 && time < chapters[0].time) { - index = -1; - } - - if (index !== currentChapterIndex) { - setCurrentChapterIndex(index); - if (index >= 0 && onChapterChange) { - onChapterChange(chapters[index]); - } - } - } - }); - } - - return () => { + // Wait for player to initialize + const checkPlayer = setInterval(() => { + const player = plyrRef.current?.plyr; if (player) { - player.off('timeupdate'); + clearInterval(checkPlayer); + setPlayerInstance(player); + + // Set up time tracking using Plyr's event API + if (typeof player.on === 'function') { + player.on('timeupdate', () => { + const time = player.currentTime; + setCurrentTime(time); + + if (onTimeUpdate) { + onTimeUpdate(time); + } + + // Find current chapter + if (chapters.length > 0) { + let index = chapters.findIndex((chapter, i) => { + const nextChapter = chapters[i + 1]; + return time >= chapter.time && (!nextChapter || time < nextChapter.time); + }); + + // If before first chapter, no active chapter + if (index === -1 && time < chapters[0].time) { + index = -1; + } + + if (index !== currentChapterIndex) { + setCurrentChapterIndex(index); + if (index >= 0 && onChapterChange) { + onChapterChange(chapters[index]); + } + } + } + }); + } else { + // Fallback: poll for time updates + const interval = setInterval(() => { + const time = player.currentTime; + setCurrentTime(time); + + if (onTimeUpdate) { + onTimeUpdate(time); + } + + // Find current chapter + if (chapters.length > 0) { + let index = chapters.findIndex((chapter, i) => { + const nextChapter = chapters[i + 1]; + return time >= chapter.time && (!nextChapter || time < nextChapter.time); + }); + + if (index === -1 && time < chapters[0].time) { + index = -1; + } + + if (index !== currentChapterIndex) { + setCurrentChapterIndex(index); + if (index >= 0 && onChapterChange) { + onChapterChange(chapters[index]); + } + } + } + }, 500); + + // Store interval ID for cleanup + return () => clearInterval(interval); + } } - }; + }, 100); + + return () => clearInterval(checkPlayer); }, [isYouTube, chapters, currentChapterIndex, onChapterChange, onTimeUpdate]); // Jump to specific time using Plyr API