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>
373 lines
9.1 KiB
Markdown
373 lines
9.1 KiB
Markdown
# Adilo Video Player - Quick AI Agent Reference
|
|
|
|
## For Your Windsurf/IDE AI Agent
|
|
|
|
Copy this into your `.codebase` instructions or share with AI agent:
|
|
|
|
---
|
|
|
|
## Project: LearnHub - Adilo M3U8 Video Player with Custom Chapters
|
|
|
|
### Problem Statement
|
|
Build a React video player that:
|
|
- Streams video from Adilo using M3U8 (HLS) direct URL
|
|
- Displays custom chapter navigation
|
|
- Allows click-to-jump to chapters
|
|
- Tracks user progress
|
|
- Saves completion data to Supabase
|
|
|
|
### Tech Stack
|
|
- **React 18+** (Hooks, Context)
|
|
- **HLS.js** - for M3U8 streaming
|
|
- **Supabase** - for progress tracking
|
|
- **HTML5 Video API** - native controls
|
|
- **CSS Modules** - styling
|
|
|
|
---
|
|
|
|
## Quick Command Reference
|
|
|
|
### Install Dependencies
|
|
```bash
|
|
npm install hls.js @supabase/supabase-js
|
|
```
|
|
|
|
### Project Structure to Create
|
|
```
|
|
src/
|
|
├── components/
|
|
│ ├── AdiloVideoPlayer.jsx # Main component
|
|
│ ├── ChapterNavigation.jsx # Chapter sidebar
|
|
│ └── ProgressBar.jsx # Progress indicator
|
|
├── hooks/
|
|
│ ├── useAdiloPlayer.js # HLS streaming logic
|
|
│ └── useChapterTracking.js # Chapter tracking
|
|
├── services/
|
|
│ ├── adiloService.js # Adilo API calls
|
|
│ └── progressService.js # Supabase progress
|
|
├── styles/
|
|
│ └── AdiloVideoPlayer.module.css
|
|
└── types/
|
|
└── video.types.js
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Phases (In Order)
|
|
|
|
### ⭐ PHASE 1: useAdiloPlayer Hook
|
|
**Goal**: Get HLS.js working with M3U8 URL
|
|
|
|
**What to build:**
|
|
- React hook that initializes HLS.js instance
|
|
- Return: videoRef, isReady, isPlaying, currentTime, duration
|
|
- Handle browser compatibility (Safari vs HLS.js)
|
|
- Clean up HLS instance on unmount
|
|
- Emit callbacks: onTimeUpdate, onEnded, onError
|
|
|
|
**Test with:**
|
|
```javascript
|
|
const { videoRef, currentTime, isReady } = useAdiloPlayer({
|
|
m3u8Url: "https://adilo.bigcommand.com/m3u8/...",
|
|
autoplay: false,
|
|
onTimeUpdate: (time) => console.log(time)
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### ⭐ PHASE 2: useChapterTracking Hook
|
|
**Goal**: Determine which chapter is currently active
|
|
|
|
**What to build:**
|
|
- React hook that tracks active chapter
|
|
- Input: chapters array, currentTime
|
|
- Return: activeChapter, activeChapterId, chapterProgress
|
|
- Detect chapter transitions
|
|
- Calculate progress percentage
|
|
|
|
**Chapter data structure:**
|
|
```javascript
|
|
{
|
|
id: "ch1",
|
|
startTime: 0,
|
|
endTime: 120,
|
|
title: "Introduction",
|
|
description: "Welcome to the course"
|
|
}
|
|
```
|
|
|
|
**Test with:**
|
|
```javascript
|
|
const { activeChapter, chapterProgress } = useChapterTracking({
|
|
chapters: [...],
|
|
currentTime: 45
|
|
});
|
|
// activeChapter should be chapter with startTime ≤ 45 < endTime
|
|
```
|
|
|
|
---
|
|
|
|
### ⭐ PHASE 3: AdiloVideoPlayer Component
|
|
**Goal**: Main player combining both hooks
|
|
|
|
**What to build:**
|
|
- Component that uses both hooks
|
|
- Renders: <video> element + video controls
|
|
- Props: m3u8Url, videoId, chapters, autoplay, showChapters
|
|
- Methods: jumpToChapter(), play(), pause()
|
|
- Callbacks: onChapterChange, onVideoComplete, onProgressUpdate
|
|
|
|
**Usage example:**
|
|
```jsx
|
|
<AdiloVideoPlayer
|
|
m3u8Url="https://adilo.bigcommand.com/m3u8/..."
|
|
chapters={[{id: "1", startTime: 0, endTime: 120, title: "Intro"}]}
|
|
onVideoComplete={() => markComplete()}
|
|
onChapterChange={(ch) => console.log(ch.title)}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
### ⭐ PHASE 4: ChapterNavigation Component
|
|
**Goal**: Display chapters user can click to jump
|
|
|
|
**What to build:**
|
|
- Sidebar/timeline showing all chapters
|
|
- Highlight current active chapter
|
|
- Show time for each chapter
|
|
- Click handler to jump to chapter
|
|
- Progress bar for each chapter
|
|
|
|
**Props:**
|
|
- chapters: Chapter[]
|
|
- activeChapterId: string
|
|
- currentTime: number
|
|
- onChapterClick: (startTime: number) => void
|
|
- completedChapters: string[]
|
|
|
|
---
|
|
|
|
### ⭐ PHASE 5: Supabase Integration
|
|
**Goal**: Save video progress to database
|
|
|
|
**Database schema needed:**
|
|
```sql
|
|
CREATE TABLE video_progress (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid NOT NULL,
|
|
video_id uuid NOT NULL,
|
|
last_position int,
|
|
completed_chapters text[],
|
|
watched_percentage int,
|
|
is_completed boolean DEFAULT false,
|
|
completed_at timestamp,
|
|
created_at timestamp DEFAULT now(),
|
|
updated_at timestamp DEFAULT now(),
|
|
UNIQUE(user_id, video_id)
|
|
);
|
|
```
|
|
|
|
**Functions to implement:**
|
|
- `saveProgress(userId, videoId, currentTime, completedChapters)`
|
|
- `getLastPosition(userId, videoId)` - resume from last position
|
|
- `markVideoComplete(userId, videoId)`
|
|
- `getVideoAnalytics(userId, videoId)`
|
|
|
|
---
|
|
|
|
### ⭐ PHASE 6: Styling
|
|
**Goal**: Make it look good and responsive
|
|
|
|
**Key CSS classes needed:**
|
|
- `.adilo-player` - main container
|
|
- `.video-container` - video wrapper
|
|
- `.chapters-sidebar` - chapter list
|
|
- `.chapter-item` - individual chapter
|
|
- `.chapter-item.active` - highlight active
|
|
- `.progress-bar` - progress visualization
|
|
|
|
**Responsive breakpoints:**
|
|
- Desktop: sidebar on right
|
|
- Tablet: sidebar below video
|
|
- Mobile: horizontal timeline under video
|
|
|
|
---
|
|
|
|
## Key Implementation Details
|
|
|
|
### HLS.js Initialization Pattern
|
|
```javascript
|
|
if (Hls.isSupported()) {
|
|
const hls = new Hls();
|
|
hls.loadSource(m3u8Url);
|
|
hls.attachMedia(videoElement);
|
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
videoElement.play();
|
|
});
|
|
} else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
|
|
// Safari native HLS support
|
|
videoElement.src = m3u8Url;
|
|
}
|
|
```
|
|
|
|
### Chapter Jump Implementation
|
|
```javascript
|
|
const jumpToChapter = (startTime) => {
|
|
if (videoRef.current) {
|
|
videoRef.current.currentTime = startTime;
|
|
videoRef.current.play();
|
|
}
|
|
};
|
|
```
|
|
|
|
### Track Current Chapter Pattern
|
|
```javascript
|
|
const current = chapters.find(
|
|
ch => currentTime >= ch.startTime && currentTime < ch.endTime
|
|
);
|
|
setActiveChapter(current?.id);
|
|
```
|
|
|
|
### Debounce Progress Saves
|
|
```javascript
|
|
// Save progress every 5 seconds, not on every timeupdate
|
|
const saveProgressDebounced = debounce(
|
|
(userId, videoId, time) => saveProgress(userId, videoId, time),
|
|
5000
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Common Tasks for AI Agent
|
|
|
|
When asking your AI agent to implement:
|
|
|
|
### Task: "Create useAdiloPlayer hook"
|
|
**Should generate:**
|
|
- Import HLS from 'hls.js'
|
|
- useRef for video element
|
|
- useEffect to initialize HLS
|
|
- useCallback for event handlers
|
|
- Clean up logic in return
|
|
|
|
### Task: "Add chapter jump functionality"
|
|
**Should implement:**
|
|
- Button click handler
|
|
- Call jumpToChapter(startTime)
|
|
- Update videoRef.current.currentTime
|
|
- Play the video
|
|
|
|
### Task: "Save progress to Supabase"
|
|
**Should implement:**
|
|
- Create/update row in video_progress table
|
|
- Include: user_id, video_id, last_position, completed_chapters
|
|
- Handle conflicts (UPSERT)
|
|
- Error handling
|
|
|
|
### Task: "Make chapters responsive"
|
|
**Should implement:**
|
|
- CSS Grid for desktop (sidebar)
|
|
- Flex column for mobile
|
|
- Media query at 768px breakpoint
|
|
- Adjust spacing and font sizes
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
### Unit Tests
|
|
- [ ] useAdiloPlayer hook returns correct refs/values
|
|
- [ ] useChapterTracking calculates active chapter correctly
|
|
- [ ] jumpToChapter updates video.currentTime
|
|
- [ ] Progress saves to Supabase
|
|
|
|
### Integration Tests
|
|
- [ ] Video plays when component mounts
|
|
- [ ] Chapter changes highlight in UI
|
|
- [ ] Clicking chapter jumps player
|
|
- [ ] Progress saves on interval
|
|
- [ ] Completion triggers callback
|
|
|
|
### Browser Tests
|
|
- [ ] Works on Chrome/Edge (HLS.js)
|
|
- [ ] Works on Firefox (HLS.js)
|
|
- [ ] Works on Safari (native HLS)
|
|
- [ ] Works on mobile browsers
|
|
|
|
### Edge Cases
|
|
- [ ] Bad M3U8 URL shows error
|
|
- [ ] Network interruption handled
|
|
- [ ] Video paused mid-chapter
|
|
- [ ] Page refresh preserves position
|
|
|
|
---
|
|
|
|
## Environment Variables Needed
|
|
```
|
|
VITE_SUPABASE_URL=your_supabase_url
|
|
VITE_SUPABASE_ANON_KEY=your_supabase_key
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging Tips
|
|
|
|
### HLS.js not loading?
|
|
- Check M3U8 URL is correct from Adilo
|
|
- Verify CORS headers from Adilo
|
|
- Check browser console for HLS.js errors
|
|
- Try `hls.on(Hls.Events.ERROR, console.error)`
|
|
|
|
### Chapter not highlighting?
|
|
- Add console.log(currentTime, chapters) to track values
|
|
- Verify chapter startTime/endTime are correct
|
|
- Check activeChapter state is updating
|
|
|
|
### Progress not saving?
|
|
- Verify Supabase connection works
|
|
- Check user_id and video_id are defined
|
|
- Add error logs to saveProgress function
|
|
- Check database table schema matches
|
|
|
|
---
|
|
|
|
## Performance Optimization Tips
|
|
|
|
1. **Memoize chapters list** to prevent re-renders
|
|
```javascript
|
|
const chapters = useMemo(() => chaptersData, [chaptersData]);
|
|
```
|
|
|
|
2. **Debounce timeupdate events** (fires 60x per second!)
|
|
```javascript
|
|
const updateChapter = debounce(() => {...}, 100);
|
|
video.addEventListener('timeupdate', updateChapter);
|
|
```
|
|
|
|
3. **Lazy load chapter images/thumbnails**
|
|
```javascript
|
|
<img loading="lazy" src={chapter.thumbnail} />
|
|
```
|
|
|
|
4. **Use React.memo for ChapterNavigation**
|
|
```javascript
|
|
export default React.memo(ChapterNavigation);
|
|
```
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- **HLS.js Docs**: https://github.com/video-dev/hls.js/wiki
|
|
- **HTML5 Video API**: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video
|
|
- **Supabase JS Client**: https://supabase.com/docs/reference/javascript/introduction
|
|
|
|
---
|
|
|
|
**Status**: Ready to implement! 🚀
|
|
|
|
Start with PHASE 1 (useAdiloPlayer hook), then PHASE 2-6 in order.
|