Files
meet-hub/adilo-player-impl-plan.md
dwindown 60baf32f73 Display bootcamp lesson chapters on Product Detail page as marketing content
This commit implements displaying lesson chapters/timeline as marketing content
on the Product Detail page for bootcamp products, helping potential buyers
understand the detailed breakdown of what they'll learn.

## Changes

### Product Detail Page (src/pages/ProductDetail.tsx)
- Updated Lesson interface to include optional chapters property
- Modified fetchCurriculum to fetch chapters along with lessons
- Enhanced renderCurriculumPreview to display chapters as nested content under lessons
- Chapters shown with timestamps and titles, clickable to navigate to bootcamp access page
- Visual hierarchy: Module → Lesson → Chapters with proper indentation and styling

### Review System Fixes
- Fixed review prompt re-appearing after submission (before admin approval)
- Added hasSubmittedReview check to prevent showing prompt when review exists
- Fixed edit review functionality to pre-populate form with existing data
- ReviewModal now handles both INSERT (new) and UPDATE (edit) operations
- Edit resets is_approved to false requiring re-approval

### Video Player Enhancements
- Implemented Adilo/Video.js integration for M3U8/HLS playback
- Added video progress tracking with refs pattern for reliability
- Implemented chapter navigation for both Adilo and YouTube players
- Added keyboard shortcuts (Space, Arrows, F, M, J, L)
- Resume prompt for returning users with saved progress

### Database Migrations
- Added Adilo video support fields (m3u8_url, mp4_url, video_host)
- Created video_progress table for tracking user watch progress
- Fixed consulting slots user_id foreign key
- Added chapters support to products and bootcamp_lessons tables

### Documentation
- Added Adilo implementation plan and quick reference docs
- Cleaned up transcript analysis files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 23:54:32 +07:00

12 KiB

Adilo Custom Video Player with Chapter System - Implementation Plan

Project Overview

Build a React video player component that uses Adilo's M3U8 streaming URL with custom chapter navigation system for LearnHub LMS.


Architecture

Components Structure

VideoLesson/
├── AdiloVideoPlayer.jsx (Main player component)
├── ChapterNavigation.jsx (Chapter sidebar/timeline)
├── VideoControls.jsx (Custom controls - optional)
└── hooks/
    ├── useAdiloPlayer.js (HLS player logic)
    └── useChapterTracking.js (Chapter progress tracking)

Data Flow

Adilo M3U8 URL 
    ↓
HLS.js (streaming)
    ↓
HTML5 <video> element
    ↓
currentTime tracking
    ↓
Chapter UI sync
    ↓
Supabase (save progress)

Step-by-Step Implementation

PHASE 1: Dependencies Setup

Install required packages:

npm install hls.js
npm install @supabase/supabase-js  # For progress tracking

No additional UI library needed - use native HTML5 video + your own CSS for chapters.


PHASE 2: Core Hook - useAdiloPlayer

File: hooks/useAdiloPlayer.js

Purpose: Handle HLS streaming with HLS.js library

Responsibilities:

  • Initialize HLS instance
  • Load M3U8 URL
  • Handle browser compatibility (Safari native HLS vs HLS.js)
  • Expose video element ref for external control
  • Emit events (play, pause, ended, timeupdate)

Function signature:

const {
  videoRef,
  isReady,
  isPlaying,
  currentTime,
  duration,
  error
} = useAdiloPlayer({
  m3u8Url: string,
  autoplay: boolean,
  onTimeUpdate: (time: number) => void,
  onEnded: () => void,
  onError: (error) => void
})

Key features:

  • Auto-dispose HLS instance on unmount
  • Handle loading states
  • Error boundary for failed streams
  • Track play/pause states

PHASE 3: Core Hook - useChapterTracking

File: hooks/useChapterTracking.js

Purpose: Track which chapter user is currently viewing

Responsibilities:

  • Determine active chapter from currentTime
  • Calculate chapter progress percentage
  • Detect chapter transitions
  • Export chapter completion data

Function signature:

const {
  activeChapter,
  activeChapterId,
  chapterProgress, // 0-100%
  completedChapters,
  chapterTimeline // for progress bar
} = useChapterTracking({
  chapters: Chapter[],
  currentTime: number,
  onChapterChange: (chapter) => void
})

Chapter object structure:

{
  id: string,
  startTime: number,      // in seconds
  endTime: number,        // in seconds
  title: string,
  description?: string,   // optional
  thumbnail?: string      // optional
}

PHASE 4: Main Component - AdiloVideoPlayer

File: components/AdiloVideoPlayer.jsx

Purpose: Main video player component that combines HLS streaming + chapter tracking

Props:

{
  m3u8Url: string,           // From Adilo dashboard
  videoId: string,           // For database tracking
  chapters: Chapter[],       // Your chapter data
  autoplay: boolean,         // Default: false
  showChapters: boolean,     // Default: true
  onVideoComplete: (data) => void,  // Callback when video ends
  onChapterChange: (chapter) => void,
  onProgressUpdate: (progress) => void
}

Component structure:

<div className="adilo-player">
  {/* Video container */}
  <div className="video-container">
    <video
      ref={videoRef}
      controls
      controlsList="nodownload"
    />
    {/* Loading indicator */}
    {!isReady && <LoadingSpinner />}
  </div>

  {/* Chapter Navigation */}
  {showChapters && (
    <ChapterNavigation
      chapters={chapters}
      activeChapterId={activeChapterId}
      currentTime={currentTime}
      onChapterClick={jumpToChapter}
      completedChapters={completedChapters}
    />
  )}

  {/* Progress bar (optional) */}
  <ProgressBar
    chapters={chapters}
    currentTime={currentTime}
  />
</div>

Key methods:

  • jumpToChapter(startTime) - Seek to chapter
  • play() / pause() - Control playback
  • getCurrentProgress() - Get session progress

PHASE 5: Chapter Navigation Component

File: components/ChapterNavigation.jsx

Purpose: Display chapters as sidebar/timeline with click-to-jump

Layout options:

  1. Sidebar - Vertical list on side (desktop)
  2. Horizontal - Timeline below video (mobile)
  3. Collapsible - Toggle on mobile

Features:

  • Show current/upcoming chapters
  • Highlight active chapter
  • Show time remaining for current chapter
  • Progress indicators
  • Drag-to-seek on timeline (optional)

Chapter item structure:

<div className="chapter-item">
  <div className="chapter-time">{formatTime(startTime)}</div>
  <div className="chapter-title">{title}</div>
  <div className="chapter-progress">{progressBar}</div>
  <button onClick={() => jumpToChapter(startTime)}>
    Jump to Chapter
  </button>
</div>

PHASE 6: Supabase Integration (Optional)

File: services/progressService.js

Purpose: Save video progress to database

Database table structure:

CREATE TABLE video_progress (
  id uuid PRIMARY KEY,
  user_id uuid NOT NULL,
  video_id uuid NOT NULL,
  last_position int,           -- seconds
  completed_chapters text[],   -- array of chapter IDs
  watched_percentage int,      -- 0-100
  is_completed boolean,
  completed_at timestamp,
  created_at timestamp,
  updated_at timestamp,
  UNIQUE(user_id, video_id)
)

Functions to implement:

// Save current progress
saveProgress(userId, videoId, currentTime, completedChapters)

// Resume from last position
getLastPosition(userId, videoId)

// Mark video as complete
markVideoComplete(userId, videoId)

// Get completion analytics
getVideoAnalytics(userId, videoId)

PHASE 7: Styling

File: styles/AdiloVideoPlayer.module.css or your preferred CSS approach

Key styles needed:

/* Video container */
.video-container {
  position: relative;
  width: 100%;
  aspect-ratio: 16/9;
  background: #000;
}

video {
  width: 100%;
  height: 100%;
}

/* Chapter sidebar */
.chapters-sidebar {
  background: #f5f5f5;
  padding: 16px;
  max-height: 400px;
  overflow-y: auto;
}

.chapter-item {
  padding: 12px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.2s;
}

.chapter-item.active {
  background: #e0f2fe;
  border-left: 4px solid #0ea5e9;
}

.chapter-time {
  font-weight: 600;
  color: #333;
}

.chapter-title {
  font-size: 14px;
  color: #666;
  margin-top: 4px;
}

/* Progress bar */
.progress-bar {
  display: flex;
  gap: 2px;
  height: 4px;
  background: #e5e5e5;
  border-radius: 2px;
}

.progress-segment {
  background: #0ea5e9;
  flex: 1;
  border-radius: 1px;
}

.progress-segment.completed {
  background: #10b981;
}

/* Responsive */
@media (max-width: 768px) {
  .chapters-sidebar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }
}

Implementation Checklist

Setup Phase

  • Install dependencies (hls.js, @supabase/supabase-js)
  • Set up folder structure
  • Configure Supabase client (if using progress tracking)

Core Development

  • Implement useAdiloPlayer hook
  • Implement useChapterTracking hook
  • Create AdiloVideoPlayer component
  • Create ChapterNavigation component
  • Add styling/CSS

Integration

  • Implement Supabase progress service
  • Add error handling & loading states
  • Add accessibility features (ARIA labels, keyboard navigation)
  • Test HLS streaming on different browsers

Testing

  • Test video playback (Chrome, Firefox, Safari, mobile)
  • Test chapter navigation
  • Test progress saving to Supabase
  • Test responsive design
  • Test error scenarios (bad M3U8 URL, network issues)

Optional Enhancements

  • Add playback speed control
  • Add quality selector (if HLS variants available)
  • Add full-screen mode
  • Add picture-in-picture
  • Add watch history
  • Add completion badges

Code Example Template

Main Usage in LearnHub

import AdiloVideoPlayer from '@/components/AdiloVideoPlayer';

function LessonPage({ lessonId }) {
  const [lesson, setLesson] = useState(null);
  const [userProgress, setUserProgress] = useState(null);
  const user = useAuth().user;

  useEffect(() => {
    // Fetch lesson with chapters
    fetchLessonData(lessonId).then(setLesson);
    
    // Get user's last progress
    getLastPosition(user.id, lessonId).then(setUserProgress);
  }, [lessonId]);

  const handleChapterChange = (chapter) => {
    console.log(`Chapter changed: ${chapter.title}`);
  };

  const handleVideoComplete = (data) => {
    // Mark as complete in Supabase
    markVideoComplete(user.id, lessonId);
    
    // Show completion message
    toast.success('Lesson completed! 🎉');
  };

  const handleProgressUpdate = (progress) => {
    // Save progress every 10 seconds
    if (progress.currentTime % 10 === 0) {
      saveProgress(user.id, lessonId, progress.currentTime, progress.completedChapters);
    }
  };

  if (!lesson) return <LoadingPage />;

  return (
    <div className="lesson-container">
      <h1>{lesson.title}</h1>
      
      <AdiloVideoPlayer
        m3u8Url={lesson.m3u8Url}
        videoId={lesson.id}
        chapters={lesson.chapters}
        autoplay={false}
        showChapters={true}
        onChapterChange={handleChapterChange}
        onVideoComplete={handleVideoComplete}
        onProgressUpdate={handleProgressUpdate}
      />

      <div className="lesson-content">
        <h2>Lesson Details</h2>
        <p>{lesson.description}</p>
      </div>
    </div>
  );
}

export default LessonPage;

Important Notes

Video URL Storage

Store the M3U8 URL in your Supabase videos or lessons table:

CREATE TABLE lessons (
  id uuid PRIMARY KEY,
  title text,
  description text,
  m3u8_url text,  -- Store the Adilo M3U8 URL here
  chapters jsonb, -- Store chapters as JSON array
  created_at timestamp
)

Security Considerations

  • Don't expose M3U8 URL in frontend code - fetch from backend
  • Validate M3U8 URLs - only allow Adilo domains
  • Use CORS headers - ensure Adilo allows cross-origin requests
  • Log access - track who watches which videos

Browser Compatibility

  • Chrome/Edge: HLS.js library
  • Firefox: HLS.js library
  • Safari: Native HLS support (no library needed)
  • Mobile browsers: Auto-detects capability

Performance Tips

  • 🚀 Lazy load chapter data
  • 🚀 Debounce progress updates
  • 🚀 Memoize chapter calculations
  • 🚀 Use video preload="metadata"

Common Pitfalls to Avoid

  1. Don't forget to dispose HLS instance - Memory leak

    • Do: Clean up in useEffect return
  2. Don't update state on every timeupdate - Performance issue

    • Do: Debounce or throttle updates
  3. Don't hardcode M3U8 URLs in component - Security issue

    • Do: Fetch from backend API
  4. Don't assume HLS.js works everywhere - Safari native support exists

    • Do: Check Hls.isSupported() and fallback
  5. Don't forget CORS headers - Cross-origin requests fail

    • Do: Verify Adilo allows your domain

Testing Commands

# Install dev dependencies
npm install --save-dev @testing-library/react @testing-library/jest-dom

# Run tests
npm test

# Build for production
npm run build

# Check bundle size
npm run analyze

Next Steps

  1. Get M3U8 URL from Adilo dashboard (You found it!)
  2. Store URL in your Supabase lessons table
  3. Create the hooks (start with useAdiloPlayer)
  4. Build the component (AdiloVideoPlayer)
  5. Integrate chapters (ChapterNavigation)
  6. Add progress tracking (Supabase integration)
  7. Style and polish (CSS/responsive design)
  8. Test thoroughly (all browsers, mobile, edge cases)

Files to Create

src/
├── components/
│   ├── AdiloVideoPlayer.jsx
│   ├── ChapterNavigation.jsx
│   └── ProgressBar.jsx
├── hooks/
│   ├── useAdiloPlayer.js
│   └── useChapterTracking.js
├── services/
│   ├── progressService.js
│   └── adiloService.js
├── styles/
│   └── AdiloVideoPlayer.module.css
└── types/
    └── video.types.js

Ready to implement? Start with Phase 1 & 2 (setup + useAdiloPlayer hook). Let me know if you need help with any specific phase!