changemaker.lite/api/package.json
bunker-admin 21208b58c7 feat(media): HLS adaptive bitrate streaming with MP4 fallback
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
2026-04-30 19:03:29 -06:00

85 lines
2.4 KiB
JSON

{
"name": "changemaker-v2-api",
"version": "2.0.0",
"description": "Unified Express.js API for Changemaker Lite v2",
"main": "dist/server.js",
"scripts": {
"dev": "tsx watch src/server.ts",
"dev:media": "tsx watch src/media-server.ts",
"build": "tsc",
"start": "node dist/server.js",
"start:media": "node dist/media-server.js",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:migrate:deploy": "prisma migrate deploy",
"prisma:seed": "tsx prisma/seed.ts",
"prisma:studio": "prisma studio",
"backfill:hls": "tsx scripts/backfill-hls.ts"
},
"dependencies": {
"@fastify/cors": "^11.2.0",
"@fastify/multipart": "^9.4.0",
"@fastify/static": "^9.0.0",
"@hocuspocus/server": "^3.4.4",
"@maxmind/geoip2-node": "^6.3.4",
"@prisma/client": "^6.3.0",
"@types/mime-types": "^3.0.1",
"bcryptjs": "^2.4.3",
"bullmq": "^5.34.0",
"compression": "^1.7.5",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"csv-parse": "^6.1.0",
"csv-stringify": "^6.6.0",
"dotenv": "^16.4.7",
"drizzle-orm": "^0.45.1",
"exif-reader": "^2.0.3",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"fastify": "^5.7.4",
"helmet": "^8.0.0",
"ical-generator": "^10.0.0",
"ioredis": "^5.4.2",
"jsonwebtoken": "^9.0.2",
"mime-types": "^3.0.2",
"multer": "^2.1.1",
"node-addon-api": "^8.5.0",
"node-ical": "^0.25.5",
"nodemailer": "^8.0.1",
"pg": "^8.18.0",
"proj4": "^2.20.2",
"prom-client": "^15.1.3",
"qrcode": "^1.5.4",
"rate-limit-redis": "^4.2.0",
"sharp": "^0.34.5",
"stripe": "^20.3.1",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
"ws": "^8.19.0",
"yaml": "^2.8.2",
"yjs": "^13.6.29",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/jsonwebtoken": "^9.0.7",
"@types/multer": "^2.0.0",
"@types/node": "^22.19.11",
"@types/nodemailer": "^7.0.11",
"@types/pg": "^8.16.0",
"@types/qrcode": "^1.5.6",
"@types/ws": "^8.18.1",
"drizzle-kit": "^0.31.9",
"prisma": "^6.3.0",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
},
"prisma": {
"seed": "npx tsx prisma/seed.ts"
}
}