# Code Templates - Copy & Paste Starting Points ## File 1: hooks/useAdiloPlayer.js ```javascript import { useRef, useEffect, useState, useCallback } from 'react'; import Hls from 'hls.js'; /** * Hook for managing Adilo video playback via HLS.js * Handles M3U8 URL streaming with browser compatibility */ export function useAdiloPlayer({ m3u8Url, autoplay = false, onTimeUpdate = () => {}, onEnded = () => {}, onError = () => {}, } = {}) { const videoRef = useRef(null); const hlsRef = useRef(null); const [isReady, setIsReady] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [error, setError] = useState(null); // Initialize HLS streaming useEffect(() => { const video = videoRef.current; if (!video || !m3u8Url) return; try { // Safari has native HLS support if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = m3u8Url; setIsReady(true); } // Other browsers use HLS.js else if (Hls.isSupported()) { const hls = new Hls({ autoStartLoad: true, startPosition: -1, }); hls.loadSource(m3u8Url); hls.attachMedia(video); hlsRef.current = hls; hls.on(Hls.Events.MANIFEST_PARSED, () => { setIsReady(true); if (autoplay) { video.play().catch(err => console.error('Autoplay failed:', err)); } }); hls.on(Hls.Events.ERROR, (event, data) => { console.error('HLS Error:', data); setError(data.message || 'HLS streaming error'); onError(data); }); } else { setError('HLS streaming not supported in this browser'); } } catch (err) { console.error('Video initialization error:', err); setError(err.message); onError(err); } // Cleanup return () => { if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } }; }, [m3u8Url, autoplay, onError]); // Track video events useEffect(() => { const video = videoRef.current; if (!video) return; const handleTimeUpdate = () => { setCurrentTime(video.currentTime); onTimeUpdate(video.currentTime); }; const handleLoadedMetadata = () => { setDuration(video.duration); }; const handlePlay = () => setIsPlaying(true); const handlePause = () => setIsPlaying(false); const handleEnded = () => { setIsPlaying(false); onEnded(); }; video.addEventListener('timeupdate', handleTimeUpdate); video.addEventListener('loadedmetadata', handleLoadedMetadata); video.addEventListener('play', handlePlay); video.addEventListener('pause', handlePause); video.addEventListener('ended', handleEnded); return () => { video.removeEventListener('timeupdate', handleTimeUpdate); video.removeEventListener('loadedmetadata', handleLoadedMetadata); video.removeEventListener('play', handlePlay); video.removeEventListener('pause', handlePause); video.removeEventListener('ended', handleEnded); }; }, [onTimeUpdate, onEnded]); // Control methods const play = useCallback(() => { videoRef.current?.play().catch(err => console.error('Play error:', err)); }, []); const pause = useCallback(() => { videoRef.current?.pause(); }, []); const seek = useCallback((time) => { if (videoRef.current) { videoRef.current.currentTime = time; } }, []); return { videoRef, isReady, isPlaying, currentTime, duration, error, play, pause, seek, }; } ``` --- ## File 2: hooks/useChapterTracking.js ```javascript import { useMemo, useEffect, useState, useCallback } from 'react'; /** * Hook for tracking which chapter is currently active * based on video's currentTime */ export function useChapterTracking({ chapters = [], currentTime = 0, onChapterChange = () => {}, } = {}) { const [activeChapterId, setActiveChapterId] = useState(null); const [completedChapters, setCompletedChapters] = useState([]); // Find active chapter from currentTime const activeChapter = useMemo(() => { return chapters.find( ch => currentTime >= ch.startTime && currentTime < ch.endTime ) || null; }, [chapters, currentTime]); // Detect chapter changes useEffect(() => { if (activeChapter?.id !== activeChapterId) { setActiveChapterId(activeChapter?.id || null); if (activeChapter) { onChapterChange(activeChapter); } } }, [activeChapter, activeChapterId, onChapterChange]); // Track completed chapters useEffect(() => { if (activeChapter?.id && !completedChapters.includes(activeChapter.id)) { // Mark chapter as visited (not necessarily completed) setCompletedChapters(prev => [...prev, activeChapter.id]); } }, [activeChapter?.id, completedChapters]); // Calculate current chapter progress const chapterProgress = useMemo(() => { if (!activeChapter) return 0; const chapterDuration = activeChapter.endTime - activeChapter.startTime; const timeInChapter = currentTime - activeChapter.startTime; return Math.round((timeInChapter / chapterDuration) * 100); }, [activeChapter, currentTime]); // Get overall video progress const overallProgress = useMemo(() => { if (!chapters.length) return 0; const lastChapter = chapters[chapters.length - 1]; return Math.round((currentTime / lastChapter.endTime) * 100); }, [chapters, currentTime]); return { activeChapter, activeChapterId, chapterProgress, // 0-100 within current chapter overallProgress, // 0-100 for entire video completedChapters, // Array of visited chapter IDs isVideoComplete: overallProgress >= 100, }; } ``` --- ## File 3: components/AdiloVideoPlayer.jsx ```javascript import React, { useState, useCallback } from 'react'; import { useAdiloPlayer } from '@/hooks/useAdiloPlayer'; import { useChapterTracking } from '@/hooks/useChapterTracking'; import ChapterNavigation from './ChapterNavigation'; import styles from './AdiloVideoPlayer.module.css'; /** * Main Adilo video player component with chapter support */ export default function AdiloVideoPlayer({ m3u8Url, videoId, chapters = [], autoplay = false, showChapters = true, onVideoComplete = () => {}, onChapterChange = () => {}, onProgressUpdate = () => {}, }) { const [lastSaveTime, setLastSaveTime] = useState(0); const { videoRef, isReady, isPlaying, currentTime, duration, error, play, pause, seek, } = useAdiloPlayer({ m3u8Url, autoplay, onTimeUpdate: handleTimeUpdate, onEnded: handleVideoEnded, onError: (err) => console.error('Player error:', err), }); const { activeChapter, activeChapterId, chapterProgress, overallProgress, completedChapters, isVideoComplete, } = useChapterTracking({ chapters, currentTime, onChapterChange, }); // Save progress periodically (every 5 seconds) function handleTimeUpdate(time) { const now = Date.now(); if (now - lastSaveTime > 5000) { onProgressUpdate({ videoId, currentTime: time, duration, progress: overallProgress, activeChapterId, completedChapters, }); setLastSaveTime(now); } } function handleVideoEnded() { onVideoComplete({ videoId, completedChapters, totalWatched: duration, }); } const handleChapterClick = useCallback((startTime) => { seek(startTime); play(); }, [seek, play]); return (
{/* Main Video Player */}