diff --git a/.gitignore b/.gitignore index adcc854..1fd4af4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ data/recent-urls.json +data/webtransport-*.pem .env .DS_Store npm-debug.log* diff --git a/AGENTS.md b/AGENTS.md index 82c794f..17e3d65 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ The app is plain Node/Express plus browser JavaScript: - `server/index.js`: API, WebSocket, source proxy/relay, ffmpeg process lifecycle, recent URL and favorites persistence. - `public/index.html`: frontend markup. -- `public/app.js`: URL submission, WebSocket frame receiving, audio element coordination, canvas drawing, overlay controls. +- `public/app.js`: URL submission, WebSocket/WebTransport frame receiving, audio element coordination, canvas drawing, overlay controls. - `public/styles.css`: two-screen player UI. - `Dockerfile`: production image with Node and ffmpeg. - `docker-compose-example.yml`: operational example and default env knobs. @@ -34,6 +34,7 @@ Main public endpoints: - `PUT /api/favorites`: replaces the global favorites list. Each favorite has a user-provided `title` and stream `url`. - `GET /audio/:sessionId`: serves MP3 audio to the browser audio element. - `WS /frames/:sessionId`: sends timed JPEG frame packets to the browser. +- `WebTransport /frames/:sessionId`: optional QUIC frame path on the separate WebTransport UDP listener. - `GET /api/health`: exposes basic health and active playback connection mode. Internal endpoint: @@ -42,7 +43,7 @@ Internal endpoint: ## Browser Playback Model -Audio is the playback clock. The server sends JPEG frames over WebSocket. Each binary frame packet is: +Audio is the playback clock. The server sends JPEG frames over WebSocket by default, or over WebTransport when explicitly enabled. Each binary frame packet is: - First 8 bytes: little-endian float64 timestamp in seconds. - Remaining bytes: one complete JPEG image. @@ -191,6 +192,15 @@ Runtime: - `FAVORITES_PATH`: favorites JSON path. - `FAVORITES_LIMIT`: favorites count, default `50`. - `LOCAL_VIDEOS`: optional local video directory. When set, the UI shows `Play Local` and lists regular files under this directory recursively. +- `FRAME_TRANSPORT`: `websocket`, `webtransport`, or `auto`, default `websocket`. +- `WEBTRANSPORT_ENABLED`: enables the WebTransport listener, default true only when `FRAME_TRANSPORT` is `webtransport` or `auto`. +- `WEBTRANSPORT_HOST`: WebTransport UDP bind host, default `0.0.0.0`. +- `WEBTRANSPORT_PORT`: WebTransport UDP port, default `PORT + 1`. +- `WEBTRANSPORT_PUBLIC_HOST`: optional host advertised to browsers for WebTransport. +- `WEBTRANSPORT_PUBLIC_PORT`: port advertised to browsers for WebTransport, default `WEBTRANSPORT_PORT`. +- `WEBTRANSPORT_CERT_PATH`: optional WebTransport certificate path. If unset, the server generates a short-lived ECDSA cert under `data/`. +- `WEBTRANSPORT_KEY_PATH`: optional WebTransport private key path. Must be set together with `WEBTRANSPORT_CERT_PATH`. +- Browser WebTransport requires a secure context. Localhost is usually treated as secure, but plain HTTP over a LAN address may not expose `window.WebTransport`; the frontend must fall back to WebSocket in that case. - `DEFAULT_FPS`: default frame rate, fallback `24`, clamped `1..30`. - `DEFAULT_FRAME_WIDTH`: default maximum frame width, fallback `960`, clamped `160..1920`. - `JPEG_QUALITY`: default JPEG quality, fallback `7`, clamped `2..18`; lower is better for ffmpeg `-q:v`. diff --git a/Dockerfile b/Dockerfile index 6fbaa95..68fa188 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-bookworm-slim +FROM node:22-trixie-slim ENV NODE_ENV=production ENV PORT=3000 @@ -10,7 +10,7 @@ WORKDIR /app ADD https://api.github.com/repos/yt-dlp/yt-dlp/commits/master /tmp/yt-dlp-master.json RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates ffmpeg python3 python3-pip \ + && apt-get install -y --no-install-recommends ca-certificates ffmpeg openssl python3 python3-pip \ && python3 -m pip install --no-cache-dir --break-system-packages --upgrade "$YT_DLP_PIP_SPEC" \ && yt-dlp --version \ && apt-get purge -y --auto-remove python3-pip \ @@ -30,6 +30,7 @@ RUN mkdir -p /app/data \ USER node EXPOSE 3000 +EXPOSE 3001/udp HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || 3000) + '/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" diff --git a/README.md b/README.md index f4a56d2..a5f14f6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A small web app that plays a remote video stream without using browser video decoding. The server uses `ffmpeg` to decode the input URL into: - an MP3 audio stream served to a normal `