Replaces single-MP4 + range-request streaming with HLS multi-bitrate
segments to fix video stutter through the Newt tunnel. Range-request
bursts were the root cause; HLS chunks are small and tunnel-friendly,
plus the player adapts bitrate to bandwidth.
Backend
- New BullMQ `hls-transcode` queue (in-process worker, concurrency 1)
- FFmpeg single-pass transcode → 360p/720p/1080p variants with aligned
keyframes; output at /media/local/hls/{id}/master.m3u8
- New /api/{videos|public}/{id}/hls/* routes serving signed manifests
and segments (URLs emitted as /media/* so nginx rewrites to media-api)
- Prisma: HlsStatus enum + 6 fields on Video + index, migration
- Upload + yt-dlp fetch paths enqueue transcode jobs
- ENABLE_HLS_TRANSCODE flag (default off; gates enqueue only)
- Backfill script: `npm run backfill:hls`
- media-api bumped to 4 CPU / 2G for FFmpeg headroom
Frontend
- New useHls hook: lazy-imports hls.js (kept out of main bundle),
native HLS on Safari/iOS, gives up after 2 NETWORK_ERRORs so MP4
fallback engages cleanly
- VideoPlayer, VideoViewerModal, ShortsPage, ProductDetailPage now
prefer HLS when ready; MP4 fallback is automatic
- ShortsPage prefetches next-3 master manifests via <link rel="prefetch">
- PublicVideoCard hover preview stays MP4 (avoids hls.js init latency)
Bunker Admin
14 lines
464 B
SQL
14 lines
464 B
SQL
-- CreateEnum
|
|
CREATE TYPE "HlsStatus" AS ENUM ('PENDING', 'PROCESSING', 'READY', 'FAILED', 'SKIPPED');
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "videos" ADD COLUMN "hls_job_id" TEXT,
|
|
ADD COLUMN "hls_manifest_path" TEXT,
|
|
ADD COLUMN "hls_status" "HlsStatus",
|
|
ADD COLUMN "hls_transcode_error" TEXT,
|
|
ADD COLUMN "hls_transcoded_at" TIMESTAMP(3),
|
|
ADD COLUMN "hls_variants" JSONB;
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "idx_videos_hls_status" ON "videos"("hls_status");
|