@torrent-tv/proxy 2.87.21 → 2.88.0
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 +4 -0
- 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 +133 -133
- 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 +50 -50
- 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/files/get.js +106 -100
- 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 +173 -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 +84 -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 +758 -758
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2184 -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 +416 -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 +1141 -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 +254 -254
- package/services/encode/quality/output-format.js +315 -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 +515 -515
- 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 +245 -245
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -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 +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- 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 +131 -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/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 +696 -696
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +992 -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/Contents.js +382 -324
- 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 -0
- 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-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 -0
- 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 +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 +111 -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 +204 -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 +1229 -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 +102 -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/source-file.test.js +133 -133
- package/test/source-files-route.test.js +12 -2
- 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 +407 -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,221 +1,221 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file A piece short of a track is judged against ITS OWN header.
|
|
3
|
-
*
|
|
4
|
-
* The question "does this piece carry every track" has two possible answers and
|
|
5
|
-
* they call for opposite actions, which is why asking it against the session's
|
|
6
|
-
* cached header alone was not enough: a piece that disagrees with that header
|
|
7
|
-
* may be short, or the header may be the odd one out. A self-contained piece
|
|
8
|
-
* carries a `moov` of its own, so it can be compared with itself, and that
|
|
9
|
-
* comparison tells the two apart without asking anything about encoders.
|
|
10
|
-
*
|
|
11
|
-
* WHAT THIS REPLACED. The action used to be chosen by "is any run of this
|
|
12
|
-
* session alive?" — a question about the SESSION, asked about a fact of the
|
|
13
|
-
* BYTES. A run anywhere in the film meant "still being written; waiting for
|
|
14
|
-
* it", and nothing re-judged or re-made the piece afterwards, so one
|
|
15
|
-
* misjudgement became permanent.
|
|
16
|
-
*
|
|
17
|
-
* Field 2026-09-14: `segment-00033.mp4` of a copied picture, already served
|
|
18
|
-
* whole to one viewer at 5 099 421 bytes, was refused to a second viewer for
|
|
19
|
-
* five minutes — four holds of sixty seconds each, then 503 — while the run in
|
|
20
|
-
* force was at #192 and beyond. That viewer's picture stood still 196.2 s and
|
|
21
|
-
* then a further 56.2 s.
|
|
22
|
-
*
|
|
23
|
-
* The run question is also unanswerable and does not need asking: a piece being
|
|
24
|
-
* written is called `making-<tag>-NNNNN.mp4` and takes its served name by a
|
|
25
|
-
* rename, which is atomic. A file under its served name is never being written.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import test from "node:test";
|
|
29
|
-
import assert from "node:assert/strict";
|
|
30
|
-
import { access, writeFile } from "node:fs/promises";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
import { SourceFile } from "../services/media/SourceFile.js";
|
|
33
|
-
import { Timeline } from "../services/encode/output/Timeline.js";
|
|
34
|
-
import { managerWithOwnStore } from "./helpers/manager.js";
|
|
35
|
-
import { startRunOn } from "./helpers/encode-run.js";
|
|
36
|
-
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
37
|
-
import { outputSpec } from "./helpers/output-spec.js";
|
|
38
|
-
|
|
39
|
-
const SESSION_ID = "3333333344445555";
|
|
40
|
-
const OUTPUT_KEY = "own-header:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
|
|
41
|
-
const MOVIE_TIMESCALE = 1000;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @param {string} type
|
|
45
|
-
* @param {Buffer} body
|
|
46
|
-
* @returns {Buffer}
|
|
47
|
-
*/
|
|
48
|
-
function box(type, body) {
|
|
49
|
-
const head = Buffer.alloc(8);
|
|
50
|
-
head.writeUInt32BE(8 + body.length, 0);
|
|
51
|
-
head.write(type, 4, "latin1");
|
|
52
|
-
return Buffer.concat([head, body]);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {number} trackId
|
|
57
|
-
* @returns {Buffer}
|
|
58
|
-
*/
|
|
59
|
-
function trak(trackId) {
|
|
60
|
-
const tkhdBody = Buffer.alloc(84);
|
|
61
|
-
tkhdBody.writeUInt32BE(trackId, 12);
|
|
62
|
-
const mdhdBody = Buffer.alloc(20);
|
|
63
|
-
mdhdBody.writeUInt32BE(90_000, 12);
|
|
64
|
-
return box("trak", Buffer.concat([
|
|
65
|
-
box("tkhd", tkhdBody),
|
|
66
|
-
box("mdia", box("mdhd", mdhdBody))
|
|
67
|
-
]));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* A header declaring `tracks` tracks.
|
|
72
|
-
*
|
|
73
|
-
* @param {number} tracks
|
|
74
|
-
* @returns {Buffer}
|
|
75
|
-
*/
|
|
76
|
-
function moovOf(tracks) {
|
|
77
|
-
const mvhdBody = Buffer.alloc(100);
|
|
78
|
-
mvhdBody.writeUInt32BE(MOVIE_TIMESCALE, 12);
|
|
79
|
-
const traks = [];
|
|
80
|
-
for (let id = 1; id <= tracks; id += 1) {
|
|
81
|
-
traks.push(trak(id));
|
|
82
|
-
}
|
|
83
|
-
return box("moov", Buffer.concat([box("mvhd", mvhdBody), ...traks]));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @param {number} trackId
|
|
88
|
-
* @returns {Buffer}
|
|
89
|
-
*/
|
|
90
|
-
function traf(trackId) {
|
|
91
|
-
const tfhdBody = Buffer.alloc(8);
|
|
92
|
-
tfhdBody.writeUInt32BE(trackId, 4);
|
|
93
|
-
const tfdtBody = Buffer.alloc(12);
|
|
94
|
-
tfdtBody.writeUInt8(1, 0);
|
|
95
|
-
tfdtBody.writeBigUInt64BE(0n, 4);
|
|
96
|
-
return box("traf", Buffer.concat([box("tfhd", tfhdBody), box("tfdt", tfdtBody)]));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* A piece shaped like the `segment` muxer's output.
|
|
101
|
-
*
|
|
102
|
-
* @param {{ declares: number, carries: number }} shape - How many tracks its own
|
|
103
|
-
* header declares, and how many its fragments actually carry. The two differ
|
|
104
|
-
* only in a piece a run closed short.
|
|
105
|
-
* @returns {Buffer}
|
|
106
|
-
*/
|
|
107
|
-
function selfContainedPiece({ declares, carries }) {
|
|
108
|
-
const trafs = [];
|
|
109
|
-
for (let id = 1; id <= carries; id += 1) {
|
|
110
|
-
trafs.push(traf(id));
|
|
111
|
-
}
|
|
112
|
-
return Buffer.concat([
|
|
113
|
-
box("ftyp", Buffer.alloc(16, 0)),
|
|
114
|
-
moovOf(declares),
|
|
115
|
-
box("moof", Buffer.concat(trafs)),
|
|
116
|
-
box("mdat", Buffer.alloc(64, 0x5a)),
|
|
117
|
-
box("mfra", Buffer.alloc(24, 0))
|
|
118
|
-
]);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* A manager with one session whose first two pieces are already on disk.
|
|
123
|
-
*
|
|
124
|
-
* @param {{ piece: Buffer, sessionHeader: Buffer }} params - The piece written
|
|
125
|
-
* for every number, and what the session has cached as its header.
|
|
126
|
-
* @returns {Promise<{ manager: object, dirPath: string }>}
|
|
127
|
-
*/
|
|
128
|
-
async function managerHolding({ piece, sessionHeader }) {
|
|
129
|
-
const { manager } = managerWithOwnStore();
|
|
130
|
-
manager.segmentStore.useFormat(OUTPUT_KEY, fmp4Format);
|
|
131
|
-
const dirPath = manager.segmentStore.directoryFor(OUTPUT_KEY);
|
|
132
|
-
await writeFile(path.join(dirPath, "segment-00000.mp4"), piece);
|
|
133
|
-
await writeFile(path.join(dirPath, "segment-00001.mp4"), piece);
|
|
134
|
-
|
|
135
|
-
const session = {
|
|
136
|
-
id: SESSION_ID,
|
|
137
|
-
spec: outputSpec({ transcodeVideo: false }),
|
|
138
|
-
outputKey: OUTPUT_KEY,
|
|
139
|
-
dirPath,
|
|
140
|
-
timeline: new Timeline({ boundaries: [0, 12.5, 25, 37.5, 50, 62.5], cutGrid: "uniform" }),
|
|
141
|
-
state: "ready",
|
|
142
|
-
file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" })
|
|
143
|
-
.learn({ durationSeconds: 62.5 }),
|
|
144
|
-
get inputFile() { return this.file; },
|
|
145
|
-
get audioFile() { return this.file; },
|
|
146
|
-
lastAloneSpeed: 2,
|
|
147
|
-
startedAt: Date.now(),
|
|
148
|
-
lastAccessedAt: Date.now(),
|
|
149
|
-
runs: new Set(),
|
|
150
|
-
lastError: "",
|
|
151
|
-
segmentCount: 5,
|
|
152
|
-
segmentFormat: fmp4Format,
|
|
153
|
-
useSyntheticPlaylist: true,
|
|
154
|
-
playlistText: "#EXTM3U\n",
|
|
155
|
-
initBytes: sessionHeader,
|
|
156
|
-
waitEpoch: 0
|
|
157
|
-
};
|
|
158
|
-
manager.outputs.set(SESSION_ID, session);
|
|
159
|
-
// No plan runs here: this file is about the path that answers a request.
|
|
160
|
-
manager.encodeRuns.planEncodersNow = () => {};
|
|
161
|
-
manager.encodeRuns.planEncodersSoon = () => {};
|
|
162
|
-
// A LIVE RUN, deliberately. Under the rule this replaced, a live run anywhere
|
|
163
|
-
// was what turned a refusal into a permanent one — so both cases below are
|
|
164
|
-
// asked with one running, which is the state the field failure happened in.
|
|
165
|
-
startRunOn(session, { from: 0, usesExplicitCuts: true, speedX: 2 });
|
|
166
|
-
return { manager, dirPath };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
test("a piece that agrees with its own header is served, whatever the session's header says", async () => {
|
|
170
|
-
// Two tracks declared, two carried: whole by its own account. The session's
|
|
171
|
-
// cached header declares three — the state that cannot be told from a short
|
|
172
|
-
// piece without asking the piece itself.
|
|
173
|
-
const piece = selfContainedPiece({ declares: 2, carries: 2 });
|
|
174
|
-
const { manager, dirPath } = await managerHolding({
|
|
175
|
-
piece,
|
|
176
|
-
sessionHeader: moovOf(3)
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
|
|
180
|
-
|
|
181
|
-
assert.equal(answer.kind, "file", "the piece is whole and must reach the viewer");
|
|
182
|
-
await access(path.join(dirPath, "segment-00000.mp4"));
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("a piece that disagrees with its own header is removed, so it can be made again", async () => {
|
|
186
|
-
// Two tracks declared, one carried: closed short by a run that was killed
|
|
187
|
-
// mid-piece, which is what this check exists to catch.
|
|
188
|
-
const piece = selfContainedPiece({ declares: 2, carries: 1 });
|
|
189
|
-
const { manager, dirPath } = await managerHolding({
|
|
190
|
-
piece,
|
|
191
|
-
sessionHeader: moovOf(2)
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
|
|
195
|
-
|
|
196
|
-
assert.equal(answer.kind, "warming-up", "a short piece is not servable");
|
|
197
|
-
await assert.rejects(
|
|
198
|
-
() => access(path.join(dirPath, "segment-00000.mp4")),
|
|
199
|
-
/ENOENT/,
|
|
200
|
-
"and it is removed, or nothing will ever make it again"
|
|
201
|
-
);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("a live run elsewhere in the film does not make a short piece permanent", async () => {
|
|
205
|
-
const piece = selfContainedPiece({ declares: 2, carries: 1 });
|
|
206
|
-
const { manager, dirPath } = await managerHolding({
|
|
207
|
-
piece,
|
|
208
|
-
sessionHeader: moovOf(2)
|
|
209
|
-
});
|
|
210
|
-
// The field state exactly: the run in force is hundreds of segments ahead of
|
|
211
|
-
// the piece being asked for.
|
|
212
|
-
startRunOn(manager.outputs.get(SESSION_ID), { from: 192, usesExplicitCuts: true, speedX: 2 });
|
|
213
|
-
|
|
214
|
-
await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
|
|
215
|
-
|
|
216
|
-
await assert.rejects(
|
|
217
|
-
() => access(path.join(dirPath, "segment-00000.mp4")),
|
|
218
|
-
/ENOENT/,
|
|
219
|
-
"a run a thousand segments away says nothing about these bytes"
|
|
220
|
-
);
|
|
221
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file A piece short of a track is judged against ITS OWN header.
|
|
3
|
+
*
|
|
4
|
+
* The question "does this piece carry every track" has two possible answers and
|
|
5
|
+
* they call for opposite actions, which is why asking it against the session's
|
|
6
|
+
* cached header alone was not enough: a piece that disagrees with that header
|
|
7
|
+
* may be short, or the header may be the odd one out. A self-contained piece
|
|
8
|
+
* carries a `moov` of its own, so it can be compared with itself, and that
|
|
9
|
+
* comparison tells the two apart without asking anything about encoders.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THIS REPLACED. The action used to be chosen by "is any run of this
|
|
12
|
+
* session alive?" — a question about the SESSION, asked about a fact of the
|
|
13
|
+
* BYTES. A run anywhere in the film meant "still being written; waiting for
|
|
14
|
+
* it", and nothing re-judged or re-made the piece afterwards, so one
|
|
15
|
+
* misjudgement became permanent.
|
|
16
|
+
*
|
|
17
|
+
* Field 2026-09-14: `segment-00033.mp4` of a copied picture, already served
|
|
18
|
+
* whole to one viewer at 5 099 421 bytes, was refused to a second viewer for
|
|
19
|
+
* five minutes — four holds of sixty seconds each, then 503 — while the run in
|
|
20
|
+
* force was at #192 and beyond. That viewer's picture stood still 196.2 s and
|
|
21
|
+
* then a further 56.2 s.
|
|
22
|
+
*
|
|
23
|
+
* The run question is also unanswerable and does not need asking: a piece being
|
|
24
|
+
* written is called `making-<tag>-NNNNN.mp4` and takes its served name by a
|
|
25
|
+
* rename, which is atomic. A file under its served name is never being written.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import test from "node:test";
|
|
29
|
+
import assert from "node:assert/strict";
|
|
30
|
+
import { access, writeFile } from "node:fs/promises";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { SourceFile } from "../services/media/SourceFile.js";
|
|
33
|
+
import { Timeline } from "../services/encode/output/Timeline.js";
|
|
34
|
+
import { managerWithOwnStore } from "./helpers/manager.js";
|
|
35
|
+
import { startRunOn } from "./helpers/encode-run.js";
|
|
36
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
37
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
38
|
+
|
|
39
|
+
const SESSION_ID = "3333333344445555";
|
|
40
|
+
const OUTPUT_KEY = "own-header:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
|
|
41
|
+
const MOVIE_TIMESCALE = 1000;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} type
|
|
45
|
+
* @param {Buffer} body
|
|
46
|
+
* @returns {Buffer}
|
|
47
|
+
*/
|
|
48
|
+
function box(type, body) {
|
|
49
|
+
const head = Buffer.alloc(8);
|
|
50
|
+
head.writeUInt32BE(8 + body.length, 0);
|
|
51
|
+
head.write(type, 4, "latin1");
|
|
52
|
+
return Buffer.concat([head, body]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {number} trackId
|
|
57
|
+
* @returns {Buffer}
|
|
58
|
+
*/
|
|
59
|
+
function trak(trackId) {
|
|
60
|
+
const tkhdBody = Buffer.alloc(84);
|
|
61
|
+
tkhdBody.writeUInt32BE(trackId, 12);
|
|
62
|
+
const mdhdBody = Buffer.alloc(20);
|
|
63
|
+
mdhdBody.writeUInt32BE(90_000, 12);
|
|
64
|
+
return box("trak", Buffer.concat([
|
|
65
|
+
box("tkhd", tkhdBody),
|
|
66
|
+
box("mdia", box("mdhd", mdhdBody))
|
|
67
|
+
]));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A header declaring `tracks` tracks.
|
|
72
|
+
*
|
|
73
|
+
* @param {number} tracks
|
|
74
|
+
* @returns {Buffer}
|
|
75
|
+
*/
|
|
76
|
+
function moovOf(tracks) {
|
|
77
|
+
const mvhdBody = Buffer.alloc(100);
|
|
78
|
+
mvhdBody.writeUInt32BE(MOVIE_TIMESCALE, 12);
|
|
79
|
+
const traks = [];
|
|
80
|
+
for (let id = 1; id <= tracks; id += 1) {
|
|
81
|
+
traks.push(trak(id));
|
|
82
|
+
}
|
|
83
|
+
return box("moov", Buffer.concat([box("mvhd", mvhdBody), ...traks]));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {number} trackId
|
|
88
|
+
* @returns {Buffer}
|
|
89
|
+
*/
|
|
90
|
+
function traf(trackId) {
|
|
91
|
+
const tfhdBody = Buffer.alloc(8);
|
|
92
|
+
tfhdBody.writeUInt32BE(trackId, 4);
|
|
93
|
+
const tfdtBody = Buffer.alloc(12);
|
|
94
|
+
tfdtBody.writeUInt8(1, 0);
|
|
95
|
+
tfdtBody.writeBigUInt64BE(0n, 4);
|
|
96
|
+
return box("traf", Buffer.concat([box("tfhd", tfhdBody), box("tfdt", tfdtBody)]));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A piece shaped like the `segment` muxer's output.
|
|
101
|
+
*
|
|
102
|
+
* @param {{ declares: number, carries: number }} shape - How many tracks its own
|
|
103
|
+
* header declares, and how many its fragments actually carry. The two differ
|
|
104
|
+
* only in a piece a run closed short.
|
|
105
|
+
* @returns {Buffer}
|
|
106
|
+
*/
|
|
107
|
+
function selfContainedPiece({ declares, carries }) {
|
|
108
|
+
const trafs = [];
|
|
109
|
+
for (let id = 1; id <= carries; id += 1) {
|
|
110
|
+
trafs.push(traf(id));
|
|
111
|
+
}
|
|
112
|
+
return Buffer.concat([
|
|
113
|
+
box("ftyp", Buffer.alloc(16, 0)),
|
|
114
|
+
moovOf(declares),
|
|
115
|
+
box("moof", Buffer.concat(trafs)),
|
|
116
|
+
box("mdat", Buffer.alloc(64, 0x5a)),
|
|
117
|
+
box("mfra", Buffer.alloc(24, 0))
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A manager with one session whose first two pieces are already on disk.
|
|
123
|
+
*
|
|
124
|
+
* @param {{ piece: Buffer, sessionHeader: Buffer }} params - The piece written
|
|
125
|
+
* for every number, and what the session has cached as its header.
|
|
126
|
+
* @returns {Promise<{ manager: object, dirPath: string }>}
|
|
127
|
+
*/
|
|
128
|
+
async function managerHolding({ piece, sessionHeader }) {
|
|
129
|
+
const { manager } = managerWithOwnStore();
|
|
130
|
+
manager.segmentStore.useFormat(OUTPUT_KEY, fmp4Format);
|
|
131
|
+
const dirPath = manager.segmentStore.directoryFor(OUTPUT_KEY);
|
|
132
|
+
await writeFile(path.join(dirPath, "segment-00000.mp4"), piece);
|
|
133
|
+
await writeFile(path.join(dirPath, "segment-00001.mp4"), piece);
|
|
134
|
+
|
|
135
|
+
const session = {
|
|
136
|
+
id: SESSION_ID,
|
|
137
|
+
spec: outputSpec({ transcodeVideo: false }),
|
|
138
|
+
outputKey: OUTPUT_KEY,
|
|
139
|
+
dirPath,
|
|
140
|
+
timeline: new Timeline({ boundaries: [0, 12.5, 25, 37.5, 50, 62.5], cutGrid: "uniform" }),
|
|
141
|
+
state: "ready",
|
|
142
|
+
file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" })
|
|
143
|
+
.learn({ durationSeconds: 62.5 }),
|
|
144
|
+
get inputFile() { return this.file; },
|
|
145
|
+
get audioFile() { return this.file; },
|
|
146
|
+
lastAloneSpeed: 2,
|
|
147
|
+
startedAt: Date.now(),
|
|
148
|
+
lastAccessedAt: Date.now(),
|
|
149
|
+
runs: new Set(),
|
|
150
|
+
lastError: "",
|
|
151
|
+
segmentCount: 5,
|
|
152
|
+
segmentFormat: fmp4Format,
|
|
153
|
+
useSyntheticPlaylist: true,
|
|
154
|
+
playlistText: "#EXTM3U\n",
|
|
155
|
+
initBytes: sessionHeader,
|
|
156
|
+
waitEpoch: 0
|
|
157
|
+
};
|
|
158
|
+
manager.outputs.set(SESSION_ID, session);
|
|
159
|
+
// No plan runs here: this file is about the path that answers a request.
|
|
160
|
+
manager.encodeRuns.planEncodersNow = () => {};
|
|
161
|
+
manager.encodeRuns.planEncodersSoon = () => {};
|
|
162
|
+
// A LIVE RUN, deliberately. Under the rule this replaced, a live run anywhere
|
|
163
|
+
// was what turned a refusal into a permanent one — so both cases below are
|
|
164
|
+
// asked with one running, which is the state the field failure happened in.
|
|
165
|
+
startRunOn(session, { from: 0, usesExplicitCuts: true, speedX: 2 });
|
|
166
|
+
return { manager, dirPath };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
test("a piece that agrees with its own header is served, whatever the session's header says", async () => {
|
|
170
|
+
// Two tracks declared, two carried: whole by its own account. The session's
|
|
171
|
+
// cached header declares three — the state that cannot be told from a short
|
|
172
|
+
// piece without asking the piece itself.
|
|
173
|
+
const piece = selfContainedPiece({ declares: 2, carries: 2 });
|
|
174
|
+
const { manager, dirPath } = await managerHolding({
|
|
175
|
+
piece,
|
|
176
|
+
sessionHeader: moovOf(3)
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
|
|
180
|
+
|
|
181
|
+
assert.equal(answer.kind, "file", "the piece is whole and must reach the viewer");
|
|
182
|
+
await access(path.join(dirPath, "segment-00000.mp4"));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("a piece that disagrees with its own header is removed, so it can be made again", async () => {
|
|
186
|
+
// Two tracks declared, one carried: closed short by a run that was killed
|
|
187
|
+
// mid-piece, which is what this check exists to catch.
|
|
188
|
+
const piece = selfContainedPiece({ declares: 2, carries: 1 });
|
|
189
|
+
const { manager, dirPath } = await managerHolding({
|
|
190
|
+
piece,
|
|
191
|
+
sessionHeader: moovOf(2)
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const answer = await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
|
|
195
|
+
|
|
196
|
+
assert.equal(answer.kind, "warming-up", "a short piece is not servable");
|
|
197
|
+
await assert.rejects(
|
|
198
|
+
() => access(path.join(dirPath, "segment-00000.mp4")),
|
|
199
|
+
/ENOENT/,
|
|
200
|
+
"and it is removed, or nothing will ever make it again"
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("a live run elsewhere in the film does not make a short piece permanent", async () => {
|
|
205
|
+
const piece = selfContainedPiece({ declares: 2, carries: 1 });
|
|
206
|
+
const { manager, dirPath } = await managerHolding({
|
|
207
|
+
piece,
|
|
208
|
+
sessionHeader: moovOf(2)
|
|
209
|
+
});
|
|
210
|
+
// The field state exactly: the run in force is hundreds of segments ahead of
|
|
211
|
+
// the piece being asked for.
|
|
212
|
+
startRunOn(manager.outputs.get(SESSION_ID), { from: 192, usesExplicitCuts: true, speedX: 2 });
|
|
213
|
+
|
|
214
|
+
await manager.serving.getFileStream(SESSION_ID, "segment-00000.mp4");
|
|
215
|
+
|
|
216
|
+
await assert.rejects(
|
|
217
|
+
() => access(path.join(dirPath, "segment-00000.mp4")),
|
|
218
|
+
/ENOENT/,
|
|
219
|
+
"a run a thousand segments away says nothing about these bytes"
|
|
220
|
+
);
|
|
221
|
+
});
|