@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.
- package/AGENTS.md +32 -12
- package/CHANGELOG.md +2340 -2330
- package/assets/calibration/audio/NOTICE.md +19 -19
- package/assets/calibration/audio/manifest.json +98 -98
- package/biome.json +412 -412
- package/docs/browser-proxy-contract.md +155 -155
- package/docs/container-architecture.md +273 -273
- package/docs/encode-run-state.md +1 -1
- package/docs/playback-start-forecast.md +113 -98
- package/package.json +51 -50
- package/routes/api/sources/files/get.js +106 -106
- package/scripts/build-audio-samples.mjs +31 -31
- package/services/encode/EncodeRun.js +67 -15
- package/services/encode/EncodeRuns.js +19 -7
- package/services/encode/OutputOpening.js +784 -784
- package/services/encode/audio-calibration.js +67 -67
- package/services/encode/audio-work-rate.js +15 -15
- package/services/encode/encode-exit.js +2 -0
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/output/index.js +24 -24
- package/services/encode/piece-completeness.js +60 -2
- package/services/encode/run-command.js +757 -755
- package/services/encode/segment-formats/index.js +1 -0
- package/services/encode/segment-formats/mp4-boxes.js +8 -1
- package/services/media/ContainerOrchestrator.js +202 -202
- package/services/media/KeyframeTables.js +356 -356
- package/services/media/SubtitleCues.js +592 -592
- package/services/media/audio-inventory.js +299 -299
- package/services/media/container/AviContainer.js +268 -268
- package/services/media/container/Container.js +632 -632
- package/services/media/container/ContainerFactory.js +38 -38
- package/services/media/container/MatroskaContainer.js +1031 -1031
- package/services/media/container/Mp4Container.js +1354 -1354
- package/services/media/container/SubtitleFileContainer.js +339 -339
- package/services/media/container/ebml-stream.js +174 -174
- package/services/media/container/matroska-clusters.js +1165 -1165
- package/services/media/container/unavailable.js +77 -77
- package/services/media/tracks/ContainerTrack.js +84 -84
- package/services/media/tracks/TextSubtitleTrack.js +295 -295
- package/services/storage/piece-store/shared-piece-store.js +1937 -1937
- package/services/storage/segment-store/SegmentStore.js +1294 -1294
- package/services/torrent/Contents.js +382 -382
- package/services/torrent/episode-naming.js +182 -182
- package/services/torrent/torrent-pool.js +2688 -2688
- package/services/torrent/worker/client.js +836 -836
- package/services/torrent/worker/held-bytes.js +231 -231
- package/services/torrent/worker/worker.js +1137 -1137
- package/services/transport/data-channel-handler.js +1373 -1373
- package/services/viewer/PriorityMap.js +421 -421
- package/test/audio-calibration.test.js +23 -23
- package/test/auto-quality-step.test.js +762 -762
- package/test/coalesce.test.js +39 -39
- package/test/coverage-follows-the-disk.test.js +5 -0
- package/test/decode-cost.test.js +475 -475
- package/test/encode-run.test.js +58 -0
- package/test/episode-naming.test.js +131 -131
- package/test/failed-encoder-still-serves-what-exists.test.js +1 -1
- package/test/fixtures/audio-frame-boundary.json +1 -1
- package/test/fixtures/refused-audio-20261002.json +10 -0
- package/test/fmp4-presentation-clock.test.js +4 -2
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +11 -13
- package/test/held-read-ownership.test.js +77 -77
- package/test/helpers/encode-run.js +14 -6
- package/test/helpers/held-file.js +77 -77
- package/test/helpers/manager.js +62 -62
- package/test/helpers/matroska-file.js +296 -296
- package/test/keyframe-table-budget.test.js +123 -123
- package/test/matroska-cluster-discovery.test.js +323 -323
- package/test/one-output-one-session.test.js +273 -273
- package/test/output-speed.test.js +6 -1
- package/test/piece-completeness.test.js +37 -1
- package/test/quality-variants.test.js +1274 -1270
- package/test/rate-control-in-the-output.test.js +4 -3
- package/test/reads-not-yet-arrived.test.js +218 -218
- package/test/seek-frees-the-viewers-requests.test.js +1 -1
- package/test/segment-closed-on-both-branches.test.js +114 -114
- package/test/segment-judged-by-its-own-header.test.js +222 -222
- package/test/segment-serve-wiring.test.js +481 -481
- package/test/serving-existing-output.test.js +111 -111
- package/test/source-files-route.test.js +113 -113
- package/test/subtitle-cue-walk.test.js +385 -385
- package/test/subtitle-metadata.test.js +15 -15
- package/test/subtitle-plan-waits.test.js +125 -125
- package/test/subtitle-track-numbering.test.js +391 -391
- package/test/two-viewers-one-picture.test.js +508 -508
- package/test/viewer-requests-admission.test.js +92 -92
- package/test/viewer-starts-on-map.test.js +27 -27
- package/test/viewer-states.test.js +5 -2
- package/test/viewing-generations.test.js +390 -390
- package/test/whole-file-held-read.test.js +30 -30
- package/utils/coalesce.js +41 -41
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
|
|
2
|
-
export { PLAYLIST_FILE_NAME } from "./playlists.js";
|
|
3
|
-
|
|
4
|
-
// FFmpeg may close a segment this far before its requested cut. Both the
|
|
5
|
-
// command and coverage validation use this existing muxer configuration.
|
|
6
|
-
export const SEGMENT_CUT_TIME_DELTA_SECONDS = 0.05;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The code an error carries when no output suits the viewer: nothing this proxy
|
|
10
|
-
* could produce or already holds is admitted by their link. Its `details` say
|
|
11
|
-
* why and with which figures. A definition shared by both sides of the
|
|
12
|
-
* interface — the encoding that decides it and the routes that answer it.
|
|
13
|
-
*/
|
|
14
|
-
export const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The code an error carries when THIS MACHINE cannot take the output: no mode
|
|
18
|
-
* of its encoder was shown at startup to hold the picture at any size this
|
|
19
|
-
* viewer could be given, or the machine has no place for one more encoder
|
|
20
|
-
* beside what already runs (roadmap item 97, step 14). Not the viewer's link —
|
|
21
|
-
* another proxy may well serve it, and the page asks the pool before anything
|
|
22
|
-
* plays. Its `details` say why, with the figures.
|
|
23
|
-
*/
|
|
24
|
-
export const OUTPUT_NO_CAPACITY = "OUTPUT_NO_CAPACITY";
|
|
1
|
+
export { OutputSpec, VideoOutput, AudioOutput, CutGrid, isOutputName } from "./OutputSpec.js";
|
|
2
|
+
export { PLAYLIST_FILE_NAME } from "./playlists.js";
|
|
3
|
+
|
|
4
|
+
// FFmpeg may close a segment this far before its requested cut. Both the
|
|
5
|
+
// command and coverage validation use this existing muxer configuration.
|
|
6
|
+
export const SEGMENT_CUT_TIME_DELTA_SECONDS = 0.05;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The code an error carries when no output suits the viewer: nothing this proxy
|
|
10
|
+
* could produce or already holds is admitted by their link. Its `details` say
|
|
11
|
+
* why and with which figures. A definition shared by both sides of the
|
|
12
|
+
* interface — the encoding that decides it and the routes that answer it.
|
|
13
|
+
*/
|
|
14
|
+
export const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The code an error carries when THIS MACHINE cannot take the output: no mode
|
|
18
|
+
* of its encoder was shown at startup to hold the picture at any size this
|
|
19
|
+
* viewer could be given, or the machine has no place for one more encoder
|
|
20
|
+
* beside what already runs (roadmap item 97, step 14). Not the viewer's link —
|
|
21
|
+
* another proxy may well serve it, and the page asks the pool before anything
|
|
22
|
+
* plays. Its `details` say why, with the figures.
|
|
23
|
+
*/
|
|
24
|
+
export const OUTPUT_NO_CAPACITY = "OUTPUT_NO_CAPACITY";
|
|
@@ -24,11 +24,17 @@ import { SEGMENT_CUT_TIME_DELTA_SECONDS } from "./output/index.js";
|
|
|
24
24
|
*/
|
|
25
25
|
export function cutOf(timeline, index) {
|
|
26
26
|
return index < timeline.segmentCount - 1
|
|
27
|
-
? timeline.publishedStartOf(index + 1) -
|
|
27
|
+
? (Math.round(timeline.publishedStartOf(index + 1) * 1_000_000) -
|
|
28
|
+
Math.round(SEGMENT_CUT_TIME_DELTA_SECONDS * 1_000_000)) / 1_000_000
|
|
28
29
|
: undefined;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
33
|
+
* Track positions read from empty edits retain their movie-tick uncertainty.
|
|
34
|
+
* Fresh closures also carry the muxer's packet end and its actual cut, so a
|
|
35
|
+
* restarted run is judged on its own reference clock. Neither allowance is a
|
|
36
|
+
* browser's frame-join rule.
|
|
37
|
+
*
|
|
32
38
|
* @param {{ producedThroughSeconds?: (ranges: object) => number | null }} format
|
|
33
39
|
* @param {object} ranges - The piece's media intervals, as the format read them.
|
|
34
40
|
* @param {number | undefined} cutSeconds - From {@link cutOf}.
|
|
@@ -39,8 +45,60 @@ export function judgePiece(format, ranges, cutSeconds) {
|
|
|
39
45
|
if (throughSeconds === null) {
|
|
40
46
|
return { whole: false, throughSeconds };
|
|
41
47
|
}
|
|
42
|
-
|
|
48
|
+
const cutMicros = ranges?.production?.cutMicros ??
|
|
49
|
+
(Number.isFinite(cutSeconds) ? BigInt(Math.round(cutSeconds * 1_000_000)) : null);
|
|
50
|
+
if (cutMicros !== null && ranges?.tracks?.length) {
|
|
51
|
+
const tracks = ranges.tracks;
|
|
52
|
+
const production = ranges.production;
|
|
53
|
+
const reference = production ? tracks.find(({ kind }) => kind === production.kind) : null;
|
|
54
|
+
const referenceEnd = reference?.ranges.at(-1)?.end;
|
|
55
|
+
const whole = tracks.every((track) => {
|
|
56
|
+
const { timescale, ranges: held, positionErrorTicks = 0n } = track;
|
|
57
|
+
const end = held.at(-1)?.end;
|
|
58
|
+
if (end === undefined) return false;
|
|
59
|
+
if (referenceEnd !== undefined) {
|
|
60
|
+
if (track === reference) return 2n * production.endMicros + 1n >= 2n * cutMicros;
|
|
61
|
+
// The segment muxer's CSV reports packet time before movenc truncates
|
|
62
|
+
// the empty edit. Compare on that clock, including another track's
|
|
63
|
+
// relative end and the precision of both written track positions.
|
|
64
|
+
const difference = (end + positionErrorTicks) * reference.timescale -
|
|
65
|
+
(referenceEnd - (reference.positionErrorTicks ?? 0n)) * timescale;
|
|
66
|
+
const denominator = timescale * reference.timescale;
|
|
67
|
+
return 2n * difference * 1_000_000n +
|
|
68
|
+
(2n * production.endMicros + 1n) * denominator >= 2n * cutMicros * denominator;
|
|
69
|
+
}
|
|
70
|
+
return (end + positionErrorTicks) * 1_000_000n >= cutMicros * timescale;
|
|
71
|
+
});
|
|
72
|
+
return { whole, throughSeconds };
|
|
73
|
+
}
|
|
74
|
+
if (cutMicros !== null && throughSeconds < Number(cutMicros) / 1_000_000) {
|
|
43
75
|
return { whole: false, throughSeconds };
|
|
44
76
|
}
|
|
45
77
|
return { whole: true, throughSeconds };
|
|
46
78
|
}
|
|
79
|
+
|
|
80
|
+
/** The muxer cuts relative to its first reference packet, not the requested seek. */
|
|
81
|
+
export function productionOf(timing, cutTimes, relativeIndex, kind) {
|
|
82
|
+
if (!timing || timing.originMicros === null || !Number.isFinite(cutTimes?.[relativeIndex])) return null;
|
|
83
|
+
return {
|
|
84
|
+
kind,
|
|
85
|
+
endMicros: timing.endMicros,
|
|
86
|
+
cutMicros: timing.originMicros + BigInt(Math.round(cutTimes[relativeIndex] * 1_000_000)) -
|
|
87
|
+
BigInt(Math.round(SEGMENT_CUT_TIME_DELTA_SECONDS * 1_000_000))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The first CSV entry has start_time = 0 even after a seek (segment.c).
|
|
93
|
+
* Recover its reference packet's PTS from the reported end minus the exact
|
|
94
|
+
* sample span. The edit-list offset cancels, including its lost precision.
|
|
95
|
+
*/
|
|
96
|
+
export function originOf(ranges, endMicros, kind) {
|
|
97
|
+
const track = ranges?.tracks?.find(track => track.kind === kind);
|
|
98
|
+
if (track?.firstSampleStart === undefined || !track.ranges.length) return null;
|
|
99
|
+
const ticks = endMicros * track.timescale -
|
|
100
|
+
(track.ranges.at(-1).end - track.firstSampleStart) * 1_000_000n;
|
|
101
|
+
// CSV rounds to microseconds; rescale with the muxer's nearest rounding.
|
|
102
|
+
return ticks >= 0n ? (2n * ticks + track.timescale) / (2n * track.timescale) :
|
|
103
|
+
-((-2n * ticks + track.timescale) / (2n * track.timescale));
|
|
104
|
+
}
|