@torrent-tv/proxy 2.85.1 → 2.87.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/CHANGELOG.md +2138 -1909
- package/CLAUDE.md +318 -197
- package/assets/calibration/NOTICE.md +2 -2
- package/bin/cli.js +684 -616
- package/biome.json +409 -233
- package/docs/container-architecture.md +218 -213
- package/docs/download-architecture.md +7 -7
- package/docs/encode-architecture.md +287 -11
- package/docs/encode-run-state.md +2 -2
- package/docs/logs.md +97 -97
- package/docs/{disk-architecture.md → storage-architecture.md} +107 -1
- package/openspec/changes/adaptive-bitrate/design.md +2 -2
- package/openspec/changes/adaptive-bitrate/proposal.md +1 -1
- package/openspec/changes/adaptive-bitrate/tasks.md +1 -1
- package/openspec/changes/chunked-request-bodies/design.md +1 -1
- package/openspec/changes/chunked-request-bodies/proposal.md +1 -1
- package/openspec/changes/chunked-request-bodies/tasks.md +1 -1
- package/openspec/changes/cold-start/design.md +1 -1
- package/openspec/changes/disk-cap/proposal.md +1 -1
- package/openspec/changes/proxy-observability/proposal.md +2 -2
- package/openspec/changes/track-selection/proposal.md +1 -1
- package/openspec/changes/transcode-quality/proposal.md +1 -1
- package/package.json +2 -2
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/routes/api/playback-plan/post.js +2 -2
- package/routes/api/sources/files/get.js +1 -1
- package/routes/api/sources/stats/get.js +1 -1
- package/routes/api/sources/warm/post.js +10 -3
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +3 -3
- package/routes/api/transcode-sessions/net-report/post.js +129 -94
- package/routes/api/transcode-sessions/post.js +160 -139
- package/routes/api/transcode-sessions/progress/get.js +3 -3
- package/routes/api/transcode-sessions/release/post.js +3 -3
- package/routes/api/transcode-sessions/seek/post.js +12 -3
- package/routes/stream/get.js +2 -2
- package/routes/transcode/audio-file/get.js +15 -6
- package/routes/transcode/audio-warm/get.js +8 -5
- package/routes/transcode/session-file/get.js +429 -268
- package/routes/transcode/variant-file/get.js +47 -7
- package/routes/transcode/variant-warm/get.js +18 -6
- package/scripts/render-run-graph.js +1 -1
- package/server.js +720 -465
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -0
- package/services/encode/EncodeAdmission.js +332 -0
- package/services/{orchestrators → encode}/EncodeOrchestrator.js +285 -17
- package/services/encode/EncodeRun.js +16 -9
- package/services/encode/EncodeRuns.js +1219 -0
- package/services/encode/Encoder.js +18 -2
- package/services/encode/EncoderSelection.js +38 -0
- package/services/encode/LocalObservations.js +359 -0
- package/services/encode/NvencEncoder.js +5 -0
- package/services/encode/OutputOpening.js +745 -0
- package/services/encode/OutputTimes.js +579 -0
- package/services/encode/Renditions.js +2162 -0
- package/services/encode/RunProgress.js +84 -0
- package/services/encode/SoftwareEncoder.js +12 -7
- package/services/encode/args.js +402 -186
- package/services/encode/calibration.js +361 -0
- package/services/encode/contention.js +128 -128
- package/services/encode/encode-run-state.js +16 -20
- package/services/encode/fingerprint.js +322 -0
- package/services/{hwaccel.js → encode/hwaccel.js} +82 -42
- package/services/{learned-median.js → encode/learned-median.js} +11 -0
- package/services/encode/output/EncodedOutput.js +57 -0
- package/services/encode/output/Output.js +131 -0
- package/services/{output/LiveOutputs.js → encode/output/OutputCatalog.js} +141 -40
- package/services/encode/output/OutputSpec.js +390 -0
- package/services/{output → encode/output}/Timeline.js +12 -37
- package/services/encode/output/index.js +20 -0
- package/services/encode/output/ladder.js +32 -0
- package/services/{output → encode/output}/playlists.js +3 -0
- package/services/encode/output-key-format.js +21 -0
- package/services/encode/quality/EncodeCost.js +1083 -0
- package/services/encode/quality/HostLoad.js +525 -0
- package/services/encode/quality/HostTimings.js +249 -0
- package/services/encode/quality/OutputQualityState.js +28 -0
- package/services/encode/quality/QualityController.js +931 -0
- package/services/encode/quality/QualityOffer.js +416 -0
- package/services/encode/quality/drain-threat.js +47 -0
- package/services/encode/quality/link-budget.js +254 -0
- package/services/encode/quality/output-format.js +266 -0
- package/services/encode/quality/serving-output.js +216 -0
- package/services/encode/quality/visible-rung.js +57 -0
- package/services/encode/run-command.js +23 -20
- package/services/encode/run-inputs.js +42 -0
- package/services/{segment-formats → encode/segment-formats}/fmp4.js +63 -0
- package/services/{segment-formats → encode/segment-formats}/index.js +3 -3
- package/services/encode/segment-formats/init-compat.js +324 -0
- package/services/{segment-formats → encode/segment-formats}/mp4-boxes.js +8 -3
- package/services/encode/throughput.js +97 -0
- package/services/{orchestrators → media}/ContainerOrchestrator.js +45 -7
- package/services/media/KeyframeTables.js +316 -0
- package/services/{source → media}/SourceFile.js +7 -55
- package/services/{torrent-worker/subtitle-cues.js → media/SubtitleCues.js} +103 -132
- package/services/media/SubtitleOrchestrator.js +167 -0
- package/services/{audio-inventory.js → media/audio-inventory.js} +7 -1
- package/services/{container → media/container}/AviContainer.js +266 -266
- package/services/{container → media/container}/Container.js +596 -601
- package/services/media/container/ContainerFactory.js +56 -0
- package/services/media/container/KeyframeTable.js +272 -0
- package/services/{container → media/container}/MatroskaContainer.js +1485 -1485
- package/services/{container → media/container}/Mp4Container.js +1244 -1244
- package/services/{container → media/container}/SubtitleFileContainer.js +322 -322
- package/services/media/keyframe-probe.js +157 -0
- package/services/media/media-info-probe.js +93 -0
- package/services/{playback-planner.js → media/playback-planner.js} +82 -39
- package/services/{tracks → media/tracks}/AudioTrack.js +131 -131
- package/services/{tracks → media/tracks}/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -0
- package/services/server/SegmentServing.js +1146 -0
- package/services/server/ViewerRequests.js +522 -0
- package/services/server/controllers/PlaybackController.js +36 -0
- package/services/{controllers → server/controllers}/SubtitleController.js +17 -33
- package/services/server/wire-outputs.js +958 -0
- package/services/storage/Diagnostics.js +202 -0
- package/services/storage/MachineBudget.js +235 -0
- package/services/{piece-store → storage}/allowance.js +107 -107
- package/services/{core-dumps.js → storage/core-dumps.js} +1 -1
- package/services/{files → storage/files}/CompletedFiles.js +116 -1
- package/services/{disk → storage}/free.js +33 -0
- package/services/storage/machine-memory.js +56 -0
- package/services/{memory-report.js → storage/memory-report.js} +575 -596
- package/services/{piece-store → storage/piece-store}/piece-disk-store.js +0 -7
- package/services/{piece-store → storage/piece-store}/shared-piece-store.js +28 -21
- package/services/{encode → storage/segment-store}/SegmentStore.js +395 -8
- package/services/storage/wire.js +175 -0
- package/services/{download → torrent/download}/SwarmSelection.js +2 -2
- package/services/{download → torrent/download}/registry.js +2 -2
- package/services/torrent/files.js +1 -1
- package/services/torrent/piece-store-of.js +29 -0
- package/services/{torrent-pool.js → torrent/torrent-pool.js} +26 -119
- package/services/{torrent-worker → torrent/worker}/background-fill.js +2 -2
- package/services/{torrent-worker → torrent/worker}/channel.js +271 -250
- package/services/{torrent-worker → torrent/worker}/client.js +77 -58
- package/services/torrent/worker/held-bytes.js +155 -0
- package/services/{torrent-worker → torrent/worker}/install-webrtc-shim.js +1 -1
- package/services/{torrent-worker → torrent/worker}/piece-reader.js +21 -30
- package/services/{torrent-worker → torrent/worker}/pool-adapter.js +101 -137
- package/services/{torrent-worker → torrent/worker}/protocol.js +26 -30
- package/services/torrent/worker/resume-warm.js +107 -0
- package/services/{torrent-worker → torrent/worker}/webrtc-shim.js +2 -2
- package/services/{torrent-worker → torrent/worker}/worker.js +68 -159
- package/services/{data-channel-handler.js → transport/data-channel-handler.js} +1356 -1356
- package/services/{delivery-probe.js → transport/delivery-probe.js} +842 -842
- package/services/{health-collector.js → transport/health-collector.js} +6 -35
- package/services/{packet-witness.js → transport/packet-witness.js} +808 -784
- package/services/{port-mapper.js → transport/port-mapper.js} +1 -1
- package/services/{usrsctp-state.js → transport/usrsctp-state.js} +1 -1
- package/services/{webrtc-manager.js → transport/webrtc-manager.js} +1 -1
- package/services/viewer/Assignments.js +340 -0
- package/services/{priority → viewer}/PriorityMap.js +390 -390
- package/services/{priority → viewer}/PriorityOrchestrator.js +325 -320
- package/services/viewer/Viewer.js +657 -489
- package/services/viewer/Viewers.js +368 -257
- package/services/viewer/active-output.js +37 -54
- package/services/viewer/audio-start.js +46 -0
- package/services/viewer/buffer-trend.js +81 -0
- package/services/viewer/choices.js +647 -0
- package/services/viewer/positions.js +126 -120
- package/services/viewer/report-intake.js +46 -44
- package/test/asset-paths.test.js +28 -0
- package/test/audio-inventory.test.js +3 -3
- package/test/audio-track-bitrate.test.js +40 -0
- package/test/auto-quality-step.test.js +707 -518
- package/test/available-share.test.js +1 -1
- package/test/background-fill.test.js +1 -1
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +1 -1
- package/test/concurrent-cost.test.js +13 -3
- package/test/container-media-info.test.js +3 -3
- package/test/core-dumps.test.js +1 -1
- package/test/coverage-follows-the-disk.test.js +40 -8
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -0
- package/test/cut-times-timeline.test.js +2 -2
- package/test/cuts-follow-published-grid.test.js +9 -8
- package/test/data-channel-frame.test.js +1 -1
- package/test/dead-torrent-handle.test.js +1 -1
- package/test/declared-rates.test.js +2 -2
- package/test/declared-tracks.test.js +2 -2
- package/test/decode-cost-fit.test.js +1 -1
- package/test/decode-cost.test.js +34 -25
- package/test/delivery-probe.test.js +1 -1
- package/test/demand-register.test.js +4 -4
- package/test/dht-bootstrap.test.js +1 -1
- package/test/drain-threat.test.js +65 -0
- package/test/duplicate-add.test.js +1 -1
- package/test/encode-admission.test.js +146 -0
- package/test/encode-cost.test.js +199 -10
- package/test/encode-orchestrator.test.js +39 -1
- package/test/encode-plan-viewers.test.js +2 -2
- package/test/encode-run.test.js +2 -2
- package/test/encode-slope.test.js +1 -1
- package/test/encoded-output.test.js +39 -0
- package/test/encoder-readings.test.js +1 -1
- package/test/every-claimant-is-told-its-share.test.js +152 -0
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -0
- package/test/failed-start-breaker.test.js +81 -30
- package/test/fastest-wires.test.js +1 -1
- package/test/ffmpeg-stream-counts.test.js +1 -1
- package/test/file-edges.test.js +3 -3
- package/test/fmp4-self-contained.test.js +1 -1
- package/test/fmp4-timestamps.test.js +1 -1
- package/test/fmp4-track-completeness.test.js +1 -1
- package/test/format-keyed-outputs.test.js +78 -0
- package/test/health-metrics.test.js +3 -2
- package/test/held-request-width.test.js +22 -23
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -0
- package/test/helpers/manager.js +57 -0
- package/test/helpers/output-spec.js +63 -0
- package/test/host-load.test.js +1 -1
- package/test/index-accuracy-is-about-the-file.test.js +128 -0
- package/test/init-compat.test.js +219 -0
- package/test/input-lost-quiets-the-plan.test.js +1 -1
- package/test/input-unavailable.test.js +1 -1
- package/test/keeping-period.test.js +4 -4
- package/test/keyframe-index-accuracy.test.js +97 -45
- package/test/keyframe-table-budget.test.js +28 -33
- package/test/keyframes-belong-to-the-file.test.js +174 -57
- package/test/learned-median.test.js +1 -1
- package/test/letting-go-asks-who-is-watching.test.js +191 -0
- package/test/limit-row-by-area.test.js +60 -0
- package/test/link-budget.test.js +111 -0
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -0
- package/test/look-ahead-coverage.test.js +1 -1
- package/test/manager-surface.test.js +294 -0
- package/test/matroska-blocks.test.js +0 -0
- package/test/matroska-cues-track.test.js +1 -1
- package/test/memory-budget.test.js +2 -2
- package/test/mp4-composition-times.test.js +0 -0
- package/test/mp4-subtitles.test.js +1 -1
- package/test/one-authority.test.js +45 -41
- package/test/one-budget.test.js +149 -0
- package/test/one-output-one-session.test.js +204 -0
- package/test/{disk-space.test.js → one-resource-divided.test.js} +30 -10
- package/test/one-serving-output-per-height.test.js +458 -0
- package/test/orchestrator-wired.test.js +46 -45
- package/test/output-catalog.test.js +39 -0
- package/test/output-shape.test.js +4 -51
- package/test/output-spec.test.js +251 -157
- package/test/output-speed.test.js +17 -15
- package/test/outputs-being-prepared.test.js +40 -0
- package/test/packet-witness-ring.test.js +2 -2
- package/test/packet-witness.test.js +1 -1
- package/test/piece-disk-store.test.js +2 -2
- package/test/piece-from-whole-file.test.js +1 -1
- package/test/piece-lru.test.js +1 -1
- package/test/piece-reader.test.js +2 -2
- package/test/piece-store-eviction.test.js +1 -1
- package/test/piece-store-never-refuses.test.js +1 -1
- package/test/piece-store-reservations.test.js +1 -1
- package/test/piece-store-slow-disk.test.js +1 -1
- package/test/piece-tail.test.js +1 -1
- package/test/plan-host-timings.test.js +1 -1
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +4 -4
- package/test/priority-map-emptied.test.js +204 -205
- package/test/priority-map-per-output.test.js +215 -209
- package/test/priority-map.test.js +1 -1
- package/test/probe-wedge-certainty.test.js +1 -1
- package/test/produced-copy-choice.test.js +15 -60
- package/test/quality-offer.test.js +162 -0
- package/test/quality-variants.test.js +1229 -1167
- package/test/rate-control-in-the-output.test.js +180 -0
- package/test/read-survives-withdrawal.test.js +2 -2
- package/test/read-window.test.js +10 -3
- package/test/resume-warm.test.js +1 -1
- package/test/run-inputs.test.js +80 -0
- package/test/run-position-follows-published-grid.test.js +13 -11
- package/test/run-progress.test.js +65 -0
- package/test/same-height-switch.test.js +664 -0
- package/test/seek-frees-the-viewers-requests.test.js +217 -0
- package/test/seek-landing.test.js +13 -13
- package/test/seek-target-not-superseded.test.js +28 -29
- package/test/segment-closed-on-both-branches.test.js +96 -0
- package/test/segment-cut-times.test.js +1 -1
- package/test/segment-hold-supersede.test.js +28 -35
- package/test/segment-judged-by-its-own-header.test.js +221 -0
- package/test/segment-serve-wiring.test.js +130 -39
- package/test/segment-store-eviction.test.js +22 -7
- package/test/segment-store.test.js +98 -14
- package/test/segment-true-start.test.js +1 -1
- package/test/segments-are-shared.test.js +17 -16
- package/test/send-chunk-bytes.test.js +1 -1
- package/test/serving-existing-output.test.js +102 -0
- package/test/serving-output.test.js +102 -0
- package/test/shared-piece-store.test.js +1 -1
- package/test/source-file.test.js +1 -1
- package/test/stale-request-after-seek.test.js +17 -21
- package/test/startup-readings.test.js +10 -5
- package/test/subtitle-cue-framing.test.js +5 -5
- package/test/subtitle-cue-source.test.js +36 -38
- package/test/subtitle-cue-walk.test.js +11 -10
- package/test/subtitle-defaults.test.js +1 -1
- package/test/subtitle-language.test.js +4 -4
- package/test/subtitle-track-numbering.test.js +5 -4
- package/test/supply-margin.test.js +1 -1
- package/test/swarm-follows-readers.test.js +3 -3
- package/test/swarm-reach.test.js +1 -1
- package/test/swarm-selection.test.js +3 -3
- package/test/tail-duplication.test.js +1 -1
- package/test/the-channel-has-both-ends.test.js +91 -0
- package/test/the-torrent-thread-serves-bytes.test.js +107 -0
- package/test/thread-pool.test.js +3 -3
- package/test/timeline.test.js +1 -12
- package/test/torrent-cost.test.js +1 -1
- package/test/tracks-begin-together.test.js +38 -24
- package/test/tunnel-renewal.test.js +1 -1
- package/test/two-viewers-one-picture.test.js +120 -87
- package/test/upload-hurry.test.js +3 -3
- package/test/usrsctp-state.test.js +1 -1
- package/test/video-facts.test.js +1 -1
- package/test/viewer-assignments.test.js +143 -0
- package/test/viewer-outputs.test.js +60 -52
- package/test/viewer-presence.test.js +22 -25
- package/test/viewer-requests-admission.test.js +89 -0
- package/test/viewer-states.test.js +189 -172
- package/test/viewer-subtitle-subscription.test.js +1 -1
- package/test/viewer.test.js +139 -136
- package/test/viewing-generations.test.js +390 -0
- package/test/visible-rung.test.js +37 -0
- package/test/wait-ledger.test.js +1 -1
- package/test/webrtc-shim.test.js +3 -3
- package/test/wedge-certainty.test.js +2 -2
- package/test/withdraw-piece-claim.test.js +2 -2
- package/test/worker-channel.test.js +1 -31
- package/utils/client-log-file.js +151 -151
- package/scripts/read-container-index.mjs +0 -108
- package/services/container/ContainerFactory.js +0 -122
- package/services/controllers/PlaybackController.js +0 -33
- package/services/disk/DiskSpace.js +0 -150
- package/services/disk/wire.js +0 -61
- package/services/hls-session-manager.js +0 -9689
- package/services/orchestrators/SubtitleOrchestrator.js +0 -160
- package/services/output/Output.js +0 -94
- package/services/output/OutputSpec.js +0 -202
- package/services/output/index.js +0 -1
- package/services/output/ladder.js +0 -26
- package/services/produced-index.js +0 -222
- package/services/quality/EncodeCost.js +0 -555
- package/services/torrent-worker/container-tracks.js +0 -414
- package/test/produced-index.test.js +0 -142
- /package/{services → bin}/thread-pool.js +0 -0
- /package/services/{available-share.js → encode/available-share.js} +0 -0
- /package/services/{decode-cost-fit.js → encode/decode-cost-fit.js} +0 -0
- /package/services/{encoder-readings.js → encode/encoder-readings.js} +0 -0
- /package/services/{host-load.js → encode/host-load.js} +0 -0
- /package/services/{output → encode/output}/cut-grid.js +0 -0
- /package/services/{output → encode/output}/rates.js +0 -0
- /package/services/{segment-formats → encode/segment-formats}/mpegts.js +0 -0
- /package/services/{torrent-cost.js → encode/torrent-cost.js} +0 -0
- /package/services/{container → media/container}/ebml-reader.js +0 -0
- /package/services/{ffmpeg-banner.js → media/ffmpeg-banner.js} +0 -0
- /package/services/{tracks → media/tracks}/ContainerTrack.js +0 -0
- /package/services/{tracks → media/tracks}/ImageSubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/SubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/TextSubtitleTrack.js +0 -0
- /package/services/{tracks → media/tracks}/VideoTrack.js +0 -0
- /package/services/{tracks → media/tracks}/index.js +0 -0
- /package/services/{files → storage/files}/piece-from-whole-file.js +0 -0
- /package/services/{disk → storage}/keep.js +0 -0
- /package/services/{piece-store → storage/piece-store}/piece-lru.js +0 -0
- /package/services/{disk → storage}/returns.js +0 -0
- /package/services/{demand → torrent/demand}/DemandRegister.js +0 -0
- /package/services/{demand → torrent/demand}/Urgency.js +0 -0
- /package/services/{demand → torrent/demand}/Window.js +0 -0
- /package/services/{demand → torrent/demand}/index.js +0 -0
- /package/services/{demand → torrent/demand}/pieces.js +0 -0
- /package/services/{download → torrent/download}/withdraw-claim.js +0 -0
- /package/services/{supply-margin.js → torrent/supply-margin.js} +0 -0
- /package/services/{torrent-source-key.js → torrent/torrent-source-key.js} +0 -0
- /package/services/{torrent-worker → torrent/worker}/fastest-wires.js +0 -0
- /package/services/{torrent-worker → torrent/worker}/handle-state.js +0 -0
- /package/services/{nat-classifier.js → transport/nat-classifier.js} +0 -0
- /package/services/{registry-api.js → transport/registry-api.js} +0 -0
- /package/services/{tunnel-client.js → transport/tunnel-client.js} +0 -0
- /package/services/{priority → viewer}/WaitLedger.js +0 -0
|
@@ -1,209 +1,215 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One fact asked at two scopes: what the swarm is asked for, and what the
|
|
3
|
-
* encoders are placed by.
|
|
4
|
-
*
|
|
5
|
-
* Both are right for what asks them. The swarm is asked for bytes of a FILE,
|
|
6
|
-
* and the picture, a quality step and a soundtrack of one film read the same
|
|
7
|
-
* bytes — so every viewer of any of them wants that file's bytes. Encoders are
|
|
8
|
-
* placed per OUTPUT, and a person watching 480p wants nothing of the 1080p
|
|
9
|
-
* output at all.
|
|
10
|
-
*
|
|
11
|
-
* One map for both was the second authority over encoders. Every output of a
|
|
12
|
-
* film was handed the whole film's map, so the plan wanted an encoder on every
|
|
13
|
-
* one of them; what actually stopped the ones nobody was watching was the
|
|
14
|
-
* session manager killing them by its own judgement — and since a viewer moving
|
|
15
|
-
* between steps also announces itself, the plan started them again on the next
|
|
16
|
-
* pass. Field 2026-09-08.
|
|
17
|
-
*
|
|
18
|
-
* Built out of the real classes throughout: the real viewer registry holds real
|
|
19
|
-
* viewers, the real `
|
|
20
|
-
* `PriorityOrchestrator` builds the maps. A session is a plain object in this
|
|
21
|
-
* proxy — there is no class for one — so the literals below are the thing
|
|
22
|
-
* itself and not a stand-in for it.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import test from "node:test";
|
|
26
|
-
import assert from "node:assert/strict";
|
|
27
|
-
import { PriorityOrchestrator } from "../services/
|
|
28
|
-
import {
|
|
29
|
-
import { Viewers } from "../services/viewer/Viewers.js";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
const FILM = { sourceKey: "source-1", fileIndex: 0, durationSeconds: 600 };
|
|
34
|
-
const STALE_AFTER_MS = 60_000;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* A session, as much of one as these classes read.
|
|
38
|
-
*
|
|
39
|
-
* @param {{ id: string, outputKey: string, isStep?: boolean, audioOnly?: boolean }} params
|
|
40
|
-
* @returns {object}
|
|
41
|
-
*/
|
|
42
|
-
function outputOf({ id, outputKey, isStep = false, audioOnly = false }) {
|
|
43
|
-
return {
|
|
44
|
-
id,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* @
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
live,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
person
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
publish();
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
viewers.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
);
|
|
209
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file One fact asked at two scopes: what the swarm is asked for, and what the
|
|
3
|
+
* encoders are placed by.
|
|
4
|
+
*
|
|
5
|
+
* Both are right for what asks them. The swarm is asked for bytes of a FILE,
|
|
6
|
+
* and the picture, a quality step and a soundtrack of one film read the same
|
|
7
|
+
* bytes — so every viewer of any of them wants that file's bytes. Encoders are
|
|
8
|
+
* placed per OUTPUT, and a person watching 480p wants nothing of the 1080p
|
|
9
|
+
* output at all.
|
|
10
|
+
*
|
|
11
|
+
* One map for both was the second authority over encoders. Every output of a
|
|
12
|
+
* film was handed the whole film's map, so the plan wanted an encoder on every
|
|
13
|
+
* one of them; what actually stopped the ones nobody was watching was the
|
|
14
|
+
* session manager killing them by its own judgement — and since a viewer moving
|
|
15
|
+
* between steps also announces itself, the plan started them again on the next
|
|
16
|
+
* pass. Field 2026-09-08.
|
|
17
|
+
*
|
|
18
|
+
* Built out of the real classes throughout: the real viewer registry holds real
|
|
19
|
+
* viewers, the real `OutputCatalog` answers what a film's shape is, and the real
|
|
20
|
+
* `PriorityOrchestrator` builds the maps. A session is a plain object in this
|
|
21
|
+
* proxy — there is no class for one — so the literals below are the thing
|
|
22
|
+
* itself and not a stand-in for it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import test from "node:test";
|
|
26
|
+
import assert from "node:assert/strict";
|
|
27
|
+
import { PriorityOrchestrator } from "../services/viewer/PriorityOrchestrator.js";
|
|
28
|
+
import { OutputCatalog } from "../services/encode/output/OutputCatalog.js";
|
|
29
|
+
import { Viewers } from "../services/viewer/Viewers.js";
|
|
30
|
+
import { runsOf } from "../services/viewer/PriorityMap.js";
|
|
31
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
32
|
+
|
|
33
|
+
const FILM = { sourceKey: "source-1", fileIndex: 0, durationSeconds: 600 };
|
|
34
|
+
const STALE_AFTER_MS = 60_000;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A session, as much of one as these classes read.
|
|
38
|
+
*
|
|
39
|
+
* @param {{ id: string, outputKey: string, isStep?: boolean, audioOnly?: boolean }} params
|
|
40
|
+
* @returns {object}
|
|
41
|
+
*/
|
|
42
|
+
function outputOf({ id, outputKey, isStep = false, audioOnly = false }) {
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
spec: outputSpec({ sourceKey: FILM.sourceKey, audioOnly }),
|
|
46
|
+
outputKey,
|
|
47
|
+
isStep,
|
|
48
|
+
audioOnly,
|
|
49
|
+
state: "ready",
|
|
50
|
+
sourceKey: FILM.sourceKey,
|
|
51
|
+
fileIndex: FILM.fileIndex,
|
|
52
|
+
file: { key: "film-1", durationSeconds: FILM.durationSeconds }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The real orchestrator over the real registry, wired the way the session
|
|
58
|
+
* manager wires it.
|
|
59
|
+
*
|
|
60
|
+
* @param {object[]} sessions
|
|
61
|
+
* @returns {{ priority: PriorityOrchestrator, viewers: Viewers, publish: () => void }}
|
|
62
|
+
*/
|
|
63
|
+
function over(sessions) {
|
|
64
|
+
const live = new OutputCatalog();
|
|
65
|
+
for (const one of sessions) {
|
|
66
|
+
live.set(one.id, one);
|
|
67
|
+
if (one.isStep) live.markStep(one);
|
|
68
|
+
}
|
|
69
|
+
const viewers = new Viewers();
|
|
70
|
+
const priority = new PriorityOrchestrator({
|
|
71
|
+
publish: () => {},
|
|
72
|
+
viewers,
|
|
73
|
+
allowanceFor: () => 10,
|
|
74
|
+
watchedBy: (session, viewer) => !live.supersededBy(session, viewer.activeVariantId ?? null)
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
priority,
|
|
78
|
+
viewers,
|
|
79
|
+
live,
|
|
80
|
+
publish: () => priority.publishFor({ sessionGroups: [sessions], staleAfterMs: STALE_AFTER_MS })
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test("a person on a step wants nothing of the picture they stepped off", () => {
|
|
85
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
86
|
+
const step = outputOf({ id: "step", outputKey: "out:480", isStep: true });
|
|
87
|
+
const { priority, viewers, publish } = over([picture, step]);
|
|
88
|
+
// ONE viewer object per person, referenced from both outputs — which is why
|
|
89
|
+
// the step they are on is known when the picture is asked about.
|
|
90
|
+
viewers.of(picture, "p");
|
|
91
|
+
const person = viewers.of(step, "p");
|
|
92
|
+
person.moveTo(300);
|
|
93
|
+
person.activeVariantId = "step";
|
|
94
|
+
publish();
|
|
95
|
+
|
|
96
|
+
assert.ok(
|
|
97
|
+
runsOf(priority.mapForOutput("out:480")).length > 0,
|
|
98
|
+
"the step they are watching is wanted"
|
|
99
|
+
);
|
|
100
|
+
assert.deepEqual(
|
|
101
|
+
runsOf(priority.mapForOutput("out:1080")),
|
|
102
|
+
[],
|
|
103
|
+
"and the picture is producing for nobody, which is what makes its encoder unwanted"
|
|
104
|
+
);
|
|
105
|
+
// The bytes are another matter: both outputs read the same file, and the
|
|
106
|
+
// swarm is asked for the file.
|
|
107
|
+
assert.ok(
|
|
108
|
+
runsOf(priority.mapFor(FILM.sourceKey, FILM.fileIndex)).length > 0,
|
|
109
|
+
"the download still wants the film, because that is what is being watched"
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("a soundtrack is wanted by whoever is registered on it", () => {
|
|
114
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
115
|
+
const sound = outputOf({ id: "rus", outputKey: "out:a1", audioOnly: true });
|
|
116
|
+
const { priority, viewers, publish } = over([picture, sound]);
|
|
117
|
+
// Standing on a STEP of the picture says nothing about the sound: the tracks
|
|
118
|
+
// nobody chose are let go of where a track is chosen, so being known to a
|
|
119
|
+
// soundtrack is listening to it.
|
|
120
|
+
const person = viewers.of(sound, "p");
|
|
121
|
+
person.moveTo(120);
|
|
122
|
+
person.activeVariantId = "step";
|
|
123
|
+
publish();
|
|
124
|
+
|
|
125
|
+
assert.ok(runsOf(priority.mapForOutput("out:a1")).length > 0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a step being warmed is wanted, and so is the picture still on screen", () => {
|
|
129
|
+
// Both are genuinely being produced through a warm-up, which is the price of
|
|
130
|
+
// the switch not being visible. Whether the machine can afford two encoders
|
|
131
|
+
// is the budget's question and not this one's.
|
|
132
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
133
|
+
const step = outputOf({ id: "step", outputKey: "out:480", isStep: true });
|
|
134
|
+
const { priority, viewers, publish } = over([picture, step]);
|
|
135
|
+
viewers.of(picture, "p");
|
|
136
|
+
const person = viewers.of(step, "p");
|
|
137
|
+
person.moveTo(300);
|
|
138
|
+
person.warmingVariantId = "step";
|
|
139
|
+
publish();
|
|
140
|
+
|
|
141
|
+
assert.ok(runsOf(priority.mapForOutput("out:1080")).length > 0, "still on screen");
|
|
142
|
+
assert.ok(runsOf(priority.mapForOutput("out:480")).length > 0, "being made ready");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("an output everybody has left is stated as empty, not left as it was", () => {
|
|
146
|
+
// The difference matters: the plan stops the encoders of an output whose map
|
|
147
|
+
// is empty, and would keep the map it had when somebody was watching.
|
|
148
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
149
|
+
const { priority, viewers, publish } = over([picture]);
|
|
150
|
+
viewers.of(picture, "p").moveTo(300);
|
|
151
|
+
|
|
152
|
+
publish();
|
|
153
|
+
assert.ok(runsOf(priority.mapForOutput("out:1080")).length > 0);
|
|
154
|
+
|
|
155
|
+
viewers.leaves(picture, "p");
|
|
156
|
+
publish();
|
|
157
|
+
assert.deepEqual(runsOf(priority.mapForOutput("out:1080")), []);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a viewer who has gone is not watching anything", () => {
|
|
161
|
+
// Presence is the connection and nothing else. Silence — however long — is a
|
|
162
|
+
// paused viewer, a throttled hidden tab, or a full cushion, and none of those
|
|
163
|
+
// is leaving; what ends it is a statement.
|
|
164
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
165
|
+
const live = new OutputCatalog();
|
|
166
|
+
live.set("pic", picture);
|
|
167
|
+
const viewers = new Viewers();
|
|
168
|
+
const priority = new PriorityOrchestrator({
|
|
169
|
+
publish: () => {},
|
|
170
|
+
viewers,
|
|
171
|
+
allowanceFor: () => 10,
|
|
172
|
+
watchedBy: (session, viewer) => !live.supersededBy(session, viewer.activeVariantId ?? null)
|
|
173
|
+
});
|
|
174
|
+
const person = viewers.of(picture, "p");
|
|
175
|
+
person.moveTo(300);
|
|
176
|
+
viewers.leaves(picture, "p");
|
|
177
|
+
|
|
178
|
+
priority.publishFor({ sessionGroups: [[picture]], now: Date.now() });
|
|
179
|
+
|
|
180
|
+
assert.deepEqual(runsOf(priority.mapForOutput("out:1080")), []);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("a file and an output that are gone are forgotten", () => {
|
|
184
|
+
// These are the projection of the live sessions, never a memory of them. Left
|
|
185
|
+
// to accumulate they were three maps that only grew, and `forget` was written
|
|
186
|
+
// for that and called from nowhere.
|
|
187
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
188
|
+
const { priority, viewers, publish } = over([picture]);
|
|
189
|
+
viewers.of(picture, "p").moveTo(300);
|
|
190
|
+
|
|
191
|
+
publish();
|
|
192
|
+
assert.ok(runsOf(priority.mapForOutput("out:1080")).length > 0);
|
|
193
|
+
|
|
194
|
+
priority.publishFor({ sessionGroups: [[]], staleAfterMs: STALE_AFTER_MS });
|
|
195
|
+
assert.deepEqual(runsOf(priority.mapForOutput("out:1080")), [], "the output is gone");
|
|
196
|
+
assert.deepEqual(
|
|
197
|
+
runsOf(priority.mapFor(FILM.sourceKey, FILM.fileIndex)),
|
|
198
|
+
[],
|
|
199
|
+
"and so is the file"
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("the picture is watched by a person who never moved off it", () => {
|
|
204
|
+
const picture = outputOf({ id: "pic", outputKey: "out:1080" });
|
|
205
|
+
const { viewers, live } = over([picture]);
|
|
206
|
+
const person = viewers.of(picture, "p");
|
|
207
|
+
|
|
208
|
+
assert.equal(live.supersededBy(picture, null), false, "no step is active");
|
|
209
|
+
person.activeVariantId = "pic";
|
|
210
|
+
assert.equal(
|
|
211
|
+
live.supersededBy(picture, person.activeVariantId),
|
|
212
|
+
false,
|
|
213
|
+
"and naming the picture itself as the step is the same statement"
|
|
214
|
+
);
|
|
215
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
|
-
import { probeWedgeIsCertain, readProbeState, PROBE_INTERVAL_MS } from "../services/delivery-probe.js";
|
|
4
|
+
import { probeWedgeIsCertain, readProbeState, PROBE_INTERVAL_MS } from "../services/transport/delivery-probe.js";
|
|
5
5
|
|
|
6
6
|
test("a seen-counter bounded lag is not a wedge, however long it lasts", () => {
|
|
7
7
|
// Session 4dcac61b, field log 2026-08-28: gap held at 6-7 probes for 95+
|
|
@@ -21,20 +21,21 @@
|
|
|
21
21
|
|
|
22
22
|
import test from "node:test";
|
|
23
23
|
import assert from "node:assert/strict";
|
|
24
|
-
import { SourceFile } from "../services/
|
|
25
|
-
import { Timeline } from "../services/output/Timeline.js";
|
|
24
|
+
import { SourceFile } from "../services/media/SourceFile.js";
|
|
25
|
+
import { Timeline } from "../services/encode/output/Timeline.js";
|
|
26
26
|
import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
27
27
|
import os from "node:os";
|
|
28
28
|
import path from "node:path";
|
|
29
|
-
import {
|
|
29
|
+
import { managerWithOwnStore } from "./helpers/manager.js";
|
|
30
30
|
import { startRunOn } from "./helpers/encode-run.js";
|
|
31
|
-
import { fmp4Format } from "../services/segment-formats/fmp4.js";
|
|
31
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
32
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
32
33
|
|
|
33
34
|
const MOVIE_TIMESCALE = 1000;
|
|
34
35
|
const VIDEO_TIMESCALE = 90_000;
|
|
35
36
|
const AUDIO_TIMESCALE = 48_000;
|
|
36
37
|
const SEGMENT_SECONDS = 12.5;
|
|
37
|
-
const SESSION_ID = "
|
|
38
|
+
const SESSION_ID = "aef21c88a8d64a9a";
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* @param {string} type
|
|
@@ -119,18 +120,15 @@ function wholePiece(offsetSeconds) {
|
|
|
119
120
|
/**
|
|
120
121
|
* A session with the output directory every run writes into.
|
|
121
122
|
*
|
|
122
|
-
* @returns {Promise<{ manager:
|
|
123
|
+
* @returns {Promise<{ manager: object, session: object, dirPath: string }>}
|
|
123
124
|
*/
|
|
124
125
|
async function sessionOnOneDirectory() {
|
|
125
126
|
const dirPath = await mkdtemp(path.join(os.tmpdir(), "produced-copy-"));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
ffmpegBin: "ffmpeg",
|
|
129
|
-
localBindHost: "127.0.0.1",
|
|
130
|
-
localPort: 9090
|
|
131
|
-
});
|
|
127
|
+
// Its own store root — see `helpers/manager.js` for what sharing one cost.
|
|
128
|
+
const { manager } = managerWithOwnStore();
|
|
132
129
|
const session = {
|
|
133
130
|
id: SESSION_ID,
|
|
131
|
+
spec: outputSpec({ transcodeVideo: false }),
|
|
134
132
|
dirPath,
|
|
135
133
|
// Where this file is cut, held by the file. A fixture that stated it
|
|
136
134
|
// on the session was describing what production no longer does.
|
|
@@ -145,20 +143,16 @@ async function sessionOnOneDirectory() {
|
|
|
145
143
|
get inputFile() { return this.file; },
|
|
146
144
|
get audioFile() { return this.file; },
|
|
147
145
|
startedAt: Date.now(),
|
|
148
|
-
createEntryMs: Date.now(),
|
|
149
146
|
lastAccessedAt: Date.now(),
|
|
150
147
|
runs: new Set(),
|
|
151
148
|
lastError: "",
|
|
152
|
-
consumers: new Set(),
|
|
153
|
-
viewers: new Map(),
|
|
154
149
|
segmentFormat: fmp4Format,
|
|
155
150
|
useSyntheticPlaylist: true,
|
|
156
151
|
playlistText: "#EXTM3U\n",
|
|
157
152
|
initBytes: fmp4Format.extractInit(wholePiece(0)),
|
|
158
|
-
firstSegmentLogged: false,
|
|
159
153
|
waitEpoch: 0
|
|
160
154
|
};
|
|
161
|
-
manager.
|
|
155
|
+
manager.outputs.set(SESSION_ID, session);
|
|
162
156
|
// A run exists and is alive — the state the field case was in, and the one in
|
|
163
157
|
// which the old test called every leftover "still being written".
|
|
164
158
|
startRunOn(session, { from: 0, usesExplicitCuts: true });
|
|
@@ -170,7 +164,7 @@ async function sessionOnOneDirectory() {
|
|
|
170
164
|
test("a leftover of a run that has ended is not served, and answering does not delete it", async (t) => {
|
|
171
165
|
const { manager, session, dirPath } = await sessionOnOneDirectory();
|
|
172
166
|
t.after(async () => {
|
|
173
|
-
await manager.disposeAll();
|
|
167
|
+
await manager.lifecycle.disposeAll();
|
|
174
168
|
await rm(dirPath, { recursive: true, force: true });
|
|
175
169
|
});
|
|
176
170
|
// Opened by a run that is gone. It has a name and no bytes, so there is
|
|
@@ -178,7 +172,7 @@ test("a leftover of a run that has ended is not served, and answering does not d
|
|
|
178
172
|
session.runs = new Set();
|
|
179
173
|
await writeFile(path.join(dirPath, "segment-00001.mp4"), Buffer.alloc(0));
|
|
180
174
|
|
|
181
|
-
const result = await manager.getFileStream(SESSION_ID, "segment-00001.mp4"
|
|
175
|
+
const result = await manager.serving.getFileStream(SESSION_ID, "segment-00001.mp4");
|
|
182
176
|
|
|
183
177
|
assert.equal(result.kind, "warming-up", "nothing servable exists yet, so the viewer waits");
|
|
184
178
|
// AND ANSWERING A REQUEST DELETES NOTHING. It used to: this test was written
|
|
@@ -198,7 +192,7 @@ test("a leftover of a run that has ended is not served, and answering does not d
|
|
|
198
192
|
test("the current run's own unfinished piece is waited for, never deleted", async (t) => {
|
|
199
193
|
const { manager, session, dirPath } = await sessionOnOneDirectory();
|
|
200
194
|
t.after(async () => {
|
|
201
|
-
await manager.disposeAll();
|
|
195
|
+
await manager.lifecycle.disposeAll();
|
|
202
196
|
await rm(dirPath, { recursive: true, force: true });
|
|
203
197
|
});
|
|
204
198
|
// The same file, in the directory of the run that is alive. It is being
|
|
@@ -210,49 +204,10 @@ test("the current run's own unfinished piece is waited for, never deleted", asyn
|
|
|
210
204
|
startRunOn(session, { from: 1, producing: false, usesExplicitCuts: true });
|
|
211
205
|
await writeFile(path.join(dirPath, "segment-00001.mp4"), Buffer.alloc(0));
|
|
212
206
|
|
|
213
|
-
const result = await manager.getFileStream(SESSION_ID, "segment-00001.mp4"
|
|
207
|
+
const result = await manager.serving.getFileStream(SESSION_ID, "segment-00001.mp4");
|
|
214
208
|
|
|
215
209
|
assert.equal(result.kind, "warming-up");
|
|
216
210
|
const left = (await readdir(dirPath)).filter((name) => name === "segment-00001.mp4");
|
|
217
211
|
assert.deepEqual(left, ["segment-00001.mp4"], "the live run's own output must be left alone");
|
|
218
212
|
});
|
|
219
213
|
|
|
220
|
-
test("the look-ahead does not count a file with nothing in it", async (t) => {
|
|
221
|
-
const dirPath = await mkdtemp(path.join(os.tmpdir(), "usable-indices-"));
|
|
222
|
-
t.after(async () => {
|
|
223
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
224
|
-
});
|
|
225
|
-
await writeFile(path.join(dirPath, "segment-00000.mp4"), wholePiece(0));
|
|
226
|
-
await writeFile(path.join(dirPath, "segment-00001.mp4"), Buffer.alloc(0));
|
|
227
|
-
|
|
228
|
-
assert.deepEqual(
|
|
229
|
-
[...usableSegmentIndices([dirPath], fmp4Format, new Set())].sort((a, b) => a - b),
|
|
230
|
-
[0],
|
|
231
|
-
"an empty file bridged the hole and bought the encoder a suspension it had not earned"
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
// The same number, made properly: now it genuinely is ready.
|
|
235
|
-
await writeFile(path.join(dirPath, "segment-00001.mp4"), wholePiece(SEGMENT_SECONDS));
|
|
236
|
-
assert.deepEqual(
|
|
237
|
-
[...usableSegmentIndices([dirPath], fmp4Format, new Set())].sort((a, b) => a - b),
|
|
238
|
-
[0, 1],
|
|
239
|
-
"a copy with bytes in it, which is what the serving path will find"
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("what the output holds is asked of the filesystem once per file", async (t) => {
|
|
244
|
-
const dirPath = await mkdtemp(path.join(os.tmpdir(), "usable-memo-"));
|
|
245
|
-
t.after(async () => {
|
|
246
|
-
await rm(dirPath, { recursive: true, force: true });
|
|
247
|
-
});
|
|
248
|
-
await writeFile(path.join(dirPath, "segment-00000.mp4"), wholePiece(0));
|
|
249
|
-
const known = new Set();
|
|
250
|
-
|
|
251
|
-
usableSegmentIndices([dirPath], fmp4Format, known);
|
|
252
|
-
|
|
253
|
-
assert.deepEqual(
|
|
254
|
-
[...known],
|
|
255
|
-
[path.join(dirPath, "segment-00000.mp4")],
|
|
256
|
-
"a piece that has bytes never loses them, and this runs on the thread carrying the data channel"
|
|
257
|
-
);
|
|
258
|
-
});
|