@torrent-tv/proxy 2.88.2 → 2.88.4
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 +2259 -2246
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +23 -4
- 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/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 -173
- 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 -84
- 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/render-run-graph.js +173 -173
- package/server.js +725 -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 +1235 -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 -758
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2282 -2184
- 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 -416
- 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 +1150 -1141
- 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 -254
- package/services/encode/quality/output-format.js +319 -315
- 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 +456 -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 +21 -6
- 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 +298 -245
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +609 -603
- 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 -1244
- 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 -131
- 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 -0
- 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 +760 -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 +13 -8
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +995 -992
- 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 +1931 -1931
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1240 -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/episode-naming.js +182 -182
- 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 +2675 -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-buffer-continuity.test.js +70 -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/episode-naming.test.js +131 -131
- 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/fixtures/audio-frame-boundary.json +1 -0
- package/test/fmp4-presentation-clock.test.js +147 -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 -111
- 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 +270 -204
- 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 +453 -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 -1229
- 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 +323 -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 -102
- 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 -0
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +113 -113
- 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 -407
- 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
|
@@ -1,202 +1,202 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file A start that cannot succeed must not be commanded for ever.
|
|
3
|
-
*
|
|
4
|
-
* Two defects met here on 2026-09-05, and only together did they produce an
|
|
5
|
-
* unbounded loop.
|
|
6
|
-
*
|
|
7
|
-
* The first is older than the second and had never once run: `#onRunEnded`
|
|
8
|
-
* removed the run from the session and THEN asked whether the run was still the
|
|
9
|
-
* session's, a question that always answers "no" after the removal. Everything
|
|
10
|
-
* below that point was unreachable — the fallback from a failed hardware
|
|
11
|
-
* encoder to software, the retry when the torrent data goes away, the limit on
|
|
12
|
-
* retrying a position that keeps failing, and the error line naming the ffmpeg
|
|
13
|
-
* command. Measured over both of the field host's log files: zero occurrences
|
|
14
|
-
* of that error line and zero of `fast failure at segment`, across every
|
|
15
|
-
* session that proxy had ever run.
|
|
16
|
-
*
|
|
17
|
-
* The second is that the limit only counted past segment #0, because it was
|
|
18
|
-
* written for seek restarts and a seek is never to the beginning — leaving the
|
|
19
|
-
* one position the plan commands first with no count at all.
|
|
20
|
-
*
|
|
21
|
-
* With what encoders should exist re-decided on a five-second timer, the two
|
|
22
|
-
* showed up as a restart every five seconds, for sixteen minutes, in the field.
|
|
23
|
-
* Re-decided the moment its inputs change, they show up as a loop as fast as
|
|
24
|
-
* spawning can fail: fifty passes of the plan before a probe stopped it.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import test from "node:test";
|
|
28
|
-
import assert from "node:assert/strict";
|
|
29
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
30
|
-
import os from "node:os";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
import { wireOutputs } from "../services/server/wire-outputs.js";
|
|
33
|
-
import { SourceFile } from "../services/media/SourceFile.js";
|
|
34
|
-
import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
|
|
35
|
-
import { outputSpec } from "./helpers/output-spec.js";
|
|
36
|
-
|
|
37
|
-
const SESSION_ID = "cccccccc00004000";
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A session with one run in it, shaped as the manager expects.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} dirPath
|
|
43
|
-
* @returns {{ session: object, run: object }}
|
|
44
|
-
*/
|
|
45
|
-
function sessionWithARun(dirPath) {
|
|
46
|
-
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …", state: "ENDED_FAILED" };
|
|
47
|
-
const session = {
|
|
48
|
-
id: SESSION_ID,
|
|
49
|
-
spec: outputSpec({ transcodeVideo: false }),
|
|
50
|
-
dirPath,
|
|
51
|
-
state: "live",
|
|
52
|
-
file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" }),
|
|
53
|
-
get inputFile() { return this.file; },
|
|
54
|
-
get audioFile() { return this.file; },
|
|
55
|
-
outputKey: "output-under-test",
|
|
56
|
-
lastRequestedSegment: 0,
|
|
57
|
-
progress: { updatedAt: 0, processedSeconds: 0, totalSeconds: 100, startPositionSeconds: 0 },
|
|
58
|
-
lastAccessedAt: Date.now()
|
|
59
|
-
};
|
|
60
|
-
return { session, run };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @param {object} run
|
|
65
|
-
* @param {number} from
|
|
66
|
-
* @returns {object}
|
|
67
|
-
*/
|
|
68
|
-
function failedFast(run, from) {
|
|
69
|
-
return {
|
|
70
|
-
address: "output-under-test",
|
|
71
|
-
run,
|
|
72
|
-
ending: ENCODE_EXIT.FAILED,
|
|
73
|
-
from,
|
|
74
|
-
to: -1,
|
|
75
|
-
livedMs: 20,
|
|
76
|
-
because: "the process could not be started: spawn ffmpeg ENOENT",
|
|
77
|
-
lastError: "the process could not be started: spawn ffmpeg ENOENT",
|
|
78
|
-
producedCount: 0
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
test("a run that ended is still recognised as the session's own", (t) => {
|
|
83
|
-
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
84
|
-
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
85
|
-
const manager = wireOutputs({
|
|
86
|
-
enabled: true,
|
|
87
|
-
ffmpegBin: "ffmpeg",
|
|
88
|
-
localBindHost: "127.0.0.1",
|
|
89
|
-
localPort: 9090
|
|
90
|
-
});
|
|
91
|
-
const { session, run } = sessionWithARun(dirPath);
|
|
92
|
-
manager.outputs.set(SESSION_ID, session);
|
|
93
|
-
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
94
|
-
|
|
95
|
-
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
96
|
-
|
|
97
|
-
// If the identity were read after the removal, nothing here would have been
|
|
98
|
-
// written: the handler would have returned at its second line.
|
|
99
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1, "the failure was counted");
|
|
100
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, 0, "at the position it happened");
|
|
101
|
-
assert.ok(manager.encodeOrchestrator.errorOf(session.outputKey).length > 0, "and the encoding owner knows what went wrong");
|
|
102
|
-
assert.equal(
|
|
103
|
-
manager.encodeOrchestrator.stateOf(session.outputKey),
|
|
104
|
-
"ENDED_FAILED",
|
|
105
|
-
"the ending remains observable after the ended run leaves the registry"
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("the count runs at segment 0, which is where a first start happens", (t) => {
|
|
110
|
-
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
111
|
-
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
112
|
-
const manager = wireOutputs({
|
|
113
|
-
enabled: true,
|
|
114
|
-
ffmpegBin: "ffmpeg",
|
|
115
|
-
localBindHost: "127.0.0.1",
|
|
116
|
-
localPort: 9090
|
|
117
|
-
});
|
|
118
|
-
const { session } = sessionWithARun(dirPath);
|
|
119
|
-
manager.outputs.set(SESSION_ID, session);
|
|
120
|
-
|
|
121
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
122
|
-
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
123
|
-
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
124
|
-
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
125
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, attempt);
|
|
126
|
-
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), attempt < 3);
|
|
127
|
-
}
|
|
128
|
-
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 1), true, "another position has its own budget");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("real work resets the count, so a transient failure is not permanent", (t) => {
|
|
132
|
-
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
133
|
-
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
134
|
-
const manager = wireOutputs({
|
|
135
|
-
enabled: true,
|
|
136
|
-
ffmpegBin: "ffmpeg",
|
|
137
|
-
localBindHost: "127.0.0.1",
|
|
138
|
-
localPort: 9090
|
|
139
|
-
});
|
|
140
|
-
const { session } = sessionWithARun(dirPath);
|
|
141
|
-
manager.outputs.set(SESSION_ID, session);
|
|
142
|
-
|
|
143
|
-
const quick = { from: 0, to: -1 };
|
|
144
|
-
manager.encodeOrchestrator.adopt(session.outputKey, quick);
|
|
145
|
-
manager.encodeRuns.noteRunEnded(session, quick, failedFast(quick, 0));
|
|
146
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1);
|
|
147
|
-
|
|
148
|
-
const lived = { from: 0, to: -1 };
|
|
149
|
-
manager.encodeOrchestrator.adopt(session.outputKey, lived);
|
|
150
|
-
manager.encodeRuns.noteRunEnded(session, lived, { ...failedFast(lived, 0), livedMs: 30_000 });
|
|
151
|
-
|
|
152
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0, "a run that did real work is not a failing start");
|
|
153
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, -1);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("a failure belongs to the output it happened on, not to the next output of the same address", async (t) => {
|
|
157
|
-
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
158
|
-
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
159
|
-
const manager = wireOutputs({
|
|
160
|
-
enabled: true,
|
|
161
|
-
ffmpegBin: "ffmpeg",
|
|
162
|
-
localBindHost: "127.0.0.1",
|
|
163
|
-
localPort: 9090
|
|
164
|
-
});
|
|
165
|
-
t.after(() => manager.lifecycle.disposeAll());
|
|
166
|
-
const { session } = sessionWithARun(dirPath);
|
|
167
|
-
manager.outputs.set(SESSION_ID, session);
|
|
168
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
169
|
-
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
170
|
-
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
171
|
-
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
172
|
-
}
|
|
173
|
-
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), false, "the first output is blocked");
|
|
174
|
-
|
|
175
|
-
await manager.lifecycle.disposeSession(SESSION_ID);
|
|
176
|
-
|
|
177
|
-
// The same parameters opened again: the name follows from the key, so the
|
|
178
|
-
// new output has the same address. What failed on the disposed one is not a
|
|
179
|
-
// fact about this one — a viewer arriving is new information.
|
|
180
|
-
assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE", "no ending is inherited");
|
|
181
|
-
assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "", "no error is inherited");
|
|
182
|
-
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), true, "no blocked position is inherited");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("a run ending after its output was disposed leaves nothing behind for the next output", (t) => {
|
|
186
|
-
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
187
|
-
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
188
|
-
const manager = wireOutputs({
|
|
189
|
-
enabled: true,
|
|
190
|
-
ffmpegBin: "ffmpeg",
|
|
191
|
-
localBindHost: "127.0.0.1",
|
|
192
|
-
localPort: 9090
|
|
193
|
-
});
|
|
194
|
-
const { session, run } = sessionWithARun(dirPath);
|
|
195
|
-
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
196
|
-
// Disposal removed the output from the registry; its process ends later.
|
|
197
|
-
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
198
|
-
|
|
199
|
-
assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE");
|
|
200
|
-
assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "");
|
|
201
|
-
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0);
|
|
202
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file A start that cannot succeed must not be commanded for ever.
|
|
3
|
+
*
|
|
4
|
+
* Two defects met here on 2026-09-05, and only together did they produce an
|
|
5
|
+
* unbounded loop.
|
|
6
|
+
*
|
|
7
|
+
* The first is older than the second and had never once run: `#onRunEnded`
|
|
8
|
+
* removed the run from the session and THEN asked whether the run was still the
|
|
9
|
+
* session's, a question that always answers "no" after the removal. Everything
|
|
10
|
+
* below that point was unreachable — the fallback from a failed hardware
|
|
11
|
+
* encoder to software, the retry when the torrent data goes away, the limit on
|
|
12
|
+
* retrying a position that keeps failing, and the error line naming the ffmpeg
|
|
13
|
+
* command. Measured over both of the field host's log files: zero occurrences
|
|
14
|
+
* of that error line and zero of `fast failure at segment`, across every
|
|
15
|
+
* session that proxy had ever run.
|
|
16
|
+
*
|
|
17
|
+
* The second is that the limit only counted past segment #0, because it was
|
|
18
|
+
* written for seek restarts and a seek is never to the beginning — leaving the
|
|
19
|
+
* one position the plan commands first with no count at all.
|
|
20
|
+
*
|
|
21
|
+
* With what encoders should exist re-decided on a five-second timer, the two
|
|
22
|
+
* showed up as a restart every five seconds, for sixteen minutes, in the field.
|
|
23
|
+
* Re-decided the moment its inputs change, they show up as a loop as fast as
|
|
24
|
+
* spawning can fail: fifty passes of the plan before a probe stopped it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import test from "node:test";
|
|
28
|
+
import assert from "node:assert/strict";
|
|
29
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
30
|
+
import os from "node:os";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { wireOutputs } from "../services/server/wire-outputs.js";
|
|
33
|
+
import { SourceFile } from "../services/media/SourceFile.js";
|
|
34
|
+
import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
|
|
35
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
36
|
+
|
|
37
|
+
const SESSION_ID = "cccccccc00004000";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A session with one run in it, shaped as the manager expects.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} dirPath
|
|
43
|
+
* @returns {{ session: object, run: object }}
|
|
44
|
+
*/
|
|
45
|
+
function sessionWithARun(dirPath) {
|
|
46
|
+
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …", state: "ENDED_FAILED" };
|
|
47
|
+
const session = {
|
|
48
|
+
id: SESSION_ID,
|
|
49
|
+
spec: outputSpec({ transcodeVideo: false }),
|
|
50
|
+
dirPath,
|
|
51
|
+
state: "live",
|
|
52
|
+
file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" }),
|
|
53
|
+
get inputFile() { return this.file; },
|
|
54
|
+
get audioFile() { return this.file; },
|
|
55
|
+
outputKey: "output-under-test",
|
|
56
|
+
lastRequestedSegment: 0,
|
|
57
|
+
progress: { updatedAt: 0, processedSeconds: 0, totalSeconds: 100, startPositionSeconds: 0 },
|
|
58
|
+
lastAccessedAt: Date.now()
|
|
59
|
+
};
|
|
60
|
+
return { session, run };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {object} run
|
|
65
|
+
* @param {number} from
|
|
66
|
+
* @returns {object}
|
|
67
|
+
*/
|
|
68
|
+
function failedFast(run, from) {
|
|
69
|
+
return {
|
|
70
|
+
address: "output-under-test",
|
|
71
|
+
run,
|
|
72
|
+
ending: ENCODE_EXIT.FAILED,
|
|
73
|
+
from,
|
|
74
|
+
to: -1,
|
|
75
|
+
livedMs: 20,
|
|
76
|
+
because: "the process could not be started: spawn ffmpeg ENOENT",
|
|
77
|
+
lastError: "the process could not be started: spawn ffmpeg ENOENT",
|
|
78
|
+
producedCount: 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test("a run that ended is still recognised as the session's own", (t) => {
|
|
83
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
84
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
85
|
+
const manager = wireOutputs({
|
|
86
|
+
enabled: true,
|
|
87
|
+
ffmpegBin: "ffmpeg",
|
|
88
|
+
localBindHost: "127.0.0.1",
|
|
89
|
+
localPort: 9090
|
|
90
|
+
});
|
|
91
|
+
const { session, run } = sessionWithARun(dirPath);
|
|
92
|
+
manager.outputs.set(SESSION_ID, session);
|
|
93
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
94
|
+
|
|
95
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
96
|
+
|
|
97
|
+
// If the identity were read after the removal, nothing here would have been
|
|
98
|
+
// written: the handler would have returned at its second line.
|
|
99
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1, "the failure was counted");
|
|
100
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, 0, "at the position it happened");
|
|
101
|
+
assert.ok(manager.encodeOrchestrator.errorOf(session.outputKey).length > 0, "and the encoding owner knows what went wrong");
|
|
102
|
+
assert.equal(
|
|
103
|
+
manager.encodeOrchestrator.stateOf(session.outputKey),
|
|
104
|
+
"ENDED_FAILED",
|
|
105
|
+
"the ending remains observable after the ended run leaves the registry"
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("the count runs at segment 0, which is where a first start happens", (t) => {
|
|
110
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
111
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
112
|
+
const manager = wireOutputs({
|
|
113
|
+
enabled: true,
|
|
114
|
+
ffmpegBin: "ffmpeg",
|
|
115
|
+
localBindHost: "127.0.0.1",
|
|
116
|
+
localPort: 9090
|
|
117
|
+
});
|
|
118
|
+
const { session } = sessionWithARun(dirPath);
|
|
119
|
+
manager.outputs.set(SESSION_ID, session);
|
|
120
|
+
|
|
121
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
122
|
+
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
123
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
124
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
125
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, attempt);
|
|
126
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), attempt < 3);
|
|
127
|
+
}
|
|
128
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 1), true, "another position has its own budget");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("real work resets the count, so a transient failure is not permanent", (t) => {
|
|
132
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
133
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
134
|
+
const manager = wireOutputs({
|
|
135
|
+
enabled: true,
|
|
136
|
+
ffmpegBin: "ffmpeg",
|
|
137
|
+
localBindHost: "127.0.0.1",
|
|
138
|
+
localPort: 9090
|
|
139
|
+
});
|
|
140
|
+
const { session } = sessionWithARun(dirPath);
|
|
141
|
+
manager.outputs.set(SESSION_ID, session);
|
|
142
|
+
|
|
143
|
+
const quick = { from: 0, to: -1 };
|
|
144
|
+
manager.encodeOrchestrator.adopt(session.outputKey, quick);
|
|
145
|
+
manager.encodeRuns.noteRunEnded(session, quick, failedFast(quick, 0));
|
|
146
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1);
|
|
147
|
+
|
|
148
|
+
const lived = { from: 0, to: -1 };
|
|
149
|
+
manager.encodeOrchestrator.adopt(session.outputKey, lived);
|
|
150
|
+
manager.encodeRuns.noteRunEnded(session, lived, { ...failedFast(lived, 0), livedMs: 30_000 });
|
|
151
|
+
|
|
152
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0, "a run that did real work is not a failing start");
|
|
153
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, -1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a failure belongs to the output it happened on, not to the next output of the same address", async (t) => {
|
|
157
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
158
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
159
|
+
const manager = wireOutputs({
|
|
160
|
+
enabled: true,
|
|
161
|
+
ffmpegBin: "ffmpeg",
|
|
162
|
+
localBindHost: "127.0.0.1",
|
|
163
|
+
localPort: 9090
|
|
164
|
+
});
|
|
165
|
+
t.after(() => manager.lifecycle.disposeAll());
|
|
166
|
+
const { session } = sessionWithARun(dirPath);
|
|
167
|
+
manager.outputs.set(SESSION_ID, session);
|
|
168
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
169
|
+
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
170
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
171
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
172
|
+
}
|
|
173
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), false, "the first output is blocked");
|
|
174
|
+
|
|
175
|
+
await manager.lifecycle.disposeSession(SESSION_ID);
|
|
176
|
+
|
|
177
|
+
// The same parameters opened again: the name follows from the key, so the
|
|
178
|
+
// new output has the same address. What failed on the disposed one is not a
|
|
179
|
+
// fact about this one — a viewer arriving is new information.
|
|
180
|
+
assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE", "no ending is inherited");
|
|
181
|
+
assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "", "no error is inherited");
|
|
182
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), true, "no blocked position is inherited");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("a run ending after its output was disposed leaves nothing behind for the next output", (t) => {
|
|
186
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
187
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
188
|
+
const manager = wireOutputs({
|
|
189
|
+
enabled: true,
|
|
190
|
+
ffmpegBin: "ffmpeg",
|
|
191
|
+
localBindHost: "127.0.0.1",
|
|
192
|
+
localPort: 9090
|
|
193
|
+
});
|
|
194
|
+
const { session, run } = sessionWithARun(dirPath);
|
|
195
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
196
|
+
// Disposal removed the output from the registry; its process ends later.
|
|
197
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
198
|
+
|
|
199
|
+
assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE");
|
|
200
|
+
assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "");
|
|
201
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0);
|
|
202
|
+
});
|