@torrent-tv/proxy 2.89.6 → 2.89.8

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.
Files changed (92) hide show
  1. package/AGENTS.md +32 -12
  2. package/CHANGELOG.md +2340 -2330
  3. package/assets/calibration/audio/NOTICE.md +19 -19
  4. package/assets/calibration/audio/manifest.json +98 -98
  5. package/biome.json +412 -412
  6. package/docs/browser-proxy-contract.md +155 -155
  7. package/docs/container-architecture.md +273 -273
  8. package/docs/encode-run-state.md +1 -1
  9. package/docs/playback-start-forecast.md +113 -98
  10. package/package.json +51 -50
  11. package/routes/api/sources/files/get.js +106 -106
  12. package/scripts/build-audio-samples.mjs +31 -31
  13. package/services/encode/EncodeRun.js +67 -15
  14. package/services/encode/EncodeRuns.js +19 -7
  15. package/services/encode/OutputOpening.js +784 -784
  16. package/services/encode/audio-calibration.js +67 -67
  17. package/services/encode/audio-work-rate.js +15 -15
  18. package/services/encode/encode-exit.js +2 -0
  19. package/services/encode/hwaccel.js +1894 -1894
  20. package/services/encode/output/index.js +24 -24
  21. package/services/encode/piece-completeness.js +60 -2
  22. package/services/encode/run-command.js +757 -755
  23. package/services/encode/segment-formats/index.js +1 -0
  24. package/services/encode/segment-formats/mp4-boxes.js +8 -1
  25. package/services/media/ContainerOrchestrator.js +202 -202
  26. package/services/media/KeyframeTables.js +356 -356
  27. package/services/media/SubtitleCues.js +592 -592
  28. package/services/media/audio-inventory.js +299 -299
  29. package/services/media/container/AviContainer.js +268 -268
  30. package/services/media/container/Container.js +632 -632
  31. package/services/media/container/ContainerFactory.js +38 -38
  32. package/services/media/container/MatroskaContainer.js +1031 -1031
  33. package/services/media/container/Mp4Container.js +1354 -1354
  34. package/services/media/container/SubtitleFileContainer.js +339 -339
  35. package/services/media/container/ebml-stream.js +174 -174
  36. package/services/media/container/matroska-clusters.js +1165 -1165
  37. package/services/media/container/unavailable.js +77 -77
  38. package/services/media/tracks/ContainerTrack.js +84 -84
  39. package/services/media/tracks/TextSubtitleTrack.js +295 -295
  40. package/services/storage/piece-store/shared-piece-store.js +1937 -1937
  41. package/services/storage/segment-store/SegmentStore.js +1294 -1294
  42. package/services/torrent/Contents.js +382 -382
  43. package/services/torrent/episode-naming.js +182 -182
  44. package/services/torrent/torrent-pool.js +2688 -2688
  45. package/services/torrent/worker/client.js +836 -836
  46. package/services/torrent/worker/held-bytes.js +231 -231
  47. package/services/torrent/worker/worker.js +1137 -1137
  48. package/services/transport/data-channel-handler.js +1373 -1373
  49. package/services/viewer/PriorityMap.js +421 -421
  50. package/test/audio-calibration.test.js +23 -23
  51. package/test/auto-quality-step.test.js +762 -762
  52. package/test/coalesce.test.js +39 -39
  53. package/test/coverage-follows-the-disk.test.js +5 -0
  54. package/test/decode-cost.test.js +475 -475
  55. package/test/encode-run.test.js +58 -0
  56. package/test/episode-naming.test.js +131 -131
  57. package/test/failed-encoder-still-serves-what-exists.test.js +1 -1
  58. package/test/fixtures/audio-frame-boundary.json +1 -1
  59. package/test/fixtures/refused-audio-20261002.json +10 -0
  60. package/test/fmp4-presentation-clock.test.js +4 -2
  61. package/test/format-keyed-outputs.test.js +78 -78
  62. package/test/health-metrics.test.js +11 -13
  63. package/test/held-read-ownership.test.js +77 -77
  64. package/test/helpers/encode-run.js +14 -6
  65. package/test/helpers/held-file.js +77 -77
  66. package/test/helpers/manager.js +62 -62
  67. package/test/helpers/matroska-file.js +296 -296
  68. package/test/keyframe-table-budget.test.js +123 -123
  69. package/test/matroska-cluster-discovery.test.js +323 -323
  70. package/test/one-output-one-session.test.js +273 -273
  71. package/test/output-speed.test.js +6 -1
  72. package/test/piece-completeness.test.js +37 -1
  73. package/test/quality-variants.test.js +1274 -1270
  74. package/test/rate-control-in-the-output.test.js +4 -3
  75. package/test/reads-not-yet-arrived.test.js +218 -218
  76. package/test/seek-frees-the-viewers-requests.test.js +1 -1
  77. package/test/segment-closed-on-both-branches.test.js +114 -114
  78. package/test/segment-judged-by-its-own-header.test.js +222 -222
  79. package/test/segment-serve-wiring.test.js +481 -481
  80. package/test/serving-existing-output.test.js +111 -111
  81. package/test/source-files-route.test.js +113 -113
  82. package/test/subtitle-cue-walk.test.js +385 -385
  83. package/test/subtitle-metadata.test.js +15 -15
  84. package/test/subtitle-plan-waits.test.js +125 -125
  85. package/test/subtitle-track-numbering.test.js +391 -391
  86. package/test/two-viewers-one-picture.test.js +508 -508
  87. package/test/viewer-requests-admission.test.js +92 -92
  88. package/test/viewer-starts-on-map.test.js +27 -27
  89. package/test/viewer-states.test.js +5 -2
  90. package/test/viewing-generations.test.js +390 -390
  91. package/test/whole-file-held-read.test.js +30 -30
  92. package/utils/coalesce.js +41 -41
@@ -1,155 +1,155 @@
1
- # Browser–proxy playback contract
2
-
3
- This document records the HTTP contract used by the browser playback code and
4
- implemented by this proxy. The same paths and payloads pass through either the
5
- direct HTTP transport or the WebRTC data channel. It is a description of the
6
- current code, not a generated schema or a version negotiation protocol.
7
-
8
- ## Source and file discovery
9
-
10
- | Request | Required input | Successful answer |
11
- |---|---|---|
12
- | `POST /api/sources` | `{ sourceType, source }` | `{ sourceKey }` identifies the registered source. |
13
- | `GET /api/sources/:sourceKey/files?maxWaitMs=N` | Registered `sourceKey`; optional wait budget | While magnet metadata is unavailable: `{ pending: true }`. When ready: `{ name, infoHash, files, items, shape }`. `files` carries `fileIndex`, `name`, `relativePath`, `length`, and `kind`; `items` maps each video `fileIndex` to audio, subtitle, and image file indices and carries `episode` — what the name states in the release's own numbering (`season`, `episodes`, `part`, `special`, `showHint`, `titleHint`) or `null`. `shape` is `single`, `series` or `undetermined` (2.88.0); an older proxy sends neither field. |
14
- | `GET /api/sources/:sourceKey/stats?fileIndex=N` | Registered source; optional file index | Current peer, transfer, header, and file progress readings. A reading may not contain every measurement; absent or `null` measurements are unknown. |
15
- | `POST /api/sources/:sourceKey/warm` | Optional `{ fileIndex, positionSeconds }`; an empty object warms source metadata and the first video | `{ started, swarm, edges, sidecars }` reports which preparation started. Work continues in the background. |
16
- | `GET /api/link-probe?bytes=N` | Positive byte count | A no-store binary body of zero bytes, capped at 2 MiB. The browser measures completed transfer time; an unavailable route leaves link speed unknown. |
17
-
18
- `pending: true` means the requested metadata or plan is not ready yet. The
19
- browser polls again. It does not mean that the torrent has no video or that the
20
- request failed. A non-pending file-list answer uses the proxy's file indices;
21
- the browser must keep those indices when opening a file.
22
-
23
- ## Playback plan and transcode session
24
-
25
- `POST /api/playback-plan` takes `{ sourceKey, fileIndex, userAgent }`. Its answer
26
- contains `mode`, `directUrl`, codec and container names, duration and source
27
- dimensions, track inventories, paired sidecar files, `offeredHeights`,
28
- `audioTracksPending`, and `pending`. The browser uses the codec fields to make
29
- the direct-versus-transcode decision itself; `mode` is advisory.
30
-
31
- When `audioTracksPending` is `true`, one or more sidecar headers were still
32
- unavailable. The browser may call `POST /api/playback-plan/audio-tracks` with
33
- `{ sourceKey, fileIndex }`; the answer contains the current `audioTracks` and
34
- `pending`. A resolved header updates metadata on the already-published first
35
- track for that sidecar. It never changes track indices or the number of HLS
36
- renditions in the active plan.
37
-
38
- Each `audioTracks` entry may carry `bitrateKbps`, the average rate the file
39
- states for the track, and `peakKbps`, the most the track's codec configuration
40
- allows it to carry (AAC-LC with 1024-sample frames, and AC-3; any other
41
- configuration has none). Both may be `null`. The browser does not need them;
42
- the proxy uses them to decide how the track is produced. A late header keeps a
43
- known value and withdraws `peakKbps` only when it contradicts the configuration
44
- it was computed from.
45
-
46
- `sidecarSubtitles` lists subtitle files the proxy paired with this video. Each
47
- entry carries the torrent `fileIndex` and the proxy's filename-derived language
48
- and release metadata. The browser uses these entries to fetch subtitle files
49
- and to identify a remembered choice. `sidecarImages` is returned beside it but
50
- is not needed for playback. Older proxies and plans made with transcoding
51
- disabled may omit these fields; the browser treats a missing or non-array list
52
- as empty.
53
-
54
- The browser maps a missing `mode` to `direct`, uses its registered `/stream`
55
- URL when `directUrl` is absent, maps missing codec/container names to empty
56
- strings, maps missing duration or dimensions to `0`, and maps missing audio or
57
- subtitle track arrays to `[]`. It maps missing or non-object `offeredHeights` to
58
- `null`; for an object value, missing or invalid `copy` and `transcode` lists
59
- become empty arrays. `null` means no offer was supplied, while empty lists
60
- explicitly offer no heights for their respective playback branch. Only
61
- `pending: true` asks the browser to poll again.
62
-
63
- `POST /api/transcode-sessions` requires `sourceKey`, `fileIndex`, and
64
- `consumerId`. The browser also sends the requested audio/video transcode flags,
65
- the visible picture size, whether audio is requested as separate renditions,
66
- the start position, selected audio track, and segment format when known. The
67
- answer contains `sessionId`, `playlistPath`, `offeredHeights`, declared `tracks`,
68
- `soundtrack`, and `lookaheadSeconds`. `masterPath` and `variantHeight` are
69
- present only when the session has quality variants.
70
-
71
- `transcodeAudio` states whether the browser can play the selected track as it
72
- is. The proxy decides the actual mode: it copies a track the browser plays
73
- when the track has a `peakKbps` or a `bitrateKbps`, and otherwise re-encodes it
74
- to stereo AAC at 128 kbit/s, including a track the browser could play.
75
- `soundtrack` is `{ trackIndex, transcode }`, the track this viewer is sent and
76
- whether it is re-encoded. Older proxies omit it.
77
-
78
- The browser interprets `409` with `outcome: "output-unavailable"` as a refusal
79
- for this viewer's link and `outcome: "no-capacity"` as a proxy capacity
80
- refusal. Other non-success statuses remain errors; an error body only adds
81
- display detail and is not required for status handling. For
82
- `output-unavailable`, `figures.verdict` is `"no safe bound"` when a part of the
83
- load has no figure; `figures.videoClass` and `figures.audioClass` are
84
- `"unknown"` for that part, and `figures.soundtracks` is the number of
85
- soundtracks the viewer can choose from.
86
-
87
- ## Session control and media files
88
-
89
- | Request | Meaning |
90
- |---|---|
91
- | `POST /api/transcode-sessions/:id/seek` | `{ positionSeconds, consumerId, generation }` reports the viewer's seek. A successful answer is `204`; the proxy handles the seek server side. |
92
- | `GET /api/transcode-sessions/:id/progress?consumer=:id` | Reads current session progress and also keeps the viewer's session alive. `playbackReadiness` is the proxy's versioned forecast used to release the startup wait; missing or unsupported versions do not authorize playback. An unreadable or failed answer does not imply that playback has ended. |
93
- | `POST /api/transcode-sessions/:id/net-report` | `{ consumerId, bufferedAheadSec, ... }` reports the viewer's buffer and optional state/link measurements. `consumerId` and non-negative `bufferedAheadSec` are required. `linkMbps` is the smoothed estimate used by the quality budget; `linkSampleMbps` and `linkSampleAt` carry the newest raw delivery measurement and its sample time for the readiness forecast. `bufferLimitSeconds` states the browser's accepted forward-buffer ceiling. Missing optional values do not reject the report or erase the last known value. Missing `playing` becomes `false`; missing `waiting` is derived from the playback and buffer state. Missing `positionSeconds` leaves the position unchanged, missing `qualityMode` leaves the prior mode, `onScreen` defaults to `true`, and `inPictureInPicture` defaults to `false`. Success is `204`. |
94
- | `POST /api/transcode-sessions/:id/fragment-far` | Diagnostic fragment and buffer positions; does not change encoding. Best effort, success `204`. |
95
- | `POST /api/transcode-sessions/:id/release` | `{ consumerId, reason }` releases this viewer's session assignment. |
96
- | `GET /transcode/:id/:fileName` | HLS playlist, init, or media segment. A file that is still being produced is answered with retryable `503`, not `202`. |
97
- | `GET /transcode/:id/v/:height/warm` and `/transcode/:id/a/:track/warm` | Prepare the requested video height or audio track at a position. `204` means ready. Audio warm-up `404` means the proxy does not support this operation for that session. Audio warm-up takes an optional `transcode=0\|1`: whether the browser cannot play THAT track's codec as it is. The proxy decides the track's mode by the rule used when a file is opened; without the parameter it assumes the browser needs what it needed for its current track. |
98
- | `GET /stream?sourceKey=…&fileIndex=N` | Direct file bytes with HTTP range support. |
99
-
100
- ### Playback readiness forecast
101
-
102
- The progress response may include `playbackReadiness`:
103
-
104
- | Field | Meaning |
105
- |---|---|
106
- | `version` | Forecast contract version. The browser releases its startup wait only for version `1` with `ready: true`. |
107
- | `ready` | The measured service model meets the remaining presentation deadlines and real client tracks cover the current position. Browser capacity is not an admission condition. |
108
- | `delaySeconds` | When `ready` is false, the predicted minimum delay from now to a safe start; `null` means the current measurements do not support a finite forecast. |
109
- | `measuredAt`, `predictedStartAt` | Proxy-clock observation and conditional start time, in milliseconds. The UI counts down using its own elapsed clock, without comparing clocks across machines. |
110
- | `operations` | Required video/audio operations, their source/output codecs, current speed and processed position. |
111
- | `preparedUntilSeconds` | Continuous proxy media predicted to be available at the forecast start. |
112
- | `bufferedSeconds` | Client buffer reported to the proxy. |
113
- | `reserveSeconds` | Maximum interruption reserve across the required video and selected audio outputs, derived from each output's measured supply waits and next segment duration. |
114
- | `neededSeconds` | Reserve shortfall at the current client buffer. |
115
- | `reason` | Machine-readable result of the forecast. |
116
- | `preparedSegments` | Number of already-produced output segments available across required tracks. |
117
- | `bufferedAtStartSeconds` | Forecast playable buffer at the proposed start time, when a finite delay is available. |
118
-
119
- The forecast considers the video output and the selected audio output when audio
120
- is delivered separately. A mixed output can depend on more than one source
121
- file. Repeated source files are counted once; distinct files keep separate
122
- remaining-work estimates and share the measured download rate of their torrent.
123
- The browser reports buffer state and delivery measurements, but does not
124
- calculate or override readiness.
125
-
126
- Service work is the integral of a nonnegative measured-rate forecast. The latest
127
- rate relaxes over the observed measurement span toward the lower of its latest
128
- value and the recent mean; the measurement history covers the output look-ahead.
129
- A positive measured rate cannot become permanently zero from an old declining
130
- linear fit. A measured zero remains zero until a new measurement arrives.
131
- The reported loader limit is bounded below by the browser buffer already held.
132
- During startup waiting the browser refreshes stale delivery measurements through
133
- the existing link probe and reports their age to avoid clock-offset errors.
134
-
135
- ## Subtitles
136
-
137
- `GET /api/subtitles` requires `sourceKey` and `fileIndex`. Without `trackIndex`
138
- it reads a paired sidecar file. With `trackIndex`, it reads cues from an
139
- embedded text track and may take `consumerId` to subscribe that viewer to
140
- subsequent cue pushes. `since` asks for cues after a previously received
141
- cursor.
142
-
143
- `200` returns WebVTT. Embedded cue answers may include
144
- `X-Subtitle-Cursor`, `X-Subtitle-Covered-Clusters`, and
145
- `X-Subtitle-Indexed-Clusters`; language detection may include
146
- `X-Subtitle-Language` and `X-Subtitle-Language-Name`. While an embedded track
147
- is being extracted, the response is `202` with `{ pending: true }`; the browser
148
- waits and repeats the request. A missing cursor or language header means that
149
- the browser has no value to preserve or display yet. A failed sidecar or
150
- embedded-track request skips that track without failing video playback.
151
-
152
- Subtitle cue pushes are delivered through the established proxy connection,
153
- not by a separate browser polling route. After reconnect, the browser repeats
154
- the embedded-track request with `since` to restore the subscription and receive
155
- missed cues.
1
+ # Browser–proxy playback contract
2
+
3
+ This document records the HTTP contract used by the browser playback code and
4
+ implemented by this proxy. The same paths and payloads pass through either the
5
+ direct HTTP transport or the WebRTC data channel. It is a description of the
6
+ current code, not a generated schema or a version negotiation protocol.
7
+
8
+ ## Source and file discovery
9
+
10
+ | Request | Required input | Successful answer |
11
+ |---|---|---|
12
+ | `POST /api/sources` | `{ sourceType, source }` | `{ sourceKey }` identifies the registered source. |
13
+ | `GET /api/sources/:sourceKey/files?maxWaitMs=N` | Registered `sourceKey`; optional wait budget | While magnet metadata is unavailable: `{ pending: true }`. When ready: `{ name, infoHash, files, items, shape }`. `files` carries `fileIndex`, `name`, `relativePath`, `length`, and `kind`; `items` maps each video `fileIndex` to audio, subtitle, and image file indices and carries `episode` — what the name states in the release's own numbering (`season`, `episodes`, `part`, `special`, `showHint`, `titleHint`) or `null`. `shape` is `single`, `series` or `undetermined` (2.88.0); an older proxy sends neither field. |
14
+ | `GET /api/sources/:sourceKey/stats?fileIndex=N` | Registered source; optional file index | Current peer, transfer, header, and file progress readings. A reading may not contain every measurement; absent or `null` measurements are unknown. |
15
+ | `POST /api/sources/:sourceKey/warm` | Optional `{ fileIndex, positionSeconds }`; an empty object warms source metadata and the first video | `{ started, swarm, edges, sidecars }` reports which preparation started. Work continues in the background. |
16
+ | `GET /api/link-probe?bytes=N` | Positive byte count | A no-store binary body of zero bytes, capped at 2 MiB. The browser measures completed transfer time; an unavailable route leaves link speed unknown. |
17
+
18
+ `pending: true` means the requested metadata or plan is not ready yet. The
19
+ browser polls again. It does not mean that the torrent has no video or that the
20
+ request failed. A non-pending file-list answer uses the proxy's file indices;
21
+ the browser must keep those indices when opening a file.
22
+
23
+ ## Playback plan and transcode session
24
+
25
+ `POST /api/playback-plan` takes `{ sourceKey, fileIndex, userAgent }`. Its answer
26
+ contains `mode`, `directUrl`, codec and container names, duration and source
27
+ dimensions, track inventories, paired sidecar files, `offeredHeights`,
28
+ `audioTracksPending`, and `pending`. The browser uses the codec fields to make
29
+ the direct-versus-transcode decision itself; `mode` is advisory.
30
+
31
+ When `audioTracksPending` is `true`, one or more sidecar headers were still
32
+ unavailable. The browser may call `POST /api/playback-plan/audio-tracks` with
33
+ `{ sourceKey, fileIndex }`; the answer contains the current `audioTracks` and
34
+ `pending`. A resolved header updates metadata on the already-published first
35
+ track for that sidecar. It never changes track indices or the number of HLS
36
+ renditions in the active plan.
37
+
38
+ Each `audioTracks` entry may carry `bitrateKbps`, the average rate the file
39
+ states for the track, and `peakKbps`, the most the track's codec configuration
40
+ allows it to carry (AAC-LC with 1024-sample frames, and AC-3; any other
41
+ configuration has none). Both may be `null`. The browser does not need them;
42
+ the proxy uses them to decide how the track is produced. A late header keeps a
43
+ known value and withdraws `peakKbps` only when it contradicts the configuration
44
+ it was computed from.
45
+
46
+ `sidecarSubtitles` lists subtitle files the proxy paired with this video. Each
47
+ entry carries the torrent `fileIndex` and the proxy's filename-derived language
48
+ and release metadata. The browser uses these entries to fetch subtitle files
49
+ and to identify a remembered choice. `sidecarImages` is returned beside it but
50
+ is not needed for playback. Older proxies and plans made with transcoding
51
+ disabled may omit these fields; the browser treats a missing or non-array list
52
+ as empty.
53
+
54
+ The browser maps a missing `mode` to `direct`, uses its registered `/stream`
55
+ URL when `directUrl` is absent, maps missing codec/container names to empty
56
+ strings, maps missing duration or dimensions to `0`, and maps missing audio or
57
+ subtitle track arrays to `[]`. It maps missing or non-object `offeredHeights` to
58
+ `null`; for an object value, missing or invalid `copy` and `transcode` lists
59
+ become empty arrays. `null` means no offer was supplied, while empty lists
60
+ explicitly offer no heights for their respective playback branch. Only
61
+ `pending: true` asks the browser to poll again.
62
+
63
+ `POST /api/transcode-sessions` requires `sourceKey`, `fileIndex`, and
64
+ `consumerId`. The browser also sends the requested audio/video transcode flags,
65
+ the visible picture size, whether audio is requested as separate renditions,
66
+ the start position, selected audio track, and segment format when known. The
67
+ answer contains `sessionId`, `playlistPath`, `offeredHeights`, declared `tracks`,
68
+ `soundtrack`, and `lookaheadSeconds`. `masterPath` and `variantHeight` are
69
+ present only when the session has quality variants.
70
+
71
+ `transcodeAudio` states whether the browser can play the selected track as it
72
+ is. The proxy decides the actual mode: it copies a track the browser plays
73
+ when the track has a `peakKbps` or a `bitrateKbps`, and otherwise re-encodes it
74
+ to stereo AAC at 128 kbit/s, including a track the browser could play.
75
+ `soundtrack` is `{ trackIndex, transcode }`, the track this viewer is sent and
76
+ whether it is re-encoded. Older proxies omit it.
77
+
78
+ The browser interprets `409` with `outcome: "output-unavailable"` as a refusal
79
+ for this viewer's link and `outcome: "no-capacity"` as a proxy capacity
80
+ refusal. Other non-success statuses remain errors; an error body only adds
81
+ display detail and is not required for status handling. For
82
+ `output-unavailable`, `figures.verdict` is `"no safe bound"` when a part of the
83
+ load has no figure; `figures.videoClass` and `figures.audioClass` are
84
+ `"unknown"` for that part, and `figures.soundtracks` is the number of
85
+ soundtracks the viewer can choose from.
86
+
87
+ ## Session control and media files
88
+
89
+ | Request | Meaning |
90
+ |---|---|
91
+ | `POST /api/transcode-sessions/:id/seek` | `{ positionSeconds, consumerId, generation }` reports the viewer's seek. A successful answer is `204`; the proxy handles the seek server side. |
92
+ | `GET /api/transcode-sessions/:id/progress?consumer=:id` | Reads current session progress and also keeps the viewer's session alive. `playbackReadiness` is the proxy's versioned forecast used to release the startup wait; missing or unsupported versions do not authorize playback. An unreadable or failed answer does not imply that playback has ended. |
93
+ | `POST /api/transcode-sessions/:id/net-report` | `{ consumerId, bufferedAheadSec, ... }` reports the viewer's buffer and optional state/link measurements. `consumerId` and non-negative `bufferedAheadSec` are required. `linkMbps` is the smoothed estimate used by the quality budget; `linkSampleMbps` and `linkSampleAt` carry the newest raw delivery measurement and its sample time for the readiness forecast. `bufferLimitSeconds` states the browser's accepted forward-buffer ceiling. Missing optional values do not reject the report or erase the last known value. Missing `playing` becomes `false`; missing `waiting` is derived from the playback and buffer state. Missing `positionSeconds` leaves the position unchanged, missing `qualityMode` leaves the prior mode, `onScreen` defaults to `true`, and `inPictureInPicture` defaults to `false`. Success is `204`. |
94
+ | `POST /api/transcode-sessions/:id/fragment-far` | Diagnostic fragment and buffer positions; does not change encoding. Best effort, success `204`. |
95
+ | `POST /api/transcode-sessions/:id/release` | `{ consumerId, reason }` releases this viewer's session assignment. |
96
+ | `GET /transcode/:id/:fileName` | HLS playlist, init, or media segment. A file that is still being produced is answered with retryable `503`, not `202`. |
97
+ | `GET /transcode/:id/v/:height/warm` and `/transcode/:id/a/:track/warm` | Prepare the requested video height or audio track at a position. `204` means ready. Audio warm-up `404` means the proxy does not support this operation for that session. Audio warm-up takes an optional `transcode=0\|1`: whether the browser cannot play THAT track's codec as it is. The proxy decides the track's mode by the rule used when a file is opened; without the parameter it assumes the browser needs what it needed for its current track. |
98
+ | `GET /stream?sourceKey=…&fileIndex=N` | Direct file bytes with HTTP range support. |
99
+
100
+ ### Playback readiness forecast
101
+
102
+ The progress response may include `playbackReadiness`:
103
+
104
+ | Field | Meaning |
105
+ |---|---|
106
+ | `version` | Forecast contract version. The browser releases its startup wait only for version `1` with `ready: true`. |
107
+ | `ready` | The measured service model meets the remaining presentation deadlines and real client tracks cover the current position. Browser capacity is not an admission condition. |
108
+ | `delaySeconds` | When `ready` is false, the predicted minimum delay from now to a safe start; `null` means the current measurements do not support a finite forecast. |
109
+ | `measuredAt`, `predictedStartAt` | Proxy-clock observation and conditional start time, in milliseconds. The UI counts down using its own elapsed clock, without comparing clocks across machines. |
110
+ | `operations` | Required video/audio operations, their source/output codecs, current speed and processed position. |
111
+ | `preparedUntilSeconds` | Continuous proxy media predicted to be available at the forecast start. |
112
+ | `bufferedSeconds` | Client buffer reported to the proxy. |
113
+ | `reserveSeconds` | Maximum interruption reserve across the required video and selected audio outputs, derived from each output's measured supply waits and next segment duration. |
114
+ | `neededSeconds` | Reserve shortfall at the current client buffer. |
115
+ | `reason` | Machine-readable result of the forecast. |
116
+ | `preparedSegments` | Number of already-produced output segments available across required tracks. |
117
+ | `bufferedAtStartSeconds` | Forecast playable buffer at the proposed start time, when a finite delay is available. |
118
+
119
+ The forecast considers the video output and the selected audio output when audio
120
+ is delivered separately. A mixed output can depend on more than one source
121
+ file. Repeated source files are counted once; distinct files keep separate
122
+ remaining-work estimates and share the measured download rate of their torrent.
123
+ The browser reports buffer state and delivery measurements, but does not
124
+ calculate or override readiness.
125
+
126
+ Service work is the integral of a nonnegative measured-rate forecast. The latest
127
+ rate relaxes over the observed measurement span toward the lower of its latest
128
+ value and the recent mean; the measurement history covers the output look-ahead.
129
+ A positive measured rate cannot become permanently zero from an old declining
130
+ linear fit. A measured zero remains zero until a new measurement arrives.
131
+ The reported loader limit is bounded below by the browser buffer already held.
132
+ During startup waiting the browser refreshes stale delivery measurements through
133
+ the existing link probe and reports their age to avoid clock-offset errors.
134
+
135
+ ## Subtitles
136
+
137
+ `GET /api/subtitles` requires `sourceKey` and `fileIndex`. Without `trackIndex`
138
+ it reads a paired sidecar file. With `trackIndex`, it reads cues from an
139
+ embedded text track and may take `consumerId` to subscribe that viewer to
140
+ subsequent cue pushes. `since` asks for cues after a previously received
141
+ cursor.
142
+
143
+ `200` returns WebVTT. Embedded cue answers may include
144
+ `X-Subtitle-Cursor`, `X-Subtitle-Covered-Clusters`, and
145
+ `X-Subtitle-Indexed-Clusters`; language detection may include
146
+ `X-Subtitle-Language` and `X-Subtitle-Language-Name`. While an embedded track
147
+ is being extracted, the response is `202` with `{ pending: true }`; the browser
148
+ waits and repeats the request. A missing cursor or language header means that
149
+ the browser has no value to preserve or display yet. A failed sidecar or
150
+ embedded-track request skips that track without failing video playback.
151
+
152
+ Subtitle cue pushes are delivered through the established proxy connection,
153
+ not by a separate browser polling route. After reconnect, the browser repeats
154
+ the embedded-track request with `since` to restore the subscription and receive
155
+ missed cues.