@torrent-tv/proxy 2.89.17 → 2.89.18
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/README.md +14 -3
- package/package.json +10 -2
- package/server.js +0 -11
- package/.claude/commands/opsx/apply.md +0 -155
- package/.claude/commands/opsx/archive.md +0 -160
- package/.claude/commands/opsx/explore.md +0 -174
- package/.claude/commands/opsx/propose.md +0 -109
- package/.claude/commands/opsx/sync.md +0 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +0 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +0 -117
- package/.claude/skills/openspec-explore/SKILL.md +0 -289
- package/.claude/skills/openspec-propose/SKILL.md +0 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +0 -147
- package/.dockerignore +0 -5
- package/AGENTS.md +0 -363
- package/CHANGELOG.md +0 -2377
- package/Dockerfile +0 -30
- package/biome.json +0 -412
- package/corpus.tmp.json +0 -1
- package/docs/browser-proxy-contract.md +0 -155
- package/docs/container-architecture.md +0 -273
- package/docs/download-architecture.md +0 -261
- package/docs/encode-architecture.md +0 -765
- package/docs/encode-run-state.md +0 -96
- package/docs/logs.md +0 -151
- package/docs/playback-start-forecast.md +0 -113
- package/docs/socks5-egress-research.md +0 -114
- package/docs/storage-architecture.md +0 -273
- package/knip.json +0 -14
- package/main2.cpuprofile +0 -1
- package/openspec/changes/adaptive-bitrate/.openspec.yaml +0 -0
- package/openspec/changes/adaptive-bitrate/design.md +0 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +0 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +0 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +0 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +0 -2
- package/openspec/changes/binary-distribution/proposal.md +0 -53
- package/openspec/changes/chunked-request-bodies/design.md +0 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +0 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +0 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +0 -40
- package/openspec/changes/cold-start/design.md +0 -98
- package/openspec/changes/cold-start/proposal.md +0 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +0 -47
- package/openspec/changes/cold-start/tasks.md +0 -48
- package/openspec/changes/disk-cap/.openspec.yaml +0 -2
- package/openspec/changes/disk-cap/proposal.md +0 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +0 -25
- package/openspec/changes/disk-cap/tasks.md +0 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +0 -2
- package/openspec/changes/proxy-observability/design.md +0 -38
- package/openspec/changes/proxy-observability/proposal.md +0 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +0 -33
- package/openspec/changes/proxy-observability/tasks.md +0 -21
- package/openspec/changes/seek-debounce/.openspec.yaml +0 -0
- package/openspec/changes/seek-debounce/design.md +0 -118
- package/openspec/changes/seek-debounce/proposal.md +0 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +0 -41
- package/openspec/changes/seek-debounce/tasks.md +0 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +0 -2
- package/openspec/changes/subtitle-language/proposal.md +0 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +0 -34
- package/openspec/changes/subtitle-language/tasks.md +0 -26
- package/openspec/changes/track-selection/.openspec.yaml +0 -2
- package/openspec/changes/track-selection/design.md +0 -47
- package/openspec/changes/track-selection/proposal.md +0 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +0 -41
- package/openspec/changes/track-selection/tasks.md +0 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +0 -2
- package/openspec/changes/transcode-quality/proposal.md +0 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +0 -139
- package/openspec/changes/transcode-quality/tasks.md +0 -63
- package/openspec/config.yaml +0 -38
- package/public/.well-known/appspecific/com.chrome.devtools.json +0 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +0 -329
- package/research/double-spawn-2026-09-10.md +0 -171
- package/research/handover-2026-09-13.md +0 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +0 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +0 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +0 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +0 -225
- package/research/valgrind-replica-2026-08-27/logs.tar.gz +0 -0
- package/scripts/build-audio-samples.mjs +0 -31
- package/scripts/render-run-graph.js +0 -173
- package/test/asset-paths.test.js +0 -28
- package/test/audio-buffer-continuity.test.js +0 -77
- package/test/audio-calibration.test.js +0 -23
- package/test/audio-inventory.test.js +0 -212
- package/test/audio-track-bitrate.test.js +0 -40
- package/test/audio-warm-recorded.test.js +0 -63
- package/test/auto-quality-step.test.js +0 -762
- package/test/available-share.test.js +0 -76
- package/test/background-fill.test.js +0 -190
- package/test/client-log-file.test.js +0 -126
- package/test/client-logs-route.test.js +0 -34
- package/test/coalesce.test.js +0 -39
- package/test/completed-files.test.js +0 -115
- package/test/concurrent-cost.test.js +0 -148
- package/test/container-media-info.test.js +0 -228
- package/test/contention.test.js +0 -76
- package/test/core-dumps.test.js +0 -57
- package/test/coverage-follows-the-disk.test.js +0 -233
- package/test/coverage-map.test.js +0 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
- package/test/cut-times-timeline.test.js +0 -65
- package/test/cuts-follow-published-grid.test.js +0 -109
- package/test/data-channel-frame.test.js +0 -63
- package/test/data-channel-viewer-lifecycle.test.js +0 -193
- package/test/dead-torrent-handle.test.js +0 -52
- package/test/declared-rates.test.js +0 -116
- package/test/declared-tracks.test.js +0 -43
- package/test/decode-cost-fit.test.js +0 -202
- package/test/decode-cost.test.js +0 -475
- package/test/delivery-probe-field.test.js +0 -173
- package/test/delivery-probe.test.js +0 -382
- package/test/demand-register.test.js +0 -206
- package/test/dht-bootstrap.test.js +0 -72
- package/test/drain-threat.test.js +0 -65
- package/test/duplicate-add.test.js +0 -48
- package/test/encode-admission.test.js +0 -170
- package/test/encode-cost.test.js +0 -275
- package/test/encode-exit.test.js +0 -111
- package/test/encode-orchestrator.test.js +0 -426
- package/test/encode-plan-viewers.test.js +0 -725
- package/test/encode-plan.test.js +0 -545
- package/test/encode-run-state.test.js +0 -314
- package/test/encode-run.test.js +0 -248
- package/test/encode-slope.test.js +0 -46
- package/test/encoded-output.test.js +0 -39
- package/test/encoder-kinds.test.js +0 -122
- package/test/encoder-readings.test.js +0 -68
- package/test/episode-naming.test.js +0 -131
- package/test/every-claimant-is-told-its-share.test.js +0 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
- package/test/failed-start-breaker.test.js +0 -202
- package/test/fastest-wires.test.js +0 -143
- package/test/ffmpeg-stream-counts.test.js +0 -101
- package/test/file-edges.test.js +0 -191
- package/test/files-in-use.test.js +0 -34
- package/test/fixtures/audio-frame-boundary.json +0 -1
- package/test/fixtures/refused-audio-20261002.json +0 -10
- package/test/fmp4-presentation-clock.test.js +0 -148
- package/test/fmp4-self-contained.test.js +0 -80
- package/test/fmp4-timestamps.test.js +0 -140
- package/test/fmp4-track-completeness.test.js +0 -41
- package/test/format-keyed-outputs.test.js +0 -78
- package/test/health-metrics.test.js +0 -36
- package/test/held-read-ownership.test.js +0 -77
- package/test/held-request-width.test.js +0 -157
- package/test/helpers/encode-run.js +0 -248
- package/test/helpers/held-file.js +0 -77
- package/test/helpers/manager.js +0 -62
- package/test/helpers/matroska-file.js +0 -296
- package/test/helpers/output-spec.js +0 -63
- package/test/host-load.test.js +0 -68
- package/test/index-accuracy-is-about-the-file.test.js +0 -128
- package/test/init-compat.test.js +0 -219
- package/test/input-lost-quiets-the-plan.test.js +0 -261
- package/test/input-unavailable.test.js +0 -48
- package/test/installation-id.test.js +0 -39
- package/test/keeping-period.test.js +0 -83
- package/test/keyframe-index-accuracy.test.js +0 -188
- package/test/keyframe-table-budget.test.js +0 -123
- package/test/keyframes-belong-to-the-file.test.js +0 -249
- package/test/learned-median.test.js +0 -37
- package/test/letting-go-asks-who-is-watching.test.js +0 -191
- package/test/limit-row-by-area.test.js +0 -60
- package/test/link-budget.test.js +0 -111
- package/test/link-probe.test.js +0 -56
- package/test/local-encoding-data.test.js +0 -273
- package/test/logger-repeats.test.js +0 -134
- package/test/look-ahead-coverage.test.js +0 -56
- package/test/manager-surface.test.js +0 -291
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cluster-discovery.test.js +0 -323
- package/test/matroska-cues-track.test.js +0 -192
- package/test/media-time.test.js +0 -62
- package/test/memory-budget.test.js +0 -390
- package/test/move-cost.test.js +0 -216
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +0 -173
- package/test/one-authority.test.js +0 -285
- package/test/one-budget.test.js +0 -149
- package/test/one-output-one-session.test.js +0 -273
- package/test/one-resource-divided.test.js +0 -185
- package/test/one-serving-output-per-height.test.js +0 -458
- package/test/orchestrator-wired.test.js +0 -207
- package/test/output-catalog.test.js +0 -39
- package/test/output-shape.test.js +0 -21
- package/test/output-spec.test.js +0 -251
- package/test/output-speed.test.js +0 -92
- package/test/outputs-being-prepared.test.js +0 -40
- package/test/packet-witness-ring.test.js +0 -236
- package/test/packet-witness.test.js +0 -148
- package/test/piece-completeness.test.js +0 -78
- package/test/piece-disk-store.test.js +0 -401
- package/test/piece-from-whole-file.test.js +0 -129
- package/test/piece-lru.test.js +0 -319
- package/test/piece-reader.test.js +0 -162
- package/test/piece-selection-offset.test.js +0 -85
- package/test/piece-store-eviction.test.js +0 -595
- package/test/piece-store-never-refuses.test.js +0 -153
- package/test/piece-store-reservations.test.js +0 -403
- package/test/piece-store-slow-disk.test.js +0 -159
- package/test/piece-tail.test.js +0 -97
- package/test/plan-host-timings.test.js +0 -68
- package/test/plan-is-steady.test.js +0 -225
- package/test/playback-readiness.test.js +0 -518
- package/test/playback-start-regressions.test.js +0 -101
- package/test/priority-map-download.test.js +0 -174
- package/test/priority-map-emptied.test.js +0 -204
- package/test/priority-map-per-output.test.js +0 -215
- package/test/priority-map.test.js +0 -163
- package/test/probe-wedge-certainty.test.js +0 -60
- package/test/produced-copy-choice.test.js +0 -213
- package/test/published-timeline.test.js +0 -83
- package/test/quality-offer.test.js +0 -160
- package/test/quality-variants.test.js +0 -1274
- package/test/rate-control-in-the-output.test.js +0 -181
- package/test/read-survives-withdrawal.test.js +0 -164
- package/test/read-window.test.js +0 -365
- package/test/reads-not-yet-arrived.test.js +0 -218
- package/test/resume-warm.test.js +0 -39
- package/test/run-graph-drift.test.js +0 -37
- package/test/run-inputs.test.js +0 -80
- package/test/run-intervals.test.js +0 -100
- package/test/run-position-follows-published-grid.test.js +0 -89
- package/test/run-progress.test.js +0 -64
- package/test/same-height-switch.test.js +0 -663
- package/test/seek-frees-the-viewers-requests.test.js +0 -217
- package/test/seek-landing.test.js +0 -109
- package/test/seek-target-not-superseded.test.js +0 -195
- package/test/segment-closed-on-both-branches.test.js +0 -114
- package/test/segment-cut-times.test.js +0 -63
- package/test/segment-demand.test.js +0 -80
- package/test/segment-hold-supersede.test.js +0 -91
- package/test/segment-judged-by-its-own-header.test.js +0 -222
- package/test/segment-serve-wiring.test.js +0 -481
- package/test/segment-store-eviction.test.js +0 -247
- package/test/segment-store.test.js +0 -352
- package/test/segment-true-start.test.js +0 -86
- package/test/segments-are-shared.test.js +0 -176
- package/test/send-chunk-bytes.test.js +0 -86
- package/test/serving-existing-output.test.js +0 -111
- package/test/serving-output.test.js +0 -102
- package/test/shared-piece-store.test.js +0 -310
- package/test/sidecar-naming.test.js +0 -142
- package/test/soundtrack-rate-bound.test.js +0 -289
- package/test/source-file.test.js +0 -133
- package/test/source-files-route.test.js +0 -113
- package/test/source-registry.test.js +0 -18
- package/test/split-a-stretch.test.js +0 -139
- package/test/stale-request-after-seek.test.js +0 -158
- package/test/startup-readings.test.js +0 -124
- package/test/stream-route.test.js +0 -237
- package/test/subtitle-cue-framing.test.js +0 -200
- package/test/subtitle-cue-source.test.js +0 -102
- package/test/subtitle-cue-walk.test.js +0 -385
- package/test/subtitle-cursor.test.js +0 -63
- package/test/subtitle-defaults.test.js +0 -118
- package/test/subtitle-language.test.js +0 -252
- package/test/subtitle-metadata.test.js +0 -15
- package/test/subtitle-plan-waits.test.js +0 -125
- package/test/subtitle-track-numbering.test.js +0 -391
- package/test/supply-margin.test.js +0 -190
- package/test/swarm-follows-readers.test.js +0 -174
- package/test/swarm-reach.test.js +0 -130
- package/test/swarm-selection.test.js +0 -220
- package/test/tail-duplication.test.js +0 -167
- package/test/the-channel-has-both-ends.test.js +0 -91
- package/test/the-torrent-thread-serves-bytes.test.js +0 -107
- package/test/thread-pool.test.js +0 -53
- package/test/timeline.test.js +0 -84
- package/test/torrent-contents.test.js +0 -235
- package/test/torrent-cost.test.js +0 -71
- package/test/torrent-files.test.js +0 -221
- package/test/torrent-naming.test.js +0 -255
- package/test/tracks-begin-together.test.js +0 -209
- package/test/tunnel-renewal.test.js +0 -167
- package/test/two-viewers-one-picture.test.js +0 -508
- package/test/upload-hurry.test.js +0 -186
- package/test/usrsctp-state.test.js +0 -131
- package/test/video-facts.test.js +0 -102
- package/test/viewer-assignments.test.js +0 -143
- package/test/viewer-outputs.test.js +0 -285
- package/test/viewer-presence.test.js +0 -116
- package/test/viewer-requests-admission.test.js +0 -92
- package/test/viewer-starts-on-map.test.js +0 -27
- package/test/viewer-states.test.js +0 -192
- package/test/viewer-subtitle-subscription.test.js +0 -96
- package/test/viewer.test.js +0 -139
- package/test/viewing-generations.test.js +0 -390
- package/test/visible-rung.test.js +0 -37
- package/test/wait-ledger.test.js +0 -100
- package/test/webrtc-shim.test.js +0 -149
- package/test/wedge-certainty.test.js +0 -131
- package/test/whole-file-held-read.test.js +0 -30
- package/test/withdraw-piece-claim.test.js +0 -212
- package/test/worker-channel.test.js +0 -158
package/test/run-inputs.test.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What an encoder is told to read.
|
|
3
|
-
*
|
|
4
|
-
* The encoder's own read of its source is the one read that tracks where the
|
|
5
|
-
* viewer is (`reader=playback`), and the only one sized in seconds of playback
|
|
6
|
-
* (`windowBytes`). Both travel on the input address. When the address stopped
|
|
7
|
-
* being kept on the session and started being built per run, both were left
|
|
8
|
-
* off it: every encoder read looked to the stream route like a probe, and the
|
|
9
|
-
* read window fell back to the reader's default.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import test from "node:test";
|
|
13
|
-
import assert from "node:assert/strict";
|
|
14
|
-
import { encoderInputs } from "../services/encode/run-inputs.js";
|
|
15
|
-
|
|
16
|
-
const BASE = "http://127.0.0.1:9090";
|
|
17
|
-
|
|
18
|
-
function file(fileIndex) {
|
|
19
|
-
return {
|
|
20
|
-
fileIndex,
|
|
21
|
-
streamUrl(baseUrl, { sessionId = "" } = {}) {
|
|
22
|
-
const url = new URL("/stream", `${baseUrl}/`);
|
|
23
|
-
url.searchParams.set("fileIndex", String(fileIndex));
|
|
24
|
-
if (sessionId) {
|
|
25
|
-
url.searchParams.set("session", sessionId);
|
|
26
|
-
}
|
|
27
|
-
return url;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
test("the picture's read says it is the playback read, with its window", () => {
|
|
33
|
-
const picture = file(0);
|
|
34
|
-
const { inputUrl } = encoderInputs({
|
|
35
|
-
picture,
|
|
36
|
-
soundtrack: picture,
|
|
37
|
-
carries: "video-only",
|
|
38
|
-
audioSeparate: true,
|
|
39
|
-
sessionId: "aaaaaaaabbbbcccc",
|
|
40
|
-
readWindowBytes: 32_000_000,
|
|
41
|
-
baseUrl: BASE
|
|
42
|
-
});
|
|
43
|
-
const url = new URL(inputUrl);
|
|
44
|
-
assert.equal(url.searchParams.get("reader"), "playback");
|
|
45
|
-
assert.equal(url.searchParams.get("windowBytes"), "32000000");
|
|
46
|
-
assert.equal(url.searchParams.get("session"), "aaaaaaaabbbbcccc");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("with no window measured the read states neither parameter", () => {
|
|
50
|
-
const picture = file(0);
|
|
51
|
-
const { inputUrl } = encoderInputs({
|
|
52
|
-
picture,
|
|
53
|
-
soundtrack: picture,
|
|
54
|
-
carries: "muxed",
|
|
55
|
-
audioSeparate: false,
|
|
56
|
-
sessionId: "aaaaaaaabbbbcccc",
|
|
57
|
-
readWindowBytes: 0,
|
|
58
|
-
baseUrl: BASE
|
|
59
|
-
});
|
|
60
|
-
const url = new URL(inputUrl);
|
|
61
|
-
assert.equal(url.searchParams.get("windowBytes"), null);
|
|
62
|
-
assert.equal(url.searchParams.get("reader"), null, "exactly as before: both are set together or neither");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("a soundtrack in its own file is read directly by its rendition, and is a second input only when muxed", () => {
|
|
66
|
-
const picture = file(0);
|
|
67
|
-
const dub = file(3);
|
|
68
|
-
const rendition = encoderInputs({
|
|
69
|
-
picture, soundtrack: dub, carries: "audio-only", audioSeparate: true,
|
|
70
|
-
sessionId: "s", readWindowBytes: 1000, baseUrl: BASE
|
|
71
|
-
});
|
|
72
|
-
assert.equal(new URL(rendition.inputUrl).searchParams.get("fileIndex"), "3");
|
|
73
|
-
assert.equal(rendition.audioInputUrl, "");
|
|
74
|
-
const muxed = encoderInputs({
|
|
75
|
-
picture, soundtrack: dub, carries: "muxed", audioSeparate: false,
|
|
76
|
-
sessionId: "s", readWindowBytes: 1000, baseUrl: BASE
|
|
77
|
-
});
|
|
78
|
-
assert.equal(new URL(muxed.inputUrl).searchParams.get("fileIndex"), "0");
|
|
79
|
-
assert.equal(new URL(muxed.audioInputUrl).searchParams.get("fileIndex"), "3");
|
|
80
|
-
});
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file How far a run may work, and who decides it.
|
|
3
|
-
*
|
|
4
|
-
* These cases used to be asked of `planRunInterval`, a second authority inside
|
|
5
|
-
* the session manager that answered by its own rules: it walked the whole track
|
|
6
|
-
* for the first free number, MOVED the start there, and counted every live run
|
|
7
|
-
* as claiming up to `head + look-ahead`. It contradicted the plan directly, and
|
|
8
|
-
* the two together produced the field oscillation of 2026-09-05 — the plan
|
|
9
|
-
* commanded a start at #46, this moved it to #78, the plan killed the run for
|
|
10
|
-
* standing outside the window it had asked for, and the same start was
|
|
11
|
-
* commanded again, 350-700ms per cycle, no segment ever produced, the viewer's
|
|
12
|
-
* picture stopped for 125 seconds.
|
|
13
|
-
*
|
|
14
|
-
* It is gone. WHERE a run starts is the plan's decision and nothing moves it;
|
|
15
|
-
* HOW FAR it may work is a fact of the one coverage map, and that is what these
|
|
16
|
-
* cases now ask. The map is the same object the plan reads, so there is no
|
|
17
|
-
* second set of rules for the two to disagree about.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import test from "node:test";
|
|
21
|
-
import assert from "node:assert/strict";
|
|
22
|
-
import { CoverageMap } from "../services/encode/CoverageMap.js";
|
|
23
|
-
|
|
24
|
-
/** A stand-in for a run: the map identifies one by being it. */
|
|
25
|
-
function run(name) {
|
|
26
|
-
return { name };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test("with nothing made, a run gets everything from where it was asked", () => {
|
|
30
|
-
const coverage = new CoverageMap({ segmentCount: 100 });
|
|
31
|
-
|
|
32
|
-
assert.equal(coverage.freeRunFrom(0), 100, "the whole track is free");
|
|
33
|
-
assert.equal(coverage.firstGapFrom(0), 0);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("a run stops before material that is already made", () => {
|
|
37
|
-
const coverage = new CoverageMap({ segmentCount: 100 });
|
|
38
|
-
coverage.setReady([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
|
|
39
|
-
|
|
40
|
-
assert.equal(coverage.freeRunFrom(0), 10, "0..9, and it stops where 10 begins");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("a run stops before a stretch another live run was given", () => {
|
|
44
|
-
const coverage = new CoverageMap({ segmentCount: 100 });
|
|
45
|
-
const other = run("other");
|
|
46
|
-
coverage.claim(other, 40, 60);
|
|
47
|
-
|
|
48
|
-
assert.equal(coverage.freeRunFrom(0), 40, "0..39, and the other run's claim begins at 40");
|
|
49
|
-
assert.equal(coverage.firstGapFrom(45), 61, "the first thing nobody holds after it");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("a run's own claim does not hold it back", () => {
|
|
53
|
-
const coverage = new CoverageMap({ segmentCount: 100 });
|
|
54
|
-
const mine = run("mine");
|
|
55
|
-
coverage.claim(mine, 40, 60);
|
|
56
|
-
|
|
57
|
-
assert.equal(
|
|
58
|
-
coverage.freeRunFrom(40, mine),
|
|
59
|
-
60,
|
|
60
|
-
"asked by the run that holds it, the stretch is its own to work through"
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("a run that has ended holds nothing back", () => {
|
|
65
|
-
const coverage = new CoverageMap({ segmentCount: 100 });
|
|
66
|
-
const dead = run("dead");
|
|
67
|
-
coverage.claim(dead, 40, 60);
|
|
68
|
-
coverage.release(dead);
|
|
69
|
-
|
|
70
|
-
assert.equal(coverage.freeRunFrom(0), 100, "what it did not finish is free again");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("what a dead run DID finish stays made", () => {
|
|
74
|
-
const coverage = new CoverageMap({ segmentCount: 100 });
|
|
75
|
-
const dead = run("dead");
|
|
76
|
-
coverage.claim(dead, 40, 60);
|
|
77
|
-
coverage.setReady([40, 41, 42]);
|
|
78
|
-
coverage.release(dead);
|
|
79
|
-
|
|
80
|
-
assert.equal(coverage.firstGapFrom(40), 43, "a closed file is closed whoever made it");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("nothing left to make is answered with nothing", () => {
|
|
84
|
-
const coverage = new CoverageMap({ segmentCount: 5 });
|
|
85
|
-
coverage.setReady([0, 1, 2, 3, 4]);
|
|
86
|
-
|
|
87
|
-
assert.equal(coverage.firstGapFrom(0), null, "and a run started here would only repeat somebody's work");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("a run without an end does not take the film away from a viewer further in", () => {
|
|
91
|
-
// The case that used to need a second authority's `head + look-ahead` guess.
|
|
92
|
-
// A run's claim is now the stretch the plan GAVE it, so a viewer opening the
|
|
93
|
-
// same film in the middle finds their own position free.
|
|
94
|
-
const coverage = new CoverageMap({ segmentCount: 1000 });
|
|
95
|
-
const first = run("first");
|
|
96
|
-
coverage.claim(first, 0, 499);
|
|
97
|
-
|
|
98
|
-
assert.equal(coverage.firstGapFrom(500), 500, "the second viewer's own position is free");
|
|
99
|
-
assert.equal(coverage.freeRunFrom(500), 500, "and everything from there to the end");
|
|
100
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file A run is POSITIONED where the player was told the segment begins.
|
|
3
|
-
*
|
|
4
|
-
* The companion of `cuts-follow-published-grid`, and the half that was missing.
|
|
5
|
-
* 2.45.0 moved the cut list onto the published table and left the position on
|
|
6
|
-
* the live one — but `-segment_times` are measured from wherever the run really
|
|
7
|
-
* began, so any distance between the two moves EVERY cut of that run by it. The
|
|
8
|
-
* live table keeps being corrected, and the corrections run backwards, so each
|
|
9
|
-
* restart began a little earlier than the grid its cuts were stated on and the
|
|
10
|
-
* distance accumulated across restarts.
|
|
11
|
-
*
|
|
12
|
-
* Field 2026-08-21, `JUFD665.mp4` (MP4, copy path, index read cleanly): after
|
|
13
|
-
* one seek restart a produced segment held the boundary two places before its
|
|
14
|
-
* own number — 16.684 s, which is 2.0000 segments — and after the next restart,
|
|
15
|
-
* four places, 33.5 s. The player's buffer then stopped extending at all,
|
|
16
|
-
* because every fragment's content landed before the time its playlist entry
|
|
17
|
-
* named: `bufferEnd` stood still at 4571.1 s through four `frag-far` warnings
|
|
18
|
-
* until hls.js gave up and jumped the viewer 16.8 s forward.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import assert from "node:assert/strict";
|
|
22
|
-
import { Timeline } from "../services/encode/output/Timeline.js";
|
|
23
|
-
import test from "node:test";
|
|
24
|
-
|
|
25
|
-
import { wireOutputs } from "../services/server/wire-outputs.js";
|
|
26
|
-
import { segmentCutTimesFrom } from "../services/encode/run-command.js";
|
|
27
|
-
import { describeGridDrift } from "../services/encode/OutputTimes.js";
|
|
28
|
-
|
|
29
|
-
/** What the playlist in the player's hands says. */
|
|
30
|
-
const PUBLISHED = [0, 8.342, 16.684, 25.026, 33.368, 41.71];
|
|
31
|
-
/** The same grid after produced segments moved two of its cuts backwards. */
|
|
32
|
-
const CORRECTED = [0, 8.342, 14.682, 25.026, 31.366, 41.71];
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @returns {{ manager: object, session: object }}
|
|
36
|
-
*/
|
|
37
|
-
function sessionWithDriftedGrid() {
|
|
38
|
-
const manager = wireOutputs({
|
|
39
|
-
enabled: true,
|
|
40
|
-
ffmpegBin: "ffmpeg",
|
|
41
|
-
localBindHost: "127.0.0.1",
|
|
42
|
-
localPort: 9090
|
|
43
|
-
});
|
|
44
|
-
const session = {
|
|
45
|
-
id: "picture",
|
|
46
|
-
timeline: new Timeline({ boundaries: [...CORRECTED], published: [...PUBLISHED], cutGrid: "uniform" })
|
|
47
|
-
};
|
|
48
|
-
return { manager, session };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
test("a run starts at the time the player was told, not at the corrected one", () => {
|
|
52
|
-
const { manager, session } = sessionWithDriftedGrid();
|
|
53
|
-
assert.equal(manager.outputTimes.runStartTimeFor(session, 2), 16.684);
|
|
54
|
-
assert.notEqual(manager.outputTimes.runStartTimeFor(session, 2), session.timeline.boundaries[2]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("position and cut list come from the same table", () => {
|
|
58
|
-
const { manager, session } = sessionWithDriftedGrid();
|
|
59
|
-
const grid = manager.outputTimes.publishedGridFor(session);
|
|
60
|
-
const start = manager.outputTimes.runStartTimeFor(session, 2);
|
|
61
|
-
// The cut list is stated as offsets from where the run begins. Adding the
|
|
62
|
-
// position back must land on the published boundaries exactly — which is the
|
|
63
|
-
// property that was false while the two came from different tables.
|
|
64
|
-
const absolute = segmentCutTimesFrom(grid, 2).map((offset) => Number((start + offset).toFixed(3)));
|
|
65
|
-
assert.deepEqual(absolute, [25.026, 33.368]);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("a session that published no grid positions on the live one", () => {
|
|
69
|
-
const { manager } = sessionWithDriftedGrid();
|
|
70
|
-
const session = { id: "no-playlist", timeline: new Timeline({ boundaries: [...CORRECTED], published: [], cutGrid: "uniform" }) };
|
|
71
|
-
assert.equal(manager.outputTimes.runStartTimeFor(session, 2), CORRECTED[2]);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("an index beyond the table is clamped rather than returning nothing", () => {
|
|
75
|
-
const { manager, session } = sessionWithDriftedGrid();
|
|
76
|
-
assert.equal(manager.outputTimes.runStartTimeFor(session, 9999), PUBLISHED[PUBLISHED.length - 1]);
|
|
77
|
-
assert.equal(manager.outputTimes.runStartTimeFor(session, -3), PUBLISHED[0]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("the drift between the two tables is stated in full", () => {
|
|
81
|
-
assert.equal(describeGridDrift(PUBLISHED, PUBLISHED), "identical");
|
|
82
|
-
assert.equal(
|
|
83
|
-
describeGridDrift(PUBLISHED, CORRECTED),
|
|
84
|
-
"2 of 6 boundaries apart, worst 2.002s at #2"
|
|
85
|
-
);
|
|
86
|
-
assert.equal(describeGridDrift(PUBLISHED, [0, 1]), "a different length (6 against 2)");
|
|
87
|
-
assert.equal(describeGridDrift(null, CORRECTED), "not comparable");
|
|
88
|
-
assert.equal(describeGridDrift([], []), "not comparable");
|
|
89
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { RunProgress } from "../services/encode/RunProgress.js";
|
|
4
|
-
import { EncodeOrchestrator } from "../services/encode/EncodeOrchestrator.js";
|
|
5
|
-
|
|
6
|
-
function orchestrator() {
|
|
7
|
-
return new EncodeOrchestrator({
|
|
8
|
-
maxRunsFor: () => 2,
|
|
9
|
-
makeRun: () => null,
|
|
10
|
-
segmentSeconds: 4,
|
|
11
|
-
logger: { info() {}, warn() {} }
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
test("a run rebases ffmpeg's relative clock onto its own source position", () => {
|
|
16
|
-
let now = 10;
|
|
17
|
-
const progress = new RunProgress({ startSeconds: 600, totalSeconds: 1000, now: () => now });
|
|
18
|
-
now = 20;
|
|
19
|
-
progress.note({ processedSeconds: 12.5 });
|
|
20
|
-
|
|
21
|
-
assert.deepEqual(progress.snapshot(), {
|
|
22
|
-
processedSeconds: 612.5,
|
|
23
|
-
startPositionSeconds: 600,
|
|
24
|
-
totalSeconds: 1000,
|
|
25
|
-
percent: 3.125,
|
|
26
|
-
remainingSeconds: 387.5,
|
|
27
|
-
updatedAt: 20
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("two runs of one output retain separate clocks", () => {
|
|
32
|
-
const made = orchestrator();
|
|
33
|
-
const first = { from: 0, to: 49, isAlive: true, progress: new RunProgress({ startSeconds: 0 }) };
|
|
34
|
-
const second = { from: 50, to: 99, isAlive: true, progress: new RunProgress({ startSeconds: 200 }) };
|
|
35
|
-
first.progress.note({ processedSeconds: 40 });
|
|
36
|
-
second.progress.note({ processedSeconds: 8, speed: "2.0x" });
|
|
37
|
-
made.adopt("picture", first);
|
|
38
|
-
made.adopt("picture", second);
|
|
39
|
-
|
|
40
|
-
assert.equal(made.progressOf("picture", 20).processedSeconds, 40);
|
|
41
|
-
assert.equal(made.progressOf("picture", 60).processedSeconds, 208);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("the last run's progress remains available after it ends", () => {
|
|
45
|
-
const made = orchestrator();
|
|
46
|
-
const progress = new RunProgress({ startSeconds: 100 });
|
|
47
|
-
progress.note({ processedSeconds: 5, speed: "1.5x" });
|
|
48
|
-
const run = { from: 25, to: 30, isAlive: false, state: "ENDED_COMPLETE", progress };
|
|
49
|
-
made.adopt("picture", run);
|
|
50
|
-
made.noteEnded({
|
|
51
|
-
address: "picture",
|
|
52
|
-
run,
|
|
53
|
-
ending: "complete",
|
|
54
|
-
because: "the assigned stretch was completed",
|
|
55
|
-
from: 25,
|
|
56
|
-
to: 30,
|
|
57
|
-
reached: 30,
|
|
58
|
-
livedMs: 1000,
|
|
59
|
-
normal: true,
|
|
60
|
-
lastError: ""
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
assert.equal(made.progressOf("picture").processedSeconds, 105);
|
|
64
|
-
});
|