# Streamhut VOD Bundle
Video playback backend for Streamhut. Serves HLS playlists, thumbnails, and preview sprites.
## Features
- **HLS Proxying** - Rewrites stream.place playlists with absolute URLs
- **Thumbnails** - Extracted from video at 3-second mark
- **Preview Sprites** - 8-frame sprite sheets for hover preview
- **VTT Coordinates** - WebVTT file mapping timestamps to sprite frames
- **AppView Integration** - Optional connection to indexer for multi-creator support
## Endpoints
### Video Discovery
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/listVideos` | List videos (from AppView or stream.place fallback) |
| POST | `/getVideo` | Get single video metadata |
| POST | `/getVideoStreams` | Get quality levels and audio tracks |
### Playback
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/getPlaylist` | HLS master playlist (browser-cacheable) |
### Media Assets
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/getThumbnail` | Video thumbnail JPEG |
| GET | `/getSprite` | Preview sprite sheet JPEG |
| GET | `/getVtt` | Sprite coordinate VTT |
| GET | `/isMetadataReady` | Check if assets are generated |
| POST | `/batchMetadataReady` | Batch check multiple videos |
### Profiles
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/getProfile` | Get creator profile from Bluesky |
| POST | `/batchProfiles` | Batch fetch multiple profiles |
### Social Sharing
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/shareVideo` | Share page with OG meta tags for videos |
| GET | `/shareCreator` | Share page with OG meta tags for creators |
The share endpoints return HTML with Open Graph meta tags for rich link previews, then redirect to the frontend. Titles use the format "Streamhut | \
".
## Background Job
**`syncAndProcessVideos`** - Runs every 10 minutes
1. Fetches video list (from AppView if configured, otherwise stream.place)
2. For each unprocessed video:
- Downloads master playlist
- Generates thumbnail via ffmpeg
- Generates sprite sheet and VTT
3. Caches everything to `/tmp/vod-cache/`
## Configuration
### Secrets
| Name | Required | Description |
|------|----------|-------------|
| `APPVIEW_URL` | No | URL to AppView bundle for multi-creator support |
### Without AppView
Falls back to querying stream.place directly (single-creator mode).
### With AppView
```bash
# Set the secret
APPVIEW_URL=https://at-run.example.com/bundle/.../streamhut-appview/latest
```
Now `/listVideos` returns videos from all registered creators.
## Cache Structure
```
/tmp/vod-cache/
└── {sha256(uri)}/
├── video.json # Video metadata
├── master.m3u8 # Cached HLS playlist
├── thumbnail.jpg # 480p thumbnail
├── sprites.jpg # 4x2 sprite grid
└── sprites.vtt # Timestamp → sprite mapping
```
## Build & Deploy
```bash
# Build
npm run bundle:vod
# Deploy
npm run deploy:vod
# Deploy with version bump
npm run deploy:minor
npm run deploy:major
```
## Usage Examples
### List Videos
```bash
curl "https://runner.example.com/bundle/.../atmosphereconf-vod/latest/listVideos?limit=10"
```
### Play Video
```javascript
import Hls from 'hls.js'
const playlistUrl = `${RUNNER_URL}/bundle/.../atmosphereconf-vod/latest/getPlaylist?uri=${encodeURIComponent(videoUri)}`
const hls = new Hls()
hls.loadSource(playlistUrl)
hls.attachMedia(videoElement)
```
### Show Thumbnail
```html
```
### Hover Preview
```javascript
// Fetch sprite VTT
const vttUrl = `${RUNNER_URL}/bundle/.../getVtt?uri=${uri}`
const vtt = await fetch(vttUrl).then(r => r.text())
// Parse and use sprite coordinates for hover preview
```
## Files
```
apps/vod/
├── src/
│ ├── index.ts # Bundle manifest and endpoints
│ ├── jobs.ts # Background sync job
│ ├── ffmpeg.ts # FFmpeg utilities
│ ├── thumbnail.ts # Thumbnail extraction
│ └── vtt.ts # Sprite generation
└── README.md
```
## Dependencies
- **FFmpeg** - Required on runner for thumbnail/sprite generation
- **stream.place** - Source of HLS video streams