@torrent-tv/proxy 2.88.4 → 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/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +2266 -2259
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- 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/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +412 -411
- package/docs/browser-proxy-contract.md +155 -152
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +56 -0
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +380 -380
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/build-audio-samples.mjs +31 -0
- package/scripts/render-run-graph.js +173 -173
- package/server.js +730 -725
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/EncodeRuns.js +1237 -1235
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +783 -783
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2282 -2282
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +418 -418
- package/services/encode/audio-calibration.js +67 -0
- package/services/encode/audio-work-rate.js +15 -0
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1167 -1150
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +349 -349
- package/services/encode/quality/output-format.js +319 -319
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +754 -754
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +458 -456
- package/services/encode/segment-formats/index.js +85 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +12 -1
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +299 -298
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +609 -609
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1344 -1344
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1150 -1150
- package/services/server/ViewerRequests.js +785 -760
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/playback-readiness.js +516 -708
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +1020 -995
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1937 -1931
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1255 -1240
- package/services/storage/wire.js +175 -175
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2688 -2675
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +765 -765
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1129 -1129
- package/services/transport/data-channel-handler.js +1369 -1369
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-calibration.test.js +23 -0
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +162 -162
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-presentation-clock.test.js +162 -147
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +112 -112
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +273 -270
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +483 -453
- package/test/playback-start-regressions.test.js +99 -99
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1257 -1257
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +338 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +105 -105
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +209 -209
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +504 -504
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
package/test/encode-run.test.js
CHANGED
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One encoder's lifetime: the stretch it was given, where it got to, and
|
|
3
|
-
* whether its ending was the normal one.
|
|
4
|
-
*
|
|
5
|
-
* The process is injected, so none of this needs ffmpeg. What is checked is the
|
|
6
|
-
* accounting the user asked for on 2026-09-04: exactly one ending is normal,
|
|
7
|
-
* every other one says why, and none of them goes unrecorded.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import test from "node:test";
|
|
11
|
-
import assert from "node:assert/strict";
|
|
12
|
-
import { EventEmitter } from "node:events";
|
|
13
|
-
import { EncodeRun } from "../services/encode/EncodeRun.js";
|
|
14
|
-
import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
|
|
15
|
-
import { SoftwareEncoder } from "../services/encode/SoftwareEncoder.js";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A process that does nothing until the test says what became of it.
|
|
19
|
-
*/
|
|
20
|
-
class FakeProcess extends EventEmitter {
|
|
21
|
-
constructor() {
|
|
22
|
-
super();
|
|
23
|
-
this.pid = 4242;
|
|
24
|
-
this.signals = [];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
kill(signal) {
|
|
28
|
-
this.signals.push(signal);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
exitWith(code, signal = null) {
|
|
32
|
-
this.emit("exit", code, signal);
|
|
33
|
-
this.emit("close", code, signal);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @param {Partial<{from:number,to:number}>} [span]
|
|
39
|
-
*/
|
|
40
|
-
function makeRun(span = {}) {
|
|
41
|
-
const lines = [];
|
|
42
|
-
const ends = [];
|
|
43
|
-
const process_ = new FakeProcess();
|
|
44
|
-
const run = new EncodeRun({
|
|
45
|
-
address: "torrent:abc:fmt=fmp4:grid=kf@0:video-only:v=0/enc/libx264/854x480@24/veryfast/none/vbv=-",
|
|
46
|
-
encoder: new SoftwareEncoder(),
|
|
47
|
-
from: span.from ?? 10,
|
|
48
|
-
to: span.to ?? 14,
|
|
49
|
-
buildArgs: () => ["-i", "in", "out"],
|
|
50
|
-
spawn: () => process_,
|
|
51
|
-
logger: {
|
|
52
|
-
info: (line) => lines.push(["info", line]),
|
|
53
|
-
warn: (line) => lines.push(["warn", line])
|
|
54
|
-
},
|
|
55
|
-
now: () => 1000,
|
|
56
|
-
onEnded: (ended) => ends.push(ended),
|
|
57
|
-
because: "nobody is making #10 and a viewer is waiting for it"
|
|
58
|
-
});
|
|
59
|
-
return { run, process: process_, lines, ends };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
test("a start says why it is starting and with what", () => {
|
|
63
|
-
// The argument list alone cannot say whether this was a first open, a seek, a
|
|
64
|
-
// quality step or a move off covered material — and with several runs at once
|
|
65
|
-
// that is the only way to tell one start from another.
|
|
66
|
-
const { lines } = makeRun();
|
|
67
|
-
const [level, line] = lines[0];
|
|
68
|
-
assert.equal(level, "info");
|
|
69
|
-
// A run is named by what it produces: the stretch it was given, on the output
|
|
70
|
-
// it makes segments of. No other live run of that output can hold it.
|
|
71
|
-
assert.match(line, /encode-run #10\.\.#14 of torrent:abc:/);
|
|
72
|
-
assert.match(line, /nobody is making #10/);
|
|
73
|
-
assert.match(line, /ffmpeg -i in out/);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("its head is the next number it will make, and moves as it makes them", () => {
|
|
77
|
-
const { run } = makeRun();
|
|
78
|
-
assert.equal(run.head, 10);
|
|
79
|
-
run.noteProduced(10);
|
|
80
|
-
run.noteProduced(11);
|
|
81
|
-
assert.equal(run.head, 12);
|
|
82
|
-
assert.equal(run.reached, 11);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("reaching the end of its stretch and exiting is the one normal ending", () => {
|
|
86
|
-
const { run, process, ends, lines } = makeRun({ from: 10, to: 12 });
|
|
87
|
-
for (const index of [10, 11, 12]) {
|
|
88
|
-
run.noteProduced(index);
|
|
89
|
-
}
|
|
90
|
-
process.exitWith(0, null);
|
|
91
|
-
assert.equal(ends.length, 1);
|
|
92
|
-
assert.equal(ends[0].ending, ENCODE_EXIT.COMPLETE);
|
|
93
|
-
assert.equal(ends[0].normal, true);
|
|
94
|
-
assert.equal(lines.at(-1)[0], "info", "a normal ending is not a warning");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("exiting cleanly short of its stretch is abnormal and says where it stopped", () => {
|
|
98
|
-
// ffmpeg exits zero both at the end of a file and when its input stops
|
|
99
|
-
// producing bytes; over a torrent the two look identical to it.
|
|
100
|
-
const { run, process, ends } = makeRun({ from: 10, to: 14 });
|
|
101
|
-
run.noteProduced(10);
|
|
102
|
-
run.noteProduced(11);
|
|
103
|
-
process.exitWith(0, null);
|
|
104
|
-
assert.equal(ends[0].ending, ENCODE_EXIT.SHORT);
|
|
105
|
-
assert.equal(ends[0].normal, false);
|
|
106
|
-
assert.equal(ends[0].reached, 11);
|
|
107
|
-
assert.match(ends[0].because, /#11 of #14/);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("a non-zero exit is a failure carrying its code", () => {
|
|
111
|
-
const { process, ends, lines } = makeRun();
|
|
112
|
-
process.exitWith(255, null);
|
|
113
|
-
assert.equal(ends[0].ending, ENCODE_EXIT.FAILED);
|
|
114
|
-
assert.equal(ends[0].code, 255);
|
|
115
|
-
assert.equal(lines.at(-1)[0], "warn");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("our own kill is abnormal too, and carries the reason we gave", () => {
|
|
119
|
-
// Hiding it among the normal endings would make a count of abnormal endings
|
|
120
|
-
// useless, which is the whole point of counting them.
|
|
121
|
-
const { run, process, ends } = makeRun();
|
|
122
|
-
run.stop("moved past 20 segments that are already made");
|
|
123
|
-
assert.deepEqual(process.signals, ["SIGTERM"]);
|
|
124
|
-
process.exitWith(null, "SIGTERM");
|
|
125
|
-
assert.equal(ends[0].ending, ENCODE_EXIT.STOPPED);
|
|
126
|
-
assert.equal(ends[0].normal, false);
|
|
127
|
-
assert.match(ends[0].because, /already made/);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("an ending reports the stretch, how far it got and how long it lived", () => {
|
|
131
|
-
const { run, process, ends } = makeRun({ from: 10, to: 14 });
|
|
132
|
-
run.noteProduced(10);
|
|
133
|
-
process.exitWith(1, null);
|
|
134
|
-
const ended = ends[0];
|
|
135
|
-
assert.equal(ended.from, 10);
|
|
136
|
-
assert.equal(ended.to, 14);
|
|
137
|
-
assert.equal(ended.reached, 10);
|
|
138
|
-
assert.equal(ended.livedMs, 0, "the clock is injected, so this is exact");
|
|
139
|
-
assert.equal(ended.address.includes("854x480"), true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("a process that could not be started ends like any other failure", () => {
|
|
143
|
-
const { process, ends } = makeRun();
|
|
144
|
-
process.emit("error", new Error("ENOENT"));
|
|
145
|
-
assert.equal(ends[0].ending, ENCODE_EXIT.FAILED);
|
|
146
|
-
assert.match(ends[0].because, /ENOENT/);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("only a measured speed is kept", () => {
|
|
150
|
-
const { run } = makeRun();
|
|
151
|
-
assert.equal(run.speedX, 0, "nothing measured yet");
|
|
152
|
-
run.noteSpeed(0);
|
|
153
|
-
assert.equal(run.speedX, 0);
|
|
154
|
-
run.noteSpeed(2.5);
|
|
155
|
-
assert.equal(run.speedX, 2.5);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("a segment number behind its own start is not its to claim", () => {
|
|
159
|
-
const { run } = makeRun({ from: 10, to: 14 });
|
|
160
|
-
run.noteProduced(4);
|
|
161
|
-
assert.deepEqual(run.produced, []);
|
|
162
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file One encoder's lifetime: the stretch it was given, where it got to, and
|
|
3
|
+
* whether its ending was the normal one.
|
|
4
|
+
*
|
|
5
|
+
* The process is injected, so none of this needs ffmpeg. What is checked is the
|
|
6
|
+
* accounting the user asked for on 2026-09-04: exactly one ending is normal,
|
|
7
|
+
* every other one says why, and none of them goes unrecorded.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { EventEmitter } from "node:events";
|
|
13
|
+
import { EncodeRun } from "../services/encode/EncodeRun.js";
|
|
14
|
+
import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
|
|
15
|
+
import { SoftwareEncoder } from "../services/encode/SoftwareEncoder.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A process that does nothing until the test says what became of it.
|
|
19
|
+
*/
|
|
20
|
+
class FakeProcess extends EventEmitter {
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.pid = 4242;
|
|
24
|
+
this.signals = [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
kill(signal) {
|
|
28
|
+
this.signals.push(signal);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
exitWith(code, signal = null) {
|
|
32
|
+
this.emit("exit", code, signal);
|
|
33
|
+
this.emit("close", code, signal);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Partial<{from:number,to:number}>} [span]
|
|
39
|
+
*/
|
|
40
|
+
function makeRun(span = {}) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
const ends = [];
|
|
43
|
+
const process_ = new FakeProcess();
|
|
44
|
+
const run = new EncodeRun({
|
|
45
|
+
address: "torrent:abc:fmt=fmp4:grid=kf@0:video-only:v=0/enc/libx264/854x480@24/veryfast/none/vbv=-",
|
|
46
|
+
encoder: new SoftwareEncoder(),
|
|
47
|
+
from: span.from ?? 10,
|
|
48
|
+
to: span.to ?? 14,
|
|
49
|
+
buildArgs: () => ["-i", "in", "out"],
|
|
50
|
+
spawn: () => process_,
|
|
51
|
+
logger: {
|
|
52
|
+
info: (line) => lines.push(["info", line]),
|
|
53
|
+
warn: (line) => lines.push(["warn", line])
|
|
54
|
+
},
|
|
55
|
+
now: () => 1000,
|
|
56
|
+
onEnded: (ended) => ends.push(ended),
|
|
57
|
+
because: "nobody is making #10 and a viewer is waiting for it"
|
|
58
|
+
});
|
|
59
|
+
return { run, process: process_, lines, ends };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("a start says why it is starting and with what", () => {
|
|
63
|
+
// The argument list alone cannot say whether this was a first open, a seek, a
|
|
64
|
+
// quality step or a move off covered material — and with several runs at once
|
|
65
|
+
// that is the only way to tell one start from another.
|
|
66
|
+
const { lines } = makeRun();
|
|
67
|
+
const [level, line] = lines[0];
|
|
68
|
+
assert.equal(level, "info");
|
|
69
|
+
// A run is named by what it produces: the stretch it was given, on the output
|
|
70
|
+
// it makes segments of. No other live run of that output can hold it.
|
|
71
|
+
assert.match(line, /encode-run #10\.\.#14 of torrent:abc:/);
|
|
72
|
+
assert.match(line, /nobody is making #10/);
|
|
73
|
+
assert.match(line, /ffmpeg -i in out/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("its head is the next number it will make, and moves as it makes them", () => {
|
|
77
|
+
const { run } = makeRun();
|
|
78
|
+
assert.equal(run.head, 10);
|
|
79
|
+
run.noteProduced(10);
|
|
80
|
+
run.noteProduced(11);
|
|
81
|
+
assert.equal(run.head, 12);
|
|
82
|
+
assert.equal(run.reached, 11);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("reaching the end of its stretch and exiting is the one normal ending", () => {
|
|
86
|
+
const { run, process, ends, lines } = makeRun({ from: 10, to: 12 });
|
|
87
|
+
for (const index of [10, 11, 12]) {
|
|
88
|
+
run.noteProduced(index);
|
|
89
|
+
}
|
|
90
|
+
process.exitWith(0, null);
|
|
91
|
+
assert.equal(ends.length, 1);
|
|
92
|
+
assert.equal(ends[0].ending, ENCODE_EXIT.COMPLETE);
|
|
93
|
+
assert.equal(ends[0].normal, true);
|
|
94
|
+
assert.equal(lines.at(-1)[0], "info", "a normal ending is not a warning");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("exiting cleanly short of its stretch is abnormal and says where it stopped", () => {
|
|
98
|
+
// ffmpeg exits zero both at the end of a file and when its input stops
|
|
99
|
+
// producing bytes; over a torrent the two look identical to it.
|
|
100
|
+
const { run, process, ends } = makeRun({ from: 10, to: 14 });
|
|
101
|
+
run.noteProduced(10);
|
|
102
|
+
run.noteProduced(11);
|
|
103
|
+
process.exitWith(0, null);
|
|
104
|
+
assert.equal(ends[0].ending, ENCODE_EXIT.SHORT);
|
|
105
|
+
assert.equal(ends[0].normal, false);
|
|
106
|
+
assert.equal(ends[0].reached, 11);
|
|
107
|
+
assert.match(ends[0].because, /#11 of #14/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a non-zero exit is a failure carrying its code", () => {
|
|
111
|
+
const { process, ends, lines } = makeRun();
|
|
112
|
+
process.exitWith(255, null);
|
|
113
|
+
assert.equal(ends[0].ending, ENCODE_EXIT.FAILED);
|
|
114
|
+
assert.equal(ends[0].code, 255);
|
|
115
|
+
assert.equal(lines.at(-1)[0], "warn");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("our own kill is abnormal too, and carries the reason we gave", () => {
|
|
119
|
+
// Hiding it among the normal endings would make a count of abnormal endings
|
|
120
|
+
// useless, which is the whole point of counting them.
|
|
121
|
+
const { run, process, ends } = makeRun();
|
|
122
|
+
run.stop("moved past 20 segments that are already made");
|
|
123
|
+
assert.deepEqual(process.signals, ["SIGTERM"]);
|
|
124
|
+
process.exitWith(null, "SIGTERM");
|
|
125
|
+
assert.equal(ends[0].ending, ENCODE_EXIT.STOPPED);
|
|
126
|
+
assert.equal(ends[0].normal, false);
|
|
127
|
+
assert.match(ends[0].because, /already made/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("an ending reports the stretch, how far it got and how long it lived", () => {
|
|
131
|
+
const { run, process, ends } = makeRun({ from: 10, to: 14 });
|
|
132
|
+
run.noteProduced(10);
|
|
133
|
+
process.exitWith(1, null);
|
|
134
|
+
const ended = ends[0];
|
|
135
|
+
assert.equal(ended.from, 10);
|
|
136
|
+
assert.equal(ended.to, 14);
|
|
137
|
+
assert.equal(ended.reached, 10);
|
|
138
|
+
assert.equal(ended.livedMs, 0, "the clock is injected, so this is exact");
|
|
139
|
+
assert.equal(ended.address.includes("854x480"), true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("a process that could not be started ends like any other failure", () => {
|
|
143
|
+
const { process, ends } = makeRun();
|
|
144
|
+
process.emit("error", new Error("ENOENT"));
|
|
145
|
+
assert.equal(ends[0].ending, ENCODE_EXIT.FAILED);
|
|
146
|
+
assert.match(ends[0].because, /ENOENT/);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("only a measured speed is kept", () => {
|
|
150
|
+
const { run } = makeRun();
|
|
151
|
+
assert.equal(run.speedX, 0, "nothing measured yet");
|
|
152
|
+
run.noteSpeed(0);
|
|
153
|
+
assert.equal(run.speedX, 0);
|
|
154
|
+
run.noteSpeed(2.5);
|
|
155
|
+
assert.equal(run.speedX, 2.5);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("a segment number behind its own start is not its to claim", () => {
|
|
159
|
+
const { run } = makeRun({ from: 10, to: 14 });
|
|
160
|
+
run.noteProduced(4);
|
|
161
|
+
assert.deepEqual(run.produced, []);
|
|
162
|
+
});
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The slope that turns ffmpeg's progress reports into a preset's speed.
|
|
3
|
-
*
|
|
4
|
-
* Written against the faults a review found in it on 2026-08-15, each of which
|
|
5
|
-
* would have opened the quality ladder rather than closing it: a window of no
|
|
6
|
-
* width at all, and a position ffmpeg reports as the smallest signed 64-bit
|
|
7
|
-
* integer when it has none yet.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import test from "node:test";
|
|
12
|
-
|
|
13
|
-
import { slopeOf } from "../services/encode/hwaccel.js";
|
|
14
|
-
|
|
15
|
-
const at = (wallSec, outSec) => ({ wallSec, outSec });
|
|
16
|
-
|
|
17
|
-
test("a steady run measures its own speed", () => {
|
|
18
|
-
assert.equal(slopeOf([at(1, 10), at(2, 14)]), 4);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("a window of no width is refused, however tempting the numbers", () => {
|
|
22
|
-
// Two reports a millisecond apart: 0.042 s of video over 0.001 s of clock is
|
|
23
|
-
// 42x, on a host that may be doing 3x.
|
|
24
|
-
assert.equal(slopeOf([at(1.0, 10), at(1.001, 10.042)], 0.2), null);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("a run that produced nothing between two reports says nothing", () => {
|
|
28
|
-
assert.equal(slopeOf([at(1, 10), at(3, 10)]), null);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("one report is not a measurement", () => {
|
|
32
|
-
assert.equal(slopeOf([at(1, 10)]), null);
|
|
33
|
-
assert.equal(slopeOf([]), null);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("an impossible speed is a fault, not a fast machine", () => {
|
|
37
|
-
// Nothing encodes 640x360 five thousand times realtime; a reading that says
|
|
38
|
-
// so is a measurement fault, and letting it through raises the bar every
|
|
39
|
-
// ladder decision is taken from.
|
|
40
|
-
assert.equal(slopeOf([at(1, 0), at(2, 5000)]), null);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("the narrowest allowed window is honoured", () => {
|
|
44
|
-
assert.equal(slopeOf([at(1.0, 10), at(1.3, 11)], 0.5), null);
|
|
45
|
-
assert.ok(slopeOf([at(1.0, 10), at(1.6, 12)], 0.5) > 0);
|
|
46
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file The slope that turns ffmpeg's progress reports into a preset's speed.
|
|
3
|
+
*
|
|
4
|
+
* Written against the faults a review found in it on 2026-08-15, each of which
|
|
5
|
+
* would have opened the quality ladder rather than closing it: a window of no
|
|
6
|
+
* width at all, and a position ffmpeg reports as the smallest signed 64-bit
|
|
7
|
+
* integer when it has none yet.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
|
|
13
|
+
import { slopeOf } from "../services/encode/hwaccel.js";
|
|
14
|
+
|
|
15
|
+
const at = (wallSec, outSec) => ({ wallSec, outSec });
|
|
16
|
+
|
|
17
|
+
test("a steady run measures its own speed", () => {
|
|
18
|
+
assert.equal(slopeOf([at(1, 10), at(2, 14)]), 4);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("a window of no width is refused, however tempting the numbers", () => {
|
|
22
|
+
// Two reports a millisecond apart: 0.042 s of video over 0.001 s of clock is
|
|
23
|
+
// 42x, on a host that may be doing 3x.
|
|
24
|
+
assert.equal(slopeOf([at(1.0, 10), at(1.001, 10.042)], 0.2), null);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("a run that produced nothing between two reports says nothing", () => {
|
|
28
|
+
assert.equal(slopeOf([at(1, 10), at(3, 10)]), null);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("one report is not a measurement", () => {
|
|
32
|
+
assert.equal(slopeOf([at(1, 10)]), null);
|
|
33
|
+
assert.equal(slopeOf([]), null);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("an impossible speed is a fault, not a fast machine", () => {
|
|
37
|
+
// Nothing encodes 640x360 five thousand times realtime; a reading that says
|
|
38
|
+
// so is a measurement fault, and letting it through raises the bar every
|
|
39
|
+
// ladder decision is taken from.
|
|
40
|
+
assert.equal(slopeOf([at(1, 0), at(2, 5000)]), null);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("the narrowest allowed window is honoured", () => {
|
|
44
|
+
assert.equal(slopeOf([at(1.0, 10), at(1.3, 11)], 0.5), null);
|
|
45
|
+
assert.ok(slopeOf([at(1.0, 10), at(1.6, 12)], 0.5) > 0);
|
|
46
|
+
});
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file An output holds what is born with it, and not what is produced.
|
|
3
|
-
*
|
|
4
|
-
* Decided with the user 2026-09-16: which pieces are made is the segment
|
|
5
|
-
* store's, and which encoders run is the encoding orchestrator's. A second
|
|
6
|
-
* holder of either is how the stale second owner stopped playback outright.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import test from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { EncodedOutput } from "../services/encode/output/EncodedOutput.js";
|
|
12
|
-
import { outputSpec } from "./helpers/output-spec.js";
|
|
13
|
-
|
|
14
|
-
function made() {
|
|
15
|
-
return new EncodedOutput({
|
|
16
|
-
id: "aaaaaaaabbbbcccc",
|
|
17
|
-
spec: outputSpec({ transcodeVideo: true, width: 1280, height: 720 }),
|
|
18
|
-
file: { key: "source-1:0" },
|
|
19
|
-
keyframes: null,
|
|
20
|
-
timeline: null,
|
|
21
|
-
segmentFormat: null,
|
|
22
|
-
output: null,
|
|
23
|
-
useSyntheticPlaylist: true,
|
|
24
|
-
playlistText: "#EXTM3U\n",
|
|
25
|
-
variantHeight: 720
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test("its address is its format, read from the spec and kept nowhere else", () => {
|
|
30
|
-
const output = made();
|
|
31
|
-
assert.equal(output.outputKey, output.spec.toKey());
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("it keeps no statement of what has been produced or what is running", () => {
|
|
35
|
-
const output = made();
|
|
36
|
-
for (const field of ["runs", "runState", "progress", "produced", "ready", "state", "lastError", "failedStartCount"]) {
|
|
37
|
-
assert.equal(field in output, false, `${field} belongs to the store or the encoding orchestrator`);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file An output holds what is born with it, and not what is produced.
|
|
3
|
+
*
|
|
4
|
+
* Decided with the user 2026-09-16: which pieces are made is the segment
|
|
5
|
+
* store's, and which encoders run is the encoding orchestrator's. A second
|
|
6
|
+
* holder of either is how the stale second owner stopped playback outright.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { EncodedOutput } from "../services/encode/output/EncodedOutput.js";
|
|
12
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
13
|
+
|
|
14
|
+
function made() {
|
|
15
|
+
return new EncodedOutput({
|
|
16
|
+
id: "aaaaaaaabbbbcccc",
|
|
17
|
+
spec: outputSpec({ transcodeVideo: true, width: 1280, height: 720 }),
|
|
18
|
+
file: { key: "source-1:0" },
|
|
19
|
+
keyframes: null,
|
|
20
|
+
timeline: null,
|
|
21
|
+
segmentFormat: null,
|
|
22
|
+
output: null,
|
|
23
|
+
useSyntheticPlaylist: true,
|
|
24
|
+
playlistText: "#EXTM3U\n",
|
|
25
|
+
variantHeight: 720
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("its address is its format, read from the spec and kept nowhere else", () => {
|
|
30
|
+
const output = made();
|
|
31
|
+
assert.equal(output.outputKey, output.spec.toKey());
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("it keeps no statement of what has been produced or what is running", () => {
|
|
35
|
+
const output = made();
|
|
36
|
+
for (const field of ["runs", "runState", "progress", "produced", "ready", "state", "lastError", "failedStartCount"]) {
|
|
37
|
+
assert.equal(field in output, false, `${field} belongs to the store or the encoding orchestrator`);
|
|
38
|
+
}
|
|
39
|
+
});
|