@torrent-tv/proxy 2.87.4 → 2.87.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +8 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +94 -94
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +129 -129
- package/routes/api/transcode-sessions/post.js +160 -160
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +332 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -2162
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1083 -1083
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +266 -266
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +212 -212
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1146 -1146
- package/services/server/ViewerRequests.js +522 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +36 -36
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +983 -983
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-pool.js +2 -2
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +1 -1
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +1356 -1356
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +657 -657
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -73
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +176 -176
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +161 -161
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +381 -381
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- package/test/source-registry.test.js +18 -0
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/{services/torrent → utils}/torrent-source-key.js +38 -38
package/test/file-edges.test.js
CHANGED
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file THE TWO ENDS OF A FILE ARE THE FILE'S, NOT A READ'S.
|
|
3
|
-
*
|
|
4
|
-
* A container keeps its directory at one end or the other: `ftyp` and an EBML
|
|
5
|
-
* header at the front, and for an MP4 that was not written for streaming the
|
|
6
|
-
* `moov` at the very back. Nothing can be read of such a file until they have
|
|
7
|
-
* arrived, and they go on being wanted for as long as it is open — a player
|
|
8
|
-
* asks for the file's shape again at every seek.
|
|
9
|
-
*
|
|
10
|
-
* Until 2.83.4 they were wanted by nobody. The prefetch that fetches them is an
|
|
11
|
-
* ordinary read, and a read withdraws what it states the moment it finishes, so
|
|
12
|
-
* the ends of an open film were held by nothing at all once the codec probe was
|
|
13
|
-
* done.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import test from "node:test";
|
|
17
|
-
import assert from "node:assert/strict";
|
|
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
|
-
|
|
22
|
-
/** A torrent of two files, of which the second is the film. */
|
|
23
|
-
function torrentOf({ length = 1_000_000 } = {}) {
|
|
24
|
-
return {
|
|
25
|
-
infoHash: `hash-${Math.random().toString(36).slice(2)}`,
|
|
26
|
-
pieceLength: 1024,
|
|
27
|
-
files: [
|
|
28
|
-
{ offset: 0, length: 4096, name: "notes.nfo" },
|
|
29
|
-
{ offset: 4096, length, name: "film.mkv" }
|
|
30
|
-
],
|
|
31
|
-
_selections: { _items: [] },
|
|
32
|
-
_select() {},
|
|
33
|
-
_deselect() {},
|
|
34
|
-
critical() {}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {object} torrent
|
|
40
|
-
* @returns {object[]}
|
|
41
|
-
*/
|
|
42
|
-
function edges(torrent) {
|
|
43
|
-
return demandFor(torrent)
|
|
44
|
-
.register.windows()
|
|
45
|
-
.filter((one) => String(one.claimant).startsWith("file-edges:"));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
test("one byte is claimed at each end, which is one piece at each end", () => {
|
|
49
|
-
const torrent = torrentOf({ length: 1_000_000 });
|
|
50
|
-
try {
|
|
51
|
-
assert.equal(stateFileEdges(torrent, 1, Urgency.TAIL), true);
|
|
52
|
-
|
|
53
|
-
const stated = edges(torrent);
|
|
54
|
-
assert.equal(stated.length, 2);
|
|
55
|
-
const head = stated.find((one) => String(one.claimant).endsWith(":head"));
|
|
56
|
-
const tail = stated.find((one) => String(one.claimant).endsWith(":tail"));
|
|
57
|
-
// File-relative, like everything else stated about a file: the claim says
|
|
58
|
-
// "the first byte of this file" and "its last", and what a byte costs to
|
|
59
|
-
// fetch — a whole piece — is the swarm's business and nobody else's.
|
|
60
|
-
assert.deepEqual([head.byteStart, head.byteEnd], [0, 0]);
|
|
61
|
-
assert.deepEqual([tail.byteStart, tail.byteEnd], [999_999, 999_999]);
|
|
62
|
-
assert.equal(head.fileIndex, 1);
|
|
63
|
-
assert.equal(tail.fileIndex, 1);
|
|
64
|
-
} finally {
|
|
65
|
-
forgetTorrent(torrent);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("while somebody is waiting for them they are urgent, and afterwards they are kept", () => {
|
|
70
|
-
const torrent = torrentOf();
|
|
71
|
-
try {
|
|
72
|
-
// The playback plan cannot answer until the file has said what is in it,
|
|
73
|
-
// and a person is watching a loading screen for as long as that takes.
|
|
74
|
-
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
75
|
-
assert.deepEqual(
|
|
76
|
-
edges(torrent).map((one) => one.urgency),
|
|
77
|
-
[Urgency.NEAR, Urgency.NEAR]
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
// Once it has answered, nobody is waiting — but a seek will want them
|
|
81
|
-
// again, so they stay stated at the level of something nobody is waiting
|
|
82
|
-
// for. Re-stating replaces; it does not pile a second claim on the first.
|
|
83
|
-
stateFileEdges(torrent, 1, Urgency.TAIL, { lower: true });
|
|
84
|
-
assert.equal(edges(torrent).length, 2);
|
|
85
|
-
assert.deepEqual(
|
|
86
|
-
edges(torrent).map((one) => one.urgency),
|
|
87
|
-
[Urgency.TAIL, Urgency.TAIL]
|
|
88
|
-
);
|
|
89
|
-
} finally {
|
|
90
|
-
forgetTorrent(torrent);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("each file of a torrent has its own ends", () => {
|
|
95
|
-
const torrent = torrentOf();
|
|
96
|
-
try {
|
|
97
|
-
stateFileEdges(torrent, 0, Urgency.TAIL);
|
|
98
|
-
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
99
|
-
assert.equal(edges(torrent).length, 4);
|
|
100
|
-
|
|
101
|
-
withdrawFileEdges(torrent, 0);
|
|
102
|
-
assert.deepEqual(
|
|
103
|
-
edges(torrent).map((one) => one.fileIndex),
|
|
104
|
-
[1, 1]
|
|
105
|
-
);
|
|
106
|
-
} finally {
|
|
107
|
-
forgetTorrent(torrent);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("a file of unknown length has no ends to claim", () => {
|
|
112
|
-
const torrent = torrentOf();
|
|
113
|
-
try {
|
|
114
|
-
assert.equal(stateFileEdges(torrent, 7, Urgency.TAIL), false);
|
|
115
|
-
assert.equal(stateFileEdges({ files: [{ length: 0 }] }, 0, Urgency.TAIL), false);
|
|
116
|
-
assert.equal(edges(torrent).length, 0);
|
|
117
|
-
} finally {
|
|
118
|
-
forgetTorrent(torrent);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("a map with nothing in it takes the ends back too", () => {
|
|
123
|
-
const torrent = torrentOf();
|
|
124
|
-
const applyPriorityMap = TorrentPool.prototype.applyPriorityMap;
|
|
125
|
-
try {
|
|
126
|
-
stateFileEdges(torrent, 1, Urgency.TAIL);
|
|
127
|
-
applyPriorityMap.call(null, torrent, 1, [{ from: 0, to: 600, priority: 100 }], 600);
|
|
128
|
-
assert.equal(edges(torrent).length, 2);
|
|
129
|
-
|
|
130
|
-
// Nobody wants this file any more, and its ends are kept only for as long
|
|
131
|
-
// as it is open.
|
|
132
|
-
applyPriorityMap.call(null, torrent, 1, [], 600);
|
|
133
|
-
assert.equal(edges(torrent).length, 0);
|
|
134
|
-
assert.deepEqual(demandFor(torrent).register.windows(), []);
|
|
135
|
-
} finally {
|
|
136
|
-
forgetTorrent(torrent);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("another file's departure leaves these ends alone", () => {
|
|
141
|
-
const torrent = torrentOf();
|
|
142
|
-
const applyPriorityMap = TorrentPool.prototype.applyPriorityMap;
|
|
143
|
-
try {
|
|
144
|
-
stateFileEdges(torrent, 0, Urgency.TAIL);
|
|
145
|
-
stateFileEdges(torrent, 1, Urgency.TAIL);
|
|
146
|
-
|
|
147
|
-
applyPriorityMap.call(null, torrent, 0, [], 600);
|
|
148
|
-
|
|
149
|
-
assert.deepEqual(
|
|
150
|
-
edges(torrent).map((one) => one.fileIndex),
|
|
151
|
-
[1, 1]
|
|
152
|
-
);
|
|
153
|
-
} finally {
|
|
154
|
-
forgetTorrent(torrent);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("a warm-up cannot take the urgency away from a plan that is waiting", () => {
|
|
159
|
-
// The two callers of the same ends: a playback plan, which somebody is
|
|
160
|
-
// watching a loading screen for, and a warm-up, which by its whole purpose
|
|
161
|
-
// nobody is waiting for. The warm-up fires off the files beside the picture
|
|
162
|
-
// without awaiting them, so it can arrive second — and lowering the plan's
|
|
163
|
-
// level there would put a person behind a film somebody else is watching.
|
|
164
|
-
const torrent = torrentOf();
|
|
165
|
-
try {
|
|
166
|
-
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
167
|
-
stateFileEdges(torrent, 1, Urgency.TAIL);
|
|
168
|
-
|
|
169
|
-
assert.deepEqual(
|
|
170
|
-
edges(torrent).map((one) => one.urgency),
|
|
171
|
-
[Urgency.NEAR, Urgency.NEAR]
|
|
172
|
-
);
|
|
173
|
-
} finally {
|
|
174
|
-
forgetTorrent(torrent);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("the read that is over is what puts them back down", () => {
|
|
179
|
-
const torrent = torrentOf();
|
|
180
|
-
try {
|
|
181
|
-
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
182
|
-
stateFileEdges(torrent, 1, Urgency.TAIL, { lower: true });
|
|
183
|
-
|
|
184
|
-
assert.deepEqual(
|
|
185
|
-
edges(torrent).map((one) => one.urgency),
|
|
186
|
-
[Urgency.TAIL, Urgency.TAIL]
|
|
187
|
-
);
|
|
188
|
-
} finally {
|
|
189
|
-
forgetTorrent(torrent);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file THE TWO ENDS OF A FILE ARE THE FILE'S, NOT A READ'S.
|
|
3
|
+
*
|
|
4
|
+
* A container keeps its directory at one end or the other: `ftyp` and an EBML
|
|
5
|
+
* header at the front, and for an MP4 that was not written for streaming the
|
|
6
|
+
* `moov` at the very back. Nothing can be read of such a file until they have
|
|
7
|
+
* arrived, and they go on being wanted for as long as it is open — a player
|
|
8
|
+
* asks for the file's shape again at every seek.
|
|
9
|
+
*
|
|
10
|
+
* Until 2.83.4 they were wanted by nobody. The prefetch that fetches them is an
|
|
11
|
+
* ordinary read, and a read withdraws what it states the moment it finishes, so
|
|
12
|
+
* the ends of an open film were held by nothing at all once the codec probe was
|
|
13
|
+
* done.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import test from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
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
|
+
|
|
22
|
+
/** A torrent of two files, of which the second is the film. */
|
|
23
|
+
function torrentOf({ length = 1_000_000 } = {}) {
|
|
24
|
+
return {
|
|
25
|
+
infoHash: `hash-${Math.random().toString(36).slice(2)}`,
|
|
26
|
+
pieceLength: 1024,
|
|
27
|
+
files: [
|
|
28
|
+
{ offset: 0, length: 4096, name: "notes.nfo" },
|
|
29
|
+
{ offset: 4096, length, name: "film.mkv" }
|
|
30
|
+
],
|
|
31
|
+
_selections: { _items: [] },
|
|
32
|
+
_select() {},
|
|
33
|
+
_deselect() {},
|
|
34
|
+
critical() {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {object} torrent
|
|
40
|
+
* @returns {object[]}
|
|
41
|
+
*/
|
|
42
|
+
function edges(torrent) {
|
|
43
|
+
return demandFor(torrent)
|
|
44
|
+
.register.windows()
|
|
45
|
+
.filter((one) => String(one.claimant).startsWith("file-edges:"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("one byte is claimed at each end, which is one piece at each end", () => {
|
|
49
|
+
const torrent = torrentOf({ length: 1_000_000 });
|
|
50
|
+
try {
|
|
51
|
+
assert.equal(stateFileEdges(torrent, 1, Urgency.TAIL), true);
|
|
52
|
+
|
|
53
|
+
const stated = edges(torrent);
|
|
54
|
+
assert.equal(stated.length, 2);
|
|
55
|
+
const head = stated.find((one) => String(one.claimant).endsWith(":head"));
|
|
56
|
+
const tail = stated.find((one) => String(one.claimant).endsWith(":tail"));
|
|
57
|
+
// File-relative, like everything else stated about a file: the claim says
|
|
58
|
+
// "the first byte of this file" and "its last", and what a byte costs to
|
|
59
|
+
// fetch — a whole piece — is the swarm's business and nobody else's.
|
|
60
|
+
assert.deepEqual([head.byteStart, head.byteEnd], [0, 0]);
|
|
61
|
+
assert.deepEqual([tail.byteStart, tail.byteEnd], [999_999, 999_999]);
|
|
62
|
+
assert.equal(head.fileIndex, 1);
|
|
63
|
+
assert.equal(tail.fileIndex, 1);
|
|
64
|
+
} finally {
|
|
65
|
+
forgetTorrent(torrent);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("while somebody is waiting for them they are urgent, and afterwards they are kept", () => {
|
|
70
|
+
const torrent = torrentOf();
|
|
71
|
+
try {
|
|
72
|
+
// The playback plan cannot answer until the file has said what is in it,
|
|
73
|
+
// and a person is watching a loading screen for as long as that takes.
|
|
74
|
+
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
75
|
+
assert.deepEqual(
|
|
76
|
+
edges(torrent).map((one) => one.urgency),
|
|
77
|
+
[Urgency.NEAR, Urgency.NEAR]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Once it has answered, nobody is waiting — but a seek will want them
|
|
81
|
+
// again, so they stay stated at the level of something nobody is waiting
|
|
82
|
+
// for. Re-stating replaces; it does not pile a second claim on the first.
|
|
83
|
+
stateFileEdges(torrent, 1, Urgency.TAIL, { lower: true });
|
|
84
|
+
assert.equal(edges(torrent).length, 2);
|
|
85
|
+
assert.deepEqual(
|
|
86
|
+
edges(torrent).map((one) => one.urgency),
|
|
87
|
+
[Urgency.TAIL, Urgency.TAIL]
|
|
88
|
+
);
|
|
89
|
+
} finally {
|
|
90
|
+
forgetTorrent(torrent);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("each file of a torrent has its own ends", () => {
|
|
95
|
+
const torrent = torrentOf();
|
|
96
|
+
try {
|
|
97
|
+
stateFileEdges(torrent, 0, Urgency.TAIL);
|
|
98
|
+
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
99
|
+
assert.equal(edges(torrent).length, 4);
|
|
100
|
+
|
|
101
|
+
withdrawFileEdges(torrent, 0);
|
|
102
|
+
assert.deepEqual(
|
|
103
|
+
edges(torrent).map((one) => one.fileIndex),
|
|
104
|
+
[1, 1]
|
|
105
|
+
);
|
|
106
|
+
} finally {
|
|
107
|
+
forgetTorrent(torrent);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a file of unknown length has no ends to claim", () => {
|
|
112
|
+
const torrent = torrentOf();
|
|
113
|
+
try {
|
|
114
|
+
assert.equal(stateFileEdges(torrent, 7, Urgency.TAIL), false);
|
|
115
|
+
assert.equal(stateFileEdges({ files: [{ length: 0 }] }, 0, Urgency.TAIL), false);
|
|
116
|
+
assert.equal(edges(torrent).length, 0);
|
|
117
|
+
} finally {
|
|
118
|
+
forgetTorrent(torrent);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("a map with nothing in it takes the ends back too", () => {
|
|
123
|
+
const torrent = torrentOf();
|
|
124
|
+
const applyPriorityMap = TorrentPool.prototype.applyPriorityMap;
|
|
125
|
+
try {
|
|
126
|
+
stateFileEdges(torrent, 1, Urgency.TAIL);
|
|
127
|
+
applyPriorityMap.call(null, torrent, 1, [{ from: 0, to: 600, priority: 100 }], 600);
|
|
128
|
+
assert.equal(edges(torrent).length, 2);
|
|
129
|
+
|
|
130
|
+
// Nobody wants this file any more, and its ends are kept only for as long
|
|
131
|
+
// as it is open.
|
|
132
|
+
applyPriorityMap.call(null, torrent, 1, [], 600);
|
|
133
|
+
assert.equal(edges(torrent).length, 0);
|
|
134
|
+
assert.deepEqual(demandFor(torrent).register.windows(), []);
|
|
135
|
+
} finally {
|
|
136
|
+
forgetTorrent(torrent);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("another file's departure leaves these ends alone", () => {
|
|
141
|
+
const torrent = torrentOf();
|
|
142
|
+
const applyPriorityMap = TorrentPool.prototype.applyPriorityMap;
|
|
143
|
+
try {
|
|
144
|
+
stateFileEdges(torrent, 0, Urgency.TAIL);
|
|
145
|
+
stateFileEdges(torrent, 1, Urgency.TAIL);
|
|
146
|
+
|
|
147
|
+
applyPriorityMap.call(null, torrent, 0, [], 600);
|
|
148
|
+
|
|
149
|
+
assert.deepEqual(
|
|
150
|
+
edges(torrent).map((one) => one.fileIndex),
|
|
151
|
+
[1, 1]
|
|
152
|
+
);
|
|
153
|
+
} finally {
|
|
154
|
+
forgetTorrent(torrent);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("a warm-up cannot take the urgency away from a plan that is waiting", () => {
|
|
159
|
+
// The two callers of the same ends: a playback plan, which somebody is
|
|
160
|
+
// watching a loading screen for, and a warm-up, which by its whole purpose
|
|
161
|
+
// nobody is waiting for. The warm-up fires off the files beside the picture
|
|
162
|
+
// without awaiting them, so it can arrive second — and lowering the plan's
|
|
163
|
+
// level there would put a person behind a film somebody else is watching.
|
|
164
|
+
const torrent = torrentOf();
|
|
165
|
+
try {
|
|
166
|
+
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
167
|
+
stateFileEdges(torrent, 1, Urgency.TAIL);
|
|
168
|
+
|
|
169
|
+
assert.deepEqual(
|
|
170
|
+
edges(torrent).map((one) => one.urgency),
|
|
171
|
+
[Urgency.NEAR, Urgency.NEAR]
|
|
172
|
+
);
|
|
173
|
+
} finally {
|
|
174
|
+
forgetTorrent(torrent);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("the read that is over is what puts them back down", () => {
|
|
179
|
+
const torrent = torrentOf();
|
|
180
|
+
try {
|
|
181
|
+
stateFileEdges(torrent, 1, Urgency.NEAR);
|
|
182
|
+
stateFileEdges(torrent, 1, Urgency.TAIL, { lower: true });
|
|
183
|
+
|
|
184
|
+
assert.deepEqual(
|
|
185
|
+
edges(torrent).map((one) => one.urgency),
|
|
186
|
+
[Urgency.TAIL, Urgency.TAIL]
|
|
187
|
+
);
|
|
188
|
+
} finally {
|
|
189
|
+
forgetTorrent(torrent);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Splitting a self-contained fMP4 piece into an init segment and a media
|
|
3
|
-
* segment.
|
|
4
|
-
*
|
|
5
|
-
* The muxer that takes explicit cut times writes every piece whole —
|
|
6
|
-
* `ftyp moov moof mdat … mfra` — but HLS wants one init named by `#EXT-X-MAP`
|
|
7
|
-
* and media segments carrying only fragments. Cutting at the wrong offset does
|
|
8
|
-
* not fail loudly; it produces something that parses as garbage, which is why
|
|
9
|
-
* the boundaries are pinned here rather than trusted.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import test from "node:test";
|
|
13
|
-
import assert from "node:assert/strict";
|
|
14
|
-
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A piece shaped like the muxer's output. Sizes are what matters, not contents.
|
|
18
|
-
*
|
|
19
|
-
* @param {{ withIndex?: boolean }} [options]
|
|
20
|
-
* @returns {{ piece: Buffer, initLength: number, fragmentsLength: number }}
|
|
21
|
-
*/
|
|
22
|
-
function makePiece({ withIndex = true } = {}) {
|
|
23
|
-
const box = (type, bodyLength) => {
|
|
24
|
-
const buffer = Buffer.alloc(8 + bodyLength);
|
|
25
|
-
buffer.writeUInt32BE(8 + bodyLength, 0);
|
|
26
|
-
buffer.write(type, 4, "latin1");
|
|
27
|
-
buffer.fill(0x5a, 8);
|
|
28
|
-
return buffer;
|
|
29
|
-
};
|
|
30
|
-
const ftyp = box("ftyp", 24);
|
|
31
|
-
const moov = box("moov", 400);
|
|
32
|
-
const moof = box("moof", 120);
|
|
33
|
-
const mdat = box("mdat", 900);
|
|
34
|
-
const mfra = box("mfra", 40);
|
|
35
|
-
const parts = withIndex ? [ftyp, moov, moof, mdat, mfra] : [ftyp, moov, moof, mdat];
|
|
36
|
-
return {
|
|
37
|
-
piece: Buffer.concat(parts),
|
|
38
|
-
initLength: ftyp.length + moov.length,
|
|
39
|
-
fragmentsLength: moof.length + mdat.length
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
test("the init is everything before the first fragment", () => {
|
|
44
|
-
const { piece, initLength } = makePiece();
|
|
45
|
-
const init = fmp4Format.extractInit(piece);
|
|
46
|
-
assert.ok(init, "no init found in a piece that has one");
|
|
47
|
-
assert.equal(init.length, initLength, "init must end exactly where the first fragment begins");
|
|
48
|
-
assert.equal(init.toString("latin1", 4, 8), "ftyp", "init must start at the file header");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("the media segment is the fragments alone, index dropped", () => {
|
|
52
|
-
const { piece, initLength, fragmentsLength } = makePiece();
|
|
53
|
-
const media = fmp4Format.stripInit(piece);
|
|
54
|
-
assert.equal(media.toString("latin1", 4, 8), "moof", "a media segment must begin at a fragment");
|
|
55
|
-
assert.equal(
|
|
56
|
-
media.length,
|
|
57
|
-
fragmentsLength,
|
|
58
|
-
"the trailing random-access index must be dropped: its offsets describe a file that no longer exists"
|
|
59
|
-
);
|
|
60
|
-
assert.equal(piece.length, initLength + fragmentsLength + 48, "test fixture sanity");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("a piece without a trailing index keeps everything to the end", () => {
|
|
64
|
-
const { piece, fragmentsLength } = makePiece({ withIndex: false });
|
|
65
|
-
assert.equal(fmp4Format.stripInit(piece).length, fragmentsLength);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("a segment that is already fragments-only is left alone", () => {
|
|
69
|
-
const alreadyMedia = fmp4Format.stripInit(makePiece().piece);
|
|
70
|
-
assert.deepEqual(
|
|
71
|
-
fmp4Format.stripInit(alreadyMedia),
|
|
72
|
-
alreadyMedia,
|
|
73
|
-
"stripping twice must not eat the first fragment"
|
|
74
|
-
);
|
|
75
|
-
assert.equal(
|
|
76
|
-
fmp4Format.extractInit(alreadyMedia),
|
|
77
|
-
null,
|
|
78
|
-
"there is no init to lift out of a media segment, and inventing one would corrupt playback"
|
|
79
|
-
);
|
|
80
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Splitting a self-contained fMP4 piece into an init segment and a media
|
|
3
|
+
* segment.
|
|
4
|
+
*
|
|
5
|
+
* The muxer that takes explicit cut times writes every piece whole —
|
|
6
|
+
* `ftyp moov moof mdat … mfra` — but HLS wants one init named by `#EXT-X-MAP`
|
|
7
|
+
* and media segments carrying only fragments. Cutting at the wrong offset does
|
|
8
|
+
* not fail loudly; it produces something that parses as garbage, which is why
|
|
9
|
+
* the boundaries are pinned here rather than trusted.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { fmp4Format } from "../services/encode/segment-formats/fmp4.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A piece shaped like the muxer's output. Sizes are what matters, not contents.
|
|
18
|
+
*
|
|
19
|
+
* @param {{ withIndex?: boolean }} [options]
|
|
20
|
+
* @returns {{ piece: Buffer, initLength: number, fragmentsLength: number }}
|
|
21
|
+
*/
|
|
22
|
+
function makePiece({ withIndex = true } = {}) {
|
|
23
|
+
const box = (type, bodyLength) => {
|
|
24
|
+
const buffer = Buffer.alloc(8 + bodyLength);
|
|
25
|
+
buffer.writeUInt32BE(8 + bodyLength, 0);
|
|
26
|
+
buffer.write(type, 4, "latin1");
|
|
27
|
+
buffer.fill(0x5a, 8);
|
|
28
|
+
return buffer;
|
|
29
|
+
};
|
|
30
|
+
const ftyp = box("ftyp", 24);
|
|
31
|
+
const moov = box("moov", 400);
|
|
32
|
+
const moof = box("moof", 120);
|
|
33
|
+
const mdat = box("mdat", 900);
|
|
34
|
+
const mfra = box("mfra", 40);
|
|
35
|
+
const parts = withIndex ? [ftyp, moov, moof, mdat, mfra] : [ftyp, moov, moof, mdat];
|
|
36
|
+
return {
|
|
37
|
+
piece: Buffer.concat(parts),
|
|
38
|
+
initLength: ftyp.length + moov.length,
|
|
39
|
+
fragmentsLength: moof.length + mdat.length
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test("the init is everything before the first fragment", () => {
|
|
44
|
+
const { piece, initLength } = makePiece();
|
|
45
|
+
const init = fmp4Format.extractInit(piece);
|
|
46
|
+
assert.ok(init, "no init found in a piece that has one");
|
|
47
|
+
assert.equal(init.length, initLength, "init must end exactly where the first fragment begins");
|
|
48
|
+
assert.equal(init.toString("latin1", 4, 8), "ftyp", "init must start at the file header");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("the media segment is the fragments alone, index dropped", () => {
|
|
52
|
+
const { piece, initLength, fragmentsLength } = makePiece();
|
|
53
|
+
const media = fmp4Format.stripInit(piece);
|
|
54
|
+
assert.equal(media.toString("latin1", 4, 8), "moof", "a media segment must begin at a fragment");
|
|
55
|
+
assert.equal(
|
|
56
|
+
media.length,
|
|
57
|
+
fragmentsLength,
|
|
58
|
+
"the trailing random-access index must be dropped: its offsets describe a file that no longer exists"
|
|
59
|
+
);
|
|
60
|
+
assert.equal(piece.length, initLength + fragmentsLength + 48, "test fixture sanity");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a piece without a trailing index keeps everything to the end", () => {
|
|
64
|
+
const { piece, fragmentsLength } = makePiece({ withIndex: false });
|
|
65
|
+
assert.equal(fmp4Format.stripInit(piece).length, fragmentsLength);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("a segment that is already fragments-only is left alone", () => {
|
|
69
|
+
const alreadyMedia = fmp4Format.stripInit(makePiece().piece);
|
|
70
|
+
assert.deepEqual(
|
|
71
|
+
fmp4Format.stripInit(alreadyMedia),
|
|
72
|
+
alreadyMedia,
|
|
73
|
+
"stripping twice must not eat the first fragment"
|
|
74
|
+
);
|
|
75
|
+
assert.equal(
|
|
76
|
+
fmp4Format.extractInit(alreadyMedia),
|
|
77
|
+
null,
|
|
78
|
+
"there is no init to lift out of a media segment, and inventing one would corrupt playback"
|
|
79
|
+
);
|
|
80
|
+
});
|