@torrent-tv/proxy 2.73.1 → 2.74.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1453 -1437
- package/CLAUDE.md +165 -160
- package/docs/container-architecture.md +192 -184
- package/package.json +1 -1
- package/routes/api/subtitles/get.js +205 -205
- package/services/container/Container.js +400 -135
- package/services/container/ContainerFactory.js +55 -31
- package/services/container/MatroskaContainer.js +1166 -516
- package/services/container/Mp4Container.js +898 -392
- package/services/container/SubtitleFileContainer.js +323 -261
- package/services/controllers/SubtitleController.js +128 -127
- package/services/delivery-probe.js +64 -6
- package/services/hls-session-manager.js +32 -35
- package/services/language-detect.js +174 -228
- package/services/playback-planner.js +747 -747
- package/services/produced-index.js +300 -0
- package/services/torrent-worker/subtitle-cues.js +549 -633
- package/services/tracks/TextSubtitleTrack.js +287 -47
- package/services/tracks/index.js +14 -14
- package/test/delivery-probe.test.js +67 -0
- package/test/matroska-blocks.test.js +0 -0
- package/test/mp4-subtitles.test.js +173 -127
- package/test/produced-index.test.js +188 -0
- package/test/subtitle-cue-framing.test.js +200 -202
- package/test/subtitle-cue-walk.test.js +369 -0
- package/test/subtitle-defaults.test.js +97 -97
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +370 -370
- package/services/container-index/matroska-blocks.js +0 -202
- package/services/container-index/matroska-subtitles.js +0 -372
- package/services/container-index/mp4-subtitles.js +0 -404
- package/services/subtitle-convert.js +0 -144
- package/services/subtitle-defaults.js +0 -157
- package/services/tracks/subtitle-markup.js +0 -104
package/CLAUDE.md
CHANGED
|
@@ -1,160 +1,165 @@
|
|
|
1
|
-
# proxy — @torrent-tv/proxy (WebTorrent + ffmpeg)
|
|
2
|
-
|
|
3
|
-
Downloads a torrent and streams the chosen file to the browser, transcoding to
|
|
4
|
-
HLS only when needed. See the parent `../CLAUDE.md` for the overall architecture
|
|
5
|
-
and release process.
|
|
6
|
-
|
|
7
|
-
## Deployment-agnostic — important
|
|
8
|
-
|
|
9
|
-
The HA addon is only ONE way to run this; bare npm and Docker are planned. Keep
|
|
10
|
-
all code free of Home-Assistant assumptions. Anything host-specific (GPU
|
|
11
|
-
devices, ffmpeg build, CLI flags) belongs in the `ha-addon` layer. Hardware
|
|
12
|
-
detection must probe at runtime and fall back gracefully; do not assume a
|
|
13
|
-
Linux-only host (e.g. POSIX-only signals must degrade elsewhere).
|
|
14
|
-
|
|
15
|
-
## Layout
|
|
16
|
-
|
|
17
|
-
- `bin/cli.js` — CLI entry; resolves `ffmpegBin` (uses `--ffmpeg-bin` if given,
|
|
18
|
-
else bundled ffmpeg-static, else PATH `ffmpeg`).
|
|
19
|
-
- `server.js` — Fastify setup; detects the video encoder at startup
|
|
20
|
-
(`detectVideoEncoder`) and passes it to `HlsSessionManager`.
|
|
21
|
-
- `routes/<path>/<method>.js` — same convention as the server repo.
|
|
22
|
-
- `routes/stream/get.js` — byte-range torrent file streaming (HTTP 206).
|
|
23
|
-
- `routes/api/playback-plan/post.js` — codec/container/duration probe result.
|
|
24
|
-
- `routes/transcode/session-file/get.js` — serves HLS playlist/segments;
|
|
25
|
-
long-polls while a segment is being produced, returns retryable 503 (never
|
|
26
|
-
202 — hls.js can't consume it).
|
|
27
|
-
- `services/`:
|
|
28
|
-
- `demand/` — what anybody wants, in BYTES: `Window` (claimant, file, byte
|
|
29
|
-
range, urgency), `Urgency` (BLOCKED / NEAR / AHEAD / TAIL / BEHIND),
|
|
30
|
-
`DemandRegister` (live windows by claimant), `pieces.js` (the one place
|
|
31
|
-
bytes become piece numbers). No WebTorrent, no piece store, no pieces.
|
|
32
|
-
- `download/` — `SwarmSelection`, the ONLY thing that calls `select`,
|
|
33
|
-
`deselect` or `critical`, and `registry.js`, which holds one per torrent and
|
|
34
|
-
owns the cross-torrent rule that withholds the speculative levels while
|
|
35
|
-
anything urgent is missing anywhere. See `docs/download-architecture.md`.
|
|
36
|
-
- `container/` — domain layer: `Container` (abstract, RFC 9559 / ISO 14496-12),
|
|
37
|
-
`MatroskaContainer` / `Mp4Container` / `AviContainer`, `ContainerFactory`
|
|
38
|
-
(sniff 16 bytes → precise subclass). See `docs/container-architecture.md`.
|
|
39
|
-
- `tracks/` — domain layer: `ContainerTrack` (base: TrackNumber, declaredIndex,
|
|
40
|
-
language/BCP47, isEnabled/isDefault) → `VideoTrack` / `AudioTrack` /
|
|
41
|
-
`SubtitleTrack` → `TextSubtitleTrack` / `ImageSubtitleTrack`,
|
|
42
|
-
`ExternalSubtitleFile`. Spec-accurate flags (FlagForced only on subtitles per
|
|
43
|
-
RFC 9559 §5.1.4.1, FlagOriginal/Commentary only on audio, tkhd
|
|
44
|
-
track_enabled / alternate_group, elng BCP47).
|
|
45
|
-
- `orchestrators/` — application layer: `ContainerOrchestrator` (detect + per-file
|
|
46
|
-
cache, `getTracks`/`getKeyframeIndex`), `SubtitleOrchestrator` (wraps
|
|
47
|
-
`torrent-worker/subtitle-cues.js`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- `
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- `
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- `
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
# proxy — @torrent-tv/proxy (WebTorrent + ffmpeg)
|
|
2
|
+
|
|
3
|
+
Downloads a torrent and streams the chosen file to the browser, transcoding to
|
|
4
|
+
HLS only when needed. See the parent `../CLAUDE.md` for the overall architecture
|
|
5
|
+
and release process.
|
|
6
|
+
|
|
7
|
+
## Deployment-agnostic — important
|
|
8
|
+
|
|
9
|
+
The HA addon is only ONE way to run this; bare npm and Docker are planned. Keep
|
|
10
|
+
all code free of Home-Assistant assumptions. Anything host-specific (GPU
|
|
11
|
+
devices, ffmpeg build, CLI flags) belongs in the `ha-addon` layer. Hardware
|
|
12
|
+
detection must probe at runtime and fall back gracefully; do not assume a
|
|
13
|
+
Linux-only host (e.g. POSIX-only signals must degrade elsewhere).
|
|
14
|
+
|
|
15
|
+
## Layout
|
|
16
|
+
|
|
17
|
+
- `bin/cli.js` — CLI entry; resolves `ffmpegBin` (uses `--ffmpeg-bin` if given,
|
|
18
|
+
else bundled ffmpeg-static, else PATH `ffmpeg`).
|
|
19
|
+
- `server.js` — Fastify setup; detects the video encoder at startup
|
|
20
|
+
(`detectVideoEncoder`) and passes it to `HlsSessionManager`.
|
|
21
|
+
- `routes/<path>/<method>.js` — same convention as the server repo.
|
|
22
|
+
- `routes/stream/get.js` — byte-range torrent file streaming (HTTP 206).
|
|
23
|
+
- `routes/api/playback-plan/post.js` — codec/container/duration probe result.
|
|
24
|
+
- `routes/transcode/session-file/get.js` — serves HLS playlist/segments;
|
|
25
|
+
long-polls while a segment is being produced, returns retryable 503 (never
|
|
26
|
+
202 — hls.js can't consume it).
|
|
27
|
+
- `services/`:
|
|
28
|
+
- `demand/` — what anybody wants, in BYTES: `Window` (claimant, file, byte
|
|
29
|
+
range, urgency), `Urgency` (BLOCKED / NEAR / AHEAD / TAIL / BEHIND),
|
|
30
|
+
`DemandRegister` (live windows by claimant), `pieces.js` (the one place
|
|
31
|
+
bytes become piece numbers). No WebTorrent, no piece store, no pieces.
|
|
32
|
+
- `download/` — `SwarmSelection`, the ONLY thing that calls `select`,
|
|
33
|
+
`deselect` or `critical`, and `registry.js`, which holds one per torrent and
|
|
34
|
+
owns the cross-torrent rule that withholds the speculative levels while
|
|
35
|
+
anything urgent is missing anywhere. See `docs/download-architecture.md`.
|
|
36
|
+
- `container/` — domain layer: `Container` (abstract, RFC 9559 / ISO 14496-12),
|
|
37
|
+
`MatroskaContainer` / `Mp4Container` / `AviContainer`, `ContainerFactory`
|
|
38
|
+
(sniff 16 bytes → precise subclass). See `docs/container-architecture.md`.
|
|
39
|
+
- `tracks/` — domain layer: `ContainerTrack` (base: TrackNumber, declaredIndex,
|
|
40
|
+
language/BCP47, isEnabled/isDefault) → `VideoTrack` / `AudioTrack` /
|
|
41
|
+
`SubtitleTrack` → `TextSubtitleTrack` / `ImageSubtitleTrack`,
|
|
42
|
+
`ExternalSubtitleFile`. Spec-accurate flags (FlagForced only on subtitles per
|
|
43
|
+
RFC 9559 §5.1.4.1, FlagOriginal/Commentary only on audio, tkhd
|
|
44
|
+
track_enabled / alternate_group, elng BCP47).
|
|
45
|
+
- `orchestrators/` — application layer: `ContainerOrchestrator` (detect + per-file
|
|
46
|
+
cache, `getTracks`/`getKeyframeIndex`), `SubtitleOrchestrator` (wraps
|
|
47
|
+
`torrent-worker/subtitle-cues.js` + `Container` tracks, warm/push). The walk
|
|
48
|
+
itself is `MatroskaContainer.walkHeldClusters` / `Mp4Container.readHeldSamples`;
|
|
49
|
+
`subtitle-cues.js` supplies the torrent's read policy and keeps the cursor.
|
|
50
|
+
- `controllers/` — interface layer: `PlaybackController` / `SubtitleController`
|
|
51
|
+
(thin adapters over orchestrators; routes depend on controllers, not services).
|
|
52
|
+
`routes/*` are now thin HTTP translators.
|
|
53
|
+
- `container-index/` — what is not specific to one media kind: EBML element
|
|
54
|
+
walking and the Matroska/MP4/AVI keyframe tables. Used internally by
|
|
55
|
+
`container/*`, deprecated as a direct import. Everything a container states
|
|
56
|
+
about its own SUBTITLES — track table, Cues, blocks in a cluster, sample
|
|
57
|
+
table, the walk of what is downloaded — lives in the container class.
|
|
58
|
+
- `docs/download-architecture.md` — the two axes of downloading: what is
|
|
59
|
+
wanted (`demand/`) against what the swarm is told (`download/`), why urgency
|
|
60
|
+
is not a number given to the library, and why the speculative levels are
|
|
61
|
+
withdrawn rather than lowered.
|
|
62
|
+
- `docs/logs.md` — where to find logs (HA `docker logs` + `/data/proxy.log`, DO
|
|
63
|
+
`infra-server-1` with forwarded frontend `POST /api/client-logs`). Browser
|
|
64
|
+
console not needed.
|
|
65
|
+
- `playback-planner.js` — single ffmpeg probe returns audioCodec, videoCodec,
|
|
66
|
+
container, durationSeconds. `mode` is advisory; the browser decides.
|
|
67
|
+
- `hls-session-manager.js` — one ffmpeg per (source, file, settings). Serves a
|
|
68
|
+
synthetic full-duration VOD playlist; produces segments on demand; restarts
|
|
69
|
+
ffmpeg at the requested segment for server-side seeking. Short idle TTL.
|
|
70
|
+
Uses the detected `videoEncoder` for video re-encode (copy otherwise).
|
|
71
|
+
- `hwaccel.js` — detect best H.264 encoder (NVENC/QSV/VAAPI/V4L2M2M) with a
|
|
72
|
+
STRICT startup test: encode `testsrc2` through the real HLS pipeline, then
|
|
73
|
+
verify each segment decodes independently (catches non-IDR/corrupted hw
|
|
74
|
+
output). Falls back to software libx264. Runtime fallback to software if a
|
|
75
|
+
hw encode later fails. v4l2m2m is gated by this test (fails on HA Yellow).
|
|
76
|
+
- `data-channel-handler.js` — forwards WebRTC data-channel requests to the
|
|
77
|
+
local HTTP server (loopback), so the same routes serve both transports.
|
|
78
|
+
|
|
79
|
+
## Gotchas
|
|
80
|
+
|
|
81
|
+
- Do NOT use `-hls_playlist_type event` — it breaks duration/seek. VOD only.
|
|
82
|
+
- A transitive dep (`ip-set`, via webtorrent) ships a hostile
|
|
83
|
+
`preinstall: npx only-allow pnpm` that breaks `npm install`. The addon works
|
|
84
|
+
around it with `--ignore-scripts` + a targeted rebuild of `node-datachannel`;
|
|
85
|
+
if you ever change install flow, keep that in mind.
|
|
86
|
+
|
|
87
|
+
## Planned: public reachability (remote access)
|
|
88
|
+
|
|
89
|
+
Decided direction — full plan in the parent `../CLAUDE.md`. Proxy-side pieces:
|
|
90
|
+
|
|
91
|
+
- **Auto port mapping** — IMPLEMENTED (`services/port-mapper.js`, changelog
|
|
92
|
+
2.9.16). UPnP IGD / NAT-PMP via `@silentbot1/nat-api` (now a direct dep; the
|
|
93
|
+
same lib WebTorrent uses for the torrent port). Maps TCP 9090 with a 2 h
|
|
94
|
+
auto-renewed lease, removed on shutdown (lease expiry covers hard kills).
|
|
95
|
+
Best-effort + start/stop timeouts; `--no-port-mapping` opts out;
|
|
96
|
+
`getMappedEndpoint()` exposes the external endpoint. NOT yet done: mapping the
|
|
97
|
+
**UDP** port WebRTC actually uses (it binds ephemeral UDP ports, so this TCP
|
|
98
|
+
mapping does not yet help WebRTC — roadmap step 3 in the parent CLAUDE.md).
|
|
99
|
+
Also pending (next iteration): a success log line in `port-mapper.js` `stop()`
|
|
100
|
+
(`removed mapping for TCP <port>`) — today stop() only logs on failure, so a
|
|
101
|
+
clean unmap on shutdown is silent.
|
|
102
|
+
- **Report endpoint to server** — ✅ DONE (proxy 2.9.17). The mapped endpoint
|
|
103
|
+
is sent over the tunnel (`tunnel-client.sendEndpoint` → `proxy-endpoint`) on
|
|
104
|
+
mapping success and on every tunnel (re)connect; the server dial-back-verifies
|
|
105
|
+
reachability (server 0.8.22, roadmap step 2).
|
|
106
|
+
- **HTTPS listener**: serve the existing routes over TLS with a per-proxy
|
|
107
|
+
certificate delivered by the server through the tunnel (persist cert+key
|
|
108
|
+
locally; ~90-day renewals are pushed the same way). Add CORS headers for the
|
|
109
|
+
web-app origin so hls.js / `<video>` can fetch cross-origin.
|
|
110
|
+
- Plain HTTPS becomes the preferred video transport; WebRTC data channel stays
|
|
111
|
+
as fallback for hosts where no port could be opened.
|
|
112
|
+
- **Later roadmap steps** (single staged roadmap in parent `../CLAUDE.md`,
|
|
113
|
+
WebRTC-first ordering): step 3 map the WebRTC UDP port (fixed
|
|
114
|
+
`portRangeBegin`/`End` + UPnP-map UDP); step 4 birthday-paradox port
|
|
115
|
+
prediction (open ~256 UDP sockets, inject predicted-port ICE candidates) for
|
|
116
|
+
symmetric NAT; step 5 IPv6-first (audit the candidate filter — do not drop
|
|
117
|
+
*global* v6) + STUN NAT pre-classification reported to the registry; then
|
|
118
|
+
the DNS+TLS path (steps 6–7); step 8 relay-then-upgrade (deferred).
|
|
119
|
+
- Future: ed25519 proxy identity (sign announcements), BEP 44 endpoint
|
|
120
|
+
announcements via the `bittorrent-dht` already bundled with WebTorrent.
|
|
121
|
+
|
|
122
|
+
All of this must stay deployment-agnostic (HA addon, bare npm, Docker).
|
|
123
|
+
|
|
124
|
+
## Disk hygiene (open item — torrent data is NOT cleaned up today)
|
|
125
|
+
|
|
126
|
+
HLS segments are handled (`hls-session-manager.js`: idle TTL, `disposeSession`,
|
|
127
|
+
`disposeAll`). Torrent data is **partially** handled: shutdown cleanup is done
|
|
128
|
+
(`TorrentPool.destroyAll()` with `destroyStore: true`, wired into the `onClose`
|
|
129
|
+
hook — proxy 2.9.15), but `deselect()` only stops further download and nothing
|
|
130
|
+
removes a torrent's data **while the proxy keeps running**, nor sweeps orphans
|
|
131
|
+
left by a previous hard kill at startup.
|
|
132
|
+
|
|
133
|
+
Level 1 — remaining: `client.remove(torrent, { destroyStore: true })` on last-
|
|
134
|
+
file refcount 0 + idle TTL (mirror the HLS session model); startup sweep of
|
|
135
|
+
orphaned store dirs under `os.tmpdir()`; global disk cap with LRU eviction of
|
|
136
|
+
whole torrents. (Shutdown teardown ✅ done.)
|
|
137
|
+
Level 2 (research): sliding-window chunk store. Full rationale in the parent
|
|
138
|
+
`../CLAUDE.md` "Disk hygiene" section.
|
|
139
|
+
|
|
140
|
+
## Cloud proxy
|
|
141
|
+
|
|
142
|
+
The same proxy code also runs as the company-hosted fallback when the user
|
|
143
|
+
pool can't serve a viewer. Keep the proxy host-agnostic so it runs unchanged on
|
|
144
|
+
rented infra (flat-rate/unmetered bandwidth — Hetzner dedicated / OVH; NOT
|
|
145
|
+
metered-egress clouds). Provider/economics analysis in the parent
|
|
146
|
+
`../CLAUDE.md` "Cloud proxy" section.
|
|
147
|
+
|
|
148
|
+
## Changelog
|
|
149
|
+
|
|
150
|
+
Every behavioural change must be recorded in `CHANGELOG.md` — add an entry under
|
|
151
|
+
a new `## <version>` heading at the top, following the existing
|
|
152
|
+
`- **New**/**Fix**/**Chore**:` format.
|
|
153
|
+
|
|
154
|
+
**Do NOT edit `package.json` version.** `npm run patch`/`minor` runs `npm
|
|
155
|
+
version …` which bumps it. Write the CHANGELOG entry at the version that bump
|
|
156
|
+
will produce: **current `package.json` version + 1 patch** (or + 1 minor).
|
|
157
|
+
Accumulate bullets into that single pending entry until it's published. See the
|
|
158
|
+
parent `../CLAUDE.md`.
|
|
159
|
+
|
|
160
|
+
## Release
|
|
161
|
+
|
|
162
|
+
`npm run patch` (publishes to npm + pushes tags). The HA addon then needs its
|
|
163
|
+
own version bump to pull the new package. Publish proxy BEFORE bumping the addon.
|
|
164
|
+
|
|
165
|
+
**Any proxy change requires bumping the ha-addon version** (`ha-addon/torrent_tv_proxy/config.yaml`). The addon installs the proxy from npm at build time and the build is cached; without a version bump the plugin will NOT update and keeps running the old proxy. So after `npm run patch`, always bump the addon `config.yaml` version, push, and update the addon.
|