@torrent-tv/proxy 2.86.0 → 2.87.1
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 +2142 -1915
- package/CLAUDE.md +322 -197
- package/assets/calibration/NOTICE.md +2 -2
- package/bin/cli.js +684 -616
- package/biome.json +409 -233
- package/docs/browser-proxy-contract.md +94 -0
- 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 +9 -6
- package/routes/transcode/session-file/get.js +52 -268
- package/routes/transcode/variant-file/get.js +46 -7
- package/routes/transcode/variant-warm/get.js +19 -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 +307 -29
- package/services/encode/EncodeRun.js +16 -9
- package/services/encode/EncodeRuns.js +1229 -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 +755 -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 +935 -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/http-responses.js +60 -0
- package/services/server/transcode-session-files.js +325 -0
- package/services/server/wire-outputs.js +983 -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 +46 -9
- 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
|
@@ -35,7 +35,7 @@ export class WorkerTorrentPool {
|
|
|
35
35
|
#torrents = new Map();
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* @param {{
|
|
38
|
+
* @param {{ memoryBytes?: number, stateDir?: string }} [options]
|
|
39
39
|
*/
|
|
40
40
|
constructor(options = {}) {
|
|
41
41
|
this.#client = new TorrentWorkerClient(options);
|
|
@@ -83,6 +83,22 @@ export class WorkerTorrentPool {
|
|
|
83
83
|
return this.#client.allowSpillBytes(bytes);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
get memoryClaim() {
|
|
87
|
+
return this.#client.memoryClaim ?? { held: 0, wanted: 0 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
allowMemoryBytes(bytes) {
|
|
91
|
+
return this.#client.allowMemoryBytes(bytes);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get wholeFileBytes() {
|
|
95
|
+
return this.#client.wholeFileBytes ?? 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
allowWholeFileBytes(bytes) {
|
|
99
|
+
return this.#client.allowWholeFileBytes(bytes);
|
|
100
|
+
}
|
|
101
|
+
|
|
86
102
|
/**
|
|
87
103
|
* Bytes every torrent here has moved.
|
|
88
104
|
*
|
|
@@ -132,62 +148,8 @@ export class WorkerTorrentPool {
|
|
|
132
148
|
return answer?.started === true;
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
/**
|
|
136
|
-
* The text subtitle tracks a file carries, read from its own header.
|
|
137
|
-
*
|
|
138
|
-
* @param {object} torrent
|
|
139
|
-
* @param {number} fileIndex
|
|
140
|
-
* @returns {Promise<object[]>}
|
|
141
|
-
*/
|
|
142
|
-
async getSubtitleTracks(torrent, fileIndex) {
|
|
143
|
-
const sourceKey = torrent?.sourceKey;
|
|
144
|
-
if (!sourceKey) {
|
|
145
|
-
return [];
|
|
146
|
-
}
|
|
147
|
-
const answer = await this.#client.getSubtitleTracks({ sourceKey, fileIndex });
|
|
148
|
-
return Array.isArray(answer?.tracks) ? answer.tracks : [];
|
|
149
|
-
}
|
|
150
151
|
|
|
151
|
-
/**
|
|
152
|
-
* What the container itself declares about its subtitle tracks, in its own
|
|
153
|
-
* order and including the picture-based ones — for lining up against
|
|
154
|
-
* ffmpeg's own numbering.
|
|
155
|
-
*
|
|
156
|
-
* @param {object} torrent
|
|
157
|
-
* @param {number} fileIndex
|
|
158
|
-
* @returns {Promise<object[]>}
|
|
159
|
-
*/
|
|
160
|
-
async getDeclaredSubtitleTracks(torrent, fileIndex) {
|
|
161
|
-
const sourceKey = torrent?.sourceKey;
|
|
162
|
-
if (!sourceKey) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
const answer = await this.#client.getSubtitleTracks({ sourceKey, fileIndex });
|
|
166
|
-
return Array.isArray(answer?.declared) ? answer.declared : [];
|
|
167
|
-
}
|
|
168
152
|
|
|
169
|
-
/**
|
|
170
|
-
* Every track one file declares, read from its own header by the container
|
|
171
|
-
* layer.
|
|
172
|
-
*
|
|
173
|
-
* The audio menu is built from ffmpeg's `-i` banner, which carries neither
|
|
174
|
-
* `FlagOriginal`, `FlagCommentary`, `FlagVisualImpaired`, `FlagEnabled` nor
|
|
175
|
-
* `LanguageBCP47` — so without this a director's commentary and the film
|
|
176
|
-
* itself are indistinguishable in it. Also how a soundtrack shipped as its own
|
|
177
|
-
* file is read: a `.mka` is Matroska and the same reader serves it.
|
|
178
|
-
*
|
|
179
|
-
* @param {object} torrent
|
|
180
|
-
* @param {number} fileIndex
|
|
181
|
-
* @returns {Promise<object[]>}
|
|
182
|
-
*/
|
|
183
|
-
async getContainerTracks(torrent, fileIndex) {
|
|
184
|
-
const sourceKey = torrent?.sourceKey;
|
|
185
|
-
if (!sourceKey) {
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
const answer = await this.#client.getContainerTracks({ sourceKey, fileIndex });
|
|
189
|
-
return Array.isArray(answer?.tracks) ? answer.tracks : [];
|
|
190
|
-
}
|
|
191
153
|
|
|
192
154
|
/**
|
|
193
155
|
* Start fetching the region a viewer is about to resume at. Named in seconds
|
|
@@ -198,99 +160,19 @@ export class WorkerTorrentPool {
|
|
|
198
160
|
* @param {number} positionSeconds
|
|
199
161
|
* @returns {Promise<boolean>}
|
|
200
162
|
*/
|
|
201
|
-
async warmResumePosition(torrent, fileIndex, positionSeconds) {
|
|
163
|
+
async warmResumePosition(torrent, fileIndex, positionSeconds, durationSeconds) {
|
|
202
164
|
const sourceKey = torrent?.sourceKey;
|
|
203
165
|
if (!sourceKey) {
|
|
204
166
|
return false;
|
|
205
167
|
}
|
|
206
|
-
const answer = await this.#client.warmResumePosition({ sourceKey, fileIndex, positionSeconds });
|
|
168
|
+
const answer = await this.#client.warmResumePosition({ sourceKey, fileIndex, positionSeconds, durationSeconds });
|
|
207
169
|
return answer?.started === true;
|
|
208
170
|
}
|
|
209
171
|
|
|
210
|
-
/**
|
|
211
|
-
* What one file declares about itself: format, duration, and where its own
|
|
212
|
-
* timeline begins.
|
|
213
|
-
*
|
|
214
|
-
* @param {object} torrent
|
|
215
|
-
* @param {number} fileIndex
|
|
216
|
-
* @returns {Promise<import("../container/Container.js").ContainerMediaInfo | null>}
|
|
217
|
-
*/
|
|
218
|
-
async getContainerMediaInfo(torrent, fileIndex) {
|
|
219
|
-
const sourceKey = torrent?.sourceKey;
|
|
220
|
-
if (!sourceKey) {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
const answer = await this.#client.getContainerMediaInfo({ sourceKey, fileIndex });
|
|
224
|
-
return answer?.info ?? null;
|
|
225
|
-
}
|
|
226
172
|
|
|
227
|
-
/**
|
|
228
|
-
* Where one file's keyframes are, from the container's own table. Null when
|
|
229
|
-
* this file has no readable index — which is a final answer about the file,
|
|
230
|
-
* and the reason a copy of it has to be re-encoded instead.
|
|
231
|
-
*
|
|
232
|
-
* @param {object} torrent
|
|
233
|
-
* @param {number} fileIndex
|
|
234
|
-
* @returns {Promise<{ times: number[], tolerance: number } | null>}
|
|
235
|
-
*/
|
|
236
|
-
async getContainerKeyframes(torrent, fileIndex) {
|
|
237
|
-
const sourceKey = torrent?.sourceKey;
|
|
238
|
-
if (!sourceKey) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
const answer = await this.#client.getContainerKeyframes({ sourceKey, fileIndex });
|
|
242
|
-
return answer?.index ?? null;
|
|
243
|
-
}
|
|
244
173
|
|
|
245
|
-
/**
|
|
246
|
-
* The audio tracks one file declares, in the order ffmpeg numbers them
|
|
247
|
-
* `0:a:N`.
|
|
248
|
-
*
|
|
249
|
-
* @param {object} torrent
|
|
250
|
-
* @param {number} fileIndex
|
|
251
|
-
* @returns {Promise<object[]>}
|
|
252
|
-
*/
|
|
253
|
-
async getDeclaredAudioTracks(torrent, fileIndex) {
|
|
254
|
-
const tracks = await this.getContainerTracks(torrent, fileIndex);
|
|
255
|
-
return tracks
|
|
256
|
-
.filter((track) => track?.type === "audio")
|
|
257
|
-
.sort((left, right) => (left.declaredIndex ?? 0) - (right.declaredIndex ?? 0));
|
|
258
|
-
}
|
|
259
174
|
|
|
260
|
-
/**
|
|
261
|
-
* The video track one file declares, or null where it declares none.
|
|
262
|
-
*
|
|
263
|
-
* One, because ffmpeg's `0:v:0` is what everything downstream is built on and
|
|
264
|
-
* a second video stream is a cover image far more often than a second film.
|
|
265
|
-
*
|
|
266
|
-
* @param {object} torrent
|
|
267
|
-
* @param {number} fileIndex
|
|
268
|
-
* @returns {Promise<object | null>}
|
|
269
|
-
*/
|
|
270
|
-
async getDeclaredVideoTrack(torrent, fileIndex) {
|
|
271
|
-
const tracks = await this.getContainerTracks(torrent, fileIndex);
|
|
272
|
-
return (
|
|
273
|
-
tracks
|
|
274
|
-
.filter((track) => track?.type === "video")
|
|
275
|
-
.sort((left, right) => (left.declaredIndex ?? 0) - (right.declaredIndex ?? 0))[0] ?? null
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
175
|
|
|
279
|
-
/**
|
|
280
|
-
* The cues of one subtitle track that the downloaded clusters already carry.
|
|
281
|
-
*
|
|
282
|
-
* @param {object} torrent
|
|
283
|
-
* @param {number} fileIndex
|
|
284
|
-
* @param {number} trackNumber
|
|
285
|
-
* @returns {Promise<object | null>}
|
|
286
|
-
*/
|
|
287
|
-
async getSubtitleCues(torrent, fileIndex, trackNumber) {
|
|
288
|
-
const sourceKey = torrent?.sourceKey;
|
|
289
|
-
if (!sourceKey) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
return this.#client.getSubtitleCues({ sourceKey, fileIndex, trackNumber });
|
|
293
|
-
}
|
|
294
176
|
|
|
295
177
|
/**
|
|
296
178
|
* Reorder piece selection around a read position.
|
|
@@ -352,6 +234,88 @@ export class WorkerTorrentPool {
|
|
|
352
234
|
* @param {{ headBytes?: number, tailBytes?: number, timeoutMs?: number }} [options]
|
|
353
235
|
* @returns {Promise<unknown>}
|
|
354
236
|
*/
|
|
237
|
+
/**
|
|
238
|
+
* One byte range of one file, as bytes, on THIS thread.
|
|
239
|
+
*
|
|
240
|
+
* What the media layer is built from: a container takes `readRange(start,
|
|
241
|
+
* end)` and nothing else, so with this it can be parsed here instead of in
|
|
242
|
+
* the torrent thread. It used to be parsed there for one stated reason —
|
|
243
|
+
* "the main thread cannot open a read stream on one of its files", which is
|
|
244
|
+
* true of WebTorrent's own API and not of the bytes: the pieces live in
|
|
245
|
+
* shared memory and this read is the same one that serves every segment,
|
|
246
|
+
* so it waits for what has not arrived and steers the swarm toward it.
|
|
247
|
+
*
|
|
248
|
+
* **It COPIES, deliberately.** The zero-copy path — positions across the
|
|
249
|
+
* channel, the piece pinned until the far side says it is done — exists for
|
|
250
|
+
* 10 MB segments on the critical path, where the copy was measured at
|
|
251
|
+
* 18.84 ms. A container header is 64-256 KB and read once per file, about
|
|
252
|
+
* 0.12 ms, against a parse of 0.8 s and a swarm wait of up to a minute. The
|
|
253
|
+
* pinning protocol would buy nothing and is not on this path.
|
|
254
|
+
*
|
|
255
|
+
* @param {object} torrent
|
|
256
|
+
* @param {number} fileIndex
|
|
257
|
+
* @param {number} start - First byte, inclusive.
|
|
258
|
+
* @param {number} end - Last byte, inclusive.
|
|
259
|
+
* @returns {Promise<Buffer | null>} Null when the read failed or was cut
|
|
260
|
+
* short, which a container reads as "this file does not say".
|
|
261
|
+
*/
|
|
262
|
+
/**
|
|
263
|
+
* The byte ranges of one file the torrent holds WHOLE.
|
|
264
|
+
*
|
|
265
|
+
* What the subtitle walk decides from: it may read only what is already
|
|
266
|
+
* downloaded, and a list taken once per pass replaces a question per cluster.
|
|
267
|
+
*
|
|
268
|
+
* @param {object} torrent
|
|
269
|
+
* @param {number} fileIndex
|
|
270
|
+
* @returns {Promise<Array<[number, number]>>}
|
|
271
|
+
*/
|
|
272
|
+
async heldRangesOf(torrent, fileIndex) {
|
|
273
|
+
const sourceKey = torrent?.sourceKey;
|
|
274
|
+
return sourceKey ? this.#client.heldRanges({ sourceKey, fileIndex }) : [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Bytes of a range the torrent already holds, never fetched.
|
|
279
|
+
*
|
|
280
|
+
* Not `readRangeOf`: that one declares demand and steers the swarm, which is
|
|
281
|
+
* right for a viewer waiting on a segment and wrong for a walk that must pull
|
|
282
|
+
* nothing the viewer is not waiting for.
|
|
283
|
+
*
|
|
284
|
+
* @param {object} torrent
|
|
285
|
+
* @param {number} fileIndex
|
|
286
|
+
* @param {number} start
|
|
287
|
+
* @param {number} end - Inclusive.
|
|
288
|
+
* @returns {Promise<Buffer | null>}
|
|
289
|
+
*/
|
|
290
|
+
async readHeldOf(torrent, fileIndex, start, end) {
|
|
291
|
+
const sourceKey = torrent?.sourceKey;
|
|
292
|
+
return sourceKey ? this.#client.readHeld({ sourceKey, fileIndex, start, end }) : null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async readRangeOf(torrent, fileIndex, start, end) {
|
|
296
|
+
const sourceKey = torrent?.sourceKey;
|
|
297
|
+
if (!sourceKey || !(end >= start) || !(start >= 0)) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const stream = this.#client.createReadStream({ sourceKey, fileIndex, start, end });
|
|
301
|
+
const chunks = [];
|
|
302
|
+
let total = 0;
|
|
303
|
+
try {
|
|
304
|
+
const reader = stream.getReader();
|
|
305
|
+
for (;;) {
|
|
306
|
+
const { done, value } = await reader.read();
|
|
307
|
+
if (done) {
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
chunks.push(Buffer.from(value));
|
|
311
|
+
total += value.length;
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
return total > 0 ? Buffer.concat(chunks, total) : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
355
319
|
async prefetchFileEdges(torrent, fileIndex, options = {}) {
|
|
356
320
|
const sourceKey = torrent?.sourceKey;
|
|
357
321
|
if (!sourceKey) {
|
|
@@ -79,37 +79,28 @@ export const Command = {
|
|
|
79
79
|
* the work is under way.
|
|
80
80
|
*/
|
|
81
81
|
FILL_FILE: "fill-file",
|
|
82
|
-
/** The text subtitle tracks a file carries, for the viewer's menu. */
|
|
83
|
-
SUBTITLE_TRACKS: "subtitle-tracks",
|
|
84
82
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* FlagCommentary, FlagVisualImpaired, FlagEnabled and LanguageBCP47 appear
|
|
88
|
-
* nowhere in it. Asked of the picture AND of a soundtrack shipped as its own
|
|
89
|
-
* file beside it, which is the same question about a different file.
|
|
90
|
-
*/
|
|
91
|
-
CONTAINER_TRACKS: "container-tracks",
|
|
92
|
-
/**
|
|
93
|
-
* What a file declares about itself as a whole — format, duration, and where
|
|
94
|
-
* its own timeline begins. Read from the same header, by the same reader, as
|
|
95
|
-
* the track table above; the alternative was a second ffmpeg over the proxy's
|
|
96
|
-
* own HTTP reading the same bytes again.
|
|
83
|
+
* Start fetching the region a viewer is about to resume at, named in seconds
|
|
84
|
+
* and turned into bytes here, where the file's own duration can be read.
|
|
97
85
|
*/
|
|
98
|
-
|
|
86
|
+
WARM_POSITION: "warm-position",
|
|
99
87
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
88
|
+
* The byte ranges of one file the torrent holds WHOLE, so the subtitle walk
|
|
89
|
+
* can decide what it may read without asking the swarm. A list rather than a
|
|
90
|
+
* question per cluster: a pass asks about every cluster of the file, and
|
|
91
|
+
* hundreds of round trips for a walk meant to be free when there is nothing
|
|
92
|
+
* new would be the cost of the split, not of the work.
|
|
104
93
|
*/
|
|
105
|
-
|
|
94
|
+
HELD_RANGES: "held-ranges",
|
|
95
|
+
|
|
106
96
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
97
|
+
* Bytes of a range the torrent already holds, read from the store and never
|
|
98
|
+
* fetched. The ordinary range read declares demand and steers the swarm,
|
|
99
|
+
* which is right for a viewer waiting on a segment and wrong for a walk that
|
|
100
|
+
* must pull nothing.
|
|
109
101
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
SUBTITLE_CUES: "subtitle-cues",
|
|
102
|
+
READ_HELD: "read-held",
|
|
103
|
+
|
|
113
104
|
/** Shut the client down, optionally deleting downloaded data. */
|
|
114
105
|
/**
|
|
115
106
|
* How much disk the spilled pieces may take between them.
|
|
@@ -120,6 +111,8 @@ export const Command = {
|
|
|
120
111
|
* divides its share between the stores it holds.
|
|
121
112
|
*/
|
|
122
113
|
SPILL_ALLOWANCE: "spill-allowance",
|
|
114
|
+
WHOLE_FILES_ALLOWANCE: "whole-files-allowance",
|
|
115
|
+
MEMORY_ALLOWANCE: "memory-allowance",
|
|
123
116
|
DESTROY_ALL: "destroy-all"
|
|
124
117
|
};
|
|
125
118
|
|
|
@@ -155,12 +148,15 @@ export const Event = {
|
|
|
155
148
|
/** A log line, so worker output reaches the same place as everything else. */
|
|
156
149
|
LOG: "log",
|
|
157
150
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
151
|
+
* Pieces of these files have arrived.
|
|
152
|
+
*
|
|
153
|
+
* ANNOUNCED, not asked: the torrent is the only thing that knows a piece just
|
|
154
|
+
* verified, and what anybody does about it — walk a subtitle track's new
|
|
155
|
+
* clusters, say — is no business of this thread. It used to walk them itself,
|
|
156
|
+
* which put the cue reading in the thread that owns the swarm.
|
|
162
157
|
*/
|
|
163
|
-
|
|
158
|
+
PIECES_ARRIVED: "pieces-arrived",
|
|
159
|
+
|
|
164
160
|
/**
|
|
165
161
|
* A file has been downloaded whole and written out as a file.
|
|
166
162
|
*
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Fetching the region under a viewer's resume position.
|
|
3
|
+
*
|
|
4
|
+
* All that is left of a file that used to read containers here as well. The
|
|
5
|
+
* parsing moved to the main thread on 2026-09-15: a container is built from one
|
|
6
|
+
* function, `readRange(start, end)`, the pieces behind it live in shared memory,
|
|
7
|
+
* and the reason it had been put here — "the main thread cannot open a read
|
|
8
|
+
* stream on one of its files" — was true of WebTorrent's own API and not of the
|
|
9
|
+
* bytes. Three commands, a second `ContainerOrchestrator` in this thread, and
|
|
10
|
+
* every answer carried back over the channel went with it.
|
|
11
|
+
*
|
|
12
|
+
* What stays is the part that really is the torrent's: turning a position in
|
|
13
|
+
* seconds into a byte offset and pulling that region off the swarm. How long
|
|
14
|
+
* the file runs is TOLD to it, because that is what the file states about
|
|
15
|
+
* itself and this thread no longer reads such things.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { logger } from "../../../utils/logger.js";
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* How much of the file to pull in under the viewer's resume position.
|
|
31
|
+
*
|
|
32
|
+
* One piece of a video torrent is 4-16 MB and a resume lands anywhere inside
|
|
33
|
+
* one, so anything smaller would still leave the encoder waiting for the piece
|
|
34
|
+
* it starts in. Eight megabytes covers that piece and usually the next.
|
|
35
|
+
*/
|
|
36
|
+
const RESUME_REGION_BYTES = 8 * 1024 * 1024;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Where a position in seconds falls in a file, in bytes.
|
|
40
|
+
*
|
|
41
|
+
* Proportional, and therefore approximate on a variable bitrate — which is what
|
|
42
|
+
* it is for: a prefetch that puts the swarm to work on roughly the right place
|
|
43
|
+
* while the plan and the session are still being built. The encoder's own read
|
|
44
|
+
* asks for the exact bytes a moment later and corrects it.
|
|
45
|
+
*
|
|
46
|
+
* A position past the end is clamped to the end rather than refused: a resume
|
|
47
|
+
* position can outlive the file it was recorded against, and reading the last
|
|
48
|
+
* bytes is harmless where reading past them is an error.
|
|
49
|
+
*
|
|
50
|
+
* @param {number} fileLength
|
|
51
|
+
* @param {number} durationSeconds
|
|
52
|
+
* @param {number} positionSeconds
|
|
53
|
+
* @returns {number}
|
|
54
|
+
*/
|
|
55
|
+
export function resumeByteOffset(fileLength, durationSeconds, positionSeconds) {
|
|
56
|
+
if (!(fileLength > 0) || !(durationSeconds > 0) || !(positionSeconds > 0)) {
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
const within = Math.min(positionSeconds, durationSeconds);
|
|
60
|
+
return Math.min(fileLength - 1, Math.floor((fileLength * within) / durationSeconds));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Start fetching the region a viewer is about to resume at.
|
|
65
|
+
*
|
|
66
|
+
* Where that region IS can only be worked out from two numbers the file itself
|
|
67
|
+
* holds — its length and its duration — so this belongs beside the container
|
|
68
|
+
* read rather than in the route: the route knows a position in seconds and
|
|
69
|
+
* nothing else. The conversion is proportional and therefore approximate on a
|
|
70
|
+
* variable bitrate; it is a prefetch, and the encoder's own read corrects it.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} torrent
|
|
73
|
+
* @param {number} fileIndex
|
|
74
|
+
* @param {string} sourceKey
|
|
75
|
+
* @param {number} positionSeconds
|
|
76
|
+
* @param {{ prefetchEdges?: () => Promise<unknown>, fetchRegion?: (start: number, bytes: number) => Promise<unknown> }} options
|
|
77
|
+
* @returns {Promise<boolean>} Whether a region was asked for.
|
|
78
|
+
*/
|
|
79
|
+
export async function warmResumePosition(torrent, fileIndex, sourceKey, positionSeconds, options = {}) {
|
|
80
|
+
const file = torrent?.files?.[fileIndex];
|
|
81
|
+
if (!file || !(positionSeconds > 0) || typeof options.fetchRegion !== "function") {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// TOLD, not worked out here. How long a file runs is what the file states
|
|
85
|
+
// about itself, and that is read on the main thread now; this one turns a
|
|
86
|
+
// position into a byte offset and fetches the region under it, which is all
|
|
87
|
+
// of the job that is actually the torrent's.
|
|
88
|
+
const duration = Number(options.durationSeconds);
|
|
89
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
90
|
+
logger.info(
|
|
91
|
+
`warm ${sourceKey.slice(0, 8)}: "${String(file.name).slice(0, 40)}" does not declare its ` +
|
|
92
|
+
"duration, so where the viewer's position falls in it cannot be worked out — " +
|
|
93
|
+
"the region under it is left to the encoder's own read"
|
|
94
|
+
);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const at = resumeByteOffset(file.length, duration, positionSeconds);
|
|
98
|
+
logger.info(
|
|
99
|
+
`warm ${sourceKey.slice(0, 8)}: fetching ${(RESUME_REGION_BYTES / (1024 * 1024)).toFixed(0)}MB under the ` +
|
|
100
|
+
`viewer's position ${positionSeconds.toFixed(1)}s of ${duration.toFixed(1)}s, which is ` +
|
|
101
|
+
`${(at / (1024 * 1024)).toFixed(1)}MB into "${String(file.name).slice(0, 40)}"`
|
|
102
|
+
);
|
|
103
|
+
await options.fetchRegion(at, RESUME_REGION_BYTES);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* does not help. The upstream issue about workers (#129) was closed in 0.4.0
|
|
10
10
|
* but only covers use from a worker ALONE, which does work.
|
|
11
11
|
*
|
|
12
|
-
* We need it twice: `webrtc-manager.js` runs the video channel to the browser on
|
|
12
|
+
* We need it twice: `transport/webrtc-manager.js` runs the video channel to the browser on
|
|
13
13
|
* the main thread, and the torrent client — which is now on its own thread —
|
|
14
14
|
* creates peer connections of its own to announce on `wss://` trackers. Before
|
|
15
15
|
* the thread split both lived in one isolate and nothing was wrong; afterwards
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* So the native stack stays where it earns its keep — the main thread, carrying
|
|
19
19
|
* video — and the torrent's trackers get a JavaScript implementation, where the
|
|
20
|
-
* traffic is a handful of signalling messages. `services/torrent
|
|
20
|
+
* traffic is a handful of signalling messages. `services/torrent/worker/worker.js`
|
|
21
21
|
* installs a module resolution hook that points `webrtc-polyfill` here; nothing
|
|
22
22
|
* outside the worker thread is affected, and no dependency is patched (the addon
|
|
23
23
|
* installs with `--ignore-scripts`, so a postinstall patch would never run).
|