@torrent-tv/proxy 2.88.3 → 2.88.5
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 +16 -0
- package/assets/calibration/audio/NOTICE.md +19 -0
- package/assets/calibration/audio/aac.m4a +0 -0
- package/assets/calibration/audio/ac3.ac3 +0 -0
- package/assets/calibration/audio/alac.m4a +0 -0
- package/assets/calibration/audio/dts.dts +0 -0
- package/assets/calibration/audio/eac3.eac3 +0 -0
- package/assets/calibration/audio/flac.flac +0 -0
- package/assets/calibration/audio/manifest.json +98 -0
- package/assets/calibration/audio/mp2.mp2 +0 -0
- package/assets/calibration/audio/mp3.mp3 +0 -0
- package/assets/calibration/audio/opus.opus +0 -0
- package/assets/calibration/audio/pcm.wav +0 -0
- package/assets/calibration/audio/truehd.thd +0 -0
- package/assets/calibration/audio/vorbis.ogg +0 -0
- package/biome.json +1 -0
- package/docs/browser-proxy-contract.md +155 -133
- package/docs/playback-start-forecast.md +56 -0
- package/package.json +1 -1
- package/routes/api/transcode-sessions/post.js +5 -0
- package/routes/transcode/audio-warm/get.js +14 -3
- package/scripts/build-audio-samples.mjs +31 -0
- package/server.js +5 -0
- package/services/encode/EncodeRuns.js +3 -1
- package/services/encode/OutputOpening.js +40 -15
- package/services/encode/Renditions.js +129 -31
- package/services/encode/args.js +5 -3
- package/services/encode/audio-calibration.js +67 -0
- package/services/encode/audio-work-rate.js +15 -0
- package/services/encode/hwaccel.js +1 -1
- package/services/encode/quality/EncodeCost.js +29 -3
- package/services/encode/quality/link-budget.js +110 -15
- package/services/encode/quality/output-format.js +10 -6
- package/services/encode/segment-formats/fmp4.js +2 -0
- package/services/encode/segment-formats/mp4-boxes.js +12 -1
- package/services/media/audio-inventory.js +60 -6
- package/services/media/container/Container.js +7 -1
- package/services/media/container/Mp4Container.js +103 -3
- package/services/media/tracks/AudioTrack.js +12 -0
- package/services/media/tracks/audio-bound.js +233 -0
- package/services/server/ViewerRequests.js +30 -5
- package/services/server/playback-readiness.js +516 -708
- package/services/server/wire-outputs.js +31 -3
- package/services/storage/piece-store/shared-piece-store.js +6 -0
- package/services/storage/segment-store/SegmentStore.js +19 -4
- package/services/torrent/episode-naming.js +182 -182
- package/services/torrent/torrent-pool.js +13 -0
- package/test/audio-buffer-continuity.test.js +70 -70
- package/test/audio-calibration.test.js +23 -0
- package/test/episode-naming.test.js +131 -131
- package/test/fixtures/audio-frame-boundary.json +1 -1
- package/test/fmp4-presentation-clock.test.js +17 -2
- package/test/link-budget.test.js +7 -6
- package/test/one-output-one-session.test.js +77 -8
- package/test/playback-readiness.test.js +36 -6
- package/test/quality-variants.test.js +36 -8
- package/test/segment-store.test.js +15 -0
- package/test/serving-existing-output.test.js +3 -0
- package/test/soundtrack-rate-bound.test.js +289 -0
- package/test/source-files-route.test.js +113 -113
- package/test/two-viewers-one-picture.test.js +97 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## 2.88.5
|
|
2
|
+
|
|
3
|
+
- **Fix**: Reject closed non-final fragments that do not reach their next declared cut, using measured coded-frame coverage. Remove such previously cached fragments from the inventory so missing media can be produced again instead of indefinitely blocking startup.
|
|
4
|
+
- **Fix**: Compute playback start from measured download, remaining per-run processing and client delivery deadlines, without browser-capacity admission, speculative rate recovery or a capped search. Slow positive service returns a finite delay and locally held input is not downloaded again.
|
|
5
|
+
- **New**: Ship short audio samples and measure per-codec copying and AAC processing at startup. Initial audio sample/byte-work rates and output sizes are refined by actual progress and segment measurements.
|
|
6
|
+
- **Fix**: Apply measured coded-frame join boundaries to video as well as audio, and expose current per-track operations for the waiting overlay.
|
|
7
|
+
|
|
8
|
+
## 2.88.4
|
|
9
|
+
|
|
10
|
+
- **Fix**: A copied soundtrack whose rate no figure states no longer refuses the video on any measured link. The container now states each audio track's codec configuration (Matroska `CodecPrivate`, MP4 `esds`), and a copied AAC-LC or AC-3 track weighs the bound its codec allows (`known`); without one it weighs the rate the file states (`estimated`); and a track with neither is re-encoded to stereo AAC even for a browser that plays it. Field case: the next episode opened on a remembered `Rus Sound` dub was refused at 80 Mbit/s.
|
|
11
|
+
- **Fix**: The soundtrack's mode is decided when it is chosen — when a file is opened, including the next episode with a remembered track, and when the page prepares a switch, where `transcode=0|1` now says whether its browser plays that track — and the viewer is recorded as receiving the output actually sent. Switching no longer carries the previous track's mode onto the new track, and a later reading of the file does not change a soundtrack already being sent.
|
|
12
|
+
- **Fix**: A re-encoded soundtrack counts as 128 kbit/s `estimated` rather than `known`: ffmpeg's AAC encoder treats `-b:a` as a target, and the output frequency is not fixed.
|
|
13
|
+
- **Fix**: A late sidecar header merges into its inventory entry by rule: a stated field replaces the old one, an empty one keeps it, and the codec configuration is replaced only whole, kept when the new reading says less, and withdrawn on a contradiction.
|
|
14
|
+
- **Fix**: The learned cost of a soundtrack is keyed by its file, track and mode, so a copy's speed no longer prices an encode and a sidecar's first track no longer shares the picture's first track's price.
|
|
15
|
+
- **Fix**: A refusal names the part of the load that has no figure, and carries the number of soundtracks the viewer can choose from.
|
|
16
|
+
|
|
1
17
|
## 2.88.3
|
|
2
18
|
|
|
3
19
|
- **Fix**: Forecast audio continuity from coded-frame timestamps and durations using the browser's frame-join boundary. Adjacent AAC fragments that Chrome buffers continuously no longer cause an impossible-start forecast; actual gaps and missing deliveries still prevent an unsafe start. Verified against captured segment metadata and independent Chrome SourceBuffer appends.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Audio calibration samples
|
|
2
|
+
|
|
3
|
+
These files contain deterministic synthetic pink noise, encoded at build time
|
|
4
|
+
by `scripts/build-audio-samples.mjs`. They contain no recorded third-party audio.
|
|
5
|
+
The manifest records the actual duration, channels, sample rate and byte count
|
|
6
|
+
reported by ffprobe. They are distributed under the package's GPL-3.0-or-later
|
|
7
|
+
license.
|
|
8
|
+
|
|
9
|
+
Startup reads each packaged sample once for packet copying and once for AAC
|
|
10
|
+
processing. It neither generates input files nor loops them. The operation
|
|
11
|
+
timeout is the existing encoder benchmark timeout; it is not an ETA coefficient.
|
|
12
|
+
|
|
13
|
+
Process startup is measured separately from FFmpeg's reported processing time;
|
|
14
|
+
its printed clock precision bounds runtime uncertainty. Input reading is
|
|
15
|
+
included in processing. For encoding, the
|
|
16
|
+
reference work scales by input audio samples (channels multiplied by sample
|
|
17
|
+
rate); for copying, a declared source bitrate scales encoded-byte work. These
|
|
18
|
+
are approximate initial rates, replaced by actual per-output progress. Unknown
|
|
19
|
+
dimensions retain the measured reference rather than fabricated metadata.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"codec": "aac",
|
|
4
|
+
"file": "aac.m4a",
|
|
5
|
+
"durationSeconds": 4,
|
|
6
|
+
"channels": 2,
|
|
7
|
+
"samplingFrequency": 48000,
|
|
8
|
+
"bytes": 66060
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"codec": "ac3",
|
|
12
|
+
"file": "ac3.ac3",
|
|
13
|
+
"durationSeconds": 4,
|
|
14
|
+
"channels": 2,
|
|
15
|
+
"samplingFrequency": 48000,
|
|
16
|
+
"bytes": 96000
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"codec": "eac3",
|
|
20
|
+
"file": "eac3.eac3",
|
|
21
|
+
"durationSeconds": 4,
|
|
22
|
+
"channels": 2,
|
|
23
|
+
"samplingFrequency": 48000,
|
|
24
|
+
"bytes": 96000
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"codec": "dts",
|
|
28
|
+
"file": "dts.dts",
|
|
29
|
+
"durationSeconds": 4.0051,
|
|
30
|
+
"channels": 2,
|
|
31
|
+
"samplingFrequency": 48000,
|
|
32
|
+
"bytes": 706500
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"codec": "flac",
|
|
36
|
+
"file": "flac.flac",
|
|
37
|
+
"durationSeconds": 4,
|
|
38
|
+
"channels": 2,
|
|
39
|
+
"samplingFrequency": 48000,
|
|
40
|
+
"bytes": 723158
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"codec": "opus",
|
|
44
|
+
"file": "opus.opus",
|
|
45
|
+
"durationSeconds": 4.0065,
|
|
46
|
+
"channels": 2,
|
|
47
|
+
"samplingFrequency": 48000,
|
|
48
|
+
"bytes": 32457
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"codec": "vorbis",
|
|
52
|
+
"file": "vorbis.ogg",
|
|
53
|
+
"durationSeconds": 4,
|
|
54
|
+
"channels": 2,
|
|
55
|
+
"samplingFrequency": 48000,
|
|
56
|
+
"bytes": 42108
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"codec": "truehd",
|
|
60
|
+
"file": "truehd.thd",
|
|
61
|
+
"durationSeconds": null,
|
|
62
|
+
"channels": 2,
|
|
63
|
+
"samplingFrequency": 48000,
|
|
64
|
+
"bytes": 589492
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"codec": "mp3",
|
|
68
|
+
"file": "mp3.mp3",
|
|
69
|
+
"durationSeconds": 4,
|
|
70
|
+
"channels": 2,
|
|
71
|
+
"samplingFrequency": 48000,
|
|
72
|
+
"bytes": 64748
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"codec": "mp2",
|
|
76
|
+
"file": "mp2.mp2",
|
|
77
|
+
"durationSeconds": 4.008,
|
|
78
|
+
"channels": 2,
|
|
79
|
+
"samplingFrequency": 48000,
|
|
80
|
+
"bytes": 192384
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"codec": "alac",
|
|
84
|
+
"file": "alac.m4a",
|
|
85
|
+
"durationSeconds": 4,
|
|
86
|
+
"channels": 2,
|
|
87
|
+
"samplingFrequency": 48000,
|
|
88
|
+
"bytes": 712871
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"codec": "pcm",
|
|
92
|
+
"file": "pcm.wav",
|
|
93
|
+
"durationSeconds": 4,
|
|
94
|
+
"channels": 2,
|
|
95
|
+
"samplingFrequency": 48000,
|
|
96
|
+
"bytes": 768078
|
|
97
|
+
}
|
|
98
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/biome.json
CHANGED
|
@@ -1,133 +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
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
the
|
|
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
|
-
The
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Playback start forecast
|
|
2
|
+
|
|
3
|
+
For resource r, measured service is S_r(t) = rate_r * t. New observations
|
|
4
|
+
replace the conditional future rate; polling cannot invent acceleration,
|
|
5
|
+
exponential recovery or a new physical measurement.
|
|
6
|
+
|
|
7
|
+
Source work uses the file's measured byte density, missing local byte ranges
|
|
8
|
+
and the observed torrent download rate. Shared input ranges are downloaded
|
|
9
|
+
once. Files on the same torrent share a queue; distinct torrents use distinct
|
|
10
|
+
observed services. Local input reading is included in effective processing
|
|
11
|
+
speed, rather than charged again as a separate disk or memory delay.
|
|
12
|
+
|
|
13
|
+
Each output's remaining processing work subtracts progress only within its
|
|
14
|
+
actual live run intervals. Initial video rates use existing codec/size/encoder
|
|
15
|
+
calibration. Initial audio rates use short packaged per-codec measurements and
|
|
16
|
+
sample work (encoding) or encoded bytes (copying). Actual output progress
|
|
17
|
+
replaces these initial rates. Startup is charged once, separately from processing
|
|
18
|
+
throughput, and disappears once actual run progress establishes processing.
|
|
19
|
+
|
|
20
|
+
Input arrival and processing overlap. With arrival time D(m), processing rate
|
|
21
|
+
e and previous completion C, completion for an interval [a,b] is
|
|
22
|
+
|
|
23
|
+
max(C + (b-a)/e, sup_m(D(m) + (b-m)/e)).
|
|
24
|
+
|
|
25
|
+
Between service boundaries these functions are linear, so only endpoints are
|
|
26
|
+
needed. Client delivery follows production and shares the observed link queue.
|
|
27
|
+
Measured client ranges and prepared media establish each track's deadlines.
|
|
28
|
+
|
|
29
|
+
The minimum start delay for this conditional trajectory is
|
|
30
|
+
|
|
31
|
+
max(0, max_j(completion_j - presentation_deadline_j)).
|
|
32
|
+
|
|
33
|
+
This covers the remaining finite movie, including rates below real time. Slow
|
|
34
|
+
positive service gives a finite preproduction delay; it is not rejected by a
|
|
35
|
+
browser-capacity threshold or a capped search. Observed supply-interruption
|
|
36
|
+
coverage is held in prepared proxy media and client media, without requiring
|
|
37
|
+
the browser to hold it all.
|
|
38
|
+
|
|
39
|
+
Critical review: future rates can change, average byte density is an initial
|
|
40
|
+
mapping rather than an exact packet index, initial audio sample-work scaling
|
|
41
|
+
does not model every decoder's nonlinear cost, and client receive/append time
|
|
42
|
+
is not separately measured. These are approximation limits, not hidden weights.
|
|
43
|
+
Actual progress, segment sizes and link observations refine the prediction.
|
|
44
|
+
The client countdown uses elapsed local monotonic time; expiration cannot
|
|
45
|
+
authorize playback. Only the proxy's readiness and real track coverage can.
|
|
46
|
+
|
|
47
|
+
Zero service, missing media facts and a genuine timestamp gap remain explicit
|
|
48
|
+
noncomputable states. The UI reports their causes instead of an estimating
|
|
49
|
+
placeholder or a fabricated duration. Small coded-frame joins follow measured
|
|
50
|
+
frame durations and Chromium's continuous-track rule, not a hand-picked margin.
|
|
51
|
+
|
|
52
|
+
A closed non-final fragment is reusable only if its measured coded-frame
|
|
53
|
+
coverage reaches its next declared cut. Interrupted one-frame fragments are
|
|
54
|
+
excluded on publication and when reading an existing cache, so the production
|
|
55
|
+
schedule treats their media as unfinished work. The final fragment uses its
|
|
56
|
+
actual media end rather than the container's approximate duration.
|
package/package.json
CHANGED
|
@@ -139,6 +139,11 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
|
|
|
139
139
|
// track that never arrives is otherwise noticed only by its absence,
|
|
140
140
|
// minutes later, as a black picture with working sound.
|
|
141
141
|
tracks: renditions.declaredTracks(session),
|
|
142
|
+
// The soundtrack this viewer is sent and whether it is re-encoded — the
|
|
143
|
+
// proxy's decision, which can differ from what the page asked for: a
|
|
144
|
+
// track no figure states a rate for is re-encoded even for a browser that
|
|
145
|
+
// would play it, so its load on the viewer's link can be asked about.
|
|
146
|
+
soundtrack: renditions.soundtrackOf(session, consumerId),
|
|
142
147
|
// How far ahead of the viewer this proxy lets the encoder run, in seconds
|
|
143
148
|
// of playback. The browser sizes its own forward buffer from it, so the
|
|
144
149
|
// two sides agree by construction instead of each carrying a constant of
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file GET /transcode/:sessionId/a/:track/warm?position=<seconds>
|
|
3
|
-
* audio track before the player is told to change to it.
|
|
2
|
+
* @file GET /transcode/:sessionId/a/:track/warm?position=<seconds>[&transcode=0|1]
|
|
3
|
+
* — prepare an audio track before the player is told to change to it.
|
|
4
|
+
*
|
|
5
|
+
* `transcode` is the page's statement about THIS track: `1` when its browser
|
|
6
|
+
* cannot play the track's codec as it stands, `0` when it can. The proxy then
|
|
7
|
+
* decides how the track is produced by the same rule as when a file is opened
|
|
8
|
+
* — a track nothing states a rate for is re-encoded even for a browser that
|
|
9
|
+
* would play it. Left out, the page is taken to need what it needs for the
|
|
10
|
+
* track it is on.
|
|
4
11
|
*
|
|
5
12
|
* The picture keeps playing while a quality rung is warmed (`variant-warm`),
|
|
6
13
|
* and a track change deserves the same: the player discards the audio it holds
|
|
@@ -44,13 +51,17 @@ export async function handleTranscodeAudioWarmGet(req, reply, { renditions, serv
|
|
|
44
51
|
return reply.code(400).send({ error: "A consumer is required." });
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
const statedNeed = req.query?.transcode;
|
|
55
|
+
const browserPlays = statedNeed === "0" ? true : statedNeed === "1" ? false : null;
|
|
56
|
+
|
|
47
57
|
let prepared;
|
|
48
58
|
try {
|
|
49
59
|
prepared = await renditions.prepareAudioTrack(
|
|
50
60
|
baseSessionId,
|
|
51
61
|
trackIndex,
|
|
52
62
|
positionSeconds,
|
|
53
|
-
consumerId
|
|
63
|
+
consumerId,
|
|
64
|
+
browserPlays
|
|
54
65
|
);
|
|
55
66
|
} catch (error) {
|
|
56
67
|
const message = error instanceof Error ? error.message : String(error);
|