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:
Cameron
2026-02-25 19:40:17 -05:00
parent 7a0da1ab4a
commit 19c099360e
19 changed files with 1691 additions and 12 deletions

View 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