Add VideoWall feature: server-side preview clip generation and mobile grid view
Backend (Rust/Actix-web): - Add video_preview_clips table and PreviewDao for tracking preview generation - Add ffmpeg preview clip generator: 10 equally-spaced 1s segments at 480p with CUDA NVENC auto-detection - Add PreviewClipGenerator actor with semaphore-limited concurrent processing - Add GET /video/preview and POST /video/preview/status endpoints - Extend file watcher to detect and queue previews for new videos - Use relative paths consistently for DB storage (matching EXIF convention) Frontend (React Native/Expo): - Add VideoWall grid view with 2-3 column layout of looping preview clips - Add VideoWallItem component with ActiveVideoPlayer sub-component for lifecycle management - Add useVideoWall hook for batch status polling with 5s refresh - Add navigation button in grid header (visible when videos exist) - Use TextureView surface type to fix Android z-ordering issues - Optimize memory: players only mount while visible via FlatList windowSize - Configure ExoPlayer buffer options and caching for short clips - Tap to toggle audio focus, long press to open in full viewer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
91
specs/001-video-wall/contracts/api-endpoints.md
Normal file
91
specs/001-video-wall/contracts/api-endpoints.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# API Contracts: VideoWall
|
||||
|
||||
## GET /video/preview
|
||||
|
||||
Retrieve the preview clip MP4 file for a given video. If the preview is not yet generated, triggers on-demand generation and returns 202.
|
||||
|
||||
**Authentication**: Required (Bearer token)
|
||||
|
||||
**Query Parameters**:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| path | string | yes | Relative path of the source video from BASE_PATH |
|
||||
|
||||
**Responses**:
|
||||
|
||||
| Status | Content-Type | Body | Description |
|
||||
|--------|-------------|------|-------------|
|
||||
| 200 | video/mp4 | MP4 file stream | Preview clip is ready and served |
|
||||
| 202 | application/json | `{"status": "processing", "path": "<path>"}` | Preview generation has been triggered; client should retry |
|
||||
| 400 | application/json | `{"error": "Invalid path"}` | Path validation failed |
|
||||
| 404 | application/json | `{"error": "Video not found"}` | Source video does not exist |
|
||||
| 500 | application/json | `{"error": "Generation failed: <detail>"}` | Preview generation failed |
|
||||
|
||||
**Behavior**:
|
||||
1. Validate path with `is_valid_full_path()`
|
||||
2. Check if preview clip exists on disk and status is `complete` → serve MP4 (200)
|
||||
3. If status is `pending` or no record exists → trigger generation, return 202
|
||||
4. If status is `processing` → return 202
|
||||
5. If status is `failed` → return 500 with error detail
|
||||
|
||||
---
|
||||
|
||||
## POST /video/preview/status
|
||||
|
||||
Check the preview generation status for a batch of video paths. Used by the mobile app to determine which previews are ready before requesting them.
|
||||
|
||||
**Authentication**: Required (Bearer token)
|
||||
|
||||
**Request Body** (application/json):
|
||||
|
||||
```json
|
||||
{
|
||||
"paths": [
|
||||
"2024/vacation/beach.mov",
|
||||
"2024/vacation/sunset.mp4",
|
||||
"2024/birthday.avi"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| paths | string[] | yes | Array of relative video paths from BASE_PATH |
|
||||
|
||||
**Response** (200, application/json):
|
||||
|
||||
```json
|
||||
{
|
||||
"previews": [
|
||||
{
|
||||
"path": "2024/vacation/beach.mov",
|
||||
"status": "complete",
|
||||
"preview_url": "/video/preview?path=2024/vacation/beach.mov"
|
||||
},
|
||||
{
|
||||
"path": "2024/vacation/sunset.mp4",
|
||||
"status": "processing",
|
||||
"preview_url": null
|
||||
},
|
||||
{
|
||||
"path": "2024/birthday.avi",
|
||||
"status": "pending",
|
||||
"preview_url": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| previews | object[] | Status for each requested path |
|
||||
| previews[].path | string | The requested video path |
|
||||
| previews[].status | string | One of: `pending`, `processing`, `complete`, `failed`, `not_found` |
|
||||
| previews[].preview_url | string? | Relative URL to fetch the preview (only when status is `complete`) |
|
||||
|
||||
**Behavior**:
|
||||
1. Accept up to 200 paths per request
|
||||
2. Batch query the `video_preview_clips` table for all paths
|
||||
3. For paths not in the table, return status `not_found` (video may not exist or hasn't been scanned yet)
|
||||
4. Return results in the same order as the input paths
|
||||
Reference in New Issue
Block a user