@osmo.inc/cli 0.1.15
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/DEPLOY.md +87 -0
- package/README.md +243 -0
- package/RELEASE.md +164 -0
- package/SETUP.md +60 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/bin/osmo.mjs +12 -0
- package/dist/osmo.js +7817 -0
- package/node_modules/@esbuild/darwin-arm64/README.md +3 -0
- package/node_modules/@esbuild/darwin-arm64/bin/esbuild +0 -0
- package/node_modules/@esbuild/darwin-arm64/package.json +20 -0
- package/node_modules/esbuild/LICENSE.md +21 -0
- package/node_modules/esbuild/README.md +3 -0
- package/node_modules/esbuild/bin/esbuild +223 -0
- package/node_modules/esbuild/install.js +289 -0
- package/node_modules/esbuild/lib/main.d.ts +716 -0
- package/node_modules/esbuild/lib/main.js +2532 -0
- package/node_modules/esbuild/package.json +49 -0
- package/node_modules/fflate/LICENSE +21 -0
- package/node_modules/fflate/esm/index.mjs +2710 -0
- package/node_modules/fflate/package.json +128 -0
- package/node_modules/typescript/LICENSE.txt +55 -0
- package/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
- package/node_modules/typescript/lib/lib.d.ts +22 -0
- package/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
- package/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
- package/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
- package/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
- package/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
- package/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
- package/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
- package/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
- package/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
- package/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
- package/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
- package/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
- package/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
- package/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
- package/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
- package/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
- package/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
- package/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
- package/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
- package/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
- package/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
- package/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
- package/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
- package/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
- package/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
- package/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
- package/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
- package/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
- package/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
- package/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
- package/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
- package/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
- package/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
- package/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
- package/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
- package/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
- package/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
- package/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
- package/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
- package/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
- package/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
- package/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
- package/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
- package/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
- package/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
- package/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
- package/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
- package/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
- package/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
- package/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
- package/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
- package/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
- package/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
- package/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
- package/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
- package/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
- package/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
- package/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
- package/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
- package/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
- package/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
- package/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
- package/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
- package/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
- package/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
- package/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
- package/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
- package/node_modules/typescript/lib/lib.es6.d.ts +23 -0
- package/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
- package/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
- package/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
- package/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
- package/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
- package/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
- package/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
- package/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
- package/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
- package/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
- package/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
- package/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
- package/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
- package/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
- package/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
- package/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
- package/node_modules/typescript/lib/typescript.js +382 -0
- package/node_modules/typescript/package.json +57 -0
- package/package.json +54 -0
- package/release.json +136 -0
- package/runtime/apps/native/tools/compile.mjs +217 -0
- package/runtime/packages/scene/scripts/validate-strict.mjs +246 -0
- package/runtime/packages/scene/src/clip.d.mts +14 -0
- package/runtime/packages/scene/src/clip.mjs +15 -0
- package/runtime/packages/scene/src/color-value.d.mts +5 -0
- package/runtime/packages/scene/src/color-value.mjs +19 -0
- package/runtime/packages/scene/src/color.ts +1 -0
- package/runtime/packages/scene/src/effects.ts +213 -0
- package/runtime/packages/scene/src/elements.ts +491 -0
- package/runtime/packages/scene/src/evaluate.ts +919 -0
- package/runtime/packages/scene/src/index.ts +69 -0
- package/runtime/packages/scene/src/jsx-runtime.ts +73 -0
- package/runtime/packages/scene/src/knob-schema.ts +115 -0
- package/runtime/packages/scene/src/knobs.ts +118 -0
- package/runtime/packages/scene/src/mask.d.mts +6 -0
- package/runtime/packages/scene/src/mask.mjs +17 -0
- package/runtime/packages/scene/src/metadata.d.mts +23 -0
- package/runtime/packages/scene/src/metadata.mjs +44 -0
- package/runtime/packages/scene/src/motion-sampling.d.mts +2 -0
- package/runtime/packages/scene/src/motion-sampling.mjs +50 -0
- package/runtime/packages/scene/src/motion.ts +121 -0
- package/runtime/packages/scene/src/shader.ts +57 -0
- package/src/resource-examples/example-image/landscape.svg +7 -0
- package/src/resource-examples/example-image/resource.json +8 -0
- package/src/resource-examples/example-model/gem.glb +0 -0
- package/src/resource-examples/example-model/preview.svg +1 -0
- package/src/resource-examples/example-model/resource.json +7 -0
- package/src/resource-examples/example-scene/resource.json +1 -0
- package/src/resource-examples/example-scene/scene.tsx +8 -0
- package/src/resource-examples/example-shader/effect.wgsl +7 -0
- package/src/resource-examples/example-shader/preview.svg +1 -0
- package/src/resource-examples/example-shader/resource.json +27 -0
- package/src/resource-examples/example-shader/tint.wgsl +7 -0
- package/src/resource-examples/example-video/ambient.mp4 +0 -0
- package/src/resource-examples/example-video/resource.json +9 -0
- package/src/starter/scene.tsx +20 -0
- package/studio/index.html +2 -0
- package/studio/public/check-circle-filled.svg +3 -0
- package/studio/public/comment-cursor/active-outer.svg +3 -0
- package/studio/public/comment-cursor/default.svg +6 -0
- package/studio/public/osmo-studio-logo.svg +12 -0
- package/studio/public/pencil-simple.svg +5 -0
- package/studio/src/active-outer-TVYUEURE.svg +3 -0
- package/studio/src/app.css +4 -0
- package/studio/src/app.js +705 -0
- package/studio/src/default-6CTRH4V5.svg +6 -0
- package/studio/src/engine_bg.wasm +0 -0
- package/studio/src/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 +0 -0
- package/studio/src/inter-cyrillic-wght-normal-JEOLYBOO.woff2 +0 -0
- package/studio/src/inter-greek-ext-wght-normal-EOVOK2B5.woff2 +0 -0
- package/studio/src/inter-greek-wght-normal-IRE366VL.woff2 +0 -0
- package/studio/src/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
- package/studio/src/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
- package/studio/src/inter-vietnamese-wght-normal-CE5GGD3W.woff2 +0 -0
- package/studio/src/osmo-studio-logo-D5R2V77B.svg +12 -0
- package/third-party/cargo/adler2@2.0.1/Cargo.toml +91 -0
- package/third-party/cargo/adler2@2.0.1/LICENSE-0BSD +12 -0
- package/third-party/cargo/adler2@2.0.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/adler2@2.0.1/LICENSE-MIT +23 -0
- package/third-party/cargo/allocator-api2@0.2.21/Cargo.toml +48 -0
- package/third-party/cargo/allocator-api2@0.2.21/LICENSE-APACHE +176 -0
- package/third-party/cargo/allocator-api2@0.2.21/LICENSE-MIT +23 -0
- package/third-party/cargo/arrayref@0.3.9/Cargo.toml +23 -0
- package/third-party/cargo/arrayref@0.3.9/LICENSE +26 -0
- package/third-party/cargo/arrayvec@0.7.8/Cargo.toml +112 -0
- package/third-party/cargo/arrayvec@0.7.8/LICENSE-APACHE +201 -0
- package/third-party/cargo/arrayvec@0.7.8/LICENSE-MIT +25 -0
- package/third-party/cargo/ascii@1.1.0/Cargo.toml +41 -0
- package/third-party/cargo/ascii@1.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/ascii@1.1.0/LICENSE-MIT +22 -0
- package/third-party/cargo/ash@0.38.0+1.3.281/Cargo.toml +66 -0
- package/third-party/cargo/ash@0.38.0+1.3.281/LICENSE-APACHE +67 -0
- package/third-party/cargo/ash@0.38.0+1.3.281/LICENSE-MIT +25 -0
- package/third-party/cargo/autocfg@1.5.1/Cargo.toml +75 -0
- package/third-party/cargo/autocfg@1.5.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/autocfg@1.5.1/LICENSE-MIT +25 -0
- package/third-party/cargo/base64@0.13.1/Cargo.toml +53 -0
- package/third-party/cargo/base64@0.13.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/base64@0.13.1/LICENSE-MIT +21 -0
- package/third-party/cargo/base64@0.22.1/Cargo.toml +85 -0
- package/third-party/cargo/base64@0.22.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/base64@0.22.1/LICENSE-MIT +21 -0
- package/third-party/cargo/bit-set@0.10.0/Cargo.toml +108 -0
- package/third-party/cargo/bit-set@0.10.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/bit-set@0.10.0/LICENSE-MIT +25 -0
- package/third-party/cargo/bit-vec@0.9.1/Cargo.toml +94 -0
- package/third-party/cargo/bit-vec@0.9.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/bit-vec@0.9.1/LICENSE-MIT +25 -0
- package/third-party/cargo/bitflags@2.13.0/Cargo.toml +120 -0
- package/third-party/cargo/bitflags@2.13.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/bitflags@2.13.0/LICENSE-MIT +25 -0
- package/third-party/cargo/block2@0.6.2/Cargo.toml +115 -0
- package/third-party/cargo/block2@0.6.2/MIT-terms.txt +22 -0
- package/third-party/cargo/block2@0.6.2/madsmtm-objc2-b4167b582b2f-LICENSE.md +34 -0
- package/third-party/cargo/bumpalo@3.20.3/Cargo.toml +99 -0
- package/third-party/cargo/bumpalo@3.20.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/bumpalo@3.20.3/LICENSE-MIT +25 -0
- package/third-party/cargo/bytemuck@1.25.1/Cargo.toml +153 -0
- package/third-party/cargo/bytemuck@1.25.1/LICENSE-APACHE +61 -0
- package/third-party/cargo/bytemuck@1.25.1/LICENSE-MIT +9 -0
- package/third-party/cargo/bytemuck@1.25.1/LICENSE-ZLIB +11 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/Cargo.toml +60 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-APACHE +61 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-MIT +9 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-ZLIB +11 -0
- package/third-party/cargo/byteorder-lite@0.1.0/Cargo.toml +57 -0
- package/third-party/cargo/byteorder-lite@0.1.0/LICENSE-MIT +21 -0
- package/third-party/cargo/byteorder-lite@0.1.0/UNLICENSE +24 -0
- package/third-party/cargo/byteorder@1.5.0/COPYING +3 -0
- package/third-party/cargo/byteorder@1.5.0/Cargo.toml +54 -0
- package/third-party/cargo/byteorder@1.5.0/LICENSE-MIT +21 -0
- package/third-party/cargo/byteorder@1.5.0/UNLICENSE +24 -0
- package/third-party/cargo/cfg-if@1.0.4/Cargo.toml +47 -0
- package/third-party/cargo/cfg-if@1.0.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/cfg-if@1.0.4/LICENSE-MIT +25 -0
- package/third-party/cargo/cfg_aliases@0.2.1/Cargo.toml +43 -0
- package/third-party/cargo/cfg_aliases@0.2.1/LICENSE +9 -0
- package/third-party/cargo/chunked_transfer@1.5.0/Cargo.toml +27 -0
- package/third-party/cargo/chunked_transfer@1.5.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/chunked_transfer@1.5.0/LICENSE-MIT +22 -0
- package/third-party/cargo/codespan-reporting@0.13.1/Cargo.toml +115 -0
- package/third-party/cargo/codespan-reporting@0.13.1/LICENSE +201 -0
- package/third-party/cargo/color_quant@1.1.0/Cargo.toml +20 -0
- package/third-party/cargo/color_quant@1.1.0/LICENSE +21 -0
- package/third-party/cargo/console_error_panic_hook@0.1.7/Cargo.toml +27 -0
- package/third-party/cargo/console_error_panic_hook@0.1.7/LICENSE-APACHE +201 -0
- package/third-party/cargo/console_error_panic_hook@0.1.7/LICENSE-MIT +25 -0
- package/third-party/cargo/core_maths@0.1.1/Cargo.toml +37 -0
- package/third-party/cargo/core_maths@0.1.1/LICENSE +21 -0
- package/third-party/cargo/cosmic-text@0.19.0/Cargo.toml +214 -0
- package/third-party/cargo/cosmic-text@0.19.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/cosmic-text@0.19.0/LICENSE-MIT +21 -0
- package/third-party/cargo/crc32fast@1.5.0/Cargo.toml +68 -0
- package/third-party/cargo/crc32fast@1.5.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/crc32fast@1.5.0/LICENSE-MIT +21 -0
- package/third-party/cargo/data-url@0.3.2/Cargo.toml +63 -0
- package/third-party/cargo/data-url@0.3.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/data-url@0.3.2/LICENSE-MIT +25 -0
- package/third-party/cargo/dispatch2@0.3.1/Cargo.toml +122 -0
- package/third-party/cargo/dispatch2@0.3.1/MIT-terms.txt +22 -0
- package/third-party/cargo/dispatch2@0.3.1/madsmtm-objc2-8852b424193c-LICENSE.md +34 -0
- package/third-party/cargo/dlib@0.5.3/Cargo.toml +43 -0
- package/third-party/cargo/dlib@0.5.3/LICENSE.txt +19 -0
- package/third-party/cargo/document-features@0.2.12/Cargo.toml +51 -0
- package/third-party/cargo/document-features@0.2.12/LICENSE-APACHE +73 -0
- package/third-party/cargo/document-features@0.2.12/LICENSE-MIT +19 -0
- package/third-party/cargo/equivalent@1.0.2/Cargo.toml +37 -0
- package/third-party/cargo/equivalent@1.0.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/equivalent@1.0.2/LICENSE-MIT +25 -0
- package/third-party/cargo/errno@0.3.14/Cargo.toml +67 -0
- package/third-party/cargo/errno@0.3.14/LICENSE-APACHE +201 -0
- package/third-party/cargo/errno@0.3.14/LICENSE-MIT +25 -0
- package/third-party/cargo/etagere@0.3.0/Cargo.toml +57 -0
- package/third-party/cargo/etagere@0.3.0/LICENSE-APACHE +13 -0
- package/third-party/cargo/etagere@0.3.0/LICENSE-MIT +20 -0
- package/third-party/cargo/euclid@0.22.14/COPYRIGHT +5 -0
- package/third-party/cargo/euclid@0.22.14/Cargo.toml +66 -0
- package/third-party/cargo/euclid@0.22.14/LICENSE-APACHE +201 -0
- package/third-party/cargo/euclid@0.22.14/LICENSE-MIT +25 -0
- package/third-party/cargo/fastrand@2.5.0/Cargo.toml +89 -0
- package/third-party/cargo/fastrand@2.5.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/fastrand@2.5.0/LICENSE-MIT +23 -0
- package/third-party/cargo/fdeflate@0.3.7/Cargo.toml +55 -0
- package/third-party/cargo/fdeflate@0.3.7/LICENSE-APACHE +177 -0
- package/third-party/cargo/fdeflate@0.3.7/LICENSE-MIT +25 -0
- package/third-party/cargo/flate2@1.1.9/Cargo.toml +274 -0
- package/third-party/cargo/flate2@1.1.9/LICENSE-APACHE +201 -0
- package/third-party/cargo/flate2@1.1.9/LICENSE-MIT +25 -0
- package/third-party/cargo/float-cmp@0.9.0/Cargo.toml +39 -0
- package/third-party/cargo/float-cmp@0.9.0/LICENSE +19 -0
- package/third-party/cargo/float_next_after@1.0.0/Cargo.toml +43 -0
- package/third-party/cargo/float_next_after@1.0.0/LICENSE +23 -0
- package/third-party/cargo/foldhash@0.2.0/Cargo.toml +80 -0
- package/third-party/cargo/foldhash@0.2.0/LICENSE +19 -0
- package/third-party/cargo/font-types@0.11.3/Cargo.toml +55 -0
- package/third-party/cargo/font-types@0.11.3/LICENSE-APACHE +67 -0
- package/third-party/cargo/font-types@0.11.3/LICENSE-MIT +25 -0
- package/third-party/cargo/fontdb@0.23.0/Cargo.toml +83 -0
- package/third-party/cargo/fontdb@0.23.0/LICENSE +21 -0
- package/third-party/cargo/futures-core@0.3.32/Cargo.toml +66 -0
- package/third-party/cargo/futures-core@0.3.32/LICENSE-APACHE +202 -0
- package/third-party/cargo/futures-core@0.3.32/LICENSE-MIT +26 -0
- package/third-party/cargo/futures-task@0.3.32/Cargo.toml +58 -0
- package/third-party/cargo/futures-task@0.3.32/LICENSE-APACHE +202 -0
- package/third-party/cargo/futures-task@0.3.32/LICENSE-MIT +26 -0
- package/third-party/cargo/futures-util@0.3.32/Cargo.toml +181 -0
- package/third-party/cargo/futures-util@0.3.32/LICENSE-APACHE +202 -0
- package/third-party/cargo/futures-util@0.3.32/LICENSE-MIT +26 -0
- package/third-party/cargo/getrandom@0.4.3/Cargo.toml +165 -0
- package/third-party/cargo/getrandom@0.4.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/getrandom@0.4.3/LICENSE-MIT +26 -0
- package/third-party/cargo/gif@0.14.2/Cargo.toml +84 -0
- package/third-party/cargo/gif@0.14.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/gif@0.14.2/LICENSE-MIT +21 -0
- package/third-party/cargo/glam@0.33.2/Cargo.toml +354 -0
- package/third-party/cargo/glam@0.33.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/glam@0.33.2/LICENSE-MIT +23 -0
- package/third-party/cargo/glow@0.17.0/Cargo.toml +122 -0
- package/third-party/cargo/glow@0.17.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/glow@0.17.0/LICENSE-MIT +23 -0
- package/third-party/cargo/glow@0.17.0/LICENSE-ZLIB +15 -0
- package/third-party/cargo/gltf-derive@1.4.1/Cargo.toml +34 -0
- package/third-party/cargo/gltf-derive@1.4.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/gltf-derive@1.4.1/LICENSE-MIT +26 -0
- package/third-party/cargo/gltf-json@1.4.1/Cargo.toml +50 -0
- package/third-party/cargo/gltf-json@1.4.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/gltf-json@1.4.1/LICENSE-MIT +26 -0
- package/third-party/cargo/gltf@1.4.1/Cargo.toml +122 -0
- package/third-party/cargo/gltf@1.4.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/gltf@1.4.1/LICENSE-MIT +26 -0
- package/third-party/cargo/glyphon@0.12.0/Cargo.toml +35 -0
- package/third-party/cargo/glyphon@0.12.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/glyphon@0.12.0/LICENSE-MIT +23 -0
- package/third-party/cargo/glyphon@0.12.0/LICENSE-ZLIB +15 -0
- package/third-party/cargo/gpu-allocator@0.28.0/Cargo.toml +180 -0
- package/third-party/cargo/gpu-allocator@0.28.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/gpu-allocator@0.28.0/LICENSE-MIT +7 -0
- package/third-party/cargo/grid@1.0.1/Cargo.toml +77 -0
- package/third-party/cargo/grid@1.0.1/LICENSE +21 -0
- package/third-party/cargo/half@2.7.1/Cargo.toml +137 -0
- package/third-party/cargo/half@2.7.1/LICENSE-APACHE +176 -0
- package/third-party/cargo/half@2.7.1/LICENSE-MIT +19 -0
- package/third-party/cargo/harfrust@0.5.2/Cargo.toml +115 -0
- package/third-party/cargo/harfrust@0.5.2/harfbuzz-harfrust-efdae3142ab7-LICENSE +22 -0
- package/third-party/cargo/hashbrown@0.16.1/Cargo.toml +180 -0
- package/third-party/cargo/hashbrown@0.16.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/hashbrown@0.16.1/LICENSE-MIT +25 -0
- package/third-party/cargo/hashbrown@0.17.1/Cargo.toml +201 -0
- package/third-party/cargo/hashbrown@0.17.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/hashbrown@0.17.1/LICENSE-MIT +25 -0
- package/third-party/cargo/httpdate@1.0.3/Cargo.toml +35 -0
- package/third-party/cargo/httpdate@1.0.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/httpdate@1.0.3/LICENSE-MIT +19 -0
- package/third-party/cargo/image-webp@0.2.4/Cargo.toml +63 -0
- package/third-party/cargo/image-webp@0.2.4/LICENSE-APACHE +177 -0
- package/third-party/cargo/image-webp@0.2.4/LICENSE-MIT +25 -0
- package/third-party/cargo/image@0.25.10/Cargo.toml +248 -0
- package/third-party/cargo/image@0.25.10/LICENSE-APACHE +177 -0
- package/third-party/cargo/image@0.25.10/LICENSE-MIT +25 -0
- package/third-party/cargo/imagesize@0.14.0/Cargo.toml +104 -0
- package/third-party/cargo/imagesize@0.14.0/LICENSE +21 -0
- package/third-party/cargo/indexmap@2.14.0/Cargo.toml +167 -0
- package/third-party/cargo/indexmap@2.14.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/indexmap@2.14.0/LICENSE-MIT +25 -0
- package/third-party/cargo/inflections@1.1.1/Cargo.toml +21 -0
- package/third-party/cargo/inflections@1.1.1/LICENSE +20 -0
- package/third-party/cargo/itoa@1.0.18/Cargo.toml +66 -0
- package/third-party/cargo/itoa@1.0.18/LICENSE-APACHE +176 -0
- package/third-party/cargo/itoa@1.0.18/LICENSE-MIT +23 -0
- package/third-party/cargo/js-sys@0.3.103/Cargo.toml +98 -0
- package/third-party/cargo/js-sys@0.3.103/LICENSE-APACHE +201 -0
- package/third-party/cargo/js-sys@0.3.103/LICENSE-MIT +25 -0
- package/third-party/cargo/khronos-egl@6.0.0/Cargo.toml +82 -0
- package/third-party/cargo/khronos-egl@6.0.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/khronos-egl@6.0.0/LICENSE-MIT +23 -0
- package/third-party/cargo/kurbo@0.13.1/Cargo.toml +230 -0
- package/third-party/cargo/kurbo@0.13.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/kurbo@0.13.1/LICENSE-MIT +25 -0
- package/third-party/cargo/lazy_static@1.5.0/Cargo.toml +46 -0
- package/third-party/cargo/lazy_static@1.5.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/lazy_static@1.5.0/LICENSE-MIT +25 -0
- package/third-party/cargo/libc@0.2.186/Cargo.toml +229 -0
- package/third-party/cargo/libc@0.2.186/LICENSE-APACHE +176 -0
- package/third-party/cargo/libc@0.2.186/LICENSE-MIT +25 -0
- package/third-party/cargo/libloading@0.8.9/Cargo.toml +90 -0
- package/third-party/cargo/libloading@0.8.9/LICENSE +12 -0
- package/third-party/cargo/libm@0.2.16/Cargo.toml +61 -0
- package/third-party/cargo/libm@0.2.16/LICENSE.txt +258 -0
- package/third-party/cargo/linebender_resource_handle@0.1.1/Cargo.toml +107 -0
- package/third-party/cargo/linebender_resource_handle@0.1.1/LICENSE-APACHE +176 -0
- package/third-party/cargo/linebender_resource_handle@0.1.1/LICENSE-MIT +19 -0
- package/third-party/cargo/litrs@1.0.0/Cargo.toml +63 -0
- package/third-party/cargo/litrs@1.0.0/LICENSE-APACHE +176 -0
- package/third-party/cargo/litrs@1.0.0/LICENSE-MIT +25 -0
- package/third-party/cargo/lock_api@0.4.14/Cargo.toml +68 -0
- package/third-party/cargo/lock_api@0.4.14/LICENSE-APACHE +201 -0
- package/third-party/cargo/lock_api@0.4.14/LICENSE-MIT +25 -0
- package/third-party/cargo/log@0.4.33/Cargo.toml +152 -0
- package/third-party/cargo/log@0.4.33/LICENSE-APACHE +201 -0
- package/third-party/cargo/log@0.4.33/LICENSE-MIT +25 -0
- package/third-party/cargo/lru@0.16.4/Cargo.toml +47 -0
- package/third-party/cargo/lru@0.16.4/LICENSE +21 -0
- package/third-party/cargo/lyon_geom@1.0.19/Cargo.toml +69 -0
- package/third-party/cargo/lyon_geom@1.0.19/nical-lyon-56219c95c658-LICENSE-APACHE +13 -0
- package/third-party/cargo/lyon_geom@1.0.19/nical-lyon-56219c95c658-LICENSE-MIT +20 -0
- package/third-party/cargo/lyon_path@1.0.19/Cargo.toml +58 -0
- package/third-party/cargo/lyon_path@1.0.19/nical-lyon-56219c95c658-LICENSE-APACHE +13 -0
- package/third-party/cargo/lyon_path@1.0.19/nical-lyon-56219c95c658-LICENSE-MIT +20 -0
- package/third-party/cargo/lyon_tessellation@1.0.20/Cargo.toml +71 -0
- package/third-party/cargo/lyon_tessellation@1.0.20/nical-lyon-7fa75ea8eea3-LICENSE-APACHE +13 -0
- package/third-party/cargo/lyon_tessellation@1.0.20/nical-lyon-7fa75ea8eea3-LICENSE-MIT +20 -0
- package/third-party/cargo/memchr@2.8.3/COPYING +3 -0
- package/third-party/cargo/memchr@2.8.3/Cargo.toml +89 -0
- package/third-party/cargo/memchr@2.8.3/LICENSE-MIT +21 -0
- package/third-party/cargo/memchr@2.8.3/UNLICENSE +24 -0
- package/third-party/cargo/memmap2@0.9.11/Cargo.toml +59 -0
- package/third-party/cargo/memmap2@0.9.11/LICENSE-APACHE +201 -0
- package/third-party/cargo/memmap2@0.9.11/LICENSE-MIT +26 -0
- package/third-party/cargo/miniz_oxide@0.8.9/Cargo.toml +91 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE +25 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-APACHE.md +177 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-MIT.md +24 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-ZLIB.md +14 -0
- package/third-party/cargo/moxcms@0.8.1/Cargo.toml +113 -0
- package/third-party/cargo/moxcms@0.8.1/LICENSE-APACHE.md +201 -0
- package/third-party/cargo/moxcms@0.8.1/LICENSE.md +26 -0
- package/third-party/cargo/naga-types@30.0.0/Cargo.toml +94 -0
- package/third-party/cargo/naga-types@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/naga-types@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/naga@30.0.0/Cargo.toml +246 -0
- package/third-party/cargo/naga@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/naga@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/num-traits@0.2.19/Cargo.toml +54 -0
- package/third-party/cargo/num-traits@0.2.19/LICENSE-APACHE +201 -0
- package/third-party/cargo/num-traits@0.2.19/LICENSE-MIT +25 -0
- package/third-party/cargo/objc2-core-foundation@0.3.2/Cargo.toml +221 -0
- package/third-party/cargo/objc2-core-foundation@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-core-foundation@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-core-graphics@0.3.2/Cargo.toml +331 -0
- package/third-party/cargo/objc2-core-graphics@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-core-graphics@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-encode@4.1.0/Cargo.toml +81 -0
- package/third-party/cargo/objc2-encode@4.1.0/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-encode@4.1.0/madsmtm-objc2-8d214f547736-LICENSE.md +34 -0
- package/third-party/cargo/objc2-foundation@0.3.2/Cargo.toml +529 -0
- package/third-party/cargo/objc2-foundation@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-foundation@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-io-surface@0.3.2/Cargo.toml +139 -0
- package/third-party/cargo/objc2-io-surface@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-io-surface@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-metal@0.3.2/Cargo.toml +601 -0
- package/third-party/cargo/objc2-metal@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-metal@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-quartz-core@0.3.2/Cargo.toml +302 -0
- package/third-party/cargo/objc2-quartz-core@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-quartz-core@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2@0.6.4/Cargo.toml +232 -0
- package/third-party/cargo/objc2@0.6.4/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2@0.6.4/madsmtm-objc2-8852b424193c-LICENSE.md +34 -0
- package/third-party/cargo/once_cell@1.21.4/Cargo.toml +119 -0
- package/third-party/cargo/once_cell@1.21.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/once_cell@1.21.4/LICENSE-MIT +23 -0
- package/third-party/cargo/ordered-float@5.3.0/Cargo.toml +162 -0
- package/third-party/cargo/ordered-float@5.3.0/LICENSE-MIT +25 -0
- package/third-party/cargo/parking_lot@0.12.5/Cargo.toml +87 -0
- package/third-party/cargo/parking_lot@0.12.5/LICENSE-APACHE +201 -0
- package/third-party/cargo/parking_lot@0.12.5/LICENSE-MIT +25 -0
- package/third-party/cargo/parking_lot_core@0.9.12/Cargo.toml +72 -0
- package/third-party/cargo/parking_lot_core@0.9.12/LICENSE-APACHE +201 -0
- package/third-party/cargo/parking_lot_core@0.9.12/LICENSE-MIT +25 -0
- package/third-party/cargo/pico-args@0.5.0/Cargo.toml +32 -0
- package/third-party/cargo/pico-args@0.5.0/LICENSE +20 -0
- package/third-party/cargo/pin-project-lite@0.2.17/Cargo.toml +198 -0
- package/third-party/cargo/pin-project-lite@0.2.17/LICENSE-APACHE +177 -0
- package/third-party/cargo/pin-project-lite@0.2.17/LICENSE-MIT +23 -0
- package/third-party/cargo/pkg-config@0.3.33/Cargo.toml +43 -0
- package/third-party/cargo/pkg-config@0.3.33/LICENSE-APACHE +201 -0
- package/third-party/cargo/pkg-config@0.3.33/LICENSE-MIT +25 -0
- package/third-party/cargo/png@0.18.1/Cargo.toml +130 -0
- package/third-party/cargo/png@0.18.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/png@0.18.1/LICENSE-MIT +25 -0
- package/third-party/cargo/pollster@0.4.0/Cargo.toml +53 -0
- package/third-party/cargo/pollster@0.4.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/pollster@0.4.0/LICENSE-MIT +25 -0
- package/third-party/cargo/polycool@0.4.0/Cargo.toml +83 -0
- package/third-party/cargo/polycool@0.4.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/polycool@0.4.0/LICENSE-MIT +25 -0
- package/third-party/cargo/pp-rs@0.2.1/Cargo.toml +23 -0
- package/third-party/cargo/pp-rs@0.2.1/LICENSE +29 -0
- package/third-party/cargo/presser@0.3.1/Cargo.toml +58 -0
- package/third-party/cargo/presser@0.3.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/presser@0.3.1/LICENSE-MIT +25 -0
- package/third-party/cargo/proc-macro2@1.0.106/Cargo.toml +105 -0
- package/third-party/cargo/proc-macro2@1.0.106/LICENSE-APACHE +176 -0
- package/third-party/cargo/proc-macro2@1.0.106/LICENSE-MIT +23 -0
- package/third-party/cargo/profiling@1.0.18/Cargo.toml +96 -0
- package/third-party/cargo/profiling@1.0.18/aclysma-profiling-8271551172eb-LICENSE-APACHE +201 -0
- package/third-party/cargo/profiling@1.0.18/aclysma-profiling-8271551172eb-LICENSE-MIT +21 -0
- package/third-party/cargo/pxfm@0.1.30/Cargo.toml +53 -0
- package/third-party/cargo/pxfm@0.1.30/LICENSE-APACHE.md +201 -0
- package/third-party/cargo/pxfm@0.1.30/LICENSE.md +26 -0
- package/third-party/cargo/quick-error@2.0.1/Cargo.toml +24 -0
- package/third-party/cargo/quick-error@2.0.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/quick-error@2.0.1/LICENSE-MIT +19 -0
- package/third-party/cargo/quote@1.0.46/Cargo.toml +70 -0
- package/third-party/cargo/quote@1.0.46/LICENSE-APACHE +176 -0
- package/third-party/cargo/quote@1.0.46/LICENSE-MIT +23 -0
- package/third-party/cargo/rangemap@1.7.1/Cargo.toml +91 -0
- package/third-party/cargo/rangemap@1.7.1/LICENSE-APACHE +191 -0
- package/third-party/cargo/rangemap@1.7.1/LICENSE-MIT +7 -0
- package/third-party/cargo/raw-window-handle@0.6.2/Cargo.toml +56 -0
- package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-APACHE.md +177 -0
- package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-MIT.md +21 -0
- package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-ZLIB.md +11 -0
- package/third-party/cargo/raw-window-metal@1.1.0/Cargo.toml +111 -0
- package/third-party/cargo/raw-window-metal@1.1.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/raw-window-metal@1.1.0/LICENSE-MIT +17 -0
- package/third-party/cargo/read-fonts@0.37.0/Cargo.toml +95 -0
- package/third-party/cargo/read-fonts@0.37.0/LICENSE-APACHE +67 -0
- package/third-party/cargo/read-fonts@0.37.0/LICENSE-MIT +25 -0
- package/third-party/cargo/read-fonts@0.39.2/Cargo.toml +96 -0
- package/third-party/cargo/read-fonts@0.39.2/LICENSE-APACHE +67 -0
- package/third-party/cargo/read-fonts@0.39.2/LICENSE-MIT +25 -0
- package/third-party/cargo/renderdoc-sys@1.1.0/Cargo.toml +39 -0
- package/third-party/cargo/renderdoc-sys@1.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/renderdoc-sys@1.1.0/LICENSE-MIT +25 -0
- package/third-party/cargo/resvg@0.47.0/Cargo.toml +112 -0
- package/third-party/cargo/resvg@0.47.0/LICENSE-APACHE +176 -0
- package/third-party/cargo/resvg@0.47.0/LICENSE-MIT +25 -0
- package/third-party/cargo/rgb@0.8.53/Cargo.toml +110 -0
- package/third-party/cargo/rgb@0.8.53/LICENSE +21 -0
- package/third-party/cargo/roxmltree@0.21.1/Cargo.toml +75 -0
- package/third-party/cargo/roxmltree@0.21.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/roxmltree@0.21.1/LICENSE-MIT +21 -0
- package/third-party/cargo/rustc-hash@1.1.0/Cargo.toml +25 -0
- package/third-party/cargo/rustc-hash@1.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/rustc-hash@1.1.0/LICENSE-MIT +23 -0
- package/third-party/cargo/rustc-hash@2.1.3/Cargo.toml +50 -0
- package/third-party/cargo/rustc-hash@2.1.3/LICENSE-APACHE +176 -0
- package/third-party/cargo/rustc-hash@2.1.3/LICENSE-MIT +23 -0
- package/third-party/cargo/rustix@1.1.4/COPYRIGHT +29 -0
- package/third-party/cargo/rustix@1.1.4/Cargo.toml +292 -0
- package/third-party/cargo/rustix@1.1.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/rustix@1.1.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/third-party/cargo/rustix@1.1.4/LICENSE-MIT +23 -0
- package/third-party/cargo/rustversion@1.0.23/Cargo.toml +69 -0
- package/third-party/cargo/rustversion@1.0.23/LICENSE-APACHE +176 -0
- package/third-party/cargo/rustversion@1.0.23/LICENSE-MIT +23 -0
- package/third-party/cargo/rustybuzz@0.20.1/Cargo.toml +110 -0
- package/third-party/cargo/rustybuzz@0.20.1/LICENSE +22 -0
- package/third-party/cargo/scopeguard@1.2.0/Cargo.toml +43 -0
- package/third-party/cargo/scopeguard@1.2.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/scopeguard@1.2.0/LICENSE-MIT +25 -0
- package/third-party/cargo/self_cell@1.2.2/Cargo.toml +33 -0
- package/third-party/cargo/self_cell@1.2.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/self_cell@1.2.2/LICENSE-GPLv2 +339 -0
- package/third-party/cargo/self_cell@1.3.0/Cargo.toml +33 -0
- package/third-party/cargo/self_cell@1.3.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/self_cell@1.3.0/LICENSE-GPLv2 +339 -0
- package/third-party/cargo/serde@1.0.228/Cargo.toml +84 -0
- package/third-party/cargo/serde@1.0.228/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde@1.0.228/LICENSE-MIT +23 -0
- package/third-party/cargo/serde_core@1.0.228/Cargo.toml +87 -0
- package/third-party/cargo/serde_core@1.0.228/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde_core@1.0.228/LICENSE-MIT +23 -0
- package/third-party/cargo/serde_derive@1.0.228/Cargo.toml +87 -0
- package/third-party/cargo/serde_derive@1.0.228/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde_derive@1.0.228/LICENSE-MIT +23 -0
- package/third-party/cargo/serde_json@1.0.150/Cargo.toml +162 -0
- package/third-party/cargo/serde_json@1.0.150/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde_json@1.0.150/LICENSE-MIT +23 -0
- package/third-party/cargo/simd-adler32@0.3.10/Cargo.toml +68 -0
- package/third-party/cargo/simd-adler32@0.3.10/LICENSE.md +21 -0
- package/third-party/cargo/simplecss@0.2.2/Cargo.toml +146 -0
- package/third-party/cargo/simplecss@0.2.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/simplecss@0.2.2/LICENSE-MIT +21 -0
- package/third-party/cargo/siphasher@1.0.3/COPYING +7 -0
- package/third-party/cargo/siphasher@1.0.3/Cargo.toml +64 -0
- package/third-party/cargo/skrifa@0.40.0/Cargo.toml +86 -0
- package/third-party/cargo/skrifa@0.40.0/LICENSE-APACHE +67 -0
- package/third-party/cargo/skrifa@0.40.0/LICENSE-MIT +25 -0
- package/third-party/cargo/skrifa@0.42.1/Cargo.toml +86 -0
- package/third-party/cargo/skrifa@0.42.1/LICENSE-APACHE +67 -0
- package/third-party/cargo/skrifa@0.42.1/LICENSE-MIT +25 -0
- package/third-party/cargo/slab@0.4.12/Cargo.toml +67 -0
- package/third-party/cargo/slab@0.4.12/LICENSE +25 -0
- package/third-party/cargo/slotmap@1.1.1/Cargo.toml +83 -0
- package/third-party/cargo/slotmap@1.1.1/LICENSE +19 -0
- package/third-party/cargo/smallvec@1.15.2/Cargo.toml +103 -0
- package/third-party/cargo/smallvec@1.15.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/smallvec@1.15.2/LICENSE-MIT +25 -0
- package/third-party/cargo/smol_str@0.3.6/Cargo.toml +155 -0
- package/third-party/cargo/smol_str@0.3.6/LICENSE-APACHE +201 -0
- package/third-party/cargo/smol_str@0.3.6/LICENSE-MIT +23 -0
- package/third-party/cargo/spirv@0.4.0+sdk-1.4.341.0/Cargo.toml +56 -0
- package/third-party/cargo/spirv@0.4.0+sdk-1.4.341.0/gfx-rs-rspirv-8afc3d0ac8e1-LICENSE +202 -0
- package/third-party/cargo/static_assertions@1.1.0/Cargo.toml +39 -0
- package/third-party/cargo/static_assertions@1.1.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/static_assertions@1.1.0/LICENSE-MIT +21 -0
- package/third-party/cargo/strict-num@0.1.1/Cargo.toml +35 -0
- package/third-party/cargo/strict-num@0.1.1/LICENSE +19 -0
- package/third-party/cargo/svgtypes@0.16.1/Cargo.toml +57 -0
- package/third-party/cargo/svgtypes@0.16.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/svgtypes@0.16.1/LICENSE-MIT +20 -0
- package/third-party/cargo/swash@0.2.9/Cargo.toml +93 -0
- package/third-party/cargo/swash@0.2.9/LICENSE-APACHE +201 -0
- package/third-party/cargo/swash@0.2.9/LICENSE-MIT +25 -0
- package/third-party/cargo/syn@2.0.118/Cargo.toml +272 -0
- package/third-party/cargo/syn@2.0.118/LICENSE-APACHE +176 -0
- package/third-party/cargo/syn@2.0.118/LICENSE-MIT +23 -0
- package/third-party/cargo/sys-locale@0.3.2/Cargo.toml +73 -0
- package/third-party/cargo/sys-locale@0.3.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/sys-locale@0.3.2/LICENSE-MIT +21 -0
- package/third-party/cargo/taffy@0.12.1/Cargo.toml +171 -0
- package/third-party/cargo/taffy@0.12.1/DioxusLabs-taffy-1c9e37a0a1cf-LICENSE.md +25 -0
- package/third-party/cargo/tempfile@3.27.0/Cargo.toml +105 -0
- package/third-party/cargo/tempfile@3.27.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/tempfile@3.27.0/LICENSE-MIT +25 -0
- package/third-party/cargo/termcolor@1.4.1/COPYING +3 -0
- package/third-party/cargo/termcolor@1.4.1/Cargo.toml +40 -0
- package/third-party/cargo/termcolor@1.4.1/LICENSE-MIT +21 -0
- package/third-party/cargo/termcolor@1.4.1/UNLICENSE +24 -0
- package/third-party/cargo/thiserror-impl@2.0.18/Cargo.toml +52 -0
- package/third-party/cargo/thiserror-impl@2.0.18/LICENSE-APACHE +176 -0
- package/third-party/cargo/thiserror-impl@2.0.18/LICENSE-MIT +23 -0
- package/third-party/cargo/thiserror@2.0.18/Cargo.toml +119 -0
- package/third-party/cargo/thiserror@2.0.18/LICENSE-APACHE +176 -0
- package/third-party/cargo/thiserror@2.0.18/LICENSE-MIT +23 -0
- package/third-party/cargo/tiny-skia-path@0.12.0/Cargo.toml +58 -0
- package/third-party/cargo/tiny-skia-path@0.12.0/LICENSE +30 -0
- package/third-party/cargo/tiny-skia@0.12.0/Cargo.toml +116 -0
- package/third-party/cargo/tiny-skia@0.12.0/LICENSE +30 -0
- package/third-party/cargo/tiny_http@0.12.0/Cargo.toml +82 -0
- package/third-party/cargo/tiny_http@0.12.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/tiny_http@0.12.0/LICENSE-MIT +25 -0
- package/third-party/cargo/tinyvec@1.12.0/Cargo.toml +184 -0
- package/third-party/cargo/tinyvec@1.12.0/LICENSE-APACHE.md +202 -0
- package/third-party/cargo/tinyvec@1.12.0/LICENSE-MIT.md +5 -0
- package/third-party/cargo/tinyvec@1.12.0/LICENSE-ZLIB.md +11 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/Cargo.toml +19 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-APACHE.md +202 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-MIT.md +21 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-ZLIB.md +20 -0
- package/third-party/cargo/ttf-parser@0.25.1/Cargo.toml +101 -0
- package/third-party/cargo/ttf-parser@0.25.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/ttf-parser@0.25.1/LICENSE-MIT +20 -0
- package/third-party/cargo/unicode-bidi-mirroring@0.4.0/Cargo.toml +22 -0
- package/third-party/cargo/unicode-bidi-mirroring@0.4.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-bidi-mirroring@0.4.0/LICENSE-MIT +21 -0
- package/third-party/cargo/unicode-bidi@0.3.18/COPYRIGHT +8 -0
- package/third-party/cargo/unicode-bidi@0.3.18/Cargo.toml +90 -0
- package/third-party/cargo/unicode-bidi@0.3.18/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-bidi@0.3.18/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-ccc@0.4.0/Cargo.toml +22 -0
- package/third-party/cargo/unicode-ccc@0.4.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-ccc@0.4.0/LICENSE-MIT +21 -0
- package/third-party/cargo/unicode-ident@1.0.24/Cargo.toml +84 -0
- package/third-party/cargo/unicode-ident@1.0.24/LICENSE-APACHE +176 -0
- package/third-party/cargo/unicode-ident@1.0.24/LICENSE-MIT +23 -0
- package/third-party/cargo/unicode-ident@1.0.24/LICENSE-UNICODE +39 -0
- package/third-party/cargo/unicode-linebreak@0.1.5/Cargo.toml +32 -0
- package/third-party/cargo/unicode-linebreak@0.1.5/LICENSE +201 -0
- package/third-party/cargo/unicode-properties@0.1.4/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-properties@0.1.4/Cargo.toml +65 -0
- package/third-party/cargo/unicode-properties@0.1.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-properties@0.1.4/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-script@0.5.8/Cargo.toml +63 -0
- package/third-party/cargo/unicode-script@0.5.8/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-script@0.5.8/LICENSE-MIT +27 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/Cargo.toml +91 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-vo@0.1.0/Cargo.toml +22 -0
- package/third-party/cargo/unicode-vo@0.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-vo@0.1.0/LICENSE-MIT +21 -0
- package/third-party/cargo/unicode-width@0.2.2/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-width@0.2.2/Cargo.toml +77 -0
- package/third-party/cargo/unicode-width@0.2.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-width@0.2.2/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-xid@0.2.6/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-xid@0.2.6/Cargo.toml +65 -0
- package/third-party/cargo/unicode-xid@0.2.6/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-xid@0.2.6/LICENSE-MIT +25 -0
- package/third-party/cargo/urlencoding@2.1.3/Cargo.toml +41 -0
- package/third-party/cargo/urlencoding@2.1.3/LICENSE +20 -0
- package/third-party/cargo/usvg@0.47.0/Cargo.toml +138 -0
- package/third-party/cargo/usvg@0.47.0/LICENSE-APACHE +176 -0
- package/third-party/cargo/usvg@0.47.0/LICENSE-MIT +25 -0
- package/third-party/cargo/version_check@0.9.5/Cargo.toml +38 -0
- package/third-party/cargo/version_check@0.9.5/LICENSE-APACHE +201 -0
- package/third-party/cargo/version_check@0.9.5/LICENSE-MIT +19 -0
- package/third-party/cargo/wasm-bindgen-futures@0.4.76/Cargo.toml +88 -0
- package/third-party/cargo/wasm-bindgen-futures@0.4.76/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-futures@0.4.76/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/Cargo.toml +83 -0
- package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen-macro@0.2.126/Cargo.toml +73 -0
- package/third-party/cargo/wasm-bindgen-macro@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-macro@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen-shared@0.2.126/Cargo.toml +66 -0
- package/third-party/cargo/wasm-bindgen-shared@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-shared@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen@0.2.126/Cargo.toml +122 -0
- package/third-party/cargo/wasm-bindgen@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wayland-sys@0.31.11/Cargo.toml +81 -0
- package/third-party/cargo/wayland-sys@0.31.11/LICENSE.txt +19 -0
- package/third-party/cargo/web-sys@0.3.103/Cargo.toml +2823 -0
- package/third-party/cargo/web-sys@0.3.103/LICENSE-APACHE +201 -0
- package/third-party/cargo/web-sys@0.3.103/LICENSE-MIT +25 -0
- package/third-party/cargo/weezl@0.1.12/Cargo.toml +120 -0
- package/third-party/cargo/weezl@0.1.12/LICENSE-APACHE +201 -0
- package/third-party/cargo/weezl@0.1.12/LICENSE-MIT +22 -0
- package/third-party/cargo/wgpu-core-deps-apple@30.0.0/Cargo.toml +53 -0
- package/third-party/cargo/wgpu-core-deps-apple@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-core-deps-apple@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-core@30.0.0/Cargo.toml +237 -0
- package/third-party/cargo/wgpu-core@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-core@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-hal@30.0.0/Cargo.toml +541 -0
- package/third-party/cargo/wgpu-hal@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-hal@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-naga-bridge@30.0.0/Cargo.toml +46 -0
- package/third-party/cargo/wgpu-naga-bridge@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-naga-bridge@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-types@30.0.0/Cargo.toml +161 -0
- package/third-party/cargo/wgpu-types@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-types@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu@30.0.0/Cargo.toml +278 -0
- package/third-party/cargo/wgpu@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/xmlwriter@0.1.0/Cargo.toml +23 -0
- package/third-party/cargo/xmlwriter@0.1.0/LICENSE +21 -0
- package/third-party/cargo/yazi@0.2.1/Cargo.toml +45 -0
- package/third-party/cargo/yazi@0.2.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/yazi@0.2.1/LICENSE-MIT +19 -0
- package/third-party/cargo/zeno@0.3.3/Cargo.toml +65 -0
- package/third-party/cargo/zeno@0.3.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/zeno@0.3.3/LICENSE-MIT +25 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/Cargo.toml +209 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-APACHE +202 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-BSD +24 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-MIT +26 -0
- package/third-party/cargo/zerocopy@0.8.54/Cargo.toml +419 -0
- package/third-party/cargo/zerocopy@0.8.54/LICENSE-APACHE +202 -0
- package/third-party/cargo/zerocopy@0.8.54/LICENSE-BSD +24 -0
- package/third-party/cargo/zerocopy@0.8.54/LICENSE-MIT +26 -0
- package/third-party/cargo/zmij@1.0.23/Cargo.toml +90 -0
- package/third-party/cargo/zmij@1.0.23/LICENSE-MIT +23 -0
- package/third-party/cargo/zune-core@0.5.1/Cargo.toml +47 -0
- package/third-party/cargo/zune-core@0.5.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/zune-core@0.5.1/LICENSE-MIT +21 -0
- package/third-party/cargo/zune-core@0.5.1/LICENSE-ZLIB +19 -0
- package/third-party/cargo/zune-jpeg@0.5.15/Cargo.toml +67 -0
- package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-APACHE +201 -0
- package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-MIT +21 -0
- package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-ZLIB +19 -0
- package/third-party/inventory.json +3614 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/esbuild-0.27.4-LICENSE.md +21 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/go-LICENSE +27 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/golang-x-sys-c0bba94af5f8-LICENSE +27 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/package.json +20 -0
- package/third-party/npm/@floating-ui__core@1.7.5/LICENSE +20 -0
- package/third-party/npm/@floating-ui__core@1.7.5/package.json +63 -0
- package/third-party/npm/@floating-ui__dom@1.7.6/LICENSE +20 -0
- package/third-party/npm/@floating-ui__dom@1.7.6/package.json +71 -0
- package/third-party/npm/@floating-ui__react-dom@2.1.8/LICENSE +20 -0
- package/third-party/npm/@floating-ui__react-dom@2.1.8/package.json +76 -0
- package/third-party/npm/@floating-ui__utils@0.2.11/LICENSE +20 -0
- package/third-party/npm/@floating-ui__utils@0.2.11/package.json +70 -0
- package/third-party/npm/@radix-ui__primitive@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__primitive@1.1.7/package.json +79 -0
- package/third-party/npm/@radix-ui__react-arrow@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-arrow@1.1.15/package.json +68 -0
- package/third-party/npm/@radix-ui__react-collection@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-collection@1.1.15/package.json +71 -0
- package/third-party/npm/@radix-ui__react-compose-refs@1.1.5/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-compose-refs@1.1.5/package.json +60 -0
- package/third-party/npm/@radix-ui__react-context-menu@2.3.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-context-menu@2.3.7/package.json +73 -0
- package/third-party/npm/@radix-ui__react-context@1.2.2/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-context@1.2.2/package.json +60 -0
- package/third-party/npm/@radix-ui__react-direction@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-direction@1.1.4/package.json +60 -0
- package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.19/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.19/package.json +74 -0
- package/third-party/npm/@radix-ui__react-focus-guards@1.1.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-focus-guards@1.1.6/package.json +60 -0
- package/third-party/npm/@radix-ui__react-focus-scope@1.1.16/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-focus-scope@1.1.16/package.json +71 -0
- package/third-party/npm/@radix-ui__react-id@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-id@1.1.4/package.json +63 -0
- package/third-party/npm/@radix-ui__react-menu@2.1.24/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-menu@2.1.24/package.json +86 -0
- package/third-party/npm/@radix-ui__react-popper@1.3.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-popper@1.3.7/package.json +78 -0
- package/third-party/npm/@radix-ui__react-portal@1.1.17/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-portal@1.1.17/package.json +69 -0
- package/third-party/npm/@radix-ui__react-presence@1.1.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-presence@1.1.10/package.json +68 -0
- package/third-party/npm/@radix-ui__react-primitive@2.1.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-primitive@2.1.10/package.json +68 -0
- package/third-party/npm/@radix-ui__react-roving-focus@1.1.19/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-roving-focus@1.1.19/package.json +78 -0
- package/third-party/npm/@radix-ui__react-slot@1.3.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-slot@1.3.3/package.json +63 -0
- package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.4/package.json +60 -0
- package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.6/package.json +65 -0
- package/third-party/npm/@radix-ui__react-use-effect-event@0.0.5/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-effect-event@0.0.5/package.json +65 -0
- package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.3/package.json +61 -0
- package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.4/package.json +60 -0
- package/third-party/npm/@radix-ui__react-use-size@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-size@1.1.4/package.json +63 -0
- package/third-party/npm/aria-hidden@1.2.6/LICENSE +21 -0
- package/third-party/npm/aria-hidden@1.2.6/package.json +71 -0
- package/third-party/npm/detect-node-es@1.1.0/LICENSE +21 -0
- package/third-party/npm/detect-node-es@1.1.0/package.json +29 -0
- package/third-party/npm/esbuild@0.27.4/LICENSE.md +21 -0
- package/third-party/npm/esbuild@0.27.4/package.json +49 -0
- package/third-party/npm/fflate@0.8.3/LICENSE +21 -0
- package/third-party/npm/fflate@0.8.3/package.json +128 -0
- package/third-party/npm/get-nonce@1.0.1/LICENSE +21 -0
- package/third-party/npm/get-nonce@1.0.1/package.json +68 -0
- package/third-party/npm/mediabunny@1.52.2/LICENSE +373 -0
- package/third-party/npm/mediabunny@1.52.2/package.json +136 -0
- package/third-party/npm/mediabunny@1.52.2/source/LICENSE +373 -0
- package/third-party/npm/mediabunny@1.52.2/source/package.json +136 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-demuxer.ts +400 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-muxer.ts +124 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-reader.ts +85 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/aes.ts +307 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/codec-data.ts +3178 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/codec.ts +1154 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/conversion.ts +2042 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/custom-coder.ts +212 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/decode.ts +263 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/demuxer.ts +43 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/encode.ts +1412 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-demuxer.ts +813 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-misc.ts +165 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-muxer.ts +350 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-demuxer.ts +995 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-misc.ts +81 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-muxer.ts +1529 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-segmented-input.ts +772 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/id3.ts +972 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/index.ts +319 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/input-format.ts +819 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/input-track.ts +1141 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/input.ts +571 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-boxes.ts +1911 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-demuxer.ts +3953 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-misc.ts +90 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-muxer.ts +1595 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-reader.ts +91 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/logging.ts +120 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/ebml.ts +770 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-demuxer.ts +2598 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-misc.ts +29 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-muxer.ts +1360 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/media-sink.ts +2589 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/media-source.ts +3041 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/metadata.ts +335 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/misc.ts +1336 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-demuxer.ts +469 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-muxer.ts +169 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-reader.ts +61 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-writer.ts +121 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-demuxer.ts +2519 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-misc.ts +32 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-muxer.ts +842 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/muxer.ts +80 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/node.ts +11 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-demuxer.ts +1087 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-misc.ts +116 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-muxer.ts +527 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-reader.ts +93 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/output-format.ts +1425 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/output.ts +940 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/packet.ts +287 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/pcm.ts +112 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/reader.ts +391 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/resample.ts +287 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/sample.ts +3231 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/segmented-input.ts +575 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/source.ts +2588 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/subtitles.ts +131 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/target.ts +871 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/tsconfig.json +16 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/wave/riff-writer.ts +36 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/wave/wave-demuxer.ts +583 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/wave/wave-muxer.ts +375 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/writer.ts +128 -0
- package/third-party/npm/react-dom@19.3.0/LICENSE +21 -0
- package/third-party/npm/react-dom@19.3.0/package.json +117 -0
- package/third-party/npm/react-remove-scroll-bar@2.3.8/package.json +85 -0
- package/third-party/npm/react-remove-scroll-bar@2.3.8/react-remove-scroll-bar-LICENSE +21 -0
- package/third-party/npm/react-remove-scroll@2.7.2/LICENSE +21 -0
- package/third-party/npm/react-remove-scroll@2.7.2/package.json +98 -0
- package/third-party/npm/react-style-singleton@2.2.3/LICENSE +21 -0
- package/third-party/npm/react-style-singleton@2.2.3/package.json +90 -0
- package/third-party/npm/react@19.3.0/LICENSE +21 -0
- package/third-party/npm/react@19.3.0/package.json +51 -0
- package/third-party/npm/scheduler@0.28.0/LICENSE +21 -0
- package/third-party/npm/scheduler@0.28.0/package.json +27 -0
- package/third-party/npm/tslib@2.8.1/LICENSE.txt +12 -0
- package/third-party/npm/tslib@2.8.1/package.json +47 -0
- package/third-party/npm/typescript@5.9.3/LICENSE.txt +55 -0
- package/third-party/npm/typescript@5.9.3/ThirdPartyNoticeText.txt +193 -0
- package/third-party/npm/typescript@5.9.3/package.json +120 -0
- package/third-party/npm/use-callback-ref@1.3.3/LICENSE +21 -0
- package/third-party/npm/use-callback-ref@1.3.3/package.json +85 -0
- package/third-party/npm/use-sidecar@1.1.3/LICENSE +21 -0
- package/third-party/npm/use-sidecar@1.1.3/package.json +93 -0
- package/third-party/packages/render-engine/assets/OFL.txt +93 -0
- package/third-party/packages/studio/public/fonts/OFL.txt +93 -0
- package/third-party/packages/studio/vendor/osmo-icons/LICENSE +21 -0
- package/third-party/packages/studio/vendor/shadcn/LICENSE +21 -0
- package/third-party/repository-notices.md +41 -0
- package/third-party/rust-standard-library/COPYRIGHT-library.html +5556 -0
- package/third-party/rust-standard-library/licenses/Apache-2.0.txt +73 -0
- package/third-party/rust-standard-library/licenses/BSD-2-Clause.txt +9 -0
- package/third-party/rust-standard-library/licenses/CC-BY-SA-4.0.txt +427 -0
- package/third-party/rust-standard-library/licenses/GCC-exception-3.1.txt +30 -0
- package/third-party/rust-standard-library/licenses/GPL-2.0-only.txt +133 -0
- package/third-party/rust-standard-library/licenses/GPL-3.0-or-later.txt +202 -0
- package/third-party/rust-standard-library/licenses/ISC.txt +7 -0
- package/third-party/rust-standard-library/licenses/LLVM-exception.txt +15 -0
- package/third-party/rust-standard-library/licenses/MIT.txt +9 -0
- package/third-party/rust-standard-library/licenses/NCSA.txt +29 -0
- package/third-party/rust-standard-library/licenses/OFL-1.1.txt +43 -0
- package/third-party/rust-standard-library/licenses/Unicode-3.0.txt +39 -0
- package/third-party/rust-standard-library/version.txt +1 -0
- package/vendor/osmo-lite +0 -0
- package/vendor/osmo-scene-0.1.0.tgz +0 -0
- package/vendor/platform.json +1 -0
|
@@ -0,0 +1,2588 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FileHandle } from 'node:fs/promises';
|
|
10
|
+
import {
|
|
11
|
+
assert,
|
|
12
|
+
binarySearchLessOrEqual,
|
|
13
|
+
clamp,
|
|
14
|
+
closedIntervalsOverlap,
|
|
15
|
+
FilePath,
|
|
16
|
+
isNumber,
|
|
17
|
+
isWebKit,
|
|
18
|
+
MaybePromise,
|
|
19
|
+
mergeRequestInit,
|
|
20
|
+
normalizeHeaders,
|
|
21
|
+
polyfillSymbolDispose,
|
|
22
|
+
promiseWithResolvers,
|
|
23
|
+
retriedFetch,
|
|
24
|
+
toDataView,
|
|
25
|
+
toUint8Array,
|
|
26
|
+
wait,
|
|
27
|
+
EventEmitter,
|
|
28
|
+
} from './misc';
|
|
29
|
+
import * as nodeAlias from './node';
|
|
30
|
+
import { InputDisposedError } from './input';
|
|
31
|
+
import { Logging } from './logging';
|
|
32
|
+
|
|
33
|
+
polyfillSymbolDispose();
|
|
34
|
+
|
|
35
|
+
const node = typeof nodeAlias !== 'undefined'
|
|
36
|
+
? nodeAlias // Aliasing it prevents some bundler warnings
|
|
37
|
+
: undefined!;
|
|
38
|
+
|
|
39
|
+
export type ReadResult = {
|
|
40
|
+
bytes: Uint8Array;
|
|
41
|
+
view: DataView;
|
|
42
|
+
/** The offset of the bytes in the file. */
|
|
43
|
+
offset: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const DEFAULT_MIN_READ_POSITION = 0;
|
|
47
|
+
export const DEFAULT_MAX_READ_POSITION = Infinity;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The events emitted by a {@link Source}, with each key being an event name and its value being the event data.
|
|
51
|
+
* @group Input sources
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export type SourceEvents = {
|
|
55
|
+
/** Emitted each time data is retrieved from the source. */
|
|
56
|
+
read: {
|
|
57
|
+
/** The start of the retrieved range, inclusive. */
|
|
58
|
+
start: number;
|
|
59
|
+
/** The end of the retrieved range, exclusive. */
|
|
60
|
+
end: number;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let sourceFinalizationRegistry: FinalizationRegistry<() => unknown> | null = null;
|
|
65
|
+
if (typeof FinalizationRegistry !== 'undefined') {
|
|
66
|
+
sourceFinalizationRegistry = new FinalizationRegistry((cleanup) => {
|
|
67
|
+
cleanup();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The source base class, representing a resource from which bytes can be read.
|
|
73
|
+
* @group Input sources
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
export abstract class Source extends EventEmitter<SourceEvents> {
|
|
77
|
+
/** @internal */
|
|
78
|
+
abstract _getFileSize(): number | null | undefined;
|
|
79
|
+
/** @internal */
|
|
80
|
+
abstract _read(
|
|
81
|
+
start: number,
|
|
82
|
+
end: number,
|
|
83
|
+
minReadPosition: number,
|
|
84
|
+
maxReadPosition: number,
|
|
85
|
+
): MaybePromise<ReadResult | null>;
|
|
86
|
+
/** @internal */
|
|
87
|
+
abstract _dispose(): void;
|
|
88
|
+
/** @internal */
|
|
89
|
+
_disposed = false;
|
|
90
|
+
/** @internal */
|
|
91
|
+
_refCount = 0;
|
|
92
|
+
/**
|
|
93
|
+
* Used internally to mark if a source stems from an HLS reading operation. Used to suppress certain warnings.
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
_usedForHls = false;
|
|
97
|
+
/**
|
|
98
|
+
* FinalizationRegistry for rogue refs to this source that didn't get freed. It lives on the Source itself so that
|
|
99
|
+
* in case the Source transitively points back to itself and forms a cycle (for example through a custom
|
|
100
|
+
* CustomSource callback) that we're not leaking memory.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
_refFinalizationRegistry: FinalizationRegistry<Source> | null = null;
|
|
104
|
+
|
|
105
|
+
/** @internal */
|
|
106
|
+
private _sizePromise: Promise<number | null> | null = null;
|
|
107
|
+
|
|
108
|
+
constructor() {
|
|
109
|
+
super();
|
|
110
|
+
|
|
111
|
+
if (typeof FinalizationRegistry !== 'undefined') {
|
|
112
|
+
this._refFinalizationRegistry = new FinalizationRegistry((source) => {
|
|
113
|
+
source._decrementRefCount();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolves with the total size of the file in bytes. This function is memoized, meaning only the first call
|
|
120
|
+
* will retrieve the size.
|
|
121
|
+
*
|
|
122
|
+
* Returns null if the source is unsized.
|
|
123
|
+
*/
|
|
124
|
+
async getSizeOrNull() {
|
|
125
|
+
if (this._disposed) {
|
|
126
|
+
throw new InputDisposedError();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return this._sizePromise ??= (async () => {
|
|
130
|
+
let size = this._getFileSize();
|
|
131
|
+
if (size !== undefined) {
|
|
132
|
+
return size;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await this._read(0, 1, DEFAULT_MIN_READ_POSITION, DEFAULT_MAX_READ_POSITION);
|
|
136
|
+
size = this._getFileSize();
|
|
137
|
+
assert(size !== undefined);
|
|
138
|
+
|
|
139
|
+
return size;
|
|
140
|
+
})();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolves with the total size of the file in bytes. This function is memoized, meaning only the first call
|
|
145
|
+
* will retrieve the size.
|
|
146
|
+
*
|
|
147
|
+
* Throws an error if the source is unsized.
|
|
148
|
+
*/
|
|
149
|
+
async getSize() {
|
|
150
|
+
if (this._disposed) {
|
|
151
|
+
throw new InputDisposedError();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const result = await this.getSizeOrNull();
|
|
155
|
+
if (result === null) {
|
|
156
|
+
throw new Error('Cannot determine the size of an unsized source.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Returns a new {@link RangedSource} that maps data onto this source using the given offset and length. If a length
|
|
164
|
+
* is not provided, the ranged source spans until the end of this source's data.
|
|
165
|
+
*
|
|
166
|
+
* Useful for reading files that are embedded within larger files.
|
|
167
|
+
*/
|
|
168
|
+
slice(offset: number, length?: number) {
|
|
169
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
170
|
+
throw new TypeError('offset must be a non-negative integer.');
|
|
171
|
+
}
|
|
172
|
+
if (length !== undefined && (!Number.isInteger(length) || length < 0)) {
|
|
173
|
+
throw new TypeError('length, when provided, must be a non-negative integer.');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return new RangedSource(this, offset, length);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Called each time data is retrieved from the source. Will be called with the retrieved range (end exclusive).
|
|
181
|
+
*
|
|
182
|
+
* @deprecated Use `source.on('read', ({ start, end }) => ...)` instead.
|
|
183
|
+
*/
|
|
184
|
+
onread: ((start: number, end: number) => unknown) | null = null;
|
|
185
|
+
|
|
186
|
+
/** @internal */
|
|
187
|
+
_dispatchRead(start: number, end: number) {
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
189
|
+
this.onread?.(start, end);
|
|
190
|
+
this._emit('read', { start, end });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates a new `SourceRef` pointing to this source. You are expected to call `.free()` on said `SourceRef` when
|
|
195
|
+
* you're done with it.
|
|
196
|
+
*/
|
|
197
|
+
ref() {
|
|
198
|
+
return new SourceRef(this);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** @internal */
|
|
202
|
+
_incrementRefCount() {
|
|
203
|
+
this._refCount++;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @internal */
|
|
207
|
+
_decrementRefCount() {
|
|
208
|
+
this._refCount--;
|
|
209
|
+
|
|
210
|
+
if (this._refCount === 0) {
|
|
211
|
+
this._dispose();
|
|
212
|
+
this._disposed = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A reference to a {@link Source}, used to manage a source's lifecycle. Creating a `SourceRef` via {@link Source.ref}
|
|
219
|
+
* increases that source's internal reference count. As long as a source has a non-zero reference count, it is assumed
|
|
220
|
+
* to still be in use. Once all references are freed via {@link SourceRef.free}, the source gets disposed.
|
|
221
|
+
*
|
|
222
|
+
* @group Input sources
|
|
223
|
+
* @public
|
|
224
|
+
*/
|
|
225
|
+
export class SourceRef<S extends Source = Source> implements Disposable {
|
|
226
|
+
/** @internal */
|
|
227
|
+
private _source: S | null;
|
|
228
|
+
/** @internal */
|
|
229
|
+
private _freed = false;
|
|
230
|
+
|
|
231
|
+
/** @internal */
|
|
232
|
+
constructor(source: S) {
|
|
233
|
+
if (source._disposed) {
|
|
234
|
+
throw new Error('Cannot ref a disposed source.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
source._incrementRefCount();
|
|
238
|
+
source._refFinalizationRegistry?.register(this, source, this);
|
|
239
|
+
|
|
240
|
+
this._source = source;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The {@link Source} this ref references. Accessing this field throws an error after having freed the ref. */
|
|
244
|
+
get source() {
|
|
245
|
+
if (!this._source) {
|
|
246
|
+
throw new Error('Can\'t get source; ref has already been freed.');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return this._source;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Whether or not this reference has been freed via {@link SourceRef.free}. */
|
|
253
|
+
get freed() {
|
|
254
|
+
return this._freed;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Frees the ref, decrementing the source's internal reference count. If the source's internal reference count
|
|
259
|
+
* reaches zero, it gets disposed. To catch bugs, this method throws if the ref is already freed.
|
|
260
|
+
*/
|
|
261
|
+
free() {
|
|
262
|
+
if (this._freed) {
|
|
263
|
+
throw new Error('Illegal operation: double free on SourceRef.');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const source = this.source;
|
|
267
|
+
assert(source._refCount > 0);
|
|
268
|
+
|
|
269
|
+
source._decrementRefCount();
|
|
270
|
+
source._refFinalizationRegistry?.unregister(this);
|
|
271
|
+
|
|
272
|
+
this._freed = true;
|
|
273
|
+
this._source = null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Calls {@link SourceRef.free}.
|
|
278
|
+
*/
|
|
279
|
+
[Symbol.dispose]() {
|
|
280
|
+
if (!this.freed) {
|
|
281
|
+
this.free();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* A source which can create new sources from file paths. Required for multi-file inputs such as HLS playlists.
|
|
288
|
+
* @public
|
|
289
|
+
* @group Input sources
|
|
290
|
+
*/
|
|
291
|
+
export abstract class PathedSource extends Source {
|
|
292
|
+
constructor(
|
|
293
|
+
/**
|
|
294
|
+
* The path that points to the root file; the entry file of the media.
|
|
295
|
+
*
|
|
296
|
+
* This path may be modified by the source to indicate a redirect: an updated path to perform new requests
|
|
297
|
+
* relative to.
|
|
298
|
+
*/
|
|
299
|
+
public rootPath: FilePath,
|
|
300
|
+
/** The callback that is called for each requested file; must return a {@link Source} or {@link SourceRef}. */
|
|
301
|
+
public readonly requestHandler: (request: SourceRequest) => MaybePromise<Source | SourceRef>,
|
|
302
|
+
) {
|
|
303
|
+
if (typeof rootPath !== 'string') {
|
|
304
|
+
throw new TypeError('rootPath must be a string.');
|
|
305
|
+
}
|
|
306
|
+
if (typeof requestHandler !== 'function') {
|
|
307
|
+
throw new TypeError('requestHandler must be a function.');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
super();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** @internal */
|
|
314
|
+
_resolveRequest(request: SourceRequest): MaybePromise<SourceRef> {
|
|
315
|
+
const result = this.requestHandler(request);
|
|
316
|
+
|
|
317
|
+
const handle = (result: Source | SourceRef) => {
|
|
318
|
+
if (!(result instanceof Source || result instanceof SourceRef)) {
|
|
319
|
+
throw new TypeError('requestHandler must return or resolve to a Source or SourceRef.');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const ref = result instanceof Source
|
|
323
|
+
? result.ref()
|
|
324
|
+
: result;
|
|
325
|
+
|
|
326
|
+
ref.source._usedForHls ||= this._usedForHls;
|
|
327
|
+
|
|
328
|
+
return ref;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
if (result instanceof Promise) {
|
|
332
|
+
return result.then(handle);
|
|
333
|
+
} else {
|
|
334
|
+
return handle(result);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* A request for a {@link Source} at the given path.
|
|
341
|
+
* @group Input sources
|
|
342
|
+
* @public
|
|
343
|
+
*/
|
|
344
|
+
export type SourceRequest = {
|
|
345
|
+
/** The requested file path. */
|
|
346
|
+
path: FilePath;
|
|
347
|
+
/** Whether the requested file is the root file. */
|
|
348
|
+
isRoot: boolean;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
export const sourceRequestsAreEqual = (a: SourceRequest, b: SourceRequest) => {
|
|
352
|
+
return a.path === b.path;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* A custom multi-file source where each file is uniquely identified by a {@link FilePath} and can be resolved to
|
|
357
|
+
* an arbitrary {@link Source}.
|
|
358
|
+
*
|
|
359
|
+
* @public
|
|
360
|
+
* @group Input sources
|
|
361
|
+
*/
|
|
362
|
+
export class CustomPathedSource extends PathedSource {
|
|
363
|
+
/** @internal */
|
|
364
|
+
_root: SourceRef | null = null;
|
|
365
|
+
/** @internal */
|
|
366
|
+
_rootRequest: Promise<SourceRef> | null = null;
|
|
367
|
+
|
|
368
|
+
/** @internal */
|
|
369
|
+
override _read(
|
|
370
|
+
start: number,
|
|
371
|
+
end: number,
|
|
372
|
+
minReadPosition: number,
|
|
373
|
+
maxReadPosition: number,
|
|
374
|
+
): MaybePromise<ReadResult | null> {
|
|
375
|
+
if (!this._root) {
|
|
376
|
+
if (!this._rootRequest) {
|
|
377
|
+
const result = this._resolveRequest({ path: this.rootPath, isRoot: true });
|
|
378
|
+
|
|
379
|
+
const handle = (result: Source | SourceRef) => {
|
|
380
|
+
const ref = result instanceof Source
|
|
381
|
+
? result.ref()
|
|
382
|
+
: result;
|
|
383
|
+
|
|
384
|
+
this._root = ref;
|
|
385
|
+
this._rootRequest = null;
|
|
386
|
+
|
|
387
|
+
return ref;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
if (result instanceof Promise) {
|
|
391
|
+
this._rootRequest = result.then(handle);
|
|
392
|
+
} else {
|
|
393
|
+
handle(result);
|
|
394
|
+
assert(this._root);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (this._rootRequest) {
|
|
399
|
+
return this._rootRequest.then(ref => ref.source._read(start, end, minReadPosition, maxReadPosition));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return this._root!.source._read(start, end, minReadPosition, maxReadPosition);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** @internal */
|
|
407
|
+
override _getFileSize(): number | null | undefined {
|
|
408
|
+
if (this._root) {
|
|
409
|
+
return this._root.source._getFileSize();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** @internal */
|
|
416
|
+
override _dispose(): void {
|
|
417
|
+
if (this._root) {
|
|
418
|
+
this._root.free();
|
|
419
|
+
} else if (this._rootRequest) {
|
|
420
|
+
void this._rootRequest
|
|
421
|
+
.then(ref => ref.free());
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* A source backed by an ArrayBuffer or ArrayBufferView, with the entire file held in memory.
|
|
428
|
+
* @group Input sources
|
|
429
|
+
* @public
|
|
430
|
+
*/
|
|
431
|
+
export class BufferSource extends Source {
|
|
432
|
+
/** @internal */
|
|
433
|
+
_bytes: Uint8Array;
|
|
434
|
+
/** @internal */
|
|
435
|
+
_view: DataView;
|
|
436
|
+
/** @internal */
|
|
437
|
+
_onreadCalled = false;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Creates a new {@link BufferSource} backed by the specified `ArrayBuffer`, `SharedArrayBuffer`,
|
|
441
|
+
* or `ArrayBufferView`.
|
|
442
|
+
*/
|
|
443
|
+
constructor(buffer: AllowSharedBufferSource) {
|
|
444
|
+
if (
|
|
445
|
+
!(buffer instanceof ArrayBuffer)
|
|
446
|
+
&& !(typeof SharedArrayBuffer !== 'undefined' && buffer instanceof SharedArrayBuffer)
|
|
447
|
+
&& !ArrayBuffer.isView(buffer)
|
|
448
|
+
) {
|
|
449
|
+
throw new TypeError('buffer must be an ArrayBuffer, SharedArrayBuffer, or ArrayBufferView.');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
super();
|
|
453
|
+
|
|
454
|
+
this._bytes = toUint8Array(buffer);
|
|
455
|
+
this._view = toDataView(buffer);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** @internal */
|
|
459
|
+
_getFileSize(): number {
|
|
460
|
+
return this._bytes.byteLength;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** @internal */
|
|
464
|
+
_read(): ReadResult {
|
|
465
|
+
if (!this._onreadCalled) {
|
|
466
|
+
// We just say the first read retrieves all bytes from the source (which, I mean, it does)
|
|
467
|
+
this._dispatchRead(0, this._bytes.byteLength);
|
|
468
|
+
this._onreadCalled = true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
bytes: this._bytes,
|
|
473
|
+
view: this._view,
|
|
474
|
+
offset: 0,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** @internal */
|
|
479
|
+
_dispose() {}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Options for {@link BlobSource}.
|
|
484
|
+
* @group Input sources
|
|
485
|
+
* @public
|
|
486
|
+
*/
|
|
487
|
+
export type BlobSourceOptions = {
|
|
488
|
+
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 8 MiB. */
|
|
489
|
+
maxCacheSize?: number;
|
|
490
|
+
/**
|
|
491
|
+
* Defaults to `true`. When `true`, Mediabunny will acquire a `ReadableStream` reader internally to efficiently read
|
|
492
|
+
* data from the blob. Since this can lead to errors in some (very) rare cases due to browser bugs, you can set this
|
|
493
|
+
* field to `false` to try a slower but more stable reading method.
|
|
494
|
+
*/
|
|
495
|
+
useStreamReader?: boolean;
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* A source backed by a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). Since a
|
|
500
|
+
* [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is also a `Blob`, this is the source to use when
|
|
501
|
+
* reading files off the disk.
|
|
502
|
+
* @group Input sources
|
|
503
|
+
* @public
|
|
504
|
+
*/
|
|
505
|
+
export class BlobSource extends Source {
|
|
506
|
+
/** @internal */
|
|
507
|
+
_blob: Blob;
|
|
508
|
+
/** @internal */
|
|
509
|
+
_options: BlobSourceOptions;
|
|
510
|
+
/** @internal */
|
|
511
|
+
_orchestrator: ReadOrchestrator;
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Creates a new {@link BlobSource} backed by the specified
|
|
515
|
+
* [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
|
|
516
|
+
*/
|
|
517
|
+
constructor(blob: Blob, options: BlobSourceOptions = {}) {
|
|
518
|
+
if (!(blob instanceof Blob)) {
|
|
519
|
+
throw new TypeError('blob must be a Blob.');
|
|
520
|
+
}
|
|
521
|
+
if (!options || typeof options !== 'object') {
|
|
522
|
+
throw new TypeError('options must be an object.');
|
|
523
|
+
}
|
|
524
|
+
if (
|
|
525
|
+
options.maxCacheSize !== undefined
|
|
526
|
+
&& (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)
|
|
527
|
+
) {
|
|
528
|
+
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative number.');
|
|
529
|
+
}
|
|
530
|
+
if (options.useStreamReader !== undefined && typeof options.useStreamReader !== 'boolean') {
|
|
531
|
+
throw new TypeError('options.useStreamReader, when provided, must be a boolean.');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
super();
|
|
535
|
+
|
|
536
|
+
this._blob = blob;
|
|
537
|
+
this._options = options;
|
|
538
|
+
|
|
539
|
+
this._orchestrator = new ReadOrchestrator({
|
|
540
|
+
maxCacheSize: options.maxCacheSize ?? (8 * 2 ** 20 /* 8 MiB */),
|
|
541
|
+
maxWorkerCount: 4,
|
|
542
|
+
runWorker: this._runWorker.bind(this),
|
|
543
|
+
prefetchProfile: PREFETCH_PROFILES.fileSystem,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
this._orchestrator.fileSize = blob.size;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** @internal */
|
|
550
|
+
_getFileSize(): number {
|
|
551
|
+
return this._orchestrator.fileSize!; // Faster than blob.size
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** @internal */
|
|
555
|
+
_read(
|
|
556
|
+
start: number,
|
|
557
|
+
end: number,
|
|
558
|
+
minReadPosition: number,
|
|
559
|
+
maxReadPosition: number,
|
|
560
|
+
): MaybePromise<ReadResult | null> {
|
|
561
|
+
return this._orchestrator.read(start, end, minReadPosition, maxReadPosition);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** @internal */
|
|
565
|
+
_readers = new WeakMap<ReadWorker, ReadableStreamDefaultReader<Uint8Array> | null>();
|
|
566
|
+
|
|
567
|
+
/** @internal */
|
|
568
|
+
private async _runWorker(worker: ReadWorker) {
|
|
569
|
+
assert(worker.strictTarget);
|
|
570
|
+
|
|
571
|
+
let reader = this._readers.get(worker);
|
|
572
|
+
if (reader === undefined) {
|
|
573
|
+
// https://github.com/Vanilagy/mediabunny/issues/184
|
|
574
|
+
// WebKit has critical bugs with blob.stream():
|
|
575
|
+
// - WebKitBlobResource error 1 when streaming large files
|
|
576
|
+
// - Memory buildup and reload loops on iOS (network process crashes)
|
|
577
|
+
// - ReadableStream stalls under backpressure (especially video)
|
|
578
|
+
// Affects Safari and all iOS browsers (Chrome, Firefox, etc.).
|
|
579
|
+
// Use arrayBuffer() fallback for WebKit browsers.
|
|
580
|
+
if ('stream' in this._blob && !isWebKit() && this._options.useStreamReader !== false) {
|
|
581
|
+
// Get a reader of the blob starting at the required offset, and then keep it around
|
|
582
|
+
const slice = this._blob.slice(worker.currentPos);
|
|
583
|
+
reader = slice.stream().getReader();
|
|
584
|
+
} else {
|
|
585
|
+
// We'll need to use more primitive ways
|
|
586
|
+
reader = null;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
this._readers.set(worker, reader);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
while (worker.currentPos < worker.targetPos && !worker.aborted) {
|
|
593
|
+
if (reader) {
|
|
594
|
+
const { done, value } = await reader.read();
|
|
595
|
+
if (done) {
|
|
596
|
+
this._orchestrator.onWorkerFinished(worker);
|
|
597
|
+
throw new Error('Blob reader stopped unexpectedly before all requested data was read.');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (worker.aborted) {
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
this._dispatchRead(worker.currentPos, worker.currentPos + value.length);
|
|
605
|
+
this._orchestrator.supplyWorkerData(worker, value);
|
|
606
|
+
} else {
|
|
607
|
+
const data = await this._blob.slice(worker.currentPos, worker.targetPos).arrayBuffer();
|
|
608
|
+
|
|
609
|
+
if (worker.aborted) {
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this._dispatchRead(worker.currentPos, worker.currentPos + data.byteLength);
|
|
614
|
+
this._orchestrator.supplyWorkerData(worker, new Uint8Array(data));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
this._orchestrator.signalWorkerStoppedRunning(worker);
|
|
619
|
+
|
|
620
|
+
if (worker.aborted) {
|
|
621
|
+
// MDN: "Calling this method signals a loss of interest in the stream by a consumer."
|
|
622
|
+
await reader?.cancel();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** @internal */
|
|
627
|
+
_dispose() {
|
|
628
|
+
this._orchestrator.dispose();
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const URL_SOURCE_MIN_LOAD_AMOUNT = 0.5 * 2 ** 20; // 0.5 MiB
|
|
633
|
+
const DEFAULT_RETRY_DELAY
|
|
634
|
+
= ((previousAttempts, error, src) => {
|
|
635
|
+
// Check if this could be a CORS error. If so, we cannot recover from it and
|
|
636
|
+
// should not attempt to retry.
|
|
637
|
+
// CORS errors are intentionally not opaque, so we need to rely on heuristics.
|
|
638
|
+
const couldBeCorsError = error instanceof Error
|
|
639
|
+
&& (
|
|
640
|
+
error.message.includes('Failed to fetch') // Chrome
|
|
641
|
+
|| error.message.includes('Load failed') // Safari
|
|
642
|
+
|| error.message.includes('NetworkError when attempting to fetch resource') // Firefox
|
|
643
|
+
)
|
|
644
|
+
&& typeof window !== 'undefined'; // CORS only happens in browser environments
|
|
645
|
+
|
|
646
|
+
if (couldBeCorsError) {
|
|
647
|
+
let originOfSrc: string | null = null;
|
|
648
|
+
// Checking if the origin is different, because only then a CORS error could originate
|
|
649
|
+
try {
|
|
650
|
+
if (typeof window !== 'undefined' && typeof window.location !== 'undefined') {
|
|
651
|
+
originOfSrc = new URL(src instanceof Request ? src.url : src, window.location.href).origin;
|
|
652
|
+
}
|
|
653
|
+
} catch {
|
|
654
|
+
// URL parse failed
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// If user is offline, it is probably not a CORS error.
|
|
658
|
+
const isOnline
|
|
659
|
+
= typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean' ? navigator.onLine : true;
|
|
660
|
+
|
|
661
|
+
if (isOnline && originOfSrc !== null && originOfSrc !== window.location.origin) {
|
|
662
|
+
Logging._warn(
|
|
663
|
+
`Request will not be retried because a CORS error was suspected due to different origins. You can`
|
|
664
|
+
+ ` modify this behavior by providing your own function for the 'getRetryDelay' option.`,
|
|
665
|
+
);
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return Math.min(2 ** (previousAttempts - 2), 16);
|
|
671
|
+
}) satisfies UrlSourceOptions['getRetryDelay'];
|
|
672
|
+
|
|
673
|
+
const warnedOrigins = new Set<string>();
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Options for {@link UrlSource}.
|
|
677
|
+
* @group Input sources
|
|
678
|
+
* @public
|
|
679
|
+
*/
|
|
680
|
+
export type UrlSourceOptions = {
|
|
681
|
+
/**
|
|
682
|
+
* The [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) used by the Fetch API. Can be
|
|
683
|
+
* used to further control the requests, such as setting custom headers.
|
|
684
|
+
*
|
|
685
|
+
* The `signal` field is not available, as Mediabunny controls request cancellation internally. If you want to
|
|
686
|
+
* cancel ongoing requests, use {@link Input.dispose}.
|
|
687
|
+
*/
|
|
688
|
+
requestInit?: Omit<RequestInit, 'signal'>;
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* A function that returns the delay (in seconds) before retrying a failed request. The function is called
|
|
692
|
+
* with the number of previous, unsuccessful attempts, as well as with the error with which the previous request
|
|
693
|
+
* failed. If the function returns `null`, no more retries will be made.
|
|
694
|
+
*
|
|
695
|
+
* By default, it uses an exponential backoff algorithm that never gives up unless
|
|
696
|
+
* a CORS error is suspected (`fetch()` did reject, `navigator.onLine` is true and origin is different).
|
|
697
|
+
*/
|
|
698
|
+
getRetryDelay?: (previousAttempts: number, error: unknown, url: string | URL | Request) => number | null;
|
|
699
|
+
|
|
700
|
+
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 64 MiB. */
|
|
701
|
+
maxCacheSize?: number;
|
|
702
|
+
|
|
703
|
+
/** The maximum number of parallel requests to use for fetching. Defaults to 2. */
|
|
704
|
+
parallelism?: number;
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* A WHATWG-compatible fetch function. You can use this field to polyfill the `fetch` function, add missing
|
|
708
|
+
* features, or use a custom implementation.
|
|
709
|
+
*/
|
|
710
|
+
fetchFn?: typeof fetch;
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* A source backed by a URL. This is useful for reading data from the network. Requests will be made using an optimized
|
|
715
|
+
* reading and prefetching pattern to minimize request count and latency.
|
|
716
|
+
* @group Input sources
|
|
717
|
+
* @public
|
|
718
|
+
*/
|
|
719
|
+
export class UrlSource extends PathedSource {
|
|
720
|
+
/** @internal */
|
|
721
|
+
_url: string | URL | Request;
|
|
722
|
+
/** @internal */
|
|
723
|
+
_getRetryDelay: (previousAttempts: number, error: unknown, url: string | URL | Request) => number | null;
|
|
724
|
+
/** @internal */
|
|
725
|
+
_options: UrlSourceOptions;
|
|
726
|
+
/** @internal */
|
|
727
|
+
_requestInit: RequestInit;
|
|
728
|
+
/** @internal */
|
|
729
|
+
_offset = 0;
|
|
730
|
+
/** @internal */
|
|
731
|
+
_length: number | null = null;
|
|
732
|
+
/** @internal */
|
|
733
|
+
_orchestrator: ReadOrchestrator;
|
|
734
|
+
/**
|
|
735
|
+
* Note that this value being true does NOT mean the file size can't change anymore; it just signals that we have at
|
|
736
|
+
* least checked if we know the file size or not.
|
|
737
|
+
* @internal
|
|
738
|
+
*/
|
|
739
|
+
_fileSizeDetermined = false;
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Creates a new {@link UrlSource} backed by the resource at the specified URL.
|
|
743
|
+
*
|
|
744
|
+
* When passing a `Request` instance, note that its `signal` will be overridden by Mediabunny; if you want to cancel
|
|
745
|
+
* ongoing requests, use {@link Input.dispose}.
|
|
746
|
+
*/
|
|
747
|
+
constructor(
|
|
748
|
+
url: string | URL | Request,
|
|
749
|
+
options: UrlSourceOptions = {},
|
|
750
|
+
) {
|
|
751
|
+
if (
|
|
752
|
+
typeof url !== 'string'
|
|
753
|
+
&& !(url instanceof URL)
|
|
754
|
+
&& !(typeof Request !== 'undefined' && url instanceof Request)
|
|
755
|
+
) {
|
|
756
|
+
throw new TypeError('url must be a string, URL or Request.');
|
|
757
|
+
}
|
|
758
|
+
if (!options || typeof options !== 'object') {
|
|
759
|
+
throw new TypeError('options must be an object.');
|
|
760
|
+
}
|
|
761
|
+
if (options.requestInit !== undefined && (!options.requestInit || typeof options.requestInit !== 'object')) {
|
|
762
|
+
throw new TypeError('options.requestInit, when provided, must be an object.');
|
|
763
|
+
}
|
|
764
|
+
if (options.getRetryDelay !== undefined && typeof options.getRetryDelay !== 'function') {
|
|
765
|
+
throw new TypeError('options.getRetryDelay, when provided, must be a function.');
|
|
766
|
+
}
|
|
767
|
+
if (
|
|
768
|
+
options.maxCacheSize !== undefined
|
|
769
|
+
&& (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)
|
|
770
|
+
) {
|
|
771
|
+
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative number.');
|
|
772
|
+
}
|
|
773
|
+
if (options.parallelism !== undefined && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
|
|
774
|
+
throw new TypeError('options.parallelism, when provided, must be a positive number.');
|
|
775
|
+
}
|
|
776
|
+
if (options.fetchFn !== undefined && typeof options.fetchFn !== 'function') {
|
|
777
|
+
throw new TypeError('options.fetchFn, when provided, must be a function.');
|
|
778
|
+
// Won't bother validating this function beyond this
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const urlString = url instanceof Request
|
|
782
|
+
? url.url
|
|
783
|
+
: url instanceof URL
|
|
784
|
+
? url.href
|
|
785
|
+
: url;
|
|
786
|
+
|
|
787
|
+
super(
|
|
788
|
+
urlString,
|
|
789
|
+
request => new UrlSource(request.path, this._options),
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
this._url = url;
|
|
793
|
+
this._options = options;
|
|
794
|
+
this._getRetryDelay = options.getRetryDelay ?? DEFAULT_RETRY_DELAY;
|
|
795
|
+
|
|
796
|
+
// A user-supplied Range header is interpreted as a byte offset (and optional length) into the resource. We
|
|
797
|
+
// pull it out of the request and remember it for subsequent requests.
|
|
798
|
+
this._requestInit = { ...options.requestInit };
|
|
799
|
+
|
|
800
|
+
let rangeHeaderValue: string | null = null;
|
|
801
|
+
|
|
802
|
+
if (options.requestInit?.headers) {
|
|
803
|
+
const headers = { ...normalizeHeaders(options.requestInit.headers) };
|
|
804
|
+
const rangeKey = Object.keys(headers).find(key => key.toLowerCase() === 'range');
|
|
805
|
+
if (rangeKey !== undefined) {
|
|
806
|
+
rangeHeaderValue = headers[rangeKey]!;
|
|
807
|
+
delete headers[rangeKey];
|
|
808
|
+
this._requestInit.headers = headers;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (url instanceof Request) {
|
|
813
|
+
const requestRange = url.headers.get('Range');
|
|
814
|
+
if (requestRange !== null) {
|
|
815
|
+
rangeHeaderValue ??= requestRange;
|
|
816
|
+
|
|
817
|
+
// Clone the request so we don't mutate the user's object, then strip the Range header
|
|
818
|
+
const strippedRequest = new Request(url);
|
|
819
|
+
strippedRequest.headers.delete('Range');
|
|
820
|
+
this._url = strippedRequest;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (rangeHeaderValue !== null) {
|
|
825
|
+
const parsed = parseByteRangeHeader(rangeHeaderValue);
|
|
826
|
+
if (parsed) {
|
|
827
|
+
this._offset = parsed.offset;
|
|
828
|
+
this._length = parsed.length;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Most files in the real-world have a single sequential access pattern, but having two in parallel can
|
|
833
|
+
// also happen
|
|
834
|
+
const DEFAULT_PARALLELISM = 2;
|
|
835
|
+
|
|
836
|
+
this._orchestrator = new ReadOrchestrator({
|
|
837
|
+
maxCacheSize: options.maxCacheSize ?? (64 * 2 ** 20 /* 64 MiB */),
|
|
838
|
+
maxWorkerCount: options.parallelism ?? DEFAULT_PARALLELISM,
|
|
839
|
+
runWorker: this._runWorker.bind(this),
|
|
840
|
+
prefetchProfile: PREFETCH_PROFILES.network,
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/** @internal */
|
|
845
|
+
_getFileSize(): number | null | undefined {
|
|
846
|
+
if (!this._fileSizeDetermined) {
|
|
847
|
+
return this._length !== null ? this._length : undefined;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const baseSize = this._orchestrator.fileSize;
|
|
851
|
+
if (baseSize === null) {
|
|
852
|
+
return this._length !== null ? this._length : null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return clamp(baseSize - this._offset, 0, this._length ?? Infinity);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** @internal */
|
|
859
|
+
_read(
|
|
860
|
+
start: number,
|
|
861
|
+
end: number,
|
|
862
|
+
minReadPosition: number,
|
|
863
|
+
maxReadPosition: number,
|
|
864
|
+
): MaybePromise<ReadResult | null> {
|
|
865
|
+
if (this._length !== null && end > this._length) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const offset = this._offset;
|
|
870
|
+
const result = this._orchestrator.read(
|
|
871
|
+
offset + start,
|
|
872
|
+
offset + end,
|
|
873
|
+
Math.max(offset + minReadPosition, offset),
|
|
874
|
+
offset + Math.min(maxReadPosition, this._length ?? Infinity),
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
const processResult = (result: ReadResult | null) => {
|
|
878
|
+
if (!result) {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
result.offset -= this._offset;
|
|
883
|
+
return result;
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
if (result instanceof Promise) {
|
|
887
|
+
return result.then(processResult);
|
|
888
|
+
} else {
|
|
889
|
+
return processResult(result);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** @internal */
|
|
894
|
+
private async _runWorker(worker: ReadWorker) {
|
|
895
|
+
// The outer loop is for resuming a request if it dies mid-response
|
|
896
|
+
while (true) {
|
|
897
|
+
const abortController = new AbortController();
|
|
898
|
+
const response = await retriedFetch(
|
|
899
|
+
this._options.fetchFn ?? fetch,
|
|
900
|
+
this._url,
|
|
901
|
+
mergeRequestInit(this._requestInit, {
|
|
902
|
+
headers: {
|
|
903
|
+
// Always sending a range request is a good way to probe if the server supports them
|
|
904
|
+
Range: `bytes=${worker.currentPos}-`,
|
|
905
|
+
},
|
|
906
|
+
signal: abortController.signal,
|
|
907
|
+
}),
|
|
908
|
+
this._getRetryDelay,
|
|
909
|
+
() => this._disposed,
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
if (!response.ok) {
|
|
913
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
914
|
+
throw new Error(`Error fetching ${String(this._url)}: ${response.status} ${response.statusText}`);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (response.redirected) {
|
|
918
|
+
// Modify our own root path so that future subrequests get made relative to the redirected URL
|
|
919
|
+
this.rootPath = response.url;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
outer:
|
|
923
|
+
if (this._orchestrator.fileSize === null) {
|
|
924
|
+
// See if we can deduce the file size from the response
|
|
925
|
+
|
|
926
|
+
const contentRange = response.headers.get('Content-Range');
|
|
927
|
+
if (contentRange) {
|
|
928
|
+
const match = /\/(\d+)/.exec(contentRange);
|
|
929
|
+
if (match) {
|
|
930
|
+
this._orchestrator.supplyFileSize(Number(match[1]));
|
|
931
|
+
break outer;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const contentLength = response.headers.get('Content-Length');
|
|
936
|
+
if (contentLength) {
|
|
937
|
+
// Note: For range requests, this is _technically_ not correct, as the range response could contain
|
|
938
|
+
// less data than was requested. In practice, it seems most servers don't do this though, and the
|
|
939
|
+
// Content-Length header actually contains the length until the end of the file.
|
|
940
|
+
this._orchestrator.supplyFileSize(worker.currentPos + Number(contentLength));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
this._fileSizeDetermined = true; // Yes, this is correct even if file size is still null
|
|
945
|
+
|
|
946
|
+
if (response.status !== 206) {
|
|
947
|
+
if (!this._usedForHls) {
|
|
948
|
+
const url = new URL(
|
|
949
|
+
this._url instanceof Request ? this._url.url : this._url,
|
|
950
|
+
typeof window !== 'undefined' ? window.location.href : undefined,
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
if (
|
|
954
|
+
url.origin !== 'null'
|
|
955
|
+
// Don't show the warning for M3U8 playlist files, it's irrelevant for those
|
|
956
|
+
&& !(url.pathname.endsWith('.m3u8') || url.pathname.endsWith('.m3u'))
|
|
957
|
+
) {
|
|
958
|
+
if (!warnedOrigins.has(url.origin)) {
|
|
959
|
+
Logging._warn(
|
|
960
|
+
`HTTP server (origin ${url.origin}) did not respond to a range request with 206 Partial`
|
|
961
|
+
+ ' Content, meaning the entire resource will now be downloaded. To enable efficient'
|
|
962
|
+
+ ' media file streaming across a network, please make sure your server supports'
|
|
963
|
+
+ ' range requests.',
|
|
964
|
+
);
|
|
965
|
+
warnedOrigins.add(url.origin);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
worker.currentPos = 0;
|
|
971
|
+
this._orchestrator.options.maxCacheSize = Infinity; // 🤷
|
|
972
|
+
|
|
973
|
+
if (this._orchestrator.fileSize !== null) {
|
|
974
|
+
worker.targetPos = this._orchestrator.fileSize;
|
|
975
|
+
} else {
|
|
976
|
+
// The server is dumb, doesn't even surface the content length, but we'll work with it.
|
|
977
|
+
worker.targetPos = Infinity;
|
|
978
|
+
worker.strictTarget = false;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
this._orchestrator.consolidateEverythingIntoOneWorker(worker);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (!response.body) {
|
|
985
|
+
throw new Error(
|
|
986
|
+
'Missing HTTP response body stream. The used fetch function must provide the response body as a'
|
|
987
|
+
+ ' ReadableStream.',
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const reader = response.body.getReader();
|
|
992
|
+
|
|
993
|
+
while (true) {
|
|
994
|
+
if (worker.currentPos >= worker.targetPos || worker.aborted) {
|
|
995
|
+
abortController.abort();
|
|
996
|
+
this._orchestrator.signalWorkerStoppedRunning(worker);
|
|
997
|
+
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
let readResult: ReadableStreamReadResult<Uint8Array>;
|
|
1002
|
+
|
|
1003
|
+
try {
|
|
1004
|
+
readResult = await reader.read();
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
if (this._disposed) {
|
|
1007
|
+
// No need to try to retry
|
|
1008
|
+
throw error;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const retryDelayInSeconds = this._getRetryDelay(1, error, this._url);
|
|
1012
|
+
if (retryDelayInSeconds !== null) {
|
|
1013
|
+
Logging._error('Error while reading response stream. Attempting to resume.', error);
|
|
1014
|
+
await wait(1000 * retryDelayInSeconds);
|
|
1015
|
+
|
|
1016
|
+
break;
|
|
1017
|
+
} else {
|
|
1018
|
+
throw error;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (worker.aborted) {
|
|
1023
|
+
continue; // Cleanup happens in next iteration
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const { done, value } = readResult;
|
|
1027
|
+
|
|
1028
|
+
if (done) {
|
|
1029
|
+
if (worker.currentPos >= worker.targetPos) {
|
|
1030
|
+
// All data was delivered, we're good
|
|
1031
|
+
this._orchestrator.onWorkerFinished(worker);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (worker.strictTarget) {
|
|
1036
|
+
// The response stopped early, before the target. This can happen if server decides to cap range
|
|
1037
|
+
// requests arbitrarily, even if the request had an uncapped end. In this case, let's fetch the
|
|
1038
|
+
// rest of the data using a new request.
|
|
1039
|
+
break;
|
|
1040
|
+
} else {
|
|
1041
|
+
// Assume we have simply reached the end of the resource
|
|
1042
|
+
this._orchestrator.onWorkerFinished(worker);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
this._dispatchRead(worker.currentPos, worker.currentPos + value.length);
|
|
1048
|
+
this._orchestrator.supplyWorkerData(worker, value);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// The previous UrlSource had logic for circumventing https://issues.chromium.org/issues/436025873; I haven't
|
|
1053
|
+
// been able to observe this bug with the new UrlSource (maybe because we're using response streaming), so the
|
|
1054
|
+
// logic for that has vanished for now. Leaving a comment here if this becomes relevant again.
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/** @internal */
|
|
1058
|
+
_dispose() {
|
|
1059
|
+
this._orchestrator.dispose();
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const BYTE_RANGE_REGEX = /^bytes=(\d+)-(\d*)$/;
|
|
1064
|
+
const parseByteRangeHeader = (value: string) => {
|
|
1065
|
+
const match = BYTE_RANGE_REGEX.exec(value.trim());
|
|
1066
|
+
if (!match) {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const offset = Number(match[1]);
|
|
1071
|
+
const end = match[2] === '' ? null : Number(match[2]);
|
|
1072
|
+
|
|
1073
|
+
if (end !== null && end < offset) {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return {
|
|
1078
|
+
offset,
|
|
1079
|
+
length: end !== null ? end - offset + 1 : null,
|
|
1080
|
+
};
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Options for {@link FilePathSource}.
|
|
1085
|
+
* @group Input sources
|
|
1086
|
+
* @public
|
|
1087
|
+
*/
|
|
1088
|
+
export type FilePathSourceOptions = {
|
|
1089
|
+
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 8 MiB. */
|
|
1090
|
+
maxCacheSize?: number;
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* A source backed by a path to a file. Intended for server-side usage in Node, Bun, or Deno.
|
|
1095
|
+
*
|
|
1096
|
+
* Make sure to call `.dispose()` on the corresponding {@link Input} when done to explicitly free the internal file
|
|
1097
|
+
* handle acquired by this source.
|
|
1098
|
+
* @group Input sources
|
|
1099
|
+
* @public
|
|
1100
|
+
*/
|
|
1101
|
+
export class FilePathSource extends PathedSource {
|
|
1102
|
+
/** @internal */
|
|
1103
|
+
_customSource: CustomSource;
|
|
1104
|
+
/** @internal */
|
|
1105
|
+
_fileHandle: FileHandle | null = null;
|
|
1106
|
+
|
|
1107
|
+
/** Creates a new {@link FilePathSource} backed by the file at the specified file path. */
|
|
1108
|
+
constructor(filePath: string, options: FilePathSourceOptions = {}) {
|
|
1109
|
+
if (typeof filePath !== 'string') {
|
|
1110
|
+
throw new TypeError('filePath must be a string.');
|
|
1111
|
+
}
|
|
1112
|
+
if (!options || typeof options !== 'object') {
|
|
1113
|
+
throw new TypeError('options must be an object.');
|
|
1114
|
+
}
|
|
1115
|
+
if (
|
|
1116
|
+
options.maxCacheSize !== undefined
|
|
1117
|
+
&& (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)
|
|
1118
|
+
) {
|
|
1119
|
+
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative number.');
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (!node.fs) {
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
'FilePathSource is only available in server-side environments (Node.js, Bun, Deno).',
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
super(filePath, request => new FilePathSource(request.path, options));
|
|
1129
|
+
|
|
1130
|
+
// Let's back this source with a CustomSource, makes the implementation very simple
|
|
1131
|
+
this._customSource = new CustomSource({
|
|
1132
|
+
getSize: async () => {
|
|
1133
|
+
const fileHandle = await node.fs.open(filePath, 'r');
|
|
1134
|
+
this._fileHandle = fileHandle;
|
|
1135
|
+
|
|
1136
|
+
sourceFinalizationRegistry?.register(this, () => {
|
|
1137
|
+
// If it's not closed, Node prints annoying warnings
|
|
1138
|
+
void fileHandle.close();
|
|
1139
|
+
}, this);
|
|
1140
|
+
|
|
1141
|
+
const stats = await fileHandle.stat();
|
|
1142
|
+
return stats.size;
|
|
1143
|
+
},
|
|
1144
|
+
read: async (start, end) => {
|
|
1145
|
+
assert(this._fileHandle);
|
|
1146
|
+
|
|
1147
|
+
const buffer = new Uint8Array(end - start);
|
|
1148
|
+
await this._fileHandle.read(buffer, 0, end - start, start);
|
|
1149
|
+
|
|
1150
|
+
return buffer;
|
|
1151
|
+
},
|
|
1152
|
+
maxCacheSize: options.maxCacheSize,
|
|
1153
|
+
prefetchProfile: 'fileSystem',
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/** @internal */
|
|
1158
|
+
_read(
|
|
1159
|
+
start: number,
|
|
1160
|
+
end: number,
|
|
1161
|
+
minReadPosition: number,
|
|
1162
|
+
maxReadPosition: number,
|
|
1163
|
+
): MaybePromise<ReadResult | null> {
|
|
1164
|
+
return this._customSource._read(start, end, minReadPosition, maxReadPosition);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/** @internal */
|
|
1168
|
+
_getFileSize(): number | null | undefined {
|
|
1169
|
+
return this._customSource._getFileSize();
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/** @internal */
|
|
1173
|
+
_dispose() {
|
|
1174
|
+
this._customSource._dispose();
|
|
1175
|
+
|
|
1176
|
+
if (this._fileHandle) {
|
|
1177
|
+
void this._fileHandle.close();
|
|
1178
|
+
this._fileHandle = null;
|
|
1179
|
+
sourceFinalizationRegistry?.unregister(this);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Options for defining a {@link CustomSource}.
|
|
1186
|
+
* @group Input sources
|
|
1187
|
+
* @public
|
|
1188
|
+
*/
|
|
1189
|
+
export type CustomSourceOptions = {
|
|
1190
|
+
/**
|
|
1191
|
+
* Called when the size of the entire file is requested. Must return or resolve to the size in bytes. This function
|
|
1192
|
+
* is guaranteed to be called before `read`.
|
|
1193
|
+
*/
|
|
1194
|
+
getSize: () => MaybePromise<number>;
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Called when data is requested. Must return or resolve to the bytes from the specified byte range, or a stream
|
|
1198
|
+
* that yields these bytes.
|
|
1199
|
+
*
|
|
1200
|
+
* You are guaranteed that `0 <= start < end < fileSize`.
|
|
1201
|
+
*/
|
|
1202
|
+
read: (start: number, end: number) => MaybePromise<Uint8Array | ReadableStream<Uint8Array>>;
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Called when the {@link Input} driven by this source is disposed.
|
|
1206
|
+
*/
|
|
1207
|
+
dispose?: () => unknown;
|
|
1208
|
+
|
|
1209
|
+
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 8 MiB. */
|
|
1210
|
+
maxCacheSize?: number;
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Specifies the prefetch profile that the reader should use with this source. A prefetch profile specifies the
|
|
1214
|
+
* pattern with which bytes outside of the requested range are preloaded to reduce latency for future reads.
|
|
1215
|
+
*
|
|
1216
|
+
* - `'none'` (default): No prefetching; only the data needed in the moment is requested.
|
|
1217
|
+
* - `'fileSystem'`: File system-optimized prefetching: a small amount of data is prefetched bidirectionally,
|
|
1218
|
+
* aligned with page boundaries.
|
|
1219
|
+
* - `'network'`: Network-optimized prefetching, or more generally, prefetching optimized for any high-latency
|
|
1220
|
+
* environment: tries to minimize the amount of read calls and aggressively prefetches data when sequential access
|
|
1221
|
+
* patterns are detected.
|
|
1222
|
+
*/
|
|
1223
|
+
prefetchProfile?: 'none' | 'fileSystem' | 'network';
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* A general-purpose, callback-driven source that can get its data from anywhere. Use this source to implement your own
|
|
1228
|
+
* custom source if the other sources don't cover your case.
|
|
1229
|
+
* @group Input sources
|
|
1230
|
+
* @public
|
|
1231
|
+
*/
|
|
1232
|
+
export class CustomSource extends Source {
|
|
1233
|
+
/** @internal */
|
|
1234
|
+
_options: CustomSourceOptions;
|
|
1235
|
+
/** @internal */
|
|
1236
|
+
_orchestrator: ReadOrchestrator;
|
|
1237
|
+
|
|
1238
|
+
/** Creates a new {@link CustomSource} whose behavior is specified by `options`. */
|
|
1239
|
+
constructor(options: CustomSourceOptions) {
|
|
1240
|
+
if (!options || typeof options !== 'object') {
|
|
1241
|
+
throw new TypeError('options must be an object.');
|
|
1242
|
+
}
|
|
1243
|
+
if (typeof options.getSize !== 'function') {
|
|
1244
|
+
throw new TypeError('options.getSize must be a function.');
|
|
1245
|
+
}
|
|
1246
|
+
if (typeof options.read !== 'function') {
|
|
1247
|
+
throw new TypeError('options.read must be a function.');
|
|
1248
|
+
}
|
|
1249
|
+
if (options.dispose !== undefined && typeof options.dispose !== 'function') {
|
|
1250
|
+
throw new TypeError('options.dispose, when provided, must be a function.');
|
|
1251
|
+
}
|
|
1252
|
+
if (
|
|
1253
|
+
options.maxCacheSize !== undefined
|
|
1254
|
+
&& (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)
|
|
1255
|
+
) {
|
|
1256
|
+
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative number.');
|
|
1257
|
+
}
|
|
1258
|
+
if (options.prefetchProfile && !['none', 'fileSystem', 'network'].includes(options.prefetchProfile)) {
|
|
1259
|
+
throw new TypeError(
|
|
1260
|
+
'options.prefetchProfile, when provided, must be one of \'none\', \'fileSystem\' or \'network\'.',
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
super();
|
|
1265
|
+
|
|
1266
|
+
this._options = options;
|
|
1267
|
+
|
|
1268
|
+
this._orchestrator = new ReadOrchestrator({
|
|
1269
|
+
maxCacheSize: options.maxCacheSize ?? (8 * 2 ** 20 /* 8 MiB */),
|
|
1270
|
+
maxWorkerCount: 2, // Fixed for now, *should* be fine
|
|
1271
|
+
prefetchProfile: PREFETCH_PROFILES[options.prefetchProfile ?? 'none'],
|
|
1272
|
+
runWorker: this._runWorker.bind(this),
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/** @internal */
|
|
1277
|
+
_getFileSize(): number | null | undefined {
|
|
1278
|
+
return this._orchestrator.fileSize ?? undefined;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/** @internal */
|
|
1282
|
+
_read(
|
|
1283
|
+
start: number,
|
|
1284
|
+
end: number,
|
|
1285
|
+
minReadPosition: number,
|
|
1286
|
+
maxReadPosition: number,
|
|
1287
|
+
): MaybePromise<ReadResult | null> {
|
|
1288
|
+
if (this._orchestrator.fileSize !== null) {
|
|
1289
|
+
return this._orchestrator.read(start, end, minReadPosition, maxReadPosition);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const result = this._options.getSize();
|
|
1293
|
+
|
|
1294
|
+
if (result instanceof Promise) {
|
|
1295
|
+
return result.then((size) => {
|
|
1296
|
+
if (!Number.isInteger(size) || size < 0) {
|
|
1297
|
+
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
this._orchestrator.fileSize = size;
|
|
1301
|
+
return this._orchestrator.read(start, end, minReadPosition, maxReadPosition);
|
|
1302
|
+
});
|
|
1303
|
+
} else {
|
|
1304
|
+
if (!Number.isInteger(result) || result < 0) {
|
|
1305
|
+
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
this._orchestrator.fileSize = result;
|
|
1309
|
+
return this._orchestrator.read(start, end, minReadPosition, maxReadPosition);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/** @internal */
|
|
1314
|
+
private async _runWorker(worker: ReadWorker) {
|
|
1315
|
+
while (worker.currentPos < worker.targetPos && !worker.aborted) {
|
|
1316
|
+
const originalCurrentPos = worker.currentPos;
|
|
1317
|
+
const originalTargetPos = worker.targetPos;
|
|
1318
|
+
|
|
1319
|
+
let data = this._options.read(worker.currentPos, originalTargetPos);
|
|
1320
|
+
if (data instanceof Promise) data = await data;
|
|
1321
|
+
|
|
1322
|
+
if (worker.aborted) {
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (data instanceof Uint8Array) {
|
|
1327
|
+
data = toUint8Array(data); // Normalize things like Node.js Buffer to Uint8Array
|
|
1328
|
+
|
|
1329
|
+
if (data.length !== originalTargetPos - worker.currentPos) {
|
|
1330
|
+
// Yes, we're that strict
|
|
1331
|
+
throw new Error(
|
|
1332
|
+
`options.read returned a Uint8Array with unexpected length: Requested ${
|
|
1333
|
+
originalTargetPos - worker.currentPos
|
|
1334
|
+
} bytes, but got ${data.length}.`,
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
this._dispatchRead(worker.currentPos, worker.currentPos + data.length);
|
|
1339
|
+
this._orchestrator.supplyWorkerData(worker, data);
|
|
1340
|
+
} else if (data instanceof ReadableStream) {
|
|
1341
|
+
const reader = data.getReader();
|
|
1342
|
+
|
|
1343
|
+
while (worker.currentPos < originalTargetPos && !worker.aborted) {
|
|
1344
|
+
const { done, value } = await reader.read();
|
|
1345
|
+
if (done) {
|
|
1346
|
+
if (worker.currentPos < originalTargetPos) {
|
|
1347
|
+
// Yes, we're *that* strict
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
`ReadableStream returned by options.read ended before supplying enough data.`
|
|
1350
|
+
+ ` Requested ${originalTargetPos - originalCurrentPos} bytes, but got ${
|
|
1351
|
+
worker.currentPos - originalCurrentPos
|
|
1352
|
+
}`,
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (!(value instanceof Uint8Array)) {
|
|
1360
|
+
throw new TypeError('ReadableStream returned by options.read must yield Uint8Array chunks.');
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (worker.aborted) {
|
|
1364
|
+
break;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
const data = toUint8Array(value); // Normalize things like Node.js Buffer to Uint8Array
|
|
1368
|
+
|
|
1369
|
+
this._dispatchRead(worker.currentPos, worker.currentPos + data.length);
|
|
1370
|
+
this._orchestrator.supplyWorkerData(worker, data);
|
|
1371
|
+
}
|
|
1372
|
+
} else {
|
|
1373
|
+
throw new TypeError('options.read must return or resolve to a Uint8Array or a ReadableStream.');
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
this._orchestrator.signalWorkerStoppedRunning(worker);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/** @internal */
|
|
1381
|
+
_dispose() {
|
|
1382
|
+
this._orchestrator.dispose();
|
|
1383
|
+
this._options.dispose?.();
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* An alias for {@link CustomSource}.
|
|
1389
|
+
* @deprecated This name is misleading and will be removed in a future release. Please use {@link CustomSource} instead.
|
|
1390
|
+
*
|
|
1391
|
+
* @group Input sources
|
|
1392
|
+
* @public
|
|
1393
|
+
*/
|
|
1394
|
+
export const StreamSource = CustomSource;
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* An alias for {@link CustomSourceOptions}.
|
|
1398
|
+
* @deprecated This name is misleading and will be removed in a future release. Please use
|
|
1399
|
+
* {@link CustomSourceOptions} instead.
|
|
1400
|
+
*
|
|
1401
|
+
* @group Input sources
|
|
1402
|
+
* @public
|
|
1403
|
+
*/
|
|
1404
|
+
export type StreamSourceOptions = CustomSourceOptions;
|
|
1405
|
+
|
|
1406
|
+
type ReadableStreamSourcePendingSlice = {
|
|
1407
|
+
start: number;
|
|
1408
|
+
end: number;
|
|
1409
|
+
bytes: Uint8Array;
|
|
1410
|
+
resolve: (bytes: ReadResult | null) => void;
|
|
1411
|
+
reject: (error: unknown) => void;
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Options for {@link ReadableStreamSource}.
|
|
1416
|
+
* @group Input sources
|
|
1417
|
+
* @public
|
|
1418
|
+
*/
|
|
1419
|
+
export type ReadableStreamSourceOptions = {
|
|
1420
|
+
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 32 MiB. */
|
|
1421
|
+
maxCacheSize?: number;
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* A source backed by a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) of
|
|
1426
|
+
* `Uint8Array`, representing an append-only byte stream of unknown length. This is the source to use for incrementally
|
|
1427
|
+
* streaming in input files that are still being constructed and whose size we don't yet know, like for example the
|
|
1428
|
+
* output chunks of [MediaRecorder](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder).
|
|
1429
|
+
*
|
|
1430
|
+
* This source is *unsized*, meaning calls to `.getSize()` will throw and readers are more limited due to the
|
|
1431
|
+
* lack of random file access. You should only use this source with sequential access patterns, such as reading all
|
|
1432
|
+
* packets from start to end. This source does not work well with random access patterns unless you increase its
|
|
1433
|
+
* max cache size.
|
|
1434
|
+
*
|
|
1435
|
+
* @group Input sources
|
|
1436
|
+
* @public
|
|
1437
|
+
*/
|
|
1438
|
+
export class ReadableStreamSource extends Source {
|
|
1439
|
+
/** @internal */
|
|
1440
|
+
_stream: ReadableStream<Uint8Array>;
|
|
1441
|
+
/** @internal */
|
|
1442
|
+
_reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
|
1443
|
+
/** @internal */
|
|
1444
|
+
_cache: CacheEntry[] = [];
|
|
1445
|
+
/** @internal */
|
|
1446
|
+
_maxCacheSize: number;
|
|
1447
|
+
/** @internal */
|
|
1448
|
+
_pendingSlices: ReadableStreamSourcePendingSlice[] = [];
|
|
1449
|
+
/** @internal */
|
|
1450
|
+
_currentIndex = 0;
|
|
1451
|
+
/** @internal */
|
|
1452
|
+
_targetIndex = 0;
|
|
1453
|
+
/** @internal */
|
|
1454
|
+
_maxRequestedIndex = 0;
|
|
1455
|
+
/** @internal */
|
|
1456
|
+
_endIndex: number | null = null;
|
|
1457
|
+
/** @internal */
|
|
1458
|
+
_pulling = false;
|
|
1459
|
+
|
|
1460
|
+
/** Creates a new {@link ReadableStreamSource} backed by the specified `ReadableStream<Uint8Array>`. */
|
|
1461
|
+
constructor(stream: ReadableStream<Uint8Array>, options: ReadableStreamSourceOptions = {}) {
|
|
1462
|
+
if (!(stream instanceof ReadableStream)) {
|
|
1463
|
+
throw new TypeError('stream must be a ReadableStream.');
|
|
1464
|
+
}
|
|
1465
|
+
if (!options || typeof options !== 'object') {
|
|
1466
|
+
throw new TypeError('options must be an object.');
|
|
1467
|
+
}
|
|
1468
|
+
if (
|
|
1469
|
+
options.maxCacheSize !== undefined
|
|
1470
|
+
&& (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)
|
|
1471
|
+
) {
|
|
1472
|
+
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative number.');
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
super();
|
|
1476
|
+
|
|
1477
|
+
this._stream = stream;
|
|
1478
|
+
this._maxCacheSize = options.maxCacheSize ?? (32 * 2 ** 20 /* 32 MiB */);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/** @internal */
|
|
1482
|
+
_getFileSize(): number | null {
|
|
1483
|
+
return this._endIndex; // Starts out as null, meaning this source is unsized
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/** @internal */
|
|
1487
|
+
_read(start: number, end: number): MaybePromise<ReadResult | null> {
|
|
1488
|
+
if (this._endIndex !== null && end > this._endIndex) {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
this._maxRequestedIndex = Math.max(this._maxRequestedIndex, end);
|
|
1493
|
+
|
|
1494
|
+
const cacheStartIndex = binarySearchLessOrEqual(this._cache, start, x => x.start);
|
|
1495
|
+
const cacheStartEntry = cacheStartIndex !== -1 ? this._cache[cacheStartIndex]! : null;
|
|
1496
|
+
|
|
1497
|
+
if (cacheStartEntry && cacheStartEntry.start <= start && end <= cacheStartEntry.end) {
|
|
1498
|
+
// The request can be satisfied with a single cache entry
|
|
1499
|
+
return {
|
|
1500
|
+
bytes: cacheStartEntry.bytes,
|
|
1501
|
+
view: cacheStartEntry.view,
|
|
1502
|
+
offset: cacheStartEntry.start,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
let lastEnd = start;
|
|
1507
|
+
const bytes = new Uint8Array(end - start);
|
|
1508
|
+
|
|
1509
|
+
if (cacheStartIndex !== -1) {
|
|
1510
|
+
// Walk over the cache to see if we can satisfy the request using multiple cache entries
|
|
1511
|
+
for (let i = cacheStartIndex; i < this._cache.length; i++) {
|
|
1512
|
+
const cacheEntry = this._cache[i]!;
|
|
1513
|
+
if (cacheEntry.start >= end) {
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
const cappedStart = Math.max(start, cacheEntry.start);
|
|
1518
|
+
if (cappedStart > lastEnd) {
|
|
1519
|
+
// We're too far behind
|
|
1520
|
+
this._throwDueToCacheMiss();
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const cappedEnd = Math.min(end, cacheEntry.end);
|
|
1524
|
+
|
|
1525
|
+
if (cappedStart < cappedEnd) {
|
|
1526
|
+
bytes.set(
|
|
1527
|
+
cacheEntry.bytes.subarray(cappedStart - cacheEntry.start, cappedEnd - cacheEntry.start),
|
|
1528
|
+
cappedStart - start,
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
lastEnd = cappedEnd;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (lastEnd === end) {
|
|
1537
|
+
return {
|
|
1538
|
+
bytes,
|
|
1539
|
+
view: toDataView(bytes),
|
|
1540
|
+
offset: start,
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// We need to pull more data
|
|
1545
|
+
|
|
1546
|
+
if (this._currentIndex > lastEnd) {
|
|
1547
|
+
// We're too far behind
|
|
1548
|
+
this._throwDueToCacheMiss();
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
const { promise, resolve, reject } = promiseWithResolvers<ReadResult | null>();
|
|
1552
|
+
|
|
1553
|
+
this._pendingSlices.push({
|
|
1554
|
+
start,
|
|
1555
|
+
end,
|
|
1556
|
+
bytes,
|
|
1557
|
+
resolve,
|
|
1558
|
+
reject,
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
this._targetIndex = Math.max(this._targetIndex, end);
|
|
1562
|
+
|
|
1563
|
+
// Start pulling from the stream if we're not already doing it
|
|
1564
|
+
if (!this._pulling) {
|
|
1565
|
+
this._pulling = true;
|
|
1566
|
+
void this._pull()
|
|
1567
|
+
.catch((error) => {
|
|
1568
|
+
this._pulling = false;
|
|
1569
|
+
|
|
1570
|
+
if (this._pendingSlices.length > 0) {
|
|
1571
|
+
this._pendingSlices.forEach(x => x.reject(error)); // Make sure to propagate any errors
|
|
1572
|
+
this._pendingSlices.length = 0;
|
|
1573
|
+
} else {
|
|
1574
|
+
throw error; // So it doesn't get swallowed
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
return promise;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/** @internal */
|
|
1583
|
+
_throwDueToCacheMiss() {
|
|
1584
|
+
throw new Error(
|
|
1585
|
+
'Read is before the cached region. With ReadableStreamSource, you must access the data more'
|
|
1586
|
+
+ ' sequentially or increase the size of its cache.',
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/** @internal */
|
|
1591
|
+
async _pull() {
|
|
1592
|
+
this._reader ??= this._stream.getReader();
|
|
1593
|
+
|
|
1594
|
+
// This is the loop that keeps pulling data from the stream until a target index is reached, filling requests
|
|
1595
|
+
// in the process
|
|
1596
|
+
while (this._currentIndex < this._targetIndex && !this._disposed) {
|
|
1597
|
+
const { done, value } = await this._reader.read();
|
|
1598
|
+
if (done) {
|
|
1599
|
+
for (const pendingSlice of this._pendingSlices) {
|
|
1600
|
+
pendingSlice.resolve(null);
|
|
1601
|
+
}
|
|
1602
|
+
this._pendingSlices.length = 0;
|
|
1603
|
+
this._endIndex = this._currentIndex; // We know how long the file is now!
|
|
1604
|
+
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
const startIndex = this._currentIndex;
|
|
1609
|
+
const endIndex = this._currentIndex + value.byteLength;
|
|
1610
|
+
|
|
1611
|
+
this._dispatchRead(startIndex, endIndex);
|
|
1612
|
+
|
|
1613
|
+
// Fill the pending slices with the data
|
|
1614
|
+
for (let i = 0; i < this._pendingSlices.length; i++) {
|
|
1615
|
+
const pendingSlice = this._pendingSlices[i]!;
|
|
1616
|
+
|
|
1617
|
+
const cappedStart = Math.max(startIndex, pendingSlice.start);
|
|
1618
|
+
const cappedEnd = Math.min(endIndex, pendingSlice.end);
|
|
1619
|
+
|
|
1620
|
+
if (cappedStart < cappedEnd) {
|
|
1621
|
+
pendingSlice.bytes.set(
|
|
1622
|
+
value.subarray(cappedStart - startIndex, cappedEnd - startIndex),
|
|
1623
|
+
cappedStart - pendingSlice.start,
|
|
1624
|
+
);
|
|
1625
|
+
if (cappedEnd === pendingSlice.end) {
|
|
1626
|
+
// Pending slice fully filled
|
|
1627
|
+
pendingSlice.resolve({
|
|
1628
|
+
bytes: pendingSlice.bytes,
|
|
1629
|
+
view: toDataView(pendingSlice.bytes),
|
|
1630
|
+
offset: pendingSlice.start,
|
|
1631
|
+
});
|
|
1632
|
+
this._pendingSlices.splice(i, 1);
|
|
1633
|
+
i--;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
this._cache.push({
|
|
1639
|
+
start: startIndex,
|
|
1640
|
+
end: endIndex,
|
|
1641
|
+
bytes: value,
|
|
1642
|
+
view: toDataView(value),
|
|
1643
|
+
age: 0, // Unused
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
// Do cache eviction, based on the distance from the last-requested index. It's important that we do it like
|
|
1647
|
+
// this and not based on where the reader is at, because if the reader is fast, we'll unnecessarily evict
|
|
1648
|
+
// data that we still might need.
|
|
1649
|
+
while (this._cache.length > 0) {
|
|
1650
|
+
const firstEntry = this._cache[0]!;
|
|
1651
|
+
const distance = this._maxRequestedIndex - firstEntry.end;
|
|
1652
|
+
|
|
1653
|
+
if (distance <= this._maxCacheSize) {
|
|
1654
|
+
break;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
this._cache.shift();
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
this._currentIndex += value.byteLength;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
this._pulling = false;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/** @internal */
|
|
1667
|
+
_dispose() {
|
|
1668
|
+
for (const pendingSlice of this._pendingSlices) {
|
|
1669
|
+
pendingSlice.reject(new InputDisposedError());
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
this._pendingSlices.length = 0;
|
|
1673
|
+
this._cache.length = 0;
|
|
1674
|
+
void this._reader?.cancel();
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
type PrefetchProfile = (start: number, end: number, workers: ReadWorker[]) => {
|
|
1679
|
+
start: number;
|
|
1680
|
+
end: number;
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
const PREFETCH_PROFILES = {
|
|
1684
|
+
none: (start, end) => ({ start, end }),
|
|
1685
|
+
fileSystem: (start, end) => {
|
|
1686
|
+
const padding = 2 ** 16;
|
|
1687
|
+
|
|
1688
|
+
start = Math.floor((start - padding) / padding) * padding;
|
|
1689
|
+
end = Math.ceil((end + padding) / padding) * padding;
|
|
1690
|
+
|
|
1691
|
+
return { start, end };
|
|
1692
|
+
},
|
|
1693
|
+
network: (start, end, workers) => {
|
|
1694
|
+
// Add a slight bit of start padding because backwards reading is painful
|
|
1695
|
+
const paddingStart = 2 ** 16;
|
|
1696
|
+
start = Math.max(0, Math.floor((start - paddingStart) / paddingStart) * paddingStart);
|
|
1697
|
+
|
|
1698
|
+
// Remote resources have extreme latency (relatively speaking), so the benefit from intelligent
|
|
1699
|
+
// prefetching is great. The network prefetch strategy is as follows: When we notice
|
|
1700
|
+
// successive reads to a worker's read region, we prefetch more data at the end of that region,
|
|
1701
|
+
// growing exponentially (up to a cap). This performs well for real-world use cases: Either we read a
|
|
1702
|
+
// small part of the file once and then never need it again, in which case the requested about of data
|
|
1703
|
+
// is small. Or, we're repeatedly doing a sequential access pattern (common in media files), in which
|
|
1704
|
+
// case we can become more and more confident to prefetch more and more data.
|
|
1705
|
+
for (const worker of workers) {
|
|
1706
|
+
const maxExtensionAmount = 8 * 2 ** 20; // 8 MiB
|
|
1707
|
+
|
|
1708
|
+
// When the read region cross the threshold point, we trigger a prefetch. This point is typically
|
|
1709
|
+
// in the middle of the worker's read region, or a fixed offset from the end if the region has grown
|
|
1710
|
+
// really large.
|
|
1711
|
+
const thresholdPoint = Math.max(
|
|
1712
|
+
(worker.startPos + worker.targetPos) / 2,
|
|
1713
|
+
worker.targetPos - maxExtensionAmount,
|
|
1714
|
+
);
|
|
1715
|
+
|
|
1716
|
+
if (closedIntervalsOverlap(
|
|
1717
|
+
start, end,
|
|
1718
|
+
thresholdPoint, worker.targetPos,
|
|
1719
|
+
)) {
|
|
1720
|
+
const size = worker.targetPos - worker.startPos;
|
|
1721
|
+
|
|
1722
|
+
// If we extend by maxExtensionAmount
|
|
1723
|
+
const a = Math.ceil((size + 1) / maxExtensionAmount) * maxExtensionAmount;
|
|
1724
|
+
// If we extend to the next power of 2
|
|
1725
|
+
const b = 2 ** Math.ceil(Math.log2(size + 1));
|
|
1726
|
+
|
|
1727
|
+
const extent = Math.min(b, a);
|
|
1728
|
+
end = Math.max(end, worker.startPos + extent);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
end = Math.max(end, start + URL_SOURCE_MIN_LOAD_AMOUNT);
|
|
1733
|
+
|
|
1734
|
+
return {
|
|
1735
|
+
start,
|
|
1736
|
+
end,
|
|
1737
|
+
};
|
|
1738
|
+
},
|
|
1739
|
+
} satisfies Record<string, PrefetchProfile>;
|
|
1740
|
+
|
|
1741
|
+
type PendingSlice = {
|
|
1742
|
+
start: number;
|
|
1743
|
+
bytes: Uint8Array;
|
|
1744
|
+
holes: Hole[];
|
|
1745
|
+
resolve: (bytes: Uint8Array | null) => void;
|
|
1746
|
+
reject: (error: unknown) => void;
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
type Hole = {
|
|
1750
|
+
start: number;
|
|
1751
|
+
end: number;
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
type CacheEntry = {
|
|
1755
|
+
start: number;
|
|
1756
|
+
end: number;
|
|
1757
|
+
bytes: Uint8Array;
|
|
1758
|
+
view: DataView;
|
|
1759
|
+
age: number;
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
type ReadWorker = {
|
|
1763
|
+
startPos: number;
|
|
1764
|
+
currentPos: number;
|
|
1765
|
+
targetPos: number;
|
|
1766
|
+
/** The target is considered _strict_ when it is an error for the worker to terminate before reaching the target. */
|
|
1767
|
+
strictTarget: boolean;
|
|
1768
|
+
running: boolean;
|
|
1769
|
+
aborted: boolean;
|
|
1770
|
+
pendingSlices: PendingSlice[];
|
|
1771
|
+
age: number;
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* Godclass for orchestrating complex, cached read operations. The reading model is as follows: Any reading task is
|
|
1776
|
+
* delegated to a *worker*, which is a sequential reader positioned somewhere along the file. All workers run in
|
|
1777
|
+
* parallel and can be stopped and resumed in their forward movement. When read requests come in, this orchestrator will
|
|
1778
|
+
* first try to satisfy the request with only the cached data. If this isn't possible, workers are spun up for all
|
|
1779
|
+
* missing parts (or existing workers are repurposed), and these workers will then fill the holes in the data as they
|
|
1780
|
+
* march along the file.
|
|
1781
|
+
*/
|
|
1782
|
+
class ReadOrchestrator {
|
|
1783
|
+
fileSize: number | null = null;
|
|
1784
|
+
nextAge = 0; // Used for multiple things
|
|
1785
|
+
workers: ReadWorker[] = [];
|
|
1786
|
+
cache: CacheEntry[] = [];
|
|
1787
|
+
currentCacheSize = 0;
|
|
1788
|
+
disposed = false;
|
|
1789
|
+
queuedReads: {
|
|
1790
|
+
hole: Hole;
|
|
1791
|
+
strictTarget: boolean;
|
|
1792
|
+
pendingSlices: PendingSlice[];
|
|
1793
|
+
age: number;
|
|
1794
|
+
}[] = [];
|
|
1795
|
+
|
|
1796
|
+
constructor(public options: {
|
|
1797
|
+
maxCacheSize: number;
|
|
1798
|
+
runWorker: (worker: ReadWorker) => Promise<void>;
|
|
1799
|
+
prefetchProfile: PrefetchProfile;
|
|
1800
|
+
maxWorkerCount: number;
|
|
1801
|
+
}) {}
|
|
1802
|
+
|
|
1803
|
+
read(
|
|
1804
|
+
innerStart: number,
|
|
1805
|
+
innerEnd: number,
|
|
1806
|
+
minReadPosition: number,
|
|
1807
|
+
maxReadPosition: number,
|
|
1808
|
+
): MaybePromise<ReadResult | null> {
|
|
1809
|
+
assert(!this.disposed);
|
|
1810
|
+
|
|
1811
|
+
const prefetchRange = this.options.prefetchProfile(innerStart, innerEnd, this.workers);
|
|
1812
|
+
const outerStart = Math.max(prefetchRange.start, minReadPosition);
|
|
1813
|
+
const outerEnd = Math.min(prefetchRange.end, this.fileSize ?? Infinity, maxReadPosition);
|
|
1814
|
+
assert(outerStart <= innerStart && innerEnd <= outerEnd);
|
|
1815
|
+
|
|
1816
|
+
let result: MaybePromise<ReadResult | null> | null = null;
|
|
1817
|
+
|
|
1818
|
+
const innerCacheStartIndex = binarySearchLessOrEqual(this.cache, innerStart, x => x.start);
|
|
1819
|
+
const innerStartEntry = innerCacheStartIndex !== -1 ? this.cache[innerCacheStartIndex] : null;
|
|
1820
|
+
|
|
1821
|
+
// See if the read request can be satisfied by a single cache entry
|
|
1822
|
+
if (innerStartEntry && innerStartEntry.start <= innerStart && innerEnd <= innerStartEntry.end) {
|
|
1823
|
+
innerStartEntry.age = this.nextAge++;
|
|
1824
|
+
|
|
1825
|
+
result = {
|
|
1826
|
+
bytes: innerStartEntry.bytes,
|
|
1827
|
+
view: innerStartEntry.view,
|
|
1828
|
+
offset: innerStartEntry.start,
|
|
1829
|
+
};
|
|
1830
|
+
// Can't return yet though, still need to check if the prefetch range might lie outside the cached area
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const outerCacheStartIndex = binarySearchLessOrEqual(this.cache, outerStart, x => x.start);
|
|
1834
|
+
|
|
1835
|
+
const bytes = result ? null : new Uint8Array(innerEnd - innerStart);
|
|
1836
|
+
let contiguousBytesWriteEnd = 0; // Used to track if the cache is able to completely cover the bytes
|
|
1837
|
+
|
|
1838
|
+
let lastEnd = outerStart;
|
|
1839
|
+
// The "holes" in the cache (the parts we need to load)
|
|
1840
|
+
const outerHoles: Hole[] = [];
|
|
1841
|
+
|
|
1842
|
+
// Loop over the cache and build up the list of holes
|
|
1843
|
+
if (outerCacheStartIndex !== -1) {
|
|
1844
|
+
for (let i = outerCacheStartIndex; i < this.cache.length; i++) {
|
|
1845
|
+
const entry = this.cache[i]!;
|
|
1846
|
+
if (entry.start >= outerEnd) {
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
if (entry.end <= outerStart) {
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
const cappedOuterStart = Math.max(outerStart, entry.start);
|
|
1854
|
+
const cappedOuterEnd = Math.min(outerEnd, entry.end);
|
|
1855
|
+
assert(cappedOuterStart <= cappedOuterEnd);
|
|
1856
|
+
|
|
1857
|
+
if (lastEnd < cappedOuterStart) {
|
|
1858
|
+
outerHoles.push({ start: lastEnd, end: cappedOuterStart });
|
|
1859
|
+
}
|
|
1860
|
+
lastEnd = cappedOuterEnd;
|
|
1861
|
+
|
|
1862
|
+
if (bytes) {
|
|
1863
|
+
const cappedInnerStart = Math.max(innerStart, entry.start);
|
|
1864
|
+
const cappedInnerEnd = Math.min(innerEnd, entry.end);
|
|
1865
|
+
|
|
1866
|
+
if (cappedInnerStart < cappedInnerEnd) {
|
|
1867
|
+
const relativeOffset = cappedInnerStart - innerStart;
|
|
1868
|
+
|
|
1869
|
+
// Fill the relevant section of the bytes with the cached data
|
|
1870
|
+
bytes.set(
|
|
1871
|
+
entry.bytes.subarray(cappedInnerStart - entry.start, cappedInnerEnd - entry.start),
|
|
1872
|
+
relativeOffset,
|
|
1873
|
+
);
|
|
1874
|
+
|
|
1875
|
+
if (relativeOffset === contiguousBytesWriteEnd) {
|
|
1876
|
+
contiguousBytesWriteEnd = cappedInnerEnd - innerStart;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
entry.age = this.nextAge++;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if (lastEnd < outerEnd) {
|
|
1884
|
+
outerHoles.push({ start: lastEnd, end: outerEnd });
|
|
1885
|
+
}
|
|
1886
|
+
} else {
|
|
1887
|
+
outerHoles.push({ start: outerStart, end: outerEnd });
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (bytes && contiguousBytesWriteEnd >= bytes.length) {
|
|
1891
|
+
// Multiple cache entries were able to completely cover the requested bytes!
|
|
1892
|
+
result = {
|
|
1893
|
+
bytes,
|
|
1894
|
+
view: toDataView(bytes),
|
|
1895
|
+
offset: innerStart,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
if (outerHoles.length === 0) {
|
|
1900
|
+
assert(result);
|
|
1901
|
+
return result;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// We need to read more data, so now we're in async land
|
|
1905
|
+
const { promise, resolve, reject } = promiseWithResolvers<Uint8Array | null>();
|
|
1906
|
+
|
|
1907
|
+
const innerHoles: typeof outerHoles = [];
|
|
1908
|
+
for (const outerHole of outerHoles) {
|
|
1909
|
+
const cappedStart = Math.max(innerStart, outerHole.start);
|
|
1910
|
+
const cappedEnd = Math.min(innerEnd, outerHole.end);
|
|
1911
|
+
|
|
1912
|
+
if (cappedStart === outerHole.start && cappedEnd === outerHole.end) {
|
|
1913
|
+
innerHoles.push(outerHole); // Can reuse without allocating a new object
|
|
1914
|
+
} else if (cappedStart < cappedEnd) {
|
|
1915
|
+
innerHoles.push({ start: cappedStart, end: cappedEnd });
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
const pendingSlice: PendingSlice | null = bytes && {
|
|
1920
|
+
start: innerStart,
|
|
1921
|
+
bytes,
|
|
1922
|
+
holes: innerHoles,
|
|
1923
|
+
resolve,
|
|
1924
|
+
reject,
|
|
1925
|
+
};
|
|
1926
|
+
|
|
1927
|
+
// Fire off workers to take care of patching the holes
|
|
1928
|
+
outer:
|
|
1929
|
+
for (const outerHole of outerHoles) {
|
|
1930
|
+
for (const worker of this.workers) {
|
|
1931
|
+
const addedToWorker = this.checkHoleAgainstWorker(
|
|
1932
|
+
worker,
|
|
1933
|
+
outerHole,
|
|
1934
|
+
pendingSlice ? [pendingSlice] : [],
|
|
1935
|
+
);
|
|
1936
|
+
|
|
1937
|
+
if (addedToWorker) {
|
|
1938
|
+
this.checkQueuedReadsAgainstWorker(worker);
|
|
1939
|
+
continue outer;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// We need to spawn a new worker
|
|
1944
|
+
const strictTarget = outerHole.end < outerEnd || this.fileSize !== null;
|
|
1945
|
+
const newWorker = this.createWorker(outerHole.start, outerHole.end, strictTarget);
|
|
1946
|
+
|
|
1947
|
+
if (newWorker) {
|
|
1948
|
+
if (pendingSlice) {
|
|
1949
|
+
newWorker.pendingSlices = [pendingSlice];
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
this.runWorker(newWorker);
|
|
1953
|
+
} else {
|
|
1954
|
+
// Max worker count has been reached, let's queue a read for later
|
|
1955
|
+
|
|
1956
|
+
let index = binarySearchLessOrEqual(this.queuedReads, outerHole.start, x => x.hole.start);
|
|
1957
|
+
let entry = index !== -1
|
|
1958
|
+
? this.queuedReads[index]!
|
|
1959
|
+
: null;
|
|
1960
|
+
|
|
1961
|
+
if (entry && outerHole.start <= entry.hole.end) {
|
|
1962
|
+
entry.hole.end = Math.max(entry.hole.end, outerHole.end);
|
|
1963
|
+
entry.strictTarget &&= strictTarget;
|
|
1964
|
+
|
|
1965
|
+
if (pendingSlice) {
|
|
1966
|
+
entry.pendingSlices.push(pendingSlice);
|
|
1967
|
+
}
|
|
1968
|
+
} else {
|
|
1969
|
+
index++;
|
|
1970
|
+
entry = {
|
|
1971
|
+
hole: {
|
|
1972
|
+
// Clone the hole because it might be mutated later
|
|
1973
|
+
start: outerHole.start,
|
|
1974
|
+
end: outerHole.end,
|
|
1975
|
+
},
|
|
1976
|
+
strictTarget,
|
|
1977
|
+
pendingSlices: pendingSlice ? [pendingSlice] : [],
|
|
1978
|
+
age: this.nextAge++,
|
|
1979
|
+
};
|
|
1980
|
+
this.queuedReads.splice(index, 0, entry);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Merge with any subsequent entries that overlap
|
|
1984
|
+
while (index + 1 < this.queuedReads.length) {
|
|
1985
|
+
const nextEntry = this.queuedReads[index + 1]!;
|
|
1986
|
+
if (nextEntry.hole.start > entry.hole.end) {
|
|
1987
|
+
break;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
entry.hole.end = Math.max(entry.hole.end, nextEntry.hole.end);
|
|
1991
|
+
entry.pendingSlices.push(...nextEntry.pendingSlices);
|
|
1992
|
+
entry.strictTarget &&= nextEntry.strictTarget;
|
|
1993
|
+
entry.age = Math.min(entry.age, nextEntry.age);
|
|
1994
|
+
this.queuedReads.splice(index + 1, 1);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
if (!result) {
|
|
2000
|
+
assert(bytes);
|
|
2001
|
+
|
|
2002
|
+
result = promise.then(bytes => bytes && ({
|
|
2003
|
+
bytes,
|
|
2004
|
+
view: toDataView(bytes),
|
|
2005
|
+
offset: innerStart,
|
|
2006
|
+
} satisfies ReadResult));
|
|
2007
|
+
} else {
|
|
2008
|
+
// The requested region was satisfied by the cache, but the entire prefetch region was not
|
|
2009
|
+
promise.catch((error) => {
|
|
2010
|
+
if (this.disposed) {
|
|
2011
|
+
return; // Swallow the error
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Nobody's awaiting this result but an errored read is still notable
|
|
2015
|
+
throw error;
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
return result;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
checkHoleAgainstWorker(worker: ReadWorker, hole: Hole, pendingSlices: PendingSlice[]) {
|
|
2023
|
+
// A small tolerance in the case that the requested region is *just* after the target position of an
|
|
2024
|
+
// existing worker. In that case, it's probably more efficient to repurpose that worker than to spawn
|
|
2025
|
+
// another one so close to it
|
|
2026
|
+
const gapTolerance = 2 ** 17;
|
|
2027
|
+
|
|
2028
|
+
// This check also implies worker.currentPos <= hole.start, a critical condition
|
|
2029
|
+
if (closedIntervalsOverlap(
|
|
2030
|
+
hole.start - gapTolerance, hole.start,
|
|
2031
|
+
worker.currentPos, worker.targetPos,
|
|
2032
|
+
)) {
|
|
2033
|
+
worker.targetPos = Math.max(worker.targetPos, hole.end); // Update the worker's target position
|
|
2034
|
+
|
|
2035
|
+
for (let i = 0; i < pendingSlices.length; i++) {
|
|
2036
|
+
const pendingSlice = pendingSlices[i]!;
|
|
2037
|
+
if (!worker.pendingSlices.includes(pendingSlice)) {
|
|
2038
|
+
worker.pendingSlices.push(pendingSlice);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
if (!worker.running) {
|
|
2043
|
+
// Kick it off if it's idle
|
|
2044
|
+
this.runWorker(worker);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
return false;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
checkQueuedReadsAgainstWorker(worker: ReadWorker) {
|
|
2054
|
+
let wasTrueOnce = false;
|
|
2055
|
+
|
|
2056
|
+
for (let i = 0; i < this.queuedReads.length; i++) {
|
|
2057
|
+
const queuedRead = this.queuedReads[i]!;
|
|
2058
|
+
const result = this.checkHoleAgainstWorker(worker, queuedRead.hole, queuedRead.pendingSlices);
|
|
2059
|
+
|
|
2060
|
+
if (result) {
|
|
2061
|
+
this.queuedReads.splice(i, 1);
|
|
2062
|
+
i--;
|
|
2063
|
+
wasTrueOnce = true;
|
|
2064
|
+
} else if (wasTrueOnce) {
|
|
2065
|
+
// We can stop since the holes are sorted
|
|
2066
|
+
break;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
createWorker(startPos: number, targetPos: number, strictTarget: boolean) {
|
|
2072
|
+
if (this.workers.length >= this.options.maxWorkerCount) {
|
|
2073
|
+
let oldestWorker: ReadWorker | null = null;
|
|
2074
|
+
let oldestIndex: number | null = null;
|
|
2075
|
+
|
|
2076
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
2077
|
+
const worker = this.workers[i]!;
|
|
2078
|
+
|
|
2079
|
+
if (
|
|
2080
|
+
!worker.running
|
|
2081
|
+
&& worker.pendingSlices.length === 0
|
|
2082
|
+
&& (!oldestWorker || worker.age < oldestWorker.age)
|
|
2083
|
+
) {
|
|
2084
|
+
oldestIndex = i;
|
|
2085
|
+
oldestWorker = worker;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
if (oldestWorker) {
|
|
2090
|
+
// LRU eviction
|
|
2091
|
+
assert(oldestIndex !== null);
|
|
2092
|
+
assert(oldestWorker.pendingSlices.length === 0);
|
|
2093
|
+
this.workers.splice(oldestIndex, 1);
|
|
2094
|
+
} else {
|
|
2095
|
+
return null; // All workers are still running, we can't create a new one
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
const worker: ReadWorker = {
|
|
2100
|
+
startPos,
|
|
2101
|
+
currentPos: startPos,
|
|
2102
|
+
targetPos,
|
|
2103
|
+
strictTarget,
|
|
2104
|
+
running: false,
|
|
2105
|
+
// Due to async shenanigans, it can happen that workers are started after disposal. In this case, instead of
|
|
2106
|
+
// simply not creating the worker, we allow it to run but immediately label it as aborted, so it can then
|
|
2107
|
+
// shut itself down.
|
|
2108
|
+
aborted: this.disposed,
|
|
2109
|
+
pendingSlices: [],
|
|
2110
|
+
age: this.nextAge++,
|
|
2111
|
+
};
|
|
2112
|
+
this.workers.push(worker);
|
|
2113
|
+
|
|
2114
|
+
return worker;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
runWorker(worker: ReadWorker) {
|
|
2118
|
+
assert(!worker.running);
|
|
2119
|
+
assert(worker.currentPos < worker.targetPos);
|
|
2120
|
+
|
|
2121
|
+
worker.running = true;
|
|
2122
|
+
worker.age = this.nextAge++;
|
|
2123
|
+
|
|
2124
|
+
void this.options.runWorker(worker)
|
|
2125
|
+
.catch((error) => {
|
|
2126
|
+
worker.running = false;
|
|
2127
|
+
|
|
2128
|
+
if (worker.pendingSlices.length > 0) {
|
|
2129
|
+
worker.pendingSlices.forEach(x => x.reject(error)); // Make sure to propagate any errors
|
|
2130
|
+
worker.pendingSlices.length = 0;
|
|
2131
|
+
} else if (!worker.aborted && !this.disposed) {
|
|
2132
|
+
throw error; // So it doesn't get swallowed
|
|
2133
|
+
}
|
|
2134
|
+
})
|
|
2135
|
+
.finally(() => {
|
|
2136
|
+
if (worker.running) {
|
|
2137
|
+
// Rare, but can happen with multiple concurrent reads. In this case, don't do anything.
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
if (this.queuedReads.length > 0) {
|
|
2142
|
+
let oldestIndex = 0;
|
|
2143
|
+
for (let i = 1; i < this.queuedReads.length; i++) {
|
|
2144
|
+
const queuedRead = this.queuedReads[i]!;
|
|
2145
|
+
if (queuedRead.age < this.queuedReads[oldestIndex]!.age) {
|
|
2146
|
+
oldestIndex = i;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
const queuedRead = this.queuedReads[oldestIndex]!;
|
|
2151
|
+
|
|
2152
|
+
const newWorker = this.createWorker(
|
|
2153
|
+
queuedRead.hole.start,
|
|
2154
|
+
queuedRead.hole.end,
|
|
2155
|
+
queuedRead.strictTarget,
|
|
2156
|
+
);
|
|
2157
|
+
if (!newWorker) {
|
|
2158
|
+
// In high-contention cases, it could be that we've already reached max worker count, so in this
|
|
2159
|
+
// case we don't do anything.
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
this.queuedReads.splice(oldestIndex, 1);
|
|
2164
|
+
newWorker.pendingSlices = queuedRead.pendingSlices;
|
|
2165
|
+
this.runWorker(newWorker);
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
consolidateEverythingIntoOneWorker(worker: ReadWorker) {
|
|
2171
|
+
// Here we merge everything into one "megaworker" that spans the entire file. We assume the passed-in worker
|
|
2172
|
+
// is already configured to be a megaworker.
|
|
2173
|
+
|
|
2174
|
+
const uniqueSlices = new Set(worker.pendingSlices);
|
|
2175
|
+
|
|
2176
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
2177
|
+
const otherWorker = this.workers[i]!;
|
|
2178
|
+
if (otherWorker === worker) {
|
|
2179
|
+
continue;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
for (const slice of otherWorker.pendingSlices) {
|
|
2183
|
+
uniqueSlices.add(slice);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
otherWorker.aborted = true;
|
|
2187
|
+
otherWorker.pendingSlices.length = 0;
|
|
2188
|
+
this.workers.splice(i, 1);
|
|
2189
|
+
i--;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
for (let i = 0; i < this.queuedReads.length; i++) {
|
|
2193
|
+
const queuedRead = this.queuedReads[i]!;
|
|
2194
|
+
|
|
2195
|
+
for (const slice of queuedRead.pendingSlices) {
|
|
2196
|
+
uniqueSlices.add(slice);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
worker.pendingSlices = [...uniqueSlices];
|
|
2201
|
+
this.queuedReads.length = 0;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
/** Called by a worker when it has read some data. */
|
|
2205
|
+
supplyWorkerData(worker: ReadWorker, bytes: Uint8Array) {
|
|
2206
|
+
assert(!worker.aborted);
|
|
2207
|
+
|
|
2208
|
+
const start = worker.currentPos;
|
|
2209
|
+
const end = start + bytes.length;
|
|
2210
|
+
|
|
2211
|
+
this.insertIntoCache({
|
|
2212
|
+
start,
|
|
2213
|
+
end,
|
|
2214
|
+
bytes,
|
|
2215
|
+
view: toDataView(bytes),
|
|
2216
|
+
age: this.nextAge++,
|
|
2217
|
+
});
|
|
2218
|
+
worker.currentPos += bytes.length;
|
|
2219
|
+
|
|
2220
|
+
if (worker.currentPos > worker.targetPos) {
|
|
2221
|
+
// In case it overshoots
|
|
2222
|
+
worker.targetPos = worker.currentPos;
|
|
2223
|
+
this.checkQueuedReadsAgainstWorker(worker);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
// Now, let's see if we can use the read bytes to fill any pending slice
|
|
2227
|
+
for (let i = 0; i < worker.pendingSlices.length; i++) {
|
|
2228
|
+
const pendingSlice = worker.pendingSlices[i]!;
|
|
2229
|
+
|
|
2230
|
+
const clampedStart = Math.max(start, pendingSlice.start);
|
|
2231
|
+
const clampedEnd = Math.min(end, pendingSlice.start + pendingSlice.bytes.length);
|
|
2232
|
+
|
|
2233
|
+
if (clampedStart < clampedEnd) {
|
|
2234
|
+
pendingSlice.bytes.set(
|
|
2235
|
+
bytes.subarray(clampedStart - start, clampedEnd - start),
|
|
2236
|
+
clampedStart - pendingSlice.start,
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
for (let j = 0; j < pendingSlice.holes.length; j++) {
|
|
2241
|
+
// The hole is intentionally not modified here if the read section starts somewhere in the middle of
|
|
2242
|
+
// the hole. We don't need to do "hole splitting", since the workers are spawned *by* the holes,
|
|
2243
|
+
// meaning there's always a worker which will consume the hole left to right.
|
|
2244
|
+
const hole = pendingSlice.holes[j]!;
|
|
2245
|
+
if (start <= hole.start && end > hole.start) {
|
|
2246
|
+
hole.start = end;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
if (hole.end <= hole.start) {
|
|
2250
|
+
pendingSlice.holes.splice(j, 1);
|
|
2251
|
+
j--;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
if (pendingSlice.holes.length === 0) {
|
|
2256
|
+
// The slice has been fulfilled, everything has been read. Let's resolve the promise
|
|
2257
|
+
pendingSlice.resolve(pendingSlice.bytes);
|
|
2258
|
+
worker.pendingSlices.splice(i, 1);
|
|
2259
|
+
i--;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// Remove other idle workers if we "ate" into their territory
|
|
2264
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
2265
|
+
const otherWorker = this.workers[i]!;
|
|
2266
|
+
if (worker === otherWorker || otherWorker.running) {
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if (closedIntervalsOverlap(
|
|
2271
|
+
start, end,
|
|
2272
|
+
otherWorker.currentPos, otherWorker.targetPos, // These should typically be equal when the worker's idle
|
|
2273
|
+
)) {
|
|
2274
|
+
this.workers.splice(i, 1);
|
|
2275
|
+
i--;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
supplyFileSize(size: number) {
|
|
2281
|
+
assert(this.fileSize === null);
|
|
2282
|
+
|
|
2283
|
+
this.fileSize = size;
|
|
2284
|
+
|
|
2285
|
+
// Trim the workers with this new information
|
|
2286
|
+
for (const worker of this.workers) {
|
|
2287
|
+
worker.targetPos = Math.min(worker.targetPos, size);
|
|
2288
|
+
worker.strictTarget = true;
|
|
2289
|
+
|
|
2290
|
+
for (let i = 0; i < worker.pendingSlices.length; i++) {
|
|
2291
|
+
const pendingSlice = worker.pendingSlices[i]!;
|
|
2292
|
+
|
|
2293
|
+
for (const hole of pendingSlice.holes) {
|
|
2294
|
+
if (hole.end > size) {
|
|
2295
|
+
// Can't satisfy this slice anymore
|
|
2296
|
+
pendingSlice.resolve(null);
|
|
2297
|
+
worker.pendingSlices.splice(i, 1);
|
|
2298
|
+
i--;
|
|
2299
|
+
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// Trim the queued reads as well
|
|
2307
|
+
for (let i = 0; i < this.queuedReads.length; i++) {
|
|
2308
|
+
const queuedRead = this.queuedReads[i]!;
|
|
2309
|
+
if (queuedRead.hole.start >= size) {
|
|
2310
|
+
// Entirely out of bounds
|
|
2311
|
+
for (const slice of queuedRead.pendingSlices) slice.resolve(null);
|
|
2312
|
+
this.queuedReads.splice(i, 1);
|
|
2313
|
+
i--;
|
|
2314
|
+
} else if (queuedRead.hole.end > size) {
|
|
2315
|
+
// Partially out of bounds
|
|
2316
|
+
queuedRead.hole.end = size;
|
|
2317
|
+
queuedRead.strictTarget = true;
|
|
2318
|
+
|
|
2319
|
+
for (let j = 0; j < queuedRead.pendingSlices.length; j++) {
|
|
2320
|
+
const slice = queuedRead.pendingSlices[j]!;
|
|
2321
|
+
// If the slice itself is out of bounds, resolve it
|
|
2322
|
+
if (slice.start >= size) {
|
|
2323
|
+
slice.resolve(null);
|
|
2324
|
+
queuedRead.pendingSlices.splice(j, 1);
|
|
2325
|
+
j--;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
signalWorkerStoppedRunning(worker: ReadWorker) {
|
|
2333
|
+
worker.running = false;
|
|
2334
|
+
|
|
2335
|
+
if (!worker.aborted) {
|
|
2336
|
+
// When a worker stops running, that means it has hit its targetPos. It might still have pendingSlices
|
|
2337
|
+
// assigned, but this is because those pending slices cover data that other workers are assigned to fill.
|
|
2338
|
+
// Since targetPos has been reached, we can confidently say that this worker has completed its share of work
|
|
2339
|
+
// on the pending slices and must no longer care about them.
|
|
2340
|
+
worker.pendingSlices.length = 0;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
/** Called when a worker reaches the end of the underlying data and must be cleaned up. */
|
|
2345
|
+
onWorkerFinished(worker: ReadWorker) {
|
|
2346
|
+
const index = this.workers.indexOf(worker);
|
|
2347
|
+
assert(index !== -1);
|
|
2348
|
+
|
|
2349
|
+
worker.running = false;
|
|
2350
|
+
this.workers.splice(index, 1);
|
|
2351
|
+
|
|
2352
|
+
if (this.fileSize === null) {
|
|
2353
|
+
// We can now deduce the file size!
|
|
2354
|
+
this.supplyFileSize(worker.currentPos);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
for (const pendingSlice of worker.pendingSlices) {
|
|
2358
|
+
pendingSlice.resolve(null);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
insertIntoCache(entry: CacheEntry) {
|
|
2363
|
+
if (this.options.maxCacheSize === 0) {
|
|
2364
|
+
return; // No caching
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
let insertionIndex = binarySearchLessOrEqual(this.cache, entry.start, x => x.start) + 1;
|
|
2368
|
+
|
|
2369
|
+
if (insertionIndex > 0) {
|
|
2370
|
+
const previous = this.cache[insertionIndex - 1]!;
|
|
2371
|
+
if (previous.end >= entry.end) {
|
|
2372
|
+
// Previous entry swallows the one to be inserted; we don't need to do anything
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
if (previous.end > entry.start) {
|
|
2377
|
+
// Partial overlap with the previous entry, let's join
|
|
2378
|
+
const joined = new Uint8Array(entry.end - previous.start);
|
|
2379
|
+
joined.set(previous.bytes, 0);
|
|
2380
|
+
joined.set(entry.bytes, entry.start - previous.start);
|
|
2381
|
+
|
|
2382
|
+
this.currentCacheSize += entry.end - previous.end;
|
|
2383
|
+
|
|
2384
|
+
previous.bytes = joined;
|
|
2385
|
+
previous.view = toDataView(joined);
|
|
2386
|
+
previous.end = entry.end;
|
|
2387
|
+
|
|
2388
|
+
// Do the rest of the logic with the previous entry instead
|
|
2389
|
+
insertionIndex--;
|
|
2390
|
+
entry = previous;
|
|
2391
|
+
} else {
|
|
2392
|
+
this.cache.splice(insertionIndex, 0, entry);
|
|
2393
|
+
this.currentCacheSize += entry.bytes.length;
|
|
2394
|
+
}
|
|
2395
|
+
} else {
|
|
2396
|
+
this.cache.splice(insertionIndex, 0, entry);
|
|
2397
|
+
this.currentCacheSize += entry.bytes.length;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
for (let i = insertionIndex + 1; i < this.cache.length; i++) {
|
|
2401
|
+
const next = this.cache[i]!;
|
|
2402
|
+
if (entry.end <= next.start) {
|
|
2403
|
+
// Even if they touch, we don't wanna merge them, no need
|
|
2404
|
+
break;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
if (entry.end >= next.end) {
|
|
2408
|
+
// The inserted entry completely swallows the next entry
|
|
2409
|
+
this.cache.splice(i, 1);
|
|
2410
|
+
this.currentCacheSize -= next.bytes.length;
|
|
2411
|
+
i--;
|
|
2412
|
+
continue;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Partial overlap, let's join
|
|
2416
|
+
const joined = new Uint8Array(next.end - entry.start);
|
|
2417
|
+
joined.set(entry.bytes, 0);
|
|
2418
|
+
joined.set(next.bytes, next.start - entry.start);
|
|
2419
|
+
|
|
2420
|
+
this.currentCacheSize -= entry.end - next.start; // Subtract the overlap
|
|
2421
|
+
|
|
2422
|
+
entry.bytes = joined;
|
|
2423
|
+
entry.view = toDataView(joined);
|
|
2424
|
+
entry.end = next.end;
|
|
2425
|
+
this.cache.splice(i, 1);
|
|
2426
|
+
|
|
2427
|
+
break; // After the join case, we're done: the next entry cannot possibly overlap with the inserted one.
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// LRU eviction of cache entries
|
|
2431
|
+
while (this.currentCacheSize > this.options.maxCacheSize) {
|
|
2432
|
+
let oldestIndex = 0;
|
|
2433
|
+
let oldestEntry = this.cache[0]!;
|
|
2434
|
+
|
|
2435
|
+
for (let i = 1; i < this.cache.length; i++) {
|
|
2436
|
+
const entry = this.cache[i]!;
|
|
2437
|
+
|
|
2438
|
+
if (entry.age < oldestEntry.age) {
|
|
2439
|
+
oldestIndex = i;
|
|
2440
|
+
oldestEntry = entry;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
if (this.currentCacheSize - oldestEntry.bytes.length <= this.options.maxCacheSize) {
|
|
2445
|
+
// Don't evict if it would shrink the cache below the max size
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
this.cache.splice(oldestIndex, 1);
|
|
2450
|
+
this.currentCacheSize -= oldestEntry.bytes.length;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
dispose() {
|
|
2455
|
+
for (const worker of this.workers) {
|
|
2456
|
+
for (const slice of worker.pendingSlices) {
|
|
2457
|
+
slice.reject(new InputDisposedError());
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
worker.pendingSlices.length = 0;
|
|
2461
|
+
worker.aborted = true;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
for (const queuedRead of this.queuedReads) {
|
|
2465
|
+
for (const slice of queuedRead.pendingSlices) {
|
|
2466
|
+
slice.reject(new InputDisposedError());
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
this.workers.length = 0;
|
|
2471
|
+
this.cache.length = 0;
|
|
2472
|
+
this.queuedReads.length = 0;
|
|
2473
|
+
this.disposed = true;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
/**
|
|
2478
|
+
* A dummy source from which no data can be read. Can be used in conjunction with input formats that get their data
|
|
2479
|
+
* from another source.
|
|
2480
|
+
*/
|
|
2481
|
+
export class NullSource extends Source {
|
|
2482
|
+
override _getFileSize(): number | null {
|
|
2483
|
+
return null;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
override _read(): MaybePromise<ReadResult | null> {
|
|
2487
|
+
return null;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
override _dispose(): void {
|
|
2491
|
+
// Do nothing
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
/**
|
|
2496
|
+
* A source that covers a range (offset + length) of another source. Useful for reading files that are embedded within
|
|
2497
|
+
* larger files.
|
|
2498
|
+
*
|
|
2499
|
+
* @group Input sources
|
|
2500
|
+
* @public
|
|
2501
|
+
*/
|
|
2502
|
+
export class RangedSource extends Source {
|
|
2503
|
+
/** @internal */
|
|
2504
|
+
_baseSource: Source;
|
|
2505
|
+
/** @internal */
|
|
2506
|
+
_ref: SourceRef | null = null;
|
|
2507
|
+
/** @internal */
|
|
2508
|
+
_offset: number;
|
|
2509
|
+
/** @internal */
|
|
2510
|
+
_length: number | null;
|
|
2511
|
+
|
|
2512
|
+
/** @internal */
|
|
2513
|
+
constructor(baseSource: Source, offset: number, length?: number) {
|
|
2514
|
+
super();
|
|
2515
|
+
|
|
2516
|
+
if (baseSource._disposed) {
|
|
2517
|
+
throw new Error('Cannot create a slice of a disposed source.');
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
this._baseSource = baseSource;
|
|
2521
|
+
this._offset = offset;
|
|
2522
|
+
this._length = length ?? null;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
/** @internal */
|
|
2526
|
+
override _getFileSize(): number | null | undefined {
|
|
2527
|
+
const baseSize = this._baseSource._getFileSize();
|
|
2528
|
+
if (baseSize === undefined) {
|
|
2529
|
+
return this._length !== null
|
|
2530
|
+
? this._length
|
|
2531
|
+
: undefined;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
if (baseSize === null) {
|
|
2535
|
+
if (this._length !== null) {
|
|
2536
|
+
return this._length;
|
|
2537
|
+
} else {
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
return clamp(baseSize - this._offset, 0, this._length ?? Infinity);
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
/** @internal */
|
|
2546
|
+
override _read(
|
|
2547
|
+
start: number,
|
|
2548
|
+
end: number,
|
|
2549
|
+
minReadPosition: number,
|
|
2550
|
+
maxReadPosition: number,
|
|
2551
|
+
): MaybePromise<ReadResult | null> {
|
|
2552
|
+
if (this._length !== null && end > this._length) {
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
const result = this._baseSource._read(
|
|
2557
|
+
this._offset + start,
|
|
2558
|
+
this._offset + end,
|
|
2559
|
+
this._offset + minReadPosition,
|
|
2560
|
+
this._offset + maxReadPosition,
|
|
2561
|
+
);
|
|
2562
|
+
|
|
2563
|
+
const processResult = (result: ReadResult | null) => {
|
|
2564
|
+
if (!result) {
|
|
2565
|
+
return null;
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
result.offset -= this._offset;
|
|
2569
|
+
return result;
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
if (result instanceof Promise) {
|
|
2573
|
+
return result.then(processResult);
|
|
2574
|
+
} else {
|
|
2575
|
+
return processResult(result);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
/** @internal */
|
|
2580
|
+
override _dispose(): void {
|
|
2581
|
+
this._ref?.free();
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
override ref() {
|
|
2585
|
+
this._ref ??= this._baseSource.ref();
|
|
2586
|
+
return super.ref();
|
|
2587
|
+
}
|
|
2588
|
+
}
|