@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
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file What the HTTP layer asks of the components, checked from the HTTP layer.
|
|
3
|
+
*
|
|
4
|
+
* Written as the net under the dismantling of `hls-session-manager.js`, which
|
|
5
|
+
* was 9608 lines and 142 methods and is now gone: its members are methods of
|
|
6
|
+
* the components that own them, wired by `services/server/wire-outputs.js`.
|
|
7
|
+
* Most tests that build those components name something INSIDE them — a field,
|
|
8
|
+
* a fake output shaped the way one happens to be shaped — so they move when the
|
|
9
|
+
* code moves, and a test that moves with the code cannot say the code still
|
|
10
|
+
* works.
|
|
11
|
+
*
|
|
12
|
+
* This one is derived from the other side: from what the HTTP layer asks of
|
|
13
|
+
* the components. That contract does not move. A route calling a member that no longer exists
|
|
14
|
+
* is the exact failure a nine-step move risks, and it is silent — Fastify
|
|
15
|
+
* answers 500 at runtime, months later, on a path no unit test walks.
|
|
16
|
+
*
|
|
17
|
+
* So the first check READS `routes/` and `server.js` rather than listing what
|
|
18
|
+
* the manager has. It cannot go stale: adding a call to a route adds it to the
|
|
19
|
+
* requirement, and removing the member it names breaks this test the same
|
|
20
|
+
* minute.
|
|
21
|
+
*
|
|
22
|
+
* The second is a characteristic record rather than a judgement: what the
|
|
23
|
+
* progress report carries today. Eighteen figures reach the page from it and
|
|
24
|
+
* nothing on this side of the wire knows which of them the page needs, so the
|
|
25
|
+
* honest statement is "these are the ones there were" — the point being that a
|
|
26
|
+
* move must not change them, not that they are the right ones.
|
|
27
|
+
*
|
|
28
|
+
* Every manager here has a store root of its own, for the reason
|
|
29
|
+
* `helpers/manager.js` records.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import test from "node:test";
|
|
33
|
+
import assert from "node:assert/strict";
|
|
34
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
35
|
+
import path from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
import { SourceFile } from "../services/media/SourceFile.js";
|
|
38
|
+
import { Timeline } from "../services/encode/output/Timeline.js";
|
|
39
|
+
import { managerWithOwnStore } from "./helpers/manager.js";
|
|
40
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
41
|
+
import { Output } from "../services/encode/output/Output.js";
|
|
42
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
43
|
+
|
|
44
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
const PROXY = path.join(HERE, "..");
|
|
46
|
+
const SESSION_ID = "aaaaaaaabbbbcccc";
|
|
47
|
+
/** The components the HTTP layer is handed. */
|
|
48
|
+
const COMPONENTS = ["serving", "viewerRequests", "renditions", "lifecycle", "quality", "outputs", "hostTimings", "viewers", "encodeRuns"];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Every `.js` file under a directory, at any depth.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} dir
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
function jsFilesUnder(dir) {
|
|
57
|
+
/** @type {string[]} */
|
|
58
|
+
const found = [];
|
|
59
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
60
|
+
const full = path.join(dir, entry.name);
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
found.push(...jsFilesUnder(full));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (entry.name.endsWith(".js")) {
|
|
66
|
+
found.push(full);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What the HTTP layer asks of each component, read from the HTTP layer.
|
|
74
|
+
*
|
|
75
|
+
* Deliberately not a list kept here: a list is a second statement of the same
|
|
76
|
+
* fact, and the two drift. The callers are the statement. A route names the
|
|
77
|
+
* component it was handed (`serving.getFileStream`); `server.js` names it
|
|
78
|
+
* through what the wiring returned (`outputParts.serving.getFileStream`).
|
|
79
|
+
*
|
|
80
|
+
* @returns {Map<string, string[]>} `component.member` to the files that ask for it.
|
|
81
|
+
*/
|
|
82
|
+
function membersTheHttpLayerCalls() {
|
|
83
|
+
/** @type {Map<string, string[]>} */
|
|
84
|
+
const asked = new Map();
|
|
85
|
+
const sources = [path.join(PROXY, "server.js"), ...jsFilesUnder(path.join(PROXY, "routes"))];
|
|
86
|
+
const pattern = new RegExp(String.raw`\b(${COMPONENTS.join("|")})\.([A-Za-z][A-Za-z0-9]*)\(`, "g");
|
|
87
|
+
for (const file of sources) {
|
|
88
|
+
const text = readFileSync(file, "utf8");
|
|
89
|
+
for (const match of text.matchAll(pattern)) {
|
|
90
|
+
const asking = `${match[1]}.${match[2]}`;
|
|
91
|
+
const where = asked.get(asking) ?? [];
|
|
92
|
+
where.push(path.relative(PROXY, file));
|
|
93
|
+
asked.set(asking, where);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return asked;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A manager with nothing running behind it, over a store of its own.
|
|
101
|
+
*
|
|
102
|
+
* Its two timers are unref'd, so nothing here keeps the process alive.
|
|
103
|
+
*
|
|
104
|
+
* @returns {object}
|
|
105
|
+
*/
|
|
106
|
+
function bareManager() {
|
|
107
|
+
return managerWithOwnStore().manager;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A session shaped like a live one, with no encoder behind it.
|
|
112
|
+
*
|
|
113
|
+
* @param {{ id?: string }} [params]
|
|
114
|
+
* @returns {object}
|
|
115
|
+
*/
|
|
116
|
+
function fakeSession({ id = SESSION_ID } = {}) {
|
|
117
|
+
return {
|
|
118
|
+
id,
|
|
119
|
+
spec: outputSpec({ cutGrid: "keyframe", audioSeparate: true }),
|
|
120
|
+
outputKey: "surface:fmt=fmp4:grid=kf@0:video-only:v=0/copy",
|
|
121
|
+
dirPath: path.join(PROXY, "test", "does-not-exist"),
|
|
122
|
+
file: new SourceFile({ sourceKey: "torrent:abc", fileIndex: 0, name: "video.mkv" })
|
|
123
|
+
.learn({ width: 1920, height: 1080 }),
|
|
124
|
+
get inputFile() { return this.file; },
|
|
125
|
+
get audioFile() { return this.file; },
|
|
126
|
+
timeline: new Timeline({
|
|
127
|
+
boundaries: Array.from({ length: 11 }, (_, index) => index * 4),
|
|
128
|
+
cutGrid: "keyframe"
|
|
129
|
+
}),
|
|
130
|
+
segmentFormat: fmp4Format,
|
|
131
|
+
output: new Output({
|
|
132
|
+
encodeWidth: 0,
|
|
133
|
+
encodeHeight: 0,
|
|
134
|
+
outputFps: 24,
|
|
135
|
+
softwarePreset: null,
|
|
136
|
+
applyTonemap: false
|
|
137
|
+
}),
|
|
138
|
+
transcodeVideo: false,
|
|
139
|
+
transcodeAudio: true,
|
|
140
|
+
audioTrackIndex: 0,
|
|
141
|
+
audioSourceTrackIndex: 0,
|
|
142
|
+
useSyntheticPlaylist: true,
|
|
143
|
+
playlistText: "#EXTM3U\n",
|
|
144
|
+
segmentCount: 10,
|
|
145
|
+
progress: { state: "running", processedSeconds: 0, startPositionSeconds: 0, speed: "1.0x" }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
test("every call the HTTP layer makes is answered", () => {
|
|
150
|
+
const parts = bareManager();
|
|
151
|
+
const asked = membersTheHttpLayerCalls();
|
|
152
|
+
// If this ever reads zero the extraction has broken, and the test would then
|
|
153
|
+
// pass by asking nothing at all.
|
|
154
|
+
assert.ok(asked.size >= 15, `expected the HTTP layer to ask for members, found ${asked.size}`);
|
|
155
|
+
|
|
156
|
+
const missing = [];
|
|
157
|
+
for (const [asking, callers] of asked) {
|
|
158
|
+
const [component, member] = asking.split(".");
|
|
159
|
+
if (!(parts[component] && member in parts[component])) {
|
|
160
|
+
missing.push(`${asking} (asked by ${[...new Set(callers)].join(", ")})`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
assert.deepEqual(missing, [], `the HTTP layer calls members no component has:\n${missing.join("\n")}`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("the progress report keeps every figure it carries today", async () => {
|
|
167
|
+
const manager = bareManager();
|
|
168
|
+
manager.outputs.set(SESSION_ID, fakeSession());
|
|
169
|
+
const progress = await manager.viewerRequests.getSessionProgress(SESSION_ID, "viewer-one");
|
|
170
|
+
assert.ok(progress, "a live session has a progress report");
|
|
171
|
+
|
|
172
|
+
// A characteristic record: what the page is given now. The claim is "a move
|
|
173
|
+
// did not change this", never "this is the right set".
|
|
174
|
+
const carried = [
|
|
175
|
+
"sessionId",
|
|
176
|
+
"state",
|
|
177
|
+
"minimumBufferSeconds",
|
|
178
|
+
"processedSeconds",
|
|
179
|
+
"inputBytes",
|
|
180
|
+
"startPositionSeconds",
|
|
181
|
+
"totalSeconds",
|
|
182
|
+
"percent",
|
|
183
|
+
"remainingSeconds",
|
|
184
|
+
"segmentDurationSec",
|
|
185
|
+
"speed",
|
|
186
|
+
"currentHeight",
|
|
187
|
+
"offeredHeights",
|
|
188
|
+
"requestedHeight",
|
|
189
|
+
"expectedSessionCreateMs",
|
|
190
|
+
"expectedFirstSegmentMs",
|
|
191
|
+
"updatedAt",
|
|
192
|
+
"error"
|
|
193
|
+
];
|
|
194
|
+
const absent = carried.filter((key) => !(key in progress));
|
|
195
|
+
assert.deepEqual(absent, [], `the progress report lost ${absent.join(", ")}`);
|
|
196
|
+
assert.equal(progress.sessionId, SESSION_ID);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("a session that is not there is answered, not invented", async () => {
|
|
200
|
+
const manager = bareManager();
|
|
201
|
+
const absent = "ffffffffffffffff";
|
|
202
|
+
|
|
203
|
+
assert.equal(await manager.viewerRequests.getSessionProgress(absent), null);
|
|
204
|
+
assert.equal((await manager.serving.getFileStream(absent, "segment-00000.mp4")).kind, "not-found");
|
|
205
|
+
// A name no session could have must be refused before anything touches the
|
|
206
|
+
// disk with it.
|
|
207
|
+
assert.equal((await manager.serving.getFileStream("../../etc", "segment-00000.mp4")).kind, "not-found");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("what a viewer states about themselves is kept and answered", () => {
|
|
211
|
+
const manager = bareManager();
|
|
212
|
+
manager.outputs.set(SESSION_ID, fakeSession());
|
|
213
|
+
|
|
214
|
+
// Nine public members had no test of any kind before the dismantling began,
|
|
215
|
+
// and six of them are the viewer's own facts — the ones that move into
|
|
216
|
+
// `viewer/`. They are cheap to state and were simply never stated.
|
|
217
|
+
|
|
218
|
+
// A SEEK DOES ONE THING: it puts the viewer where they now are. Recorded
|
|
219
|
+
// here because it used to do eleven, and because the one remaining effect is
|
|
220
|
+
// what every reading of a viewer's position now rests on.
|
|
221
|
+
assert.equal(manager.viewerRequests.requestSeek(SESSION_ID, 120, "viewer-one"), true);
|
|
222
|
+
assert.equal(manager.viewerRequests.viewerPositionOf(SESSION_ID, "viewer-one"), 120);
|
|
223
|
+
assert.equal(manager.viewerRequests.requestSeek("no-such-session", 120, "viewer-one"), false);
|
|
224
|
+
|
|
225
|
+
// `seekEpoch` is NOT moved by a seek, whatever its name says: its two writers
|
|
226
|
+
// are the variant switch and the soundtrack switch, and they move it on the
|
|
227
|
+
// output the viewer has LEFT. Recorded as it is, so the dismantling can give
|
|
228
|
+
// the fact its real name instead of discovering this by breaking it.
|
|
229
|
+
assert.equal(manager.serving.seekEpoch(SESSION_ID), 0, "a seek leaves the wait epoch alone");
|
|
230
|
+
assert.equal(manager.serving.seekEpoch("no-such-session"), 0);
|
|
231
|
+
|
|
232
|
+
manager.encodeRuns.noteInputBytes(SESSION_ID, 4096);
|
|
233
|
+
manager.encodeRuns.noteInputBytes(SESSION_ID, 1024);
|
|
234
|
+
assert.equal(
|
|
235
|
+
manager.encodeRuns.inputBytesOf(manager.outputs.get(SESSION_ID)),
|
|
236
|
+
5120,
|
|
237
|
+
"input bytes accumulate at their owner"
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// A far fragment is a reading and must never throw, whatever the player says.
|
|
241
|
+
manager.serving.recordFragmentFar(SESSION_ID, {
|
|
242
|
+
sn: 40, track: "video", fragStartSec: 200, bufferEndSec: 130, currentTimeSec: 128
|
|
243
|
+
});
|
|
244
|
+
manager.serving.recordFragmentFar("no-such-session", { sn: 1, track: "video" });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("a session nobody has touched is disposed, one that is being watched is not", async () => {
|
|
248
|
+
const manager = bareManager();
|
|
249
|
+
const stale = fakeSession({ id: "1111111111111111" });
|
|
250
|
+
const fresh = fakeSession({ id: "2222222222222222" });
|
|
251
|
+
// Older than any TTL this manager could carry, without naming one here: the
|
|
252
|
+
// period is the manager's business and this test is about the rule.
|
|
253
|
+
manager.outputs.set(stale.id, stale);
|
|
254
|
+
manager.outputs.set(fresh.id, fresh);
|
|
255
|
+
manager.outputs.touch(stale, Date.now() - (2 * 60 * 60 * 1000));
|
|
256
|
+
|
|
257
|
+
await manager.lifecycle.cleanupExpired();
|
|
258
|
+
|
|
259
|
+
assert.equal(manager.outputs.has(stale.id), false, "an untouched session goes");
|
|
260
|
+
assert.equal("state" in stale, false, "removal from the registry is the only lifetime fact");
|
|
261
|
+
assert.equal(manager.outputs.has(fresh.id), true, "a session just read stays");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("what a file declares and what this host could offer are answered without a session", () => {
|
|
265
|
+
const manager = bareManager();
|
|
266
|
+
const session = fakeSession();
|
|
267
|
+
manager.outputs.set(SESSION_ID, session);
|
|
268
|
+
|
|
269
|
+
// `declaredTracks` reads the session's own record and must answer even when
|
|
270
|
+
// nothing has probed the file yet.
|
|
271
|
+
assert.doesNotThrow(() => manager.renditions.declaredTracks(session));
|
|
272
|
+
|
|
273
|
+
// The offer is predicted before any session exists — that is its whole point,
|
|
274
|
+
// the menu being complete from the moment a file is opened.
|
|
275
|
+
assert.equal(manager.quality.predictOfferedHeights({ height: 0, width: 0 }), null, "an unknown picture offers nothing");
|
|
276
|
+
const offered = manager.quality.predictOfferedHeights({
|
|
277
|
+
height: 1080, width: 1920, fps: 24, sourceKey: "torrent:abc", fileIndex: 0
|
|
278
|
+
});
|
|
279
|
+
// TWO ANSWERS, not one: what this host can hold when the picture is copied,
|
|
280
|
+
// and what it can hold when it is re-encoded. The browser decides which of
|
|
281
|
+
// the two applies, because whether it can play the source is its own fact.
|
|
282
|
+
assert.ok(Array.isArray(offered?.copy), "the copied branch gets a list");
|
|
283
|
+
assert.ok(Array.isArray(offered?.transcode), "the re-encoded branch gets a list");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("a segment name that is not one is refused", async () => {
|
|
287
|
+
const manager = bareManager();
|
|
288
|
+
manager.outputs.set(SESSION_ID, fakeSession());
|
|
289
|
+
|
|
290
|
+
for (const name of ["../key.txt", "segment-00000.mp4/../../x", "making-0-00000.mp4"]) {
|
|
291
|
+
const answer = await manager.serving.getFileStream(SESSION_ID, name);
|
|
292
|
+
assert.equal(answer.kind, "not-found", `${name} must not be servable`);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
Binary file
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import test from "node:test";
|
|
18
18
|
import assert from "node:assert/strict";
|
|
19
|
-
import { MatroskaContainer } from "../services/container/MatroskaContainer.js";
|
|
19
|
+
import { MatroskaContainer } from "../services/media/container/MatroskaContainer.js";
|
|
20
20
|
|
|
21
21
|
const ID_EBML = 0x1a45dfa3;
|
|
22
22
|
const ID_SEGMENT = 0x18538067;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
readingIsWorthWriting,
|
|
7
7
|
summariseMappings,
|
|
8
8
|
watchedFigures
|
|
9
|
-
} from "../services/memory-report.js";
|
|
9
|
+
} from "../services/storage/memory-report.js";
|
|
10
10
|
import {
|
|
11
11
|
divideAllowance,
|
|
12
12
|
forgetMachineMemory,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
machineReserveBytes,
|
|
15
15
|
noteMachineMemory,
|
|
16
16
|
SharedPieceStore
|
|
17
|
-
} from "../services/piece-store/shared-piece-store.js";
|
|
17
|
+
} from "../services/storage/piece-store/shared-piece-store.js";
|
|
18
18
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
19
19
|
import os from "node:os";
|
|
20
20
|
import path from "node:path";
|
|
Binary file
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import test from "node:test";
|
|
10
10
|
import assert from "node:assert/strict";
|
|
11
|
-
import { Mp4Container } from "../services/container/Mp4Container.js";
|
|
11
|
+
import { Mp4Container } from "../services/media/container/Mp4Container.js";
|
|
12
12
|
|
|
13
13
|
function box(type, payload) {
|
|
14
14
|
const header = Buffer.alloc(8);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import test from "node:test";
|
|
16
16
|
import assert from "node:assert/strict";
|
|
17
|
-
import { readFileSync } from "node:fs";
|
|
17
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { EventEmitter } from "node:events";
|
|
@@ -30,6 +30,21 @@ function source(relative) {
|
|
|
30
30
|
return readFileSync(path.join(HERE, "..", relative), "utf8");
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Every source file of the proxy's services, read together.
|
|
35
|
+
*
|
|
36
|
+
* A rule that something must not come back is asked of all of them: asked of
|
|
37
|
+
* one file, it passes as soon as the code moves to another.
|
|
38
|
+
*
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
function everything() {
|
|
42
|
+
return readdirSync(path.join(HERE, "..", "services"), { recursive: true })
|
|
43
|
+
.filter((name) => String(name).endsWith(".js"))
|
|
44
|
+
.map((name) => source(path.join("services", String(name))))
|
|
45
|
+
.join("\n");
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
/**
|
|
34
49
|
* Lines of code, without comments or blanks: a rule about what the code does
|
|
35
50
|
* must not be answered by what a comment says about it.
|
|
@@ -48,26 +63,30 @@ test("an encoder is stopped for scheduling reasons in exactly one place", () =>
|
|
|
48
63
|
// The orchestrator decides; nobody else may. What is left in the session
|
|
49
64
|
// manager is teardown — the session is going away and its encoders with it —
|
|
50
65
|
// which is not a decision about which encoders should exist.
|
|
51
|
-
const orchestrator = statements(source("services/
|
|
66
|
+
const orchestrator = statements(source("services/encode/EncodeOrchestrator.js"));
|
|
52
67
|
const stopsInOrchestrator = orchestrator.filter((line) => line.includes("run.stop("));
|
|
53
68
|
assert.equal(stopsInOrchestrator.length, 1, "the orchestrator stops runs in one place");
|
|
54
69
|
|
|
55
|
-
const
|
|
56
|
-
|
|
70
|
+
const wiring = statements(source("services/server/wire-outputs.js"));
|
|
71
|
+
assert.deepEqual(wiring.filter((line) => line.includes(".stop(")), [], "the wiring stops nothing");
|
|
72
|
+
const lifecycle = statements(source("services/server/OutputLifecycle.js"));
|
|
73
|
+
const stopsAtTeardown = lifecycle.filter((line) => line.includes(".stop("));
|
|
57
74
|
assert.equal(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
stopsInManager.join(" / ")
|
|
75
|
+
stopsAtTeardown.length,
|
|
76
|
+
1,
|
|
77
|
+
"an output's end stops its runs, and nothing else there does: " + stopsAtTeardown.join(" / ")
|
|
62
78
|
);
|
|
79
|
+
// And the runs themselves stop one for a stated reason in one place, which is
|
|
80
|
+
// what a corrected cut table asks through.
|
|
81
|
+
const runs = statements(source("services/encode/EncodeRuns.js"));
|
|
82
|
+
assert.equal(runs.filter((line) => line.includes(".stop(")).length, 1);
|
|
63
83
|
});
|
|
64
84
|
|
|
65
85
|
test("nothing outside the encoding layer starts an encoder", () => {
|
|
66
86
|
// A run is built in one place. Two places building them is how a start came
|
|
67
87
|
// to kill what the plan had decided to keep — the killing lived in the
|
|
68
88
|
// building.
|
|
69
|
-
const
|
|
70
|
-
const builds = manager.filter((line) => line.includes("new EncodeRun("));
|
|
89
|
+
const builds = statements(everything()).filter((line) => line.includes("new EncodeRun("));
|
|
71
90
|
assert.equal(builds.length, 1, "one place builds an encoder");
|
|
72
91
|
});
|
|
73
92
|
|
|
@@ -86,8 +105,9 @@ test("a run exists means its process is running, so nothing can start one twice"
|
|
|
86
105
|
// there is no second act for a second owner to perform.
|
|
87
106
|
const files = [
|
|
88
107
|
"services/encode/EncodeRun.js",
|
|
89
|
-
"services/
|
|
90
|
-
"services/
|
|
108
|
+
"services/encode/EncodeOrchestrator.js",
|
|
109
|
+
"services/encode/EncodeRuns.js",
|
|
110
|
+
"services/server/wire-outputs.js"
|
|
91
111
|
];
|
|
92
112
|
for (const file of files) {
|
|
93
113
|
const starts = statements(source(file)).filter(
|
|
@@ -103,7 +123,7 @@ test("starting an encoder stops nothing", () => {
|
|
|
103
123
|
// The rule that broke it: the start path looked for a live run whose own
|
|
104
124
|
// start was not below the new one's and killed it. It is not enough that the
|
|
105
125
|
// line is gone — the words it was written with must not come back.
|
|
106
|
-
const manager =
|
|
126
|
+
const manager = everything();
|
|
107
127
|
assert.equal(
|
|
108
128
|
manager.includes("previousRun"),
|
|
109
129
|
false,
|
|
@@ -116,11 +136,10 @@ test("a seek moves the viewer and nothing else", () => {
|
|
|
116
136
|
// It used to do eleven things and write the position into five places. What
|
|
117
137
|
// follows from a viewer moving is the map's business, and the orchestrators
|
|
118
138
|
// read the map.
|
|
119
|
-
const manager = source("services/
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
139
|
+
const manager = source("services/server/ViewerRequests.js");
|
|
140
|
+
const at = manager.indexOf("requestSeek(sessionId, positionSeconds");
|
|
141
|
+
assert.notEqual(at, -1, "the seek is where this reads it");
|
|
142
|
+
const seek = manager.slice(at, at + 2000);
|
|
124
143
|
const body = seek.slice(0, seek.indexOf("\n }\n"));
|
|
125
144
|
assert.equal(body.includes("#startEncodeRun"), false, "a seek starts no encoder");
|
|
126
145
|
assert.equal(body.includes("setTimeout"), false, "and waits for nothing");
|
|
@@ -130,7 +149,7 @@ test("how far an encoder may work is answered once", () => {
|
|
|
130
149
|
// The plan computes the stretch and it reaches ffmpeg. A second computation
|
|
131
150
|
// somewhere else is what made the first one pointless: it was passed and then
|
|
132
151
|
// dropped by a parameter list that did not name it.
|
|
133
|
-
const orchestrator = source("services/
|
|
152
|
+
const orchestrator = source("services/encode/EncodeOrchestrator.js");
|
|
134
153
|
assert.match(
|
|
135
154
|
orchestrator,
|
|
136
155
|
/makeRun\(\{ address, from, to, because \}\)/,
|
|
@@ -146,12 +165,12 @@ test("only the plan places an encoder", () => {
|
|
|
146
165
|
// a settled seek. Each of them chose a position by a rule of its own, and the
|
|
147
166
|
// plan — which is arithmetic over what is made, what is being made and what is
|
|
148
167
|
// wanted — was left to compare its answer against theirs.
|
|
149
|
-
const manager = source("services/
|
|
168
|
+
const manager = source("services/encode/EncodeRuns.js");
|
|
150
169
|
const starts = [...manager.matchAll(/this\.#startEncodeRun\(/g)].length;
|
|
151
170
|
assert.equal(starts, 1, "one caller, and it is the one the plan asks through");
|
|
152
171
|
assert.match(
|
|
153
172
|
manager,
|
|
154
|
-
|
|
173
|
+
/makeRunAt\(address, from, to, because\)[\s\S]{0,3000}this\.#startEncodeRun\(base, from, because, \{ to \}\)/,
|
|
155
174
|
"and that caller is what the plan is given to build runs with"
|
|
156
175
|
);
|
|
157
176
|
});
|
|
@@ -162,7 +181,7 @@ test("nobody stops an encoder for being unwatched", () => {
|
|
|
162
181
|
// with nothing in it. Answered here as well, it was answered twice by two
|
|
163
182
|
// rules — and since a viewer moving between steps announces itself, the plan
|
|
164
183
|
// started again what this class had just killed, several times a second.
|
|
165
|
-
const manager =
|
|
184
|
+
const manager = everything();
|
|
166
185
|
assert.equal(
|
|
167
186
|
manager.includes("no viewer is watching"),
|
|
168
187
|
false,
|
|
@@ -182,7 +201,7 @@ test("the seek settle machinery is gone, whole", () => {
|
|
|
182
201
|
// second debounce on a signal the browser had already debounced, and every
|
|
183
202
|
// millisecond of it was dead time in front of the viewer; the cooldown behind
|
|
184
203
|
// it existed because segment REQUESTS once steered the encoder.
|
|
185
|
-
const manager =
|
|
204
|
+
const manager = everything();
|
|
186
205
|
for (const gone of [
|
|
187
206
|
"seekSettleTimer",
|
|
188
207
|
"seekTarget",
|
|
@@ -203,10 +222,10 @@ test("where a soundtrack begins is read off the table, not handed in", () => {
|
|
|
203
222
|
// the live table every session of the file shares. Passed as an argument by
|
|
204
223
|
// the one caller that had measured it, only a run started by that caller ever
|
|
205
224
|
// had it, and a run the plan placed at the same number landed apart again.
|
|
206
|
-
const manager = source("services/
|
|
225
|
+
const manager = source("services/encode/EncodeRuns.js");
|
|
207
226
|
assert.match(
|
|
208
227
|
manager,
|
|
209
|
-
/const positionSecondsOverride = session\.
|
|
228
|
+
/const positionSecondsOverride = session\.spec\.carries === "audio-only"\s*\n?\s*\? trueStartOf\(session\.timeline, startIndex\)/,
|
|
210
229
|
"derived where the run is built"
|
|
211
230
|
);
|
|
212
231
|
assert.equal(
|
|
@@ -216,27 +235,12 @@ test("where a soundtrack begins is read off the table, not handed in", () => {
|
|
|
216
235
|
);
|
|
217
236
|
});
|
|
218
237
|
|
|
219
|
-
test("a changed bitrate cap stops the encoder carrying the old one and nothing more", () => {
|
|
220
|
-
// An argument list is fixed when a process starts, so a run carrying the
|
|
221
|
-
// previous cap cannot be told about the new one — that is what is known here.
|
|
222
|
-
// Where the replacement stands is a different question, and the old answer to
|
|
223
|
-
// it was neither where a viewer is nor a gap in the material: it was the
|
|
224
|
-
// segment the process being replaced happened to have reached.
|
|
225
|
-
const manager = source("services/hls-session-manager.js");
|
|
226
|
-
assert.equal(manager.includes("#restartAtViewer"), false, "the second answer is gone");
|
|
227
|
-
assert.match(
|
|
228
|
-
manager,
|
|
229
|
-
/#reencodeAtNewRate\(session\) \{\s*\n\s*this\.#stopEncodeRun\(session, "its bitrate cap changed"\);\s*\n\s*this\.planEncodersSoon\(\);\s*\n\s*\}/,
|
|
230
|
-
"stopped, and then decided again"
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
238
|
test("each output is handed its own priority map, and the plan is what reads it", () => {
|
|
235
239
|
// The map is one fact asked at two scopes, and both are right for what asks
|
|
236
240
|
// them: the swarm is asked for bytes of a FILE, which every output of it
|
|
237
241
|
// reads, and encoders are placed per OUTPUT, which a person watching 480p
|
|
238
242
|
// wants nothing of at 1080p.
|
|
239
|
-
const manager = source("services/
|
|
243
|
+
const manager = source("services/encode/EncodeRuns.js");
|
|
240
244
|
assert.match(
|
|
241
245
|
manager,
|
|
242
246
|
/notePriorityMap\([\s\S]{0,200}mapForOutput\(address\)/,
|