@torrent-tv/proxy 2.89.17 → 2.89.19

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 (300) hide show
  1. package/README.md +14 -3
  2. package/package.json +10 -2
  3. package/server.js +0 -11
  4. package/utils/client-log-file.js +147 -42
  5. package/.claude/commands/opsx/apply.md +0 -155
  6. package/.claude/commands/opsx/archive.md +0 -160
  7. package/.claude/commands/opsx/explore.md +0 -174
  8. package/.claude/commands/opsx/propose.md +0 -109
  9. package/.claude/commands/opsx/sync.md +0 -143
  10. package/.claude/skills/openspec-apply-change/SKILL.md +0 -159
  11. package/.claude/skills/openspec-archive-change/SKILL.md +0 -117
  12. package/.claude/skills/openspec-explore/SKILL.md +0 -289
  13. package/.claude/skills/openspec-propose/SKILL.md +0 -113
  14. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -147
  15. package/.dockerignore +0 -5
  16. package/AGENTS.md +0 -363
  17. package/CHANGELOG.md +0 -2377
  18. package/Dockerfile +0 -30
  19. package/biome.json +0 -412
  20. package/corpus.tmp.json +0 -1
  21. package/docs/browser-proxy-contract.md +0 -155
  22. package/docs/container-architecture.md +0 -273
  23. package/docs/download-architecture.md +0 -261
  24. package/docs/encode-architecture.md +0 -765
  25. package/docs/encode-run-state.md +0 -96
  26. package/docs/logs.md +0 -151
  27. package/docs/playback-start-forecast.md +0 -113
  28. package/docs/socks5-egress-research.md +0 -114
  29. package/docs/storage-architecture.md +0 -273
  30. package/knip.json +0 -14
  31. package/main2.cpuprofile +0 -1
  32. package/openspec/changes/adaptive-bitrate/.openspec.yaml +0 -0
  33. package/openspec/changes/adaptive-bitrate/design.md +0 -92
  34. package/openspec/changes/adaptive-bitrate/proposal.md +0 -56
  35. package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +0 -51
  36. package/openspec/changes/adaptive-bitrate/tasks.md +0 -40
  37. package/openspec/changes/binary-distribution/.openspec.yaml +0 -2
  38. package/openspec/changes/binary-distribution/proposal.md +0 -53
  39. package/openspec/changes/chunked-request-bodies/design.md +0 -81
  40. package/openspec/changes/chunked-request-bodies/proposal.md +0 -53
  41. package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +0 -30
  42. package/openspec/changes/chunked-request-bodies/tasks.md +0 -40
  43. package/openspec/changes/cold-start/design.md +0 -98
  44. package/openspec/changes/cold-start/proposal.md +0 -62
  45. package/openspec/changes/cold-start/specs/cold-start/spec.md +0 -47
  46. package/openspec/changes/cold-start/tasks.md +0 -48
  47. package/openspec/changes/disk-cap/.openspec.yaml +0 -2
  48. package/openspec/changes/disk-cap/proposal.md +0 -37
  49. package/openspec/changes/disk-cap/specs/disk-cap/spec.md +0 -25
  50. package/openspec/changes/disk-cap/tasks.md +0 -19
  51. package/openspec/changes/proxy-observability/.openspec.yaml +0 -2
  52. package/openspec/changes/proxy-observability/design.md +0 -38
  53. package/openspec/changes/proxy-observability/proposal.md +0 -49
  54. package/openspec/changes/proxy-observability/specs/observability/spec.md +0 -33
  55. package/openspec/changes/proxy-observability/tasks.md +0 -21
  56. package/openspec/changes/seek-debounce/.openspec.yaml +0 -0
  57. package/openspec/changes/seek-debounce/design.md +0 -118
  58. package/openspec/changes/seek-debounce/proposal.md +0 -59
  59. package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +0 -41
  60. package/openspec/changes/seek-debounce/tasks.md +0 -33
  61. package/openspec/changes/subtitle-language/.openspec.yaml +0 -2
  62. package/openspec/changes/subtitle-language/proposal.md +0 -50
  63. package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +0 -34
  64. package/openspec/changes/subtitle-language/tasks.md +0 -26
  65. package/openspec/changes/track-selection/.openspec.yaml +0 -2
  66. package/openspec/changes/track-selection/design.md +0 -47
  67. package/openspec/changes/track-selection/proposal.md +0 -45
  68. package/openspec/changes/track-selection/specs/track-selection/spec.md +0 -41
  69. package/openspec/changes/track-selection/tasks.md +0 -20
  70. package/openspec/changes/transcode-quality/.openspec.yaml +0 -2
  71. package/openspec/changes/transcode-quality/proposal.md +0 -51
  72. package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +0 -139
  73. package/openspec/changes/transcode-quality/tasks.md +0 -63
  74. package/openspec/config.yaml +0 -38
  75. package/public/.well-known/appspecific/com.chrome.devtools.json +0 -1
  76. package/research/delivery-wedge-stand-ladder-2026-09-13.md +0 -329
  77. package/research/double-spawn-2026-09-10.md +0 -171
  78. package/research/handover-2026-09-13.md +0 -209
  79. package/research/handover-reader-claims-removal-2026-09-12.md +0 -166
  80. package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +0 -175
  81. package/research/preset-benchmark-size-scaling-2026-08-22.md +0 -55
  82. package/research/priority-map-is-the-truth-2026-09-12.md +0 -225
  83. package/research/valgrind-replica-2026-08-27/logs.tar.gz +0 -0
  84. package/scripts/build-audio-samples.mjs +0 -31
  85. package/scripts/render-run-graph.js +0 -173
  86. package/test/asset-paths.test.js +0 -28
  87. package/test/audio-buffer-continuity.test.js +0 -77
  88. package/test/audio-calibration.test.js +0 -23
  89. package/test/audio-inventory.test.js +0 -212
  90. package/test/audio-track-bitrate.test.js +0 -40
  91. package/test/audio-warm-recorded.test.js +0 -63
  92. package/test/auto-quality-step.test.js +0 -762
  93. package/test/available-share.test.js +0 -76
  94. package/test/background-fill.test.js +0 -190
  95. package/test/client-log-file.test.js +0 -126
  96. package/test/client-logs-route.test.js +0 -34
  97. package/test/coalesce.test.js +0 -39
  98. package/test/completed-files.test.js +0 -115
  99. package/test/concurrent-cost.test.js +0 -148
  100. package/test/container-media-info.test.js +0 -228
  101. package/test/contention.test.js +0 -76
  102. package/test/core-dumps.test.js +0 -57
  103. package/test/coverage-follows-the-disk.test.js +0 -233
  104. package/test/coverage-map.test.js +0 -195
  105. package/test/cue-push-carries-what-the-walk-found.test.js +0 -58
  106. package/test/cut-times-timeline.test.js +0 -65
  107. package/test/cuts-follow-published-grid.test.js +0 -109
  108. package/test/data-channel-frame.test.js +0 -63
  109. package/test/data-channel-viewer-lifecycle.test.js +0 -193
  110. package/test/dead-torrent-handle.test.js +0 -52
  111. package/test/declared-rates.test.js +0 -116
  112. package/test/declared-tracks.test.js +0 -43
  113. package/test/decode-cost-fit.test.js +0 -202
  114. package/test/decode-cost.test.js +0 -475
  115. package/test/delivery-probe-field.test.js +0 -173
  116. package/test/delivery-probe.test.js +0 -382
  117. package/test/demand-register.test.js +0 -206
  118. package/test/dht-bootstrap.test.js +0 -72
  119. package/test/drain-threat.test.js +0 -65
  120. package/test/duplicate-add.test.js +0 -48
  121. package/test/encode-admission.test.js +0 -170
  122. package/test/encode-cost.test.js +0 -275
  123. package/test/encode-exit.test.js +0 -111
  124. package/test/encode-orchestrator.test.js +0 -426
  125. package/test/encode-plan-viewers.test.js +0 -725
  126. package/test/encode-plan.test.js +0 -545
  127. package/test/encode-run-state.test.js +0 -314
  128. package/test/encode-run.test.js +0 -248
  129. package/test/encode-slope.test.js +0 -46
  130. package/test/encoded-output.test.js +0 -39
  131. package/test/encoder-kinds.test.js +0 -122
  132. package/test/encoder-readings.test.js +0 -68
  133. package/test/episode-naming.test.js +0 -131
  134. package/test/every-claimant-is-told-its-share.test.js +0 -152
  135. package/test/failed-encoder-still-serves-what-exists.test.js +0 -354
  136. package/test/failed-start-breaker.test.js +0 -202
  137. package/test/fastest-wires.test.js +0 -143
  138. package/test/ffmpeg-stream-counts.test.js +0 -101
  139. package/test/file-edges.test.js +0 -191
  140. package/test/files-in-use.test.js +0 -34
  141. package/test/fixtures/audio-frame-boundary.json +0 -1
  142. package/test/fixtures/refused-audio-20261002.json +0 -10
  143. package/test/fmp4-presentation-clock.test.js +0 -148
  144. package/test/fmp4-self-contained.test.js +0 -80
  145. package/test/fmp4-timestamps.test.js +0 -140
  146. package/test/fmp4-track-completeness.test.js +0 -41
  147. package/test/format-keyed-outputs.test.js +0 -78
  148. package/test/health-metrics.test.js +0 -36
  149. package/test/held-read-ownership.test.js +0 -77
  150. package/test/held-request-width.test.js +0 -157
  151. package/test/helpers/encode-run.js +0 -248
  152. package/test/helpers/held-file.js +0 -77
  153. package/test/helpers/manager.js +0 -62
  154. package/test/helpers/matroska-file.js +0 -296
  155. package/test/helpers/output-spec.js +0 -63
  156. package/test/host-load.test.js +0 -68
  157. package/test/index-accuracy-is-about-the-file.test.js +0 -128
  158. package/test/init-compat.test.js +0 -219
  159. package/test/input-lost-quiets-the-plan.test.js +0 -261
  160. package/test/input-unavailable.test.js +0 -48
  161. package/test/installation-id.test.js +0 -39
  162. package/test/keeping-period.test.js +0 -83
  163. package/test/keyframe-index-accuracy.test.js +0 -188
  164. package/test/keyframe-table-budget.test.js +0 -123
  165. package/test/keyframes-belong-to-the-file.test.js +0 -249
  166. package/test/learned-median.test.js +0 -37
  167. package/test/letting-go-asks-who-is-watching.test.js +0 -191
  168. package/test/limit-row-by-area.test.js +0 -60
  169. package/test/link-budget.test.js +0 -111
  170. package/test/link-probe.test.js +0 -56
  171. package/test/local-encoding-data.test.js +0 -273
  172. package/test/logger-repeats.test.js +0 -134
  173. package/test/look-ahead-coverage.test.js +0 -56
  174. package/test/manager-surface.test.js +0 -291
  175. package/test/matroska-blocks.test.js +0 -0
  176. package/test/matroska-cluster-discovery.test.js +0 -323
  177. package/test/matroska-cues-track.test.js +0 -192
  178. package/test/media-time.test.js +0 -62
  179. package/test/memory-budget.test.js +0 -390
  180. package/test/move-cost.test.js +0 -216
  181. package/test/mp4-composition-times.test.js +0 -0
  182. package/test/mp4-subtitles.test.js +0 -173
  183. package/test/one-authority.test.js +0 -285
  184. package/test/one-budget.test.js +0 -149
  185. package/test/one-output-one-session.test.js +0 -273
  186. package/test/one-resource-divided.test.js +0 -185
  187. package/test/one-serving-output-per-height.test.js +0 -458
  188. package/test/orchestrator-wired.test.js +0 -207
  189. package/test/output-catalog.test.js +0 -39
  190. package/test/output-shape.test.js +0 -21
  191. package/test/output-spec.test.js +0 -251
  192. package/test/output-speed.test.js +0 -92
  193. package/test/outputs-being-prepared.test.js +0 -40
  194. package/test/packet-witness-ring.test.js +0 -236
  195. package/test/packet-witness.test.js +0 -148
  196. package/test/piece-completeness.test.js +0 -78
  197. package/test/piece-disk-store.test.js +0 -401
  198. package/test/piece-from-whole-file.test.js +0 -129
  199. package/test/piece-lru.test.js +0 -319
  200. package/test/piece-reader.test.js +0 -162
  201. package/test/piece-selection-offset.test.js +0 -85
  202. package/test/piece-store-eviction.test.js +0 -595
  203. package/test/piece-store-never-refuses.test.js +0 -153
  204. package/test/piece-store-reservations.test.js +0 -403
  205. package/test/piece-store-slow-disk.test.js +0 -159
  206. package/test/piece-tail.test.js +0 -97
  207. package/test/plan-host-timings.test.js +0 -68
  208. package/test/plan-is-steady.test.js +0 -225
  209. package/test/playback-readiness.test.js +0 -518
  210. package/test/playback-start-regressions.test.js +0 -101
  211. package/test/priority-map-download.test.js +0 -174
  212. package/test/priority-map-emptied.test.js +0 -204
  213. package/test/priority-map-per-output.test.js +0 -215
  214. package/test/priority-map.test.js +0 -163
  215. package/test/probe-wedge-certainty.test.js +0 -60
  216. package/test/produced-copy-choice.test.js +0 -213
  217. package/test/published-timeline.test.js +0 -83
  218. package/test/quality-offer.test.js +0 -160
  219. package/test/quality-variants.test.js +0 -1274
  220. package/test/rate-control-in-the-output.test.js +0 -181
  221. package/test/read-survives-withdrawal.test.js +0 -164
  222. package/test/read-window.test.js +0 -365
  223. package/test/reads-not-yet-arrived.test.js +0 -218
  224. package/test/resume-warm.test.js +0 -39
  225. package/test/run-graph-drift.test.js +0 -37
  226. package/test/run-inputs.test.js +0 -80
  227. package/test/run-intervals.test.js +0 -100
  228. package/test/run-position-follows-published-grid.test.js +0 -89
  229. package/test/run-progress.test.js +0 -64
  230. package/test/same-height-switch.test.js +0 -663
  231. package/test/seek-frees-the-viewers-requests.test.js +0 -217
  232. package/test/seek-landing.test.js +0 -109
  233. package/test/seek-target-not-superseded.test.js +0 -195
  234. package/test/segment-closed-on-both-branches.test.js +0 -114
  235. package/test/segment-cut-times.test.js +0 -63
  236. package/test/segment-demand.test.js +0 -80
  237. package/test/segment-hold-supersede.test.js +0 -91
  238. package/test/segment-judged-by-its-own-header.test.js +0 -222
  239. package/test/segment-serve-wiring.test.js +0 -481
  240. package/test/segment-store-eviction.test.js +0 -247
  241. package/test/segment-store.test.js +0 -352
  242. package/test/segment-true-start.test.js +0 -86
  243. package/test/segments-are-shared.test.js +0 -176
  244. package/test/send-chunk-bytes.test.js +0 -86
  245. package/test/serving-existing-output.test.js +0 -111
  246. package/test/serving-output.test.js +0 -102
  247. package/test/shared-piece-store.test.js +0 -310
  248. package/test/sidecar-naming.test.js +0 -142
  249. package/test/soundtrack-rate-bound.test.js +0 -289
  250. package/test/source-file.test.js +0 -133
  251. package/test/source-files-route.test.js +0 -113
  252. package/test/source-registry.test.js +0 -18
  253. package/test/split-a-stretch.test.js +0 -139
  254. package/test/stale-request-after-seek.test.js +0 -158
  255. package/test/startup-readings.test.js +0 -124
  256. package/test/stream-route.test.js +0 -237
  257. package/test/subtitle-cue-framing.test.js +0 -200
  258. package/test/subtitle-cue-source.test.js +0 -102
  259. package/test/subtitle-cue-walk.test.js +0 -385
  260. package/test/subtitle-cursor.test.js +0 -63
  261. package/test/subtitle-defaults.test.js +0 -118
  262. package/test/subtitle-language.test.js +0 -252
  263. package/test/subtitle-metadata.test.js +0 -15
  264. package/test/subtitle-plan-waits.test.js +0 -125
  265. package/test/subtitle-track-numbering.test.js +0 -391
  266. package/test/supply-margin.test.js +0 -190
  267. package/test/swarm-follows-readers.test.js +0 -174
  268. package/test/swarm-reach.test.js +0 -130
  269. package/test/swarm-selection.test.js +0 -220
  270. package/test/tail-duplication.test.js +0 -167
  271. package/test/the-channel-has-both-ends.test.js +0 -91
  272. package/test/the-torrent-thread-serves-bytes.test.js +0 -107
  273. package/test/thread-pool.test.js +0 -53
  274. package/test/timeline.test.js +0 -84
  275. package/test/torrent-contents.test.js +0 -235
  276. package/test/torrent-cost.test.js +0 -71
  277. package/test/torrent-files.test.js +0 -221
  278. package/test/torrent-naming.test.js +0 -255
  279. package/test/tracks-begin-together.test.js +0 -209
  280. package/test/tunnel-renewal.test.js +0 -167
  281. package/test/two-viewers-one-picture.test.js +0 -508
  282. package/test/upload-hurry.test.js +0 -186
  283. package/test/usrsctp-state.test.js +0 -131
  284. package/test/video-facts.test.js +0 -102
  285. package/test/viewer-assignments.test.js +0 -143
  286. package/test/viewer-outputs.test.js +0 -285
  287. package/test/viewer-presence.test.js +0 -116
  288. package/test/viewer-requests-admission.test.js +0 -92
  289. package/test/viewer-starts-on-map.test.js +0 -27
  290. package/test/viewer-states.test.js +0 -192
  291. package/test/viewer-subtitle-subscription.test.js +0 -96
  292. package/test/viewer.test.js +0 -139
  293. package/test/viewing-generations.test.js +0 -390
  294. package/test/visible-rung.test.js +0 -37
  295. package/test/wait-ledger.test.js +0 -100
  296. package/test/webrtc-shim.test.js +0 -149
  297. package/test/wedge-certainty.test.js +0 -131
  298. package/test/whole-file-held-read.test.js +0 -30
  299. package/test/withdraw-piece-claim.test.js +0 -212
  300. package/test/worker-channel.test.js +0 -158
@@ -1,63 +0,0 @@
1
- /**
2
- * @file The wire format of a body frame.
3
- *
4
- * The browser parses these bytes, so the layout is a contract:
5
- * `[flags(1)][idLen(1)][requestId][payload]`. The framing was rewritten to stop
6
- * copying every chunk twice (75.9 ms per 13 MB segment against 40.0 on the
7
- * field host), and a rewrite of something a client parses needs the format
8
- * pinned down, not just the timing.
9
- */
10
-
11
- import test from "node:test";
12
- import assert from "node:assert/strict";
13
- import { encodeFrame } from "../services/transport/data-channel-handler.js";
14
-
15
- const requestId = Buffer.from("abc123", "ascii");
16
-
17
- test("a body frame carries the id and the payload unchanged", () => {
18
- const payload = Buffer.from([1, 2, 3, 4, 250, 255]);
19
- const frame = encodeFrame(requestId, payload, false);
20
-
21
- assert.equal(frame[0], 0, "flagged as done");
22
- assert.equal(frame[1], requestId.length);
23
- assert.deepEqual(frame.subarray(2, 2 + requestId.length), requestId);
24
- assert.deepEqual(frame.subarray(2 + requestId.length), payload);
25
- assert.equal(frame.length, 2 + requestId.length + payload.length);
26
- });
27
-
28
- test("the done frame carries no payload", () => {
29
- const frame = encodeFrame(requestId, null, true);
30
-
31
- assert.equal(frame[0], 1);
32
- assert.equal(frame.length, 2 + requestId.length);
33
- });
34
-
35
- test("an empty payload is treated as no payload", () => {
36
- const frame = encodeFrame(requestId, new Uint8Array(0), false);
37
- assert.equal(frame.length, 2 + requestId.length);
38
- });
39
-
40
- test("a payload that is a view into a larger buffer is copied correctly", () => {
41
- // Chunks arrive as views into a bigger allocation, so copying the whole
42
- // underlying buffer instead of the view would send the wrong bytes at the
43
- // wrong length — silently.
44
- const backing = Buffer.alloc(64, 9);
45
- backing.fill(42, 16, 32);
46
- const view = new Uint8Array(backing.buffer, backing.byteOffset + 16, 16);
47
-
48
- const frame = encodeFrame(requestId, view, false);
49
-
50
- assert.equal(frame.length, 2 + requestId.length + 16);
51
- assert.deepEqual(frame.subarray(2 + requestId.length), Buffer.alloc(16, 42));
52
- });
53
-
54
- test("a large payload survives framing byte for byte", () => {
55
- const payload = Buffer.allocUnsafeSlow(64 * 1024);
56
- for (let at = 0; at < payload.length; at += 1) {
57
- payload[at] = at % 251;
58
- }
59
-
60
- const frame = encodeFrame(requestId, payload, false);
61
-
62
- assert.deepEqual(frame.subarray(2 + requestId.length), payload);
63
- });
@@ -1,193 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { createDataChannelHandler } from "../services/transport/data-channel-handler.js";
4
-
5
- class FakeDataChannel {
6
- constructor(label) {
7
- this.label = label;
8
- this.messages = [];
9
- this.closed = null;
10
- }
11
-
12
- getLabel() {
13
- return this.label;
14
- }
15
-
16
- onMessage(callback) {
17
- this.message = callback;
18
- }
19
-
20
- onClosed(callback) {
21
- this.closed = callback;
22
- }
23
-
24
- onError() {}
25
-
26
- sendMessage(message) {
27
- this.messages.push(message);
28
- }
29
-
30
- close() {
31
- this.closed?.();
32
- }
33
- }
34
-
35
- test("a viewer remains present until every data channel on the connection closes", () => {
36
- const gone = [];
37
- const handler = createDataChannelHandler({
38
- proxyPort: 9090,
39
- getTransportSnapshot: () => ({ bytesSent: 0, bytesReceived: 0 }),
40
- onViewerGone: (consumerId, reason) => gone.push({ consumerId, reason })
41
- });
42
- const media = new FakeDataChannel("proxy");
43
- const control = new FakeDataChannel("proxy-control");
44
- const probe = new FakeDataChannel("proxy-fast");
45
-
46
- handler.handleChannel("peer-session", media);
47
- handler.handleChannel("peer-session", control);
48
- handler.handleChannel("peer-session", probe);
49
- control.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
50
-
51
- control.close();
52
- assert.deepEqual(gone, [], "closing the control channel alone does not release the viewer");
53
-
54
- media.close();
55
- assert.deepEqual(gone, [], "the viewer remains while the probe channel is still open");
56
-
57
- probe.close();
58
- assert.deepEqual(gone, [
59
- { consumerId: "viewer-1", reason: "the connection closed" }
60
- ]);
61
- });
62
-
63
- test("a closing connection does not release a viewer already named on its replacement", () => {
64
- const gone = [];
65
- const handler = createDataChannelHandler({
66
- proxyPort: 9090,
67
- getTransportSnapshot: () => ({ bytesSent: 0, bytesReceived: 0 }),
68
- onViewerGone: (consumerId, reason) => gone.push({ consumerId, reason })
69
- });
70
- const oldControl = new FakeDataChannel("proxy-control");
71
- const oldMedia = new FakeDataChannel("proxy");
72
- const newControl = new FakeDataChannel("proxy-control");
73
- const newMedia = new FakeDataChannel("proxy");
74
-
75
- handler.handleChannel("old-peer", oldControl);
76
- handler.handleChannel("old-peer", oldMedia);
77
- oldControl.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
78
- handler.handleChannel("new-peer", newControl);
79
- handler.handleChannel("new-peer", newMedia);
80
- newControl.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
81
-
82
- oldControl.close();
83
- oldMedia.close();
84
- assert.deepEqual(gone, [], "the old connection no longer owns the viewer");
85
-
86
- newControl.close();
87
- assert.deepEqual(gone, [], "the replacement still has its media channel");
88
- newMedia.close();
89
- assert.deepEqual(gone, [
90
- { consumerId: "viewer-1", reason: "the connection closed" }
91
- ]);
92
- });
93
-
94
- /**
95
- * A page on an old connection that raises a trial connection beside it, the
96
- * way it does when the old one stops delivering.
97
- */
98
- function trialBeside(gone) {
99
- const handler = createDataChannelHandler({
100
- proxyPort: 9090,
101
- getTransportSnapshot: () => ({ bytesSent: 0, bytesReceived: 0 }),
102
- viewersWantingCues: () => ["viewer-1"],
103
- onViewerGone: (consumerId, reason) => gone.push({ consumerId, reason })
104
- });
105
- const old = { control: new FakeDataChannel("proxy-control"), media: new FakeDataChannel("proxy") };
106
- const trial = { control: new FakeDataChannel("proxy-control"), media: new FakeDataChannel("proxy") };
107
- handler.handleChannel("old-peer", old.control);
108
- handler.handleChannel("old-peer", old.media);
109
- old.control.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
110
- handler.handleChannel("trial-peer", trial.control);
111
- handler.handleChannel("trial-peer", trial.media);
112
- trial.control.message(JSON.stringify({ type: "viewer", consumerId: "viewer-1" }));
113
- const cuesOn = (channel) => channel.messages.filter((message) => message.includes("subtitle-cues")).length;
114
- const push = () =>
115
- handler.publishSubtitleCues({
116
- sourceKey: "torrent:abc",
117
- fileIndex: 0,
118
- trackIndex: 0,
119
- cues: [{ start: 1, end: 2, text: "line" }],
120
- language: "en",
121
- detectedLanguage: null,
122
- cursor: 1
123
- });
124
- return { old, trial, cuesOn, push };
125
- }
126
-
127
- test("closing a trial connection keeps the viewer and their cues on the old one", () => {
128
- // The old connection delivered again before the trial was adopted, so the
129
- // page closes the trial. The viewer never left.
130
- const gone = [];
131
- const { old, trial, cuesOn, push } = trialBeside(gone);
132
- trial.control.close();
133
- trial.media.close();
134
- assert.deepEqual(gone, [], "the viewer is still on the old connection");
135
- push();
136
- assert.equal(cuesOn(old.control), 1, "cues reach the channel the viewer named themselves on");
137
- old.control.close();
138
- old.media.close();
139
- assert.deepEqual(gone, [{ consumerId: "viewer-1", reason: "the connection closed" }]);
140
- });
141
-
142
- test("after a trial connection is adopted, cues go to it and the old one's close releases nobody", () => {
143
- const gone = [];
144
- const { old, trial, cuesOn, push } = trialBeside(gone);
145
- push();
146
- assert.equal(cuesOn(trial.control), 1, "the newest connection carrying the viewer is used");
147
- assert.equal(cuesOn(old.control), 0);
148
- old.control.close();
149
- old.media.close();
150
- assert.deepEqual(gone, []);
151
- push();
152
- assert.equal(cuesOn(trial.control), 2);
153
- });
154
-
155
- test("a viewer whose newest channel closed is reached on the older connection", () => {
156
- const gone = [];
157
- const { old, trial, cuesOn, push } = trialBeside(gone);
158
- trial.control.close();
159
- push();
160
- assert.equal(cuesOn(old.control), 1);
161
- assert.deepEqual(gone, [], "the trial connection still has its media channel");
162
- });
163
-
164
- test("a connection the transport no longer knows is not probed any more, though no channel said it closed", (t) => {
165
- t.mock.timers.enable({ apis: ["setInterval", "setTimeout", "Date"] });
166
- let alive = true;
167
- const handler = createDataChannelHandler({
168
- proxyPort: 9090,
169
- getTransportSnapshot: () => (alive ? { bytesSent: 0, bytesReceived: 0 } : null),
170
- onViewerGone: () => {}
171
- });
172
- const channels = ["proxy", "proxy-control", "proxy-fast"].map((label) => new FakeDataChannel(label));
173
- for (const channel of channels) {
174
- handler.handleChannel("trial-peer", channel);
175
- }
176
- const probes = () =>
177
- channels.reduce((sum, channel) => sum + channel.messages.filter((message) => message.includes('"probe"')).length, 0);
178
- t.mock.timers.tick(2_000);
179
- assert.ok(probes() > 0, "an open connection is probed");
180
-
181
- // Field 2026-10-04: the peer connection closed 70 ms after its channels
182
- // opened, and none of them reported `onClosed`.
183
- alive = false;
184
- const advance = (ms) => {
185
- // Second by second: the watcher compares the clock between its own ticks,
186
- // and one long jump lands every tick on the same instant.
187
- for (let elapsed = 0; elapsed < ms; elapsed += 1_000) t.mock.timers.tick(1_000);
188
- };
189
- advance(30_000);
190
- const afterGone = probes();
191
- advance(30_000);
192
- assert.equal(probes(), afterGone, "nothing is sent on a connection that is gone");
193
- });
@@ -1,52 +0,0 @@
1
- /**
2
- * @file A torrent that was destroyed must not be handed to a reader.
3
- *
4
- * The worker remembers each source as a promise, and only ever forgot one when
5
- * the ADD failed. But the pool destroys a torrent that has gone unread for a
6
- * quarter of an hour, and under disk pressure — clearing its own map, not this
7
- * one. The promise then went on resolving to a corpse: a destroyed torrent
8
- * keeps its object and loses its files.
9
- *
10
- * What that did to a viewer, measured 2026-08-06 on two sessions in a row: the
11
- * plan answered from cache in 23 ms, the session was created from cache in
12
- * 2 ms, nothing waited for metadata because everything believed the torrent was
13
- * known, and ffmpeg's first read died 130 ms in with `File 0 not found`. Every
14
- * request for the playlist then answered 500 until the viewer gave up.
15
- *
16
- * The rule under test is the whole fix: a handle that cannot be read from is
17
- * not returned, it is replaced.
18
- */
19
-
20
- import test from "node:test";
21
- import assert from "node:assert/strict";
22
- import { isUsableTorrentHandle } from "../services/torrent/worker/handle-state.js";
23
-
24
- test("a live torrent is usable", () => {
25
- assert.equal(isUsableTorrentHandle({ destroyed: false, files: [{ name: "a.mkv" }] }), true);
26
- });
27
-
28
- test("a destroyed torrent is not, even while it still lists files", () => {
29
- assert.equal(
30
- isUsableTorrentHandle({ destroyed: true, files: [{ name: "a.mkv" }] }),
31
- false,
32
- "WebTorrent's own flag is the earliest signal that a handle is finished"
33
- );
34
- });
35
-
36
- test("a torrent with no files is not — that is what the reader actually hits", () => {
37
- assert.equal(
38
- isUsableTorrentHandle({ destroyed: false, files: [] }),
39
- false,
40
- "an empty file list is what produced `File 0 not found` in the field"
41
- );
42
- assert.equal(
43
- isUsableTorrentHandle({ destroyed: false }),
44
- false,
45
- "and a handle with no file list at all is the same case"
46
- );
47
- });
48
-
49
- test("nothing at all is not usable", () => {
50
- assert.equal(isUsableTorrentHandle(null), false);
51
- assert.equal(isUsableTorrentHandle(undefined), false);
52
- });
@@ -1,116 +0,0 @@
1
- /**
2
- * @file What the master playlist declares a variant carries.
3
- *
4
- * Measured, and the measurement was available all along: it used to be
5
- * `height * height * 3.2`, justified by a comment saying no measurement existed
6
- * before encoding starts. Field 2026-09-08: 3.73 Mbit/s declared for a file
7
- * carrying 18.4, and the browser sizes its cushion in BYTES from that figure —
8
- * 120 s asked bought 26 s of film, and the deepest it ever held was 17.1 s.
9
- */
10
-
11
- import test from "node:test";
12
- import assert from "node:assert/strict";
13
- import { declaredRates } from "../services/encode/output/rates.js";
14
- import { bitrateFor } from "../services/encode/output/playlists.js";
15
-
16
- // The field file: 2 806 246 976 bytes over 20:18, cut into 291 pieces.
17
- const FIELD = { fileLength: 2_806_246_976, durationSeconds: 1218.73 };
18
-
19
- test("the average is the file's own length over its duration", () => {
20
- const { averageBitsPerSecond } = declaredRates(FIELD);
21
-
22
- // 18.4 Mbit/s, against the 3.73 that was declared.
23
- assert.ok(
24
- Math.abs(averageBitsPerSecond / 1_000_000 - 18.42) < 0.01,
25
- `got ${averageBitsPerSecond}`
26
- );
27
- });
28
-
29
- test("nothing measured is stated as nothing, not as a guess", () => {
30
- assert.deepEqual(
31
- declaredRates({ fileLength: 0, durationSeconds: 0 }),
32
- { averageBitsPerSecond: 0, peakOverAverage: 1 }
33
- );
34
- assert.deepEqual(
35
- declaredRates({ fileLength: 100, durationSeconds: 0 }),
36
- { averageBitsPerSecond: 0, peakOverAverage: 1 }
37
- );
38
- });
39
-
40
- test("the peak comes from the biggest piece made and the span it covers", () => {
41
- // The field peak: 38.4 MB over 4.2 s is 73 Mbit/s, four times the average.
42
- const boundaries = Array.from({ length: 292 }, (_, index) => index * 4.2);
43
- const { averageBitsPerSecond, peakOverAverage } = declaredRates({
44
- ...FIELD,
45
- largest: { index: 2, size: 40_288_299 },
46
- boundaries
47
- });
48
-
49
- const peak = averageBitsPerSecond * peakOverAverage;
50
- assert.ok(Math.abs(peak / 1_000_000 - 76.7) < 0.5, `got ${peak / 1_000_000}Mbit/s`);
51
- });
52
-
53
- test("no piece made yet means the peak is not yet measured, so it equals the average", () => {
54
- // A ratio invented meanwhile is exactly the fabrication this replaced.
55
- assert.equal(declaredRates(FIELD).peakOverAverage, 1);
56
- assert.equal(
57
- declaredRates({ ...FIELD, largest: { index: -1, size: 0 }, boundaries: [0, 4] }).peakOverAverage,
58
- 1
59
- );
60
- });
61
-
62
- test("the peak is never below the average", () => {
63
- // A short session whose only piece happens to be a small one must not lower
64
- // the figure the player sizes its cushion from.
65
- const boundaries = Array.from({ length: 292 }, (_, index) => index * 4.2);
66
- assert.equal(
67
- declaredRates({ ...FIELD, largest: { index: 5, size: 1000 }, boundaries }).peakOverAverage,
68
- 1
69
- );
70
- });
71
-
72
- test("a smaller picture is declared at the pixel share of the source's rate", () => {
73
- const measured = 18_420_000;
74
-
75
- assert.equal(
76
- bitrateFor({ averageBitsPerSecond: measured, height: 1080, sourceHeight: 1080 }),
77
- Math.round(measured),
78
- "the source's own height carries the source's bits"
79
- );
80
- assert.equal(
81
- bitrateFor({ averageBitsPerSecond: measured, height: 2160, sourceHeight: 1080 }),
82
- Math.round(measured),
83
- "and a height above it carries no more — there is nothing to upscale from"
84
- );
85
- assert.equal(
86
- bitrateFor({ averageBitsPerSecond: measured, height: 540, sourceHeight: 1080 }),
87
- Math.round(measured / 4),
88
- "half the height is a quarter of the pixels"
89
- );
90
- });
91
-
92
- test("a capped height is declared at its cap, which is exact", () => {
93
- assert.equal(
94
- bitrateFor({ averageBitsPerSecond: 18_420_000, height: 720, sourceHeight: 1080, capKbps: 2500 }),
95
- 2_500_000,
96
- "what we impose is known, not estimated"
97
- );
98
- assert.equal(
99
- bitrateFor({ averageBitsPerSecond: 1_000_000, height: 1080, sourceHeight: 1080, capKbps: 99_000 }),
100
- 1_000_000,
101
- "and a cap above what the source carries takes nothing away from it"
102
- );
103
- });
104
-
105
- test("the floor is a floor on what may be declared, not a belief about content", () => {
106
- assert.equal(
107
- bitrateFor({ averageBitsPerSecond: 0, height: 1080, sourceHeight: 1080 }),
108
- 400_000,
109
- "a file whose rate is not known yet"
110
- );
111
- assert.equal(
112
- bitrateFor({ averageBitsPerSecond: 18_420_000, height: 4, sourceHeight: 1080 }),
113
- 400_000,
114
- "and a variant so small the arithmetic would go under it"
115
- );
116
- });
@@ -1,43 +0,0 @@
1
- /**
2
- * @file What the proxy promises the browser must be answerable.
3
- *
4
- * The session manager asks the planner's media-info cache which tracks the
5
- * output will carry. That cache never held the codecs — it holds dimensions,
6
- * duration, fps, start time and an HDR flag — so the read produced `undefined`
7
- * for both and the declaration came out `{video: false, audio: false}` for
8
- * EVERY session since the check existed. Measured 2026-08-11 in the field.
9
- *
10
- * Nothing caught it: the writer and the reader each looked correct alone, and
11
- * no test ever compared the shape of what is stored with the shape of what is
12
- * read. That comparison is this file's whole job.
13
- */
14
-
15
- import test from "node:test";
16
- import assert from "node:assert/strict";
17
- import { readFileSync } from "node:fs";
18
-
19
- const planner = readFileSync(new URL("../services/media/playback-planner.js", import.meta.url), "utf8");
20
- const sessions = readFileSync(new URL("../services/encode/Renditions.js", import.meta.url), "utf8");
21
-
22
- test("every field the declaration reads is a field the cache stores", () => {
23
- // What `declaredTracks` reads off the cached media info.
24
- const read = [...sessions.matchAll(/probed\?\.([A-Za-z]+)/g)].map((match) => match[1]);
25
- assert.ok(read.length > 0, "the declaration must read something, or it declares nothing");
26
-
27
- const stored = planner.slice(planner.indexOf("mediaInfoCache.set("));
28
- for (const field of new Set(read)) {
29
- assert.ok(
30
- stored.includes(`${field}:`),
31
- `the declaration reads "${field}" and the media-info cache never stores it — ` +
32
- "the read yields undefined and the promise silently becomes false"
33
- );
34
- }
35
- });
36
-
37
- test("an empty container track list remains pending for late sidecar metadata", () => {
38
- assert.match(
39
- planner,
40
- /complete: Array\.isArray\(tracks\) && tracks\.length > 0/,
41
- "ContainerOrchestrator leaves empty track reads uncached while the header is unavailable"
42
- );
43
- });
@@ -1,202 +0,0 @@
1
- /**
2
- * @file The decode-cost fit, and the case it exists to prevent.
3
- *
4
- * The failure being pinned is real and dated: on 2026-08-17 three clips for
5
- * three unknowns returned `0.007542 × Mpx/s + 0.000000 × Mbit/s + 0.0000 s/s`,
6
- * with the bitrate term and the constant exactly zero, and the prediction built
7
- * on it was 1.8-2.2x optimistic. An exact system cannot notice that two of its
8
- * points said the same thing; these tests hold the replacement to noticing.
9
- */
10
-
11
- import assert from "node:assert/strict";
12
- import test from "node:test";
13
-
14
- import { decodeCostOf, fitDecodeCost } from "../services/encode/decode-cost-fit.js";
15
-
16
- const FPS = 24;
17
-
18
- /**
19
- * A clip's measurement, priced by a known truth so a fit can be checked against
20
- * the answer it should recover.
21
- *
22
- * @param {number} width
23
- * @param {number} height
24
- * @param {number} megabitsPerSecond
25
- * @param {{ pixel: number, bitrate: number, constant: number, noise?: number }} truth
26
- * @returns {{ megapixelsPerSecond: number, megabitsPerSecond: number, costSecondsPerSecond: number }}
27
- */
28
- function clip(width, height, megabitsPerSecond, truth) {
29
- const megapixelsPerSecond = (width * height * FPS) / 1e6;
30
- const cost =
31
- truth.pixel * megapixelsPerSecond + truth.bitrate * megabitsPerSecond + truth.constant + (truth.noise ?? 0);
32
- return { megapixelsPerSecond, megabitsPerSecond, costSecondsPerSecond: cost };
33
- }
34
-
35
- /** The set the clips were cut to: three sizes, two bitrates, varied independently. */
36
- const SIZES = [
37
- [1920, 1080],
38
- [1280, 720],
39
- [854, 480]
40
- ];
41
- const BITRATES = [9.5, 1.1];
42
-
43
- /**
44
- * @param {{ pixel: number, bitrate: number, constant: number }} truth
45
- * @param {number[]} [noise] - Per-clip disturbance, in order.
46
- * @returns {ReturnType<typeof clip>[]}
47
- */
48
- function wellConditionedSet(truth, noise = []) {
49
- const samples = [];
50
- let index = 0;
51
- for (const [width, height] of SIZES) {
52
- for (const bitrate of BITRATES) {
53
- samples.push(clip(width, height, bitrate, { ...truth, noise: noise[index] ?? 0 }));
54
- index += 1;
55
- }
56
- }
57
- return samples;
58
- }
59
-
60
- test("a well-conditioned set recovers every term", () => {
61
- const truth = { pixel: 0.0055, bitrate: 0.0099, constant: 0.057 };
62
- const model = fitDecodeCost(wellConditionedSet(truth));
63
-
64
- assert.ok(model, "six clips over three unknowns must produce a model");
65
- assert.equal(model.shape, "pixels+bitrate+constant");
66
- assert.deepEqual(model.dropped, []);
67
- assert.ok(Math.abs(model.pixelTerm - truth.pixel) < 1e-6, `pixel term ${model.pixelTerm}`);
68
- assert.ok(Math.abs(model.bitrateTerm - truth.bitrate) < 1e-6, `bitrate term ${model.bitrateTerm}`);
69
- assert.ok(Math.abs(model.constantTerm - truth.constant) < 1e-6, `constant ${model.constantTerm}`);
70
- });
71
-
72
- test("three clips are refused outright — an exact system cannot see its own degeneracy", () => {
73
- // Exactly the shape that shipped: two clips at the same pixel rate differing
74
- // only in bitrate, and a third at another size.
75
- const truth = { pixel: 0.0055, bitrate: 0.0099, constant: 0.057 };
76
- const three = [clip(1920, 1080, 11.4, truth), clip(1920, 1080, 0.97, truth), clip(1280, 720, 2.25, truth)];
77
-
78
- assert.equal(
79
- fitDecodeCost(three),
80
- null,
81
- "with no residual there is nothing to notice a degeneracy with, so no model may be published"
82
- );
83
- });
84
-
85
- test("a term the measurements do not determine is dropped, and said so", () => {
86
- // A host whose decoding does not depend on bitrate at all: the term is not
87
- // small, it is absent. What must NOT happen is publishing a zero as though it
88
- // had been measured.
89
- const truth = { pixel: 0.0055, bitrate: 0, constant: 0.057 };
90
- // Deliberately NOT alternating with the bitrate column: an earlier version
91
- // of this test put positive noise on every high-bitrate clip and negative on
92
- // every low one, which IS a bitrate signal — the fit found it, correctly, and
93
- // the test was wrong.
94
- const noise = [0.004, 0.003, -0.004, 0.002, -0.003, -0.002];
95
- const model = fitDecodeCost(wellConditionedSet(truth, noise));
96
-
97
- assert.ok(model);
98
- assert.ok(model.dropped.includes("bitrate"), `dropped: ${model.dropped.join(",") || "nothing"}`);
99
- assert.equal(model.bitrateTerm, 0);
100
- assert.ok(model.shape.includes("pixels"));
101
- });
102
-
103
- test("noise in the readings does not become a term", () => {
104
- // Pure noise around a pixels-only truth, larger than any bitrate effect.
105
- const truth = { pixel: 0.0055, bitrate: 0, constant: 0 };
106
- const noise = [0.01, 0.009, -0.012, 0.011, -0.008, -0.01];
107
- const model = fitDecodeCost(wellConditionedSet(truth, noise));
108
-
109
- assert.ok(model);
110
- assert.ok(model.pixelTerm > 0, "the one relationship every reading agrees on survives");
111
- assert.ok(
112
- model.dropped.length > 0,
113
- "and the terms the noise could have supplied are reported as undetermined"
114
- );
115
- });
116
-
117
- test("without a measurable dependence on the source there is no model", () => {
118
- // Every clip costs the same regardless of size or bitrate: nothing here says
119
- // what a bigger picture costs, and inventing it is worse than having none.
120
- const flat = wellConditionedSet({ pixel: 0, bitrate: 0, constant: 0.3 });
121
- assert.equal(fitDecodeCost(flat), null);
122
- assert.equal(fitDecodeCost([]), null);
123
- assert.equal(fitDecodeCost(null), null);
124
- });
125
-
126
- test("readings that are not measurements are left out", () => {
127
- const truth = { pixel: 0.0055, bitrate: 0.0099, constant: 0.057 };
128
- const samples = [
129
- ...wellConditionedSet(truth),
130
- { megapixelsPerSecond: Number.NaN, megabitsPerSecond: 5, costSecondsPerSecond: 1 },
131
- { megapixelsPerSecond: 20, megabitsPerSecond: 5, costSecondsPerSecond: 0 }
132
- ];
133
- const model = fitDecodeCost(samples);
134
- assert.ok(model);
135
- assert.equal(model.samples, 6);
136
- });
137
-
138
- test("what a model prices a film at", () => {
139
- const model = fitDecodeCost(wellConditionedSet({ pixel: 0.0055, bitrate: 0.0099, constant: 0.057 }));
140
- // The field film: 1080p24 at about 8 Mbit/s.
141
- const cost = decodeCostOf(model, { megapixelsPerSecond: 49.77, megabitsPerSecond: 8 });
142
- assert.ok(cost > 0);
143
- // Which is a decode speed of 1/cost — the figure the quality offer rests on.
144
- assert.ok(1 / cost > 1 && 1 / cost < 10, `decodes at ${(1 / cost).toFixed(2)}x`);
145
- });
146
-
147
- /**
148
- * THE ORDERINGS, STATED WHERE THEY ARE DETERMINISTIC.
149
- *
150
- * What the quality offer needs of this model is not precision but ORDER: a
151
- * bigger, richer, dearer-to-decode source must never be priced cheaper, because
152
- * the model is asked about rungs nobody has decoded. That is a property of the
153
- * fit, and it is a property of the fit whatever machine runs the test.
154
- *
155
- * These three used to be asserted over LIVE readings — decode two clips through
156
- * real ffmpeg and require the smaller to come back faster. That measures the
157
- * scheduler, not the code: four failures in one day, and the worst of them read
158
- * 480p at 8.4x against 1080p at 20.8x, an inversion of two and a half times
159
- * with nothing wrong. The readings themselves are unavailable to a test at all,
160
- * since a clip that "said nothing" under load makes the whole benchmark answer
161
- * with nothing.
162
- */
163
-
164
- test("a bigger picture is priced dearer than a smaller one of the same bitrate", () => {
165
- const model = fitDecodeCost(wellConditionedSet({ pixel: 0.0055, bitrate: 0.0099, constant: 0.057 }));
166
- const big = decodeCostOf(model, { megapixelsPerSecond: (1920 * 1080 * FPS) / 1e6, megabitsPerSecond: 4 });
167
- const small = decodeCostOf(model, { megapixelsPerSecond: (854 * 480 * FPS) / 1e6, megabitsPerSecond: 4 });
168
-
169
- assert.ok(big > small, `1080p at ${big.toFixed(4)} s/s against 480p at ${small.toFixed(4)}`);
170
- });
171
-
172
- test("a thicker stream is priced dearer than a thin one of the same size", () => {
173
- const model = fitDecodeCost(wellConditionedSet({ pixel: 0.0055, bitrate: 0.0099, constant: 0.057 }));
174
- const pixels = (854 * 480 * FPS) / 1e6;
175
- const thick = decodeCostOf(model, { megapixelsPerSecond: pixels, megabitsPerSecond: 9.5 });
176
- const thin = decodeCostOf(model, { megapixelsPerSecond: pixels, megabitsPerSecond: 1.1 });
177
-
178
- assert.ok(thick > thin, `9.5 Mbit/s at ${thick.toFixed(4)} s/s against 1.1 at ${thin.toFixed(4)}`);
179
- });
180
-
181
- test("a family measured to be dearer prices its own sources dearer", () => {
182
- // Why the model is fitted per codec family at all: HEVC costs more than H.264
183
- // for the same picture on the same machine, so a source that has to be
184
- // re-encoded — which is usually one the browser could not decode — must be
185
- // priced by its own family and not by H.264's.
186
- const cheap = { pixel: 0.0055, bitrate: 0.0099, constant: 0.057 };
187
- const dear = { pixel: 0.011, bitrate: 0.0198, constant: 0.114 };
188
- const model = {
189
- ...fitDecodeCost(wellConditionedSet(cheap)),
190
- families: {
191
- h264: fitDecodeCost(wellConditionedSet(cheap)),
192
- hevc: fitDecodeCost(wellConditionedSet(dear))
193
- }
194
- };
195
- const source = { megapixelsPerSecond: (854 * 480 * FPS) / 1e6, megabitsPerSecond: 1.1 };
196
-
197
- assert.ok(
198
- decodeCostOf(model, { ...source, codec: "hevc" }) >
199
- decodeCostOf(model, { ...source, codec: "h264" }),
200
- "the family chooses the terms, and the dearer family answers dearer"
201
- );
202
- });