Files
triviathang/YOUTUBE_AUDIO_IMPLEMENTATION_STATUS.md
2025-12-22 14:47:25 -05:00

304 lines
10 KiB
Markdown

# YouTube Audio Feature - Implementation Status
## ✅ COMPLETED (Backend + Infrastructure)
### Infrastructure (100%)
- ✅ Added Python dependencies: celery, redis, yt-dlp, pydub
- ✅ Updated Dockerfile.backend to install ffmpeg
- ✅ Added Redis, Celery worker, and Celery Flower services to docker-compose.yml
- ✅ Configured audio volume mounts in Docker
### Configuration (100%)
- ✅ Added audio and Celery configuration to `backend/config.py`
- Audio folder, file size limits, allowed extensions
- Celery broker/backend URLs
- yt-dlp settings (format, quality)
### Database Models (100%)
- ✅ Added `YOUTUBE_AUDIO` to `QuestionType` enum
- ✅ Added YouTube fields to Question model:
- `youtube_url` - YouTube video URL
- `audio_path` - Path to trimmed audio file
- `start_time` - Start time in seconds
- `end_time` - End time in seconds
- ✅ Created `DownloadJob` model to track async processing
- Tracks status (pending/processing/completed/failed)
- Progress (0-100%)
- Error messages
- Celery task ID
### Backend Services (100%)
- ✅ Created `backend/celery_app.py` - Celery configuration
- ✅ Created `backend/services/audio_service.py` - Audio file utilities
- UUID-based filename generation
- File deletion cleanup
- ✅ Created `backend/services/youtube_service.py` - YouTube validation
- URL validation with regex
- Video duration fetching
- Timestamp range validation
- ✅ Created `backend/tasks/youtube_tasks.py` - Celery download task
- Downloads full audio via yt-dlp
- Trims to timestamp range using pydub
- Updates progress in database
- Handles errors and cleanup
### API Routes (100%)
- ✅ Updated `backend/routes/questions.py`:
- Accepts YouTube audio question type
- Validates URL and timestamps
- Creates question with pending status
- Spawns Celery download task
- Returns 202 with job ID
- Deletes audio files on question deletion
- ✅ Created `backend/routes/download_jobs.py`:
- `GET /api/download-jobs/<id>` - Get job status
- `GET /api/download-jobs/question/<question_id>` - Get job by question
- ✅ Updated `backend/routes/admin.py` with audio control endpoints:
- `POST /api/admin/game/<id>/audio/play` - Broadcast play command
- `POST /api/admin/game/<id>/audio/pause` - Broadcast pause command
- `POST /api/admin/game/<id>/audio/stop` - Broadcast stop command
- `POST /api/admin/game/<id>/audio/seek` - Broadcast seek command
### WebSocket Broadcasts (100%)
- ✅ Updated `backend/services/game_service.py` with audio broadcast functions:
- `broadcast_audio_play()` - Sends play event to contestant room
- `broadcast_audio_pause()` - Sends pause event
- `broadcast_audio_stop()` - Sends stop event
- `broadcast_audio_seek()` - Sends seek event with position
### App Integration (100%)
- ✅ Updated `backend/app.py`:
- Creates audio folder on startup
- Registers download_jobs blueprint
### Frontend API Client (100%)
- ✅ Updated `frontend/frontend/src/services/api.js`:
- Added `downloadJobsAPI` for job status polling
- Added `audioControlAPI` for playback controls
---
## 🚧 REMAINING (Frontend UI Components)
### 1. Question Creation Form
**File**: `frontend/frontend/src/components/questionbank/QuestionBankView.jsx`
**Required Changes**:
- Add `"youtube_audio"` option to type dropdown
- Add form state for YouTube fields:
```javascript
const [formData, setFormData] = useState({
// ... existing fields
youtube_url: '',
start_time: 0,
end_time: 0
});
const [downloadJob, setDownloadJob] = useState(null);
const [downloadProgress, setDownloadProgress] = useState(0);
```
- Add conditional form fields when type === 'youtube_audio':
- YouTube URL input
- Start time number input (seconds)
- End time number input (seconds)
- Display calculated duration
- Update submit handler to POST YouTube data as JSON
- Implement polling logic:
```javascript
const pollDownloadStatus = async (jobId) => {
const pollInterval = setInterval(async () => {
const response = await downloadJobsAPI.getStatus(jobId);
setDownloadProgress(response.data.progress);
if (response.data.status === 'completed') {
clearInterval(pollInterval);
// Reload questions, show success message
}
}, 2000);
};
```
- Add download progress indicator UI (fixed position, bottom-right)
### 2. Audio Player Component
**File**: `frontend/frontend/src/components/audio/AudioPlayer.jsx` (NEW)
**Component Structure**:
```jsx
export default function AudioPlayer({ audioPath, isAdmin, socket, gameId }) {
const audioRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
// Contestant mode: Listen to WebSocket events
useEffect(() => {
if (!isAdmin && socket) {
socket.on('audio_play', () => audioRef.current?.play());
socket.on('audio_pause', () => audioRef.current?.pause());
socket.on('audio_stop', () => {
audioRef.current?.pause();
audioRef.current.currentTime = 0;
});
socket.on('audio_seek', (data) => {
audioRef.current.currentTime = data.position;
});
}
}, [socket, isAdmin]);
// Admin controls
const handlePlay = async () => {
await audioControlAPI.play(gameId);
audioRef.current?.play();
};
// ... similar for pause, stop
return (
<div style={{ background: '#f5f5f5', padding: '1.5rem', borderRadius: '8px' }}>
<audio ref={audioRef} src={audioPath} preload="auto" />
{/* Progress bar */}
{/* Time display */}
{/* Admin controls (conditional) */}
</div>
);
}
```
**Styling**:
- Progress bar: clickable (admin only), blue fill (#2196F3)
- Time display: MM:SS format
- Buttons: Play (green #4CAF50), Pause (orange #ff9800), Stop (red #f44336)
### 3. Contestant View Update
**File**: `frontend/frontend/src/components/contestant/ContestantView.jsx`
**Required Changes**:
```jsx
import AudioPlayer from '../audio/AudioPlayer';
// In render, after image block:
{currentQuestion.type === 'youtube_audio' && currentQuestion.audio_path && (
<AudioPlayer
audioPath={currentQuestion.audio_path}
isAdmin={false}
socket={socket}
gameId={gameId}
/>
)}
```
### 4. Admin View Update
**File**: `frontend/frontend/src/components/admin/GameAdminView.jsx`
**Required Changes**:
```jsx
import AudioPlayer from '../audio/AudioPlayer';
// In current question display section:
{currentQuestion?.type === 'youtube_audio' && currentQuestion?.audio_path && (
<AudioPlayer
audioPath={currentQuestion.audio_path}
isAdmin={true}
socket={socket}
gameId={gameId}
/>
)}
```
---
## 📋 Testing Checklist
### Backend Tests
- [ ] Start containers: `docker compose up --build`
- [ ] Create migration: `docker compose exec backend uv run flask db migrate -m "Add YouTube audio support"`
- [ ] Apply migration: `docker compose exec backend uv run flask db upgrade`
- [ ] Test invalid YouTube URL: Should return 400 error
- [ ] Test invalid timestamps: Should return validation error
- [ ] Test job status endpoint: `curl http://localhost:5001/api/download-jobs/1`
### Frontend Tests (after UI completion)
- [ ] Create YouTube audio question
- Submit URL with timestamps
- Verify progress indicator appears
- Wait for completion alert
- Verify question appears in list
- [ ] Test audio playback
- Start game with YouTube question
- Open contestant view (tab 1)
- Open admin view (tab 2)
- Admin clicks Play → Contestant audio plays
- Admin clicks Pause → Contestant audio pauses
- Admin clicks Stop → Contestant audio resets
- [ ] Test question navigation
- Admin clicks Next → Audio stops
- Return to audio question → Audio ready to play again
---
## 🎯 Next Steps
1. **Complete frontend UI components** (4 files to update/create)
2. **Run database migration** (see MIGRATION_GUIDE.md)
3. **Test end-to-end workflow**
4. **Optional enhancements**:
- Add audio waveform visualization
- Add timestamp selector UI with embedded YouTube player
- Add video thumbnail preview
- Support multiple clips per question
---
## 📁 Files Modified/Created
### Backend (15 files)
- ✅ `pyproject.toml`
- ✅ `docker-compose.yml`
- ✅ `Dockerfile.backend`
- ✅ `backend/config.py`
- ✅ `backend/models.py`
- ✅ `backend/app.py`
- ✅ `backend/celery_app.py` (NEW)
- ✅ `backend/services/audio_service.py` (NEW)
- ✅ `backend/services/youtube_service.py` (NEW)
- ✅ `backend/tasks/__init__.py` (NEW)
- ✅ `backend/tasks/youtube_tasks.py` (NEW)
- ✅ `backend/routes/questions.py`
- ✅ `backend/routes/download_jobs.py` (NEW)
- ✅ `backend/routes/admin.py`
- ✅ `backend/services/game_service.py`
### Frontend (4 files - 1 complete, 3 remaining)
- ✅ `frontend/frontend/src/services/api.js`
- 🚧 `frontend/frontend/src/components/questionbank/QuestionBankView.jsx`
- 🚧 `frontend/frontend/src/components/audio/AudioPlayer.jsx` (NEW)
- 🚧 `frontend/frontend/src/components/contestant/ContestantView.jsx`
- 🚧 `frontend/frontend/src/components/admin/GameAdminView.jsx`
### Documentation
- ✅ `MIGRATION_GUIDE.md` (NEW)
- ✅ `YOUTUBE_AUDIO_IMPLEMENTATION_STATUS.md` (NEW)
---
## 🔑 Key Implementation Details
### Async Flow
1. User submits YouTube question → API creates Question with null `audio_path`
2. Celery task starts → Downloads full audio → Trims clip → Saves MP3
3. Frontend polls job status every 2 seconds
4. On completion → Question's `audio_path` updated → Frontend shows success
### Audio Playback Architecture
- **Admin**: Play/Pause/Stop buttons → API → WebSocket broadcast to contestants
- **Contestants**: Listen to WebSocket events → Control HTML5 `<audio>` element
- No direct contestant controls (fully synchronized)
### File Storage
- Audio files: `backend/static/audio/{uuid}.mp3`
- Served at: `/static/audio/{uuid}.mp3`
- Max size: 50MB, Max duration: 5 minutes
### Error Handling
- Invalid URL → 400 error with message
- Timestamp validation → 400 error
- Download failure → Job status set to "failed" with error message
- Question deletion → Cleanup audio file automatically