@torrent-tv/proxy 2.86.0 → 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 -1915
- 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 -483
- package/services/viewer/Viewers.js +368 -257
- package/services/viewer/active-output.js +37 -54
- package/services/viewer/audio-start.js +4 -3
- 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 -45
- 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 -515
- 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 -202
- 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 -9608
- 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/services/viewer/link-readings.js +0 -56
- 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
|
@@ -29,11 +29,12 @@ import assert from "node:assert/strict";
|
|
|
29
29
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
30
30
|
import os from "node:os";
|
|
31
31
|
import path from "node:path";
|
|
32
|
-
import {
|
|
33
|
-
import { SourceFile } from "../services/
|
|
32
|
+
import { wireOutputs } from "../services/server/wire-outputs.js";
|
|
33
|
+
import { SourceFile } from "../services/media/SourceFile.js";
|
|
34
34
|
import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
|
|
35
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
35
36
|
|
|
36
|
-
const SESSION_ID = "
|
|
37
|
+
const SESSION_ID = "cccccccc00004000";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* A session with one run in it, shaped as the manager expects.
|
|
@@ -45,19 +46,13 @@ function sessionWithARun(dirPath) {
|
|
|
45
46
|
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …", state: "ENDED_FAILED" };
|
|
46
47
|
const session = {
|
|
47
48
|
id: SESSION_ID,
|
|
49
|
+
spec: outputSpec({ transcodeVideo: false }),
|
|
48
50
|
dirPath,
|
|
49
51
|
state: "live",
|
|
50
52
|
file: new SourceFile({ sourceKey: "source-1", fileIndex: 0, name: "video.mkv" }),
|
|
51
53
|
get inputFile() { return this.file; },
|
|
52
54
|
get audioFile() { return this.file; },
|
|
53
55
|
outputKey: "output-under-test",
|
|
54
|
-
consumers: new Set(["someone"]),
|
|
55
|
-
viewers: new Map(),
|
|
56
|
-
runs: new Set([run]),
|
|
57
|
-
transcodeVideo: false,
|
|
58
|
-
failedStartAt: -1,
|
|
59
|
-
failedStartCount: 0,
|
|
60
|
-
lastError: "",
|
|
61
56
|
lastRequestedSegment: 0,
|
|
62
57
|
progress: { updatedAt: 0, processedSeconds: 0, totalSeconds: 100, startPositionSeconds: 0 },
|
|
63
58
|
lastAccessedAt: Date.now()
|
|
@@ -87,65 +82,121 @@ function failedFast(run, from) {
|
|
|
87
82
|
test("a run that ended is still recognised as the session's own", (t) => {
|
|
88
83
|
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
89
84
|
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
90
|
-
const manager =
|
|
85
|
+
const manager = wireOutputs({
|
|
91
86
|
enabled: true,
|
|
92
87
|
ffmpegBin: "ffmpeg",
|
|
93
88
|
localBindHost: "127.0.0.1",
|
|
94
89
|
localPort: 9090
|
|
95
90
|
});
|
|
96
91
|
const { session, run } = sessionWithARun(dirPath);
|
|
97
|
-
manager.
|
|
92
|
+
manager.outputs.set(SESSION_ID, session);
|
|
93
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
98
94
|
|
|
99
|
-
manager.noteRunEnded(session, run, failedFast(run, 0));
|
|
95
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
100
96
|
|
|
101
97
|
// If the identity were read after the removal, nothing here would have been
|
|
102
98
|
// written: the handler would have returned at its second line.
|
|
103
|
-
assert.equal(session.
|
|
104
|
-
assert.equal(session.
|
|
105
|
-
assert.ok(session.
|
|
99
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1, "the failure was counted");
|
|
100
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, 0, "at the position it happened");
|
|
101
|
+
assert.ok(manager.encodeOrchestrator.errorOf(session.outputKey).length > 0, "and the encoding owner knows what went wrong");
|
|
102
|
+
assert.equal(
|
|
103
|
+
manager.encodeOrchestrator.stateOf(session.outputKey),
|
|
104
|
+
"ENDED_FAILED",
|
|
105
|
+
"the ending remains observable after the ended run leaves the registry"
|
|
106
|
+
);
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
test("the count runs at segment 0, which is where a first start happens", (t) => {
|
|
109
110
|
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
110
111
|
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
111
|
-
const manager =
|
|
112
|
+
const manager = wireOutputs({
|
|
112
113
|
enabled: true,
|
|
113
114
|
ffmpegBin: "ffmpeg",
|
|
114
115
|
localBindHost: "127.0.0.1",
|
|
115
116
|
localPort: 9090
|
|
116
117
|
});
|
|
117
118
|
const { session } = sessionWithARun(dirPath);
|
|
118
|
-
manager.
|
|
119
|
+
manager.outputs.set(SESSION_ID, session);
|
|
119
120
|
|
|
120
121
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
121
122
|
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
122
|
-
|
|
123
|
-
manager.noteRunEnded(session, run, failedFast(run, 0));
|
|
124
|
-
assert.equal(session.
|
|
123
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
124
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
125
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, attempt);
|
|
126
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), attempt < 3);
|
|
125
127
|
}
|
|
128
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 1), true, "another position has its own budget");
|
|
126
129
|
});
|
|
127
130
|
|
|
128
131
|
test("real work resets the count, so a transient failure is not permanent", (t) => {
|
|
129
132
|
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
130
133
|
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
131
|
-
const manager =
|
|
134
|
+
const manager = wireOutputs({
|
|
132
135
|
enabled: true,
|
|
133
136
|
ffmpegBin: "ffmpeg",
|
|
134
137
|
localBindHost: "127.0.0.1",
|
|
135
138
|
localPort: 9090
|
|
136
139
|
});
|
|
137
140
|
const { session } = sessionWithARun(dirPath);
|
|
138
|
-
manager.
|
|
141
|
+
manager.outputs.set(SESSION_ID, session);
|
|
139
142
|
|
|
140
143
|
const quick = { from: 0, to: -1 };
|
|
141
|
-
|
|
142
|
-
manager.noteRunEnded(session, quick, failedFast(quick, 0));
|
|
143
|
-
assert.equal(session.
|
|
144
|
+
manager.encodeOrchestrator.adopt(session.outputKey, quick);
|
|
145
|
+
manager.encodeRuns.noteRunEnded(session, quick, failedFast(quick, 0));
|
|
146
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 1);
|
|
144
147
|
|
|
145
148
|
const lived = { from: 0, to: -1 };
|
|
146
|
-
|
|
147
|
-
manager.noteRunEnded(session, lived, { ...failedFast(lived, 0), livedMs: 30_000 });
|
|
149
|
+
manager.encodeOrchestrator.adopt(session.outputKey, lived);
|
|
150
|
+
manager.encodeRuns.noteRunEnded(session, lived, { ...failedFast(lived, 0), livedMs: 30_000 });
|
|
148
151
|
|
|
149
|
-
assert.equal(session.
|
|
150
|
-
assert.equal(session.
|
|
152
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0, "a run that did real work is not a failing start");
|
|
153
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).at, -1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a failure belongs to the output it happened on, not to the next output of the same address", async (t) => {
|
|
157
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
158
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
159
|
+
const manager = wireOutputs({
|
|
160
|
+
enabled: true,
|
|
161
|
+
ffmpegBin: "ffmpeg",
|
|
162
|
+
localBindHost: "127.0.0.1",
|
|
163
|
+
localPort: 9090
|
|
164
|
+
});
|
|
165
|
+
t.after(() => manager.lifecycle.disposeAll());
|
|
166
|
+
const { session } = sessionWithARun(dirPath);
|
|
167
|
+
manager.outputs.set(SESSION_ID, session);
|
|
168
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
169
|
+
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
170
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
171
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
172
|
+
}
|
|
173
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), false, "the first output is blocked");
|
|
174
|
+
|
|
175
|
+
await manager.lifecycle.disposeSession(SESSION_ID);
|
|
176
|
+
|
|
177
|
+
// The same parameters opened again: the name follows from the key, so the
|
|
178
|
+
// new output has the same address. What failed on the disposed one is not a
|
|
179
|
+
// fact about this one — a viewer arriving is new information.
|
|
180
|
+
assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE", "no ending is inherited");
|
|
181
|
+
assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "", "no error is inherited");
|
|
182
|
+
assert.equal(manager.encodeOrchestrator.mayStartAt(session.outputKey, 0), true, "no blocked position is inherited");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("a run ending after its output was disposed leaves nothing behind for the next output", (t) => {
|
|
186
|
+
const dirPath = mkdtempSync(path.join(os.tmpdir(), "breaker-"));
|
|
187
|
+
t.after(() => rmSync(dirPath, { recursive: true, force: true }));
|
|
188
|
+
const manager = wireOutputs({
|
|
189
|
+
enabled: true,
|
|
190
|
+
ffmpegBin: "ffmpeg",
|
|
191
|
+
localBindHost: "127.0.0.1",
|
|
192
|
+
localPort: 9090
|
|
193
|
+
});
|
|
194
|
+
const { session, run } = sessionWithARun(dirPath);
|
|
195
|
+
manager.encodeOrchestrator.adopt(session.outputKey, run);
|
|
196
|
+
// Disposal removed the output from the registry; its process ends later.
|
|
197
|
+
manager.encodeRuns.noteRunEnded(session, run, failedFast(run, 0));
|
|
198
|
+
|
|
199
|
+
assert.equal(manager.encodeOrchestrator.stateOf(session.outputKey), "IDLE");
|
|
200
|
+
assert.equal(manager.encodeOrchestrator.errorOf(session.outputKey), "");
|
|
201
|
+
assert.equal(manager.encodeOrchestrator.startFailureOf(session.outputKey).count, 0);
|
|
151
202
|
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import assert from "node:assert/strict";
|
|
11
11
|
import test from "node:test";
|
|
12
12
|
|
|
13
|
-
import { askFastestWiresFor, canPlaceRequests, wiresForPiece } from "../services/torrent
|
|
13
|
+
import { askFastestWiresFor, canPlaceRequests, wiresForPiece } from "../services/torrent/worker/fastest-wires.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {{ speed: number, has?: boolean, choking?: boolean, destroyed?: boolean }} options
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
12
|
import test from "node:test";
|
|
13
13
|
|
|
14
|
-
import { parseFfmpegStreamCounts } from "../services/ffmpeg-banner.js";
|
|
14
|
+
import { parseFfmpegStreamCounts } from "../services/media/ffmpeg-banner.js";
|
|
15
15
|
|
|
16
16
|
const FIELD_BANNER = `
|
|
17
17
|
Input #0, matroska,webm, from 'http://127.0.0.1:9090/stream?sourceKey=b3f08efc':
|
package/test/file-edges.test.js
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
import test from "node:test";
|
|
17
17
|
import assert from "node:assert/strict";
|
|
18
|
-
import { TorrentPool, stateFileEdges, withdrawFileEdges } from "../services/torrent-pool.js";
|
|
19
|
-
import { demandFor, forgetTorrent } from "../services/download/registry.js";
|
|
20
|
-
import { Urgency } from "../services/demand/index.js";
|
|
18
|
+
import { TorrentPool, stateFileEdges, withdrawFileEdges } from "../services/torrent/torrent-pool.js";
|
|
19
|
+
import { demandFor, forgetTorrent } from "../services/torrent/download/registry.js";
|
|
20
|
+
import { Urgency } from "../services/torrent/demand/index.js";
|
|
21
21
|
|
|
22
22
|
/** A torrent of two files, of which the second is the film. */
|
|
23
23
|
function torrentOf({ length = 1_000_000 } = {}) {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import test from "node:test";
|
|
13
13
|
import assert from "node:assert/strict";
|
|
14
|
-
import { fmp4Format } from "../services/segment-formats/fmp4.js";
|
|
14
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* A piece shaped like the muxer's output. Sizes are what matters, not contents.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import test from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
|
-
import { stampSegmentStartTime } from "../services/segment-formats/mp4-boxes.js";
|
|
13
|
+
import { stampSegmentStartTime } from "../services/encode/segment-formats/mp4-boxes.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {string} type
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import test from "node:test";
|
|
14
14
|
import assert from "node:assert/strict";
|
|
15
|
-
import { fmp4Format } from "../services/segment-formats/fmp4.js";
|
|
15
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
16
16
|
// Build a minimal init declaring two tracks, and fragments declaring one/two.
|
|
17
17
|
function box(type, body) {
|
|
18
18
|
const b = Buffer.alloc(8 + body.length);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file An output is named by the format it produces, and nothing may put
|
|
3
|
+
* pieces of another format under that name.
|
|
4
|
+
*
|
|
5
|
+
* Two ways that could still happen once the name became the format: a
|
|
6
|
+
* directory left by an earlier version, whose key named the box a viewer asked
|
|
7
|
+
* for and so cannot say what format is inside; and a hardware encoder failing,
|
|
8
|
+
* after which the proxy encodes with software while outputs named by the failed
|
|
9
|
+
* encoder are still being watched.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { managerWithOwnStore } from "./helpers/manager.js";
|
|
17
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
18
|
+
import { ENCODE_EXIT } from "../services/encode/encode-exit.js";
|
|
19
|
+
import { SourceFile } from "../services/media/SourceFile.js";
|
|
20
|
+
|
|
21
|
+
test("a directory whose key names an asked-for box is not adopted, and one naming a format is", (t) => {
|
|
22
|
+
const { store, root, manager, cleanup } = managerWithOwnStore();
|
|
23
|
+
t.after(cleanup);
|
|
24
|
+
const oldKey = "torrent:abc:fmt=fmp4:grid=even@0:video-only:v=0/enc:1280x720:budget";
|
|
25
|
+
const newKey = outputSpec({ sourceKey: "torrent:abc", transcodeVideo: true, width: 1280, height: 720, audioSeparate: true }).toKey();
|
|
26
|
+
const oldDir = store.directoryFor(oldKey);
|
|
27
|
+
const newDir = store.directoryFor(newKey);
|
|
28
|
+
writeFileSync(path.join(oldDir, "segment-00000.mp4"), Buffer.alloc(10));
|
|
29
|
+
writeFileSync(path.join(newDir, "segment-00000.mp4"), Buffer.alloc(10));
|
|
30
|
+
|
|
31
|
+
const result = manager.lifecycle.adoptSegmentsLeftBehind();
|
|
32
|
+
|
|
33
|
+
assert.equal(result.adopted, 1);
|
|
34
|
+
assert.equal(result.dropped, 1);
|
|
35
|
+
assert.equal(existsSync(oldDir), false, "the old directory goes");
|
|
36
|
+
assert.equal(existsSync(newDir), true, "the named format stays");
|
|
37
|
+
assert.ok(root);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("when a hardware encoder fails, the outputs it named are closed rather than continued by software", async (t) => {
|
|
41
|
+
const { manager, cleanup } = managerWithOwnStore({
|
|
42
|
+
videoEncoder: { kind: "vaapi", name: "h264_vaapi", inputArgs: [] }
|
|
43
|
+
});
|
|
44
|
+
t.after(async () => {
|
|
45
|
+
await manager.lifecycle.disposeAll();
|
|
46
|
+
cleanup();
|
|
47
|
+
});
|
|
48
|
+
manager.encodeRuns.planEncodersNow = () => {};
|
|
49
|
+
manager.encodeRuns.planEncodersSoon = () => {};
|
|
50
|
+
const file = new SourceFile({ sourceKey: "torrent:abc", fileIndex: 0, name: "film.mkv" });
|
|
51
|
+
const record = (id, encoder) => {
|
|
52
|
+
const spec = outputSpec({ sourceKey: "torrent:abc", transcodeVideo: true, width: 1280, height: 720, encoder });
|
|
53
|
+
const output = { id, spec, get outputKey() { return this.spec.toKey(); }, file };
|
|
54
|
+
manager.outputs.set(id, output);
|
|
55
|
+
return output;
|
|
56
|
+
};
|
|
57
|
+
const failing = record("aaaaaaaa00000001", "h264_vaapi");
|
|
58
|
+
const software = record("aaaaaaaa00000002", "libx264");
|
|
59
|
+
const run = { from: 0, to: -1, argsDescribed: "ffmpeg …" };
|
|
60
|
+
manager.encodeOrchestrator.adopt(failing.outputKey, run);
|
|
61
|
+
|
|
62
|
+
manager.encodeRuns.noteRunEnded(failing, run, {
|
|
63
|
+
address: failing.outputKey,
|
|
64
|
+
run,
|
|
65
|
+
ending: ENCODE_EXIT.FAILED,
|
|
66
|
+
from: 0,
|
|
67
|
+
to: -1,
|
|
68
|
+
livedMs: 30_000,
|
|
69
|
+
because: "vaapi failed",
|
|
70
|
+
lastError: "vaapi failed",
|
|
71
|
+
producedCount: 3
|
|
72
|
+
});
|
|
73
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
74
|
+
|
|
75
|
+
assert.equal(manager.videoEncoder.kind, "software");
|
|
76
|
+
assert.equal(manager.outputs.has(failing.id), false, "an output of the failed encoder is closed");
|
|
77
|
+
assert.equal(manager.outputs.has(software.id), true, "an output of another encoder is untouched");
|
|
78
|
+
});
|
|
@@ -6,7 +6,8 @@ import test from "node:test";
|
|
|
6
6
|
import assert from "node:assert/strict";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { collectHealthMetrics } from "../services/transport/health-collector.js";
|
|
10
|
+
import { availableMemoryBytes } from "../services/storage/machine-memory.js";
|
|
10
11
|
|
|
11
12
|
test("free memory is what could be given out, not what is idle this instant", () => {
|
|
12
13
|
const available = availableMemoryBytes();
|
|
@@ -27,7 +28,7 @@ test("free memory is what could be given out, not what is idle this instant", ()
|
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
test("the health report is three bounded numbers", () => {
|
|
30
|
-
const metrics = collectHealthMetrics();
|
|
31
|
+
const metrics = collectHealthMetrics({ availableMemoryBytes });
|
|
31
32
|
assert.ok(metrics.cpuLoad >= 0);
|
|
32
33
|
assert.ok(metrics.memFree > 0 && metrics.memFree <= 1);
|
|
33
34
|
assert.ok(Number.isInteger(metrics.uptime) && metrics.uptime >= 0);
|
|
@@ -11,23 +11,22 @@
|
|
|
11
11
|
|
|
12
12
|
import test from "node:test";
|
|
13
13
|
import assert from "node:assert/strict";
|
|
14
|
-
import { Timeline } from "../services/output/Timeline.js";
|
|
14
|
+
import { Timeline } from "../services/encode/output/Timeline.js";
|
|
15
15
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
import {
|
|
19
|
-
import { fmp4Format } from "../services/segment-formats/fmp4.js";
|
|
20
|
-
import { viewerOf } from "../services/viewer/Viewer.js";
|
|
18
|
+
import { wireOutputs } from "../services/server/wire-outputs.js";
|
|
19
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
21
20
|
|
|
22
|
-
const SESSION_ID = "
|
|
21
|
+
const SESSION_ID = "aaaaaaaabbbbcccc";
|
|
23
22
|
const SEGMENT_SECONDS = 4;
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
|
-
* @returns {Promise<{ manager:
|
|
25
|
+
* @returns {Promise<{ manager: object, dirPath: string }>}
|
|
27
26
|
*/
|
|
28
27
|
async function managerWithSession() {
|
|
29
28
|
const dirPath = await mkdtemp(path.join(os.tmpdir(), "held-request-"));
|
|
30
|
-
const manager =
|
|
29
|
+
const manager = wireOutputs({
|
|
31
30
|
enabled: true,
|
|
32
31
|
ffmpegBin: "ffmpeg",
|
|
33
32
|
localBindHost: "127.0.0.1",
|
|
@@ -47,10 +46,10 @@ async function managerWithSession() {
|
|
|
47
46
|
segmentCount: 200,
|
|
48
47
|
useSyntheticPlaylist: true
|
|
49
48
|
};
|
|
50
|
-
manager.
|
|
49
|
+
manager.outputs.set(SESSION_ID, session);
|
|
51
50
|
// The viewer is at segment #25, and says so themselves.
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
manager.viewers.of(session, "viewer").moveTo(25 * SEGMENT_SECONDS);
|
|
52
|
+
manager.viewers.of(session, "viewer").playing = false;
|
|
54
53
|
return { manager, dirPath };
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -70,9 +69,9 @@ test("the width is the encoder's own look-ahead, in segments", async (t) => {
|
|
|
70
69
|
|
|
71
70
|
const width = Math.ceil(manager.lookaheadSeconds / manager.segmentDurationSec);
|
|
72
71
|
assert.equal(width, 30, "120 s of look-ahead over 4 s segments");
|
|
73
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(25 + width)), true, "the far edge");
|
|
72
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(25 + width)), true, "the far edge");
|
|
74
73
|
assert.equal(
|
|
75
|
-
manager.requestStillWanted(SESSION_ID, segment(25 + width + 1)),
|
|
74
|
+
manager.serving.requestStillWanted(SESSION_ID, segment(25 + width + 1)),
|
|
76
75
|
false,
|
|
77
76
|
"past everything the encoder is allowed to have produced"
|
|
78
77
|
);
|
|
@@ -87,7 +86,7 @@ test("a request the old eight-segment width would have refused is kept", async (
|
|
|
87
86
|
// #34 is nine segments ahead of the viewer — inside a 120 s cushion and
|
|
88
87
|
// outside the eight the width used to be. This is the request a browser
|
|
89
88
|
// holding the whole cushion makes constantly.
|
|
90
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(34)), true);
|
|
89
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(34)), true);
|
|
91
90
|
});
|
|
92
91
|
|
|
93
92
|
test("a segment behind the viewer is still released", async (t) => {
|
|
@@ -96,7 +95,7 @@ test("a segment behind the viewer is still released", async (t) => {
|
|
|
96
95
|
await rm(dirPath, { recursive: true, force: true });
|
|
97
96
|
});
|
|
98
97
|
|
|
99
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(24)), false);
|
|
98
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(24)), false);
|
|
100
99
|
});
|
|
101
100
|
|
|
102
101
|
test("a seek by one viewer does not release the request held for another", async (t) => {
|
|
@@ -108,8 +107,8 @@ test("a seek by one viewer does not release the request held for another", async
|
|
|
108
107
|
// One is at segment #25, the other far ahead at #150 — inside the fixture's
|
|
109
108
|
// own 200-segment timeline, since a position past the end of the grid is
|
|
110
109
|
// clamped to it and would prove nothing about the width.
|
|
111
|
-
manager.requestSeek(SESSION_ID, 100, "behind");
|
|
112
|
-
manager.requestSeek(SESSION_ID, 600, "ahead");
|
|
110
|
+
manager.viewerRequests.requestSeek(SESSION_ID, 100, "behind");
|
|
111
|
+
manager.viewerRequests.requestSeek(SESSION_ID, 600, "ahead");
|
|
113
112
|
|
|
114
113
|
// A SEEK DOES ONE THING: it puts the viewer where they now are. It used to
|
|
115
114
|
// write that position into five places, this session's shared field among
|
|
@@ -120,12 +119,12 @@ test("a seek by one viewer does not release the request held for another", async
|
|
|
120
119
|
assert.equal(manager.viewers.get("ahead").positionSeconds(), 600);
|
|
121
120
|
|
|
122
121
|
assert.equal(
|
|
123
|
-
manager.requestStillWanted(SESSION_ID, segment(26), "behind"),
|
|
122
|
+
manager.serving.requestStillWanted(SESSION_ID, segment(26), "behind"),
|
|
124
123
|
true,
|
|
125
124
|
"the segment the viewer behind is waiting for is still theirs to wait for"
|
|
126
125
|
);
|
|
127
126
|
assert.equal(
|
|
128
|
-
manager.requestStillWanted(SESSION_ID, segment(26), "ahead"),
|
|
127
|
+
manager.serving.requestStillWanted(SESSION_ID, segment(26), "ahead"),
|
|
129
128
|
false,
|
|
130
129
|
"and for the one in front it is a place they have left"
|
|
131
130
|
);
|
|
@@ -140,9 +139,9 @@ test("a viewer's own head moves with their seek", async (t) => {
|
|
|
140
139
|
// Their last request was at #25; they jump to 600 s, which is #150. The
|
|
141
140
|
// segment at the target must be wanted — refusing it there is the freeze of
|
|
142
141
|
// 2026-08-18.
|
|
143
|
-
manager.requestSeek(SESSION_ID, 600, "viewer");
|
|
144
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(150), "viewer"), true);
|
|
145
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(25), "viewer"), false);
|
|
142
|
+
manager.viewerRequests.requestSeek(SESSION_ID, 600, "viewer");
|
|
143
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(150), "viewer"), true);
|
|
144
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(25), "viewer"), false);
|
|
146
145
|
});
|
|
147
146
|
|
|
148
147
|
test("a viewer nobody can name is judged against the one shared position", async (t) => {
|
|
@@ -153,6 +152,6 @@ test("a viewer nobody can name is judged against the one shared position", async
|
|
|
153
152
|
|
|
154
153
|
// A plain HTTP transport builds its own URLs and carries no id. The old
|
|
155
154
|
// behaviour is what remains, which for a single viewer is the same thing.
|
|
156
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(26), ""), true);
|
|
157
|
-
assert.equal(manager.requestStillWanted(SESSION_ID, segment(24), ""), false);
|
|
155
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(26), ""), true);
|
|
156
|
+
assert.equal(manager.serving.requestStillWanted(SESSION_ID, segment(24), ""), false);
|
|
158
157
|
});
|