@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,2589 @@
|
|
|
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 { parsePcmCodec, PCM_AUDIO_CODECS, PcmAudioCodec, VideoCodec, AudioCodec } from './codec';
|
|
10
|
+
import {
|
|
11
|
+
AvcNalUnitType,
|
|
12
|
+
concatAvcNalUnits,
|
|
13
|
+
deserializeAvcDecoderConfigurationRecord,
|
|
14
|
+
determineVideoPacketType,
|
|
15
|
+
extractNalUnitTypeForAvc,
|
|
16
|
+
extractNalUnitTypeForHevc,
|
|
17
|
+
HevcNalUnitType,
|
|
18
|
+
iterateAvcNalUnits,
|
|
19
|
+
iterateHevcNalUnits,
|
|
20
|
+
parseAvcSps,
|
|
21
|
+
sanitizeHevcPacketForChromium,
|
|
22
|
+
} from './codec-data';
|
|
23
|
+
import { CustomVideoDecoder, customVideoDecoders, CustomAudioDecoder, customAudioDecoders } from './custom-coder';
|
|
24
|
+
import { InputDisposedError } from './input';
|
|
25
|
+
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
|
|
26
|
+
import {
|
|
27
|
+
AnyIterable,
|
|
28
|
+
assert,
|
|
29
|
+
assertNever,
|
|
30
|
+
CallSerializer,
|
|
31
|
+
clamp,
|
|
32
|
+
getInt24,
|
|
33
|
+
getUint24,
|
|
34
|
+
insertSorted,
|
|
35
|
+
isChromium,
|
|
36
|
+
isFirefox,
|
|
37
|
+
isNumber,
|
|
38
|
+
isWebKit,
|
|
39
|
+
last,
|
|
40
|
+
mapAsyncGenerator,
|
|
41
|
+
promiseWithResolvers,
|
|
42
|
+
removeItem,
|
|
43
|
+
Rotation,
|
|
44
|
+
toAsyncIterator,
|
|
45
|
+
toDataView,
|
|
46
|
+
toUint8Array,
|
|
47
|
+
validateAnyIterable,
|
|
48
|
+
} from './misc';
|
|
49
|
+
import { EncodedPacket } from './packet';
|
|
50
|
+
import { fromAlaw, fromUlaw } from './pcm';
|
|
51
|
+
import {
|
|
52
|
+
AudioSample,
|
|
53
|
+
clampCropRectangle,
|
|
54
|
+
CropRectangle,
|
|
55
|
+
validateCropRectangle,
|
|
56
|
+
VideoSample,
|
|
57
|
+
VideoSamplePixelFormat,
|
|
58
|
+
} from './sample';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Additional options for controlling packet retrieval.
|
|
62
|
+
* @group Media sinks
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
export type PacketRetrievalOptions = {
|
|
66
|
+
/**
|
|
67
|
+
* When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not
|
|
68
|
+
* be loaded.
|
|
69
|
+
*/
|
|
70
|
+
metadataOnly?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* When set to `true`, key packets will be verified upon retrieval by looking into the packet's bitstream.
|
|
74
|
+
* If not enabled, the packet types will be determined solely by what's stored in the containing file and may be
|
|
75
|
+
* incorrect, potentially leading to decoder errors. Since determining a packet's actual type requires looking into
|
|
76
|
+
* its data, this option cannot be enabled together with `metadataOnly`.
|
|
77
|
+
*/
|
|
78
|
+
verifyKeyPackets?: boolean;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* When querying packets in live media that are in the future relative to the current live edge, Mediabunny will,
|
|
82
|
+
* by default, wait for the stream to advance until the query can be satisfied. In a sense, Mediabunny simply treats
|
|
83
|
+
* live streams as media files that are still being written, and any read that depends on future information will
|
|
84
|
+
* wait until it can be fulfilled.
|
|
85
|
+
*
|
|
86
|
+
* If you want to query packets based only on the currently known information, set this field to `true` - this way,
|
|
87
|
+
* Mediabunny will never wait for the live stream to catch up.
|
|
88
|
+
*
|
|
89
|
+
* For non-live media, this field has no effect.
|
|
90
|
+
*/
|
|
91
|
+
skipLiveWait?: boolean;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const validatePacketRetrievalOptions = (options: PacketRetrievalOptions) => {
|
|
95
|
+
if (!options || typeof options !== 'object') {
|
|
96
|
+
throw new TypeError('options must be an object.');
|
|
97
|
+
}
|
|
98
|
+
if (options.metadataOnly !== undefined && typeof options.metadataOnly !== 'boolean') {
|
|
99
|
+
throw new TypeError('options.metadataOnly, when defined, must be a boolean.');
|
|
100
|
+
}
|
|
101
|
+
if (options.verifyKeyPackets !== undefined && typeof options.verifyKeyPackets !== 'boolean') {
|
|
102
|
+
throw new TypeError('options.verifyKeyPackets, when defined, must be a boolean.');
|
|
103
|
+
}
|
|
104
|
+
if (options.verifyKeyPackets && options.metadataOnly) {
|
|
105
|
+
throw new TypeError('options.verifyKeyPackets and options.metadataOnly cannot be enabled together.');
|
|
106
|
+
}
|
|
107
|
+
if (options.skipLiveWait !== undefined && typeof options.skipLiveWait !== 'boolean') {
|
|
108
|
+
throw new TypeError('options.skipLiveWait, when defined, must be a boolean.');
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const validateTimestamp = (timestamp: number) => {
|
|
113
|
+
if (!isNumber(timestamp)) {
|
|
114
|
+
throw new TypeError('timestamp must be a number.'); // It can be non-finite, that's fine
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const maybeFixPacketType = (
|
|
119
|
+
track: InputTrack,
|
|
120
|
+
promise: Promise<EncodedPacket | null>,
|
|
121
|
+
options: PacketRetrievalOptions,
|
|
122
|
+
) => {
|
|
123
|
+
if (options.verifyKeyPackets) {
|
|
124
|
+
return promise.then(async (packet) => {
|
|
125
|
+
if (!packet || packet.type === 'delta') {
|
|
126
|
+
return packet;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const determinedType = await track.determinePacketType(packet);
|
|
130
|
+
if (determinedType) {
|
|
131
|
+
// @ts-expect-error Technically readonly
|
|
132
|
+
packet.type = determinedType;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return packet;
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
return promise;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Sink for retrieving encoded packets from an input track.
|
|
144
|
+
* @group Media sinks
|
|
145
|
+
* @public
|
|
146
|
+
*/
|
|
147
|
+
export class EncodedPacketSink {
|
|
148
|
+
/** @internal */
|
|
149
|
+
_track: InputTrack;
|
|
150
|
+
|
|
151
|
+
/** Creates a new {@link EncodedPacketSink} for the given {@link InputTrack}. */
|
|
152
|
+
constructor(track: InputTrack) {
|
|
153
|
+
if (!(track instanceof InputTrack)) {
|
|
154
|
+
throw new TypeError('track must be an InputTrack.');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this._track = track;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Retrieves the track's first packet (in decode order), or null if it has no packets. The first packet is very
|
|
162
|
+
* likely to be a key packet, but it doesn't have to be.
|
|
163
|
+
*/
|
|
164
|
+
async getFirstPacket(options: PacketRetrievalOptions = {}) {
|
|
165
|
+
validatePacketRetrievalOptions(options);
|
|
166
|
+
|
|
167
|
+
if (this._track.input._disposed) {
|
|
168
|
+
throw new InputDisposedError();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return maybeFixPacketType(this._track, this._track._backing.getFirstPacket(options), options);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Retrieves the track's first key packet (in decode order), or null if it has no key packets. */
|
|
175
|
+
async getFirstKeyPacket(options: PacketRetrievalOptions = {}) {
|
|
176
|
+
validatePacketRetrievalOptions(options);
|
|
177
|
+
|
|
178
|
+
const firstPacket = await this.getFirstPacket(options);
|
|
179
|
+
if (!firstPacket) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (firstPacket.type === 'key') {
|
|
184
|
+
// Great
|
|
185
|
+
return firstPacket;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return this.getNextKeyPacket(firstPacket, options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Retrieves the packet corresponding to the given timestamp, in seconds. More specifically, returns the last packet
|
|
193
|
+
* (in presentation order) with a start timestamp less than or equal to the given timestamp. This method can be
|
|
194
|
+
* used to retrieve a track's last packet using `getPacket(Infinity)`. The method returns null if the timestamp
|
|
195
|
+
* is before the first packet in the track.
|
|
196
|
+
*
|
|
197
|
+
* @param timestamp - The timestamp used for retrieval, in seconds.
|
|
198
|
+
*/
|
|
199
|
+
async getPacket(timestamp: number, options: PacketRetrievalOptions = {}) {
|
|
200
|
+
validateTimestamp(timestamp);
|
|
201
|
+
validatePacketRetrievalOptions(options);
|
|
202
|
+
|
|
203
|
+
if (this._track.input._disposed) {
|
|
204
|
+
throw new InputDisposedError();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return maybeFixPacketType(this._track, this._track._backing.getPacket(timestamp, options), options);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Retrieves the packet following the given packet (in decode order), or null if the given packet is the
|
|
212
|
+
* last packet.
|
|
213
|
+
*/
|
|
214
|
+
async getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions = {}) {
|
|
215
|
+
if (!(packet instanceof EncodedPacket)) {
|
|
216
|
+
throw new TypeError('packet must be an EncodedPacket.');
|
|
217
|
+
}
|
|
218
|
+
validatePacketRetrievalOptions(options);
|
|
219
|
+
|
|
220
|
+
if (this._track.input._disposed) {
|
|
221
|
+
throw new InputDisposedError();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return maybeFixPacketType(this._track, this._track._backing.getNextPacket(packet, options), options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Retrieves the key packet corresponding to the given timestamp, in seconds. More specifically, returns the last
|
|
229
|
+
* key packet (in presentation order) with a start timestamp less than or equal to the given timestamp. A key packet
|
|
230
|
+
* is a packet that doesn't require previous packets to be decoded. This method can be used to retrieve a track's
|
|
231
|
+
* last key packet using `getKeyPacket(Infinity)`. The method returns null if the timestamp is before the first
|
|
232
|
+
* key packet in the track.
|
|
233
|
+
*
|
|
234
|
+
* To ensure that the returned packet is guaranteed to be a real key frame, enable `options.verifyKeyPackets`.
|
|
235
|
+
*
|
|
236
|
+
* @param timestamp - The timestamp used for retrieval, in seconds.
|
|
237
|
+
*/
|
|
238
|
+
async getKeyPacket(timestamp: number, options: PacketRetrievalOptions = {}): Promise<EncodedPacket | null> {
|
|
239
|
+
validateTimestamp(timestamp);
|
|
240
|
+
validatePacketRetrievalOptions(options);
|
|
241
|
+
|
|
242
|
+
if (this._track.input._disposed) {
|
|
243
|
+
throw new InputDisposedError();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!options.verifyKeyPackets) {
|
|
247
|
+
return this._track._backing.getKeyPacket(timestamp, options);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const packet = await this._track._backing.getKeyPacket(timestamp, options);
|
|
251
|
+
if (!packet) {
|
|
252
|
+
return packet;
|
|
253
|
+
}
|
|
254
|
+
assert(packet.type === 'key');
|
|
255
|
+
|
|
256
|
+
const determinedType = await this._track.determinePacketType(packet);
|
|
257
|
+
if (determinedType === 'delta') {
|
|
258
|
+
// Try returning the previous key packet (in hopes that it's actually a key packet)
|
|
259
|
+
return this.getKeyPacket(packet.timestamp - 1 / await this._track.getTimeResolution(), options);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return packet;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Retrieves the key packet following the given packet (in decode order), or null if the given packet is the last
|
|
267
|
+
* key packet.
|
|
268
|
+
*
|
|
269
|
+
* To ensure that the returned packet is guaranteed to be a real key frame, enable `options.verifyKeyPackets`.
|
|
270
|
+
*/
|
|
271
|
+
async getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions = {}): Promise<EncodedPacket | null> {
|
|
272
|
+
if (!(packet instanceof EncodedPacket)) {
|
|
273
|
+
throw new TypeError('packet must be an EncodedPacket.');
|
|
274
|
+
}
|
|
275
|
+
validatePacketRetrievalOptions(options);
|
|
276
|
+
|
|
277
|
+
if (this._track.input._disposed) {
|
|
278
|
+
throw new InputDisposedError();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!options.verifyKeyPackets) {
|
|
282
|
+
return this._track._backing.getNextKeyPacket(packet, options);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const nextPacket = await this._track._backing.getNextKeyPacket(packet, options);
|
|
286
|
+
if (!nextPacket) {
|
|
287
|
+
return nextPacket;
|
|
288
|
+
}
|
|
289
|
+
assert(nextPacket.type === 'key');
|
|
290
|
+
|
|
291
|
+
const determinedType = await this._track.determinePacketType(nextPacket);
|
|
292
|
+
if (determinedType === 'delta') {
|
|
293
|
+
// Try returning the next key packet (in hopes that it's actually a key packet)
|
|
294
|
+
return this.getNextKeyPacket(nextPacket, options);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return nextPacket;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Creates an async iterator that yields the packets in this track in decode order. To enable fast iteration, this
|
|
302
|
+
* method will intelligently preload packets based on the speed of the consumer.
|
|
303
|
+
*
|
|
304
|
+
* @param startPacket - (optional) The packet from which iteration should begin. This packet will also be yielded.
|
|
305
|
+
* @param endPacket - (optional) The packet at which iteration should end. This packet will _not_ be yielded.
|
|
306
|
+
*/
|
|
307
|
+
packets(
|
|
308
|
+
startPacket?: EncodedPacket,
|
|
309
|
+
endPacket?: EncodedPacket,
|
|
310
|
+
options: PacketRetrievalOptions = {},
|
|
311
|
+
): AsyncGenerator<EncodedPacket, void, unknown> {
|
|
312
|
+
if (startPacket !== undefined && !(startPacket instanceof EncodedPacket)) {
|
|
313
|
+
throw new TypeError('startPacket must be an EncodedPacket.');
|
|
314
|
+
}
|
|
315
|
+
if (startPacket !== undefined && startPacket.isMetadataOnly && !options?.metadataOnly) {
|
|
316
|
+
throw new TypeError('startPacket can only be metadata-only if options.metadataOnly is enabled.');
|
|
317
|
+
}
|
|
318
|
+
if (endPacket !== undefined && !(endPacket instanceof EncodedPacket)) {
|
|
319
|
+
throw new TypeError('endPacket must be an EncodedPacket.');
|
|
320
|
+
}
|
|
321
|
+
validatePacketRetrievalOptions(options);
|
|
322
|
+
|
|
323
|
+
if (this._track.input._disposed) {
|
|
324
|
+
throw new InputDisposedError();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const packetQueue: EncodedPacket[] = [];
|
|
328
|
+
|
|
329
|
+
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
|
|
330
|
+
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
|
|
331
|
+
let ended = false;
|
|
332
|
+
let terminated = false;
|
|
333
|
+
|
|
334
|
+
// This stores errors that are "out of band" in the sense that they didn't occur in the normal flow of this
|
|
335
|
+
// method but instead in a different context. This error should not go unnoticed and must be bubbled up to
|
|
336
|
+
// the consumer.
|
|
337
|
+
let outOfBandError = null as unknown;
|
|
338
|
+
let hasOutOfBandError = false;
|
|
339
|
+
|
|
340
|
+
const timestamps: number[] = [];
|
|
341
|
+
// The queue should always be big enough to hold 1 second worth of packets
|
|
342
|
+
const maxQueueSize = () => Math.max(2, timestamps.length);
|
|
343
|
+
|
|
344
|
+
// The following is the "pump" process that keeps pumping packets into the queue
|
|
345
|
+
(async () => {
|
|
346
|
+
let packet = startPacket ?? await this.getFirstPacket(options);
|
|
347
|
+
|
|
348
|
+
while (packet && !terminated && !this._track.input._disposed) {
|
|
349
|
+
if (endPacket && packet.sequenceNumber >= endPacket?.sequenceNumber) {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (packetQueue.length > maxQueueSize()) {
|
|
354
|
+
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
|
355
|
+
await queueDequeue;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
packetQueue.push(packet);
|
|
360
|
+
|
|
361
|
+
onQueueNotEmpty();
|
|
362
|
+
({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
|
|
363
|
+
|
|
364
|
+
packet = await this.getNextPacket(packet, options);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
ended = true;
|
|
368
|
+
onQueueNotEmpty();
|
|
369
|
+
})().catch((error) => {
|
|
370
|
+
if (!hasOutOfBandError) {
|
|
371
|
+
outOfBandError = error;
|
|
372
|
+
hasOutOfBandError = true;
|
|
373
|
+
onQueueNotEmpty();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const track = this._track;
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
async next() {
|
|
381
|
+
while (true) {
|
|
382
|
+
if (track.input._disposed) {
|
|
383
|
+
throw new InputDisposedError();
|
|
384
|
+
} else if (terminated) {
|
|
385
|
+
return { value: undefined, done: true };
|
|
386
|
+
} else if (hasOutOfBandError) {
|
|
387
|
+
throw outOfBandError;
|
|
388
|
+
} else if (packetQueue.length > 0) {
|
|
389
|
+
const value = packetQueue.shift()!;
|
|
390
|
+
const now = performance.now();
|
|
391
|
+
timestamps.push(now);
|
|
392
|
+
|
|
393
|
+
while (timestamps.length > 0 && now - timestamps[0]! >= 1000) {
|
|
394
|
+
timestamps.shift();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
onQueueDequeue();
|
|
398
|
+
|
|
399
|
+
return { value, done: false };
|
|
400
|
+
} else if (ended) {
|
|
401
|
+
return { value: undefined, done: true };
|
|
402
|
+
} else {
|
|
403
|
+
await queueNotEmpty;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
async return() {
|
|
408
|
+
terminated = true;
|
|
409
|
+
onQueueDequeue();
|
|
410
|
+
onQueueNotEmpty();
|
|
411
|
+
|
|
412
|
+
return { value: undefined, done: true };
|
|
413
|
+
},
|
|
414
|
+
async throw(error) {
|
|
415
|
+
throw error;
|
|
416
|
+
},
|
|
417
|
+
[Symbol.asyncIterator]() {
|
|
418
|
+
return this;
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
abstract class DecoderWrapper<
|
|
425
|
+
MediaSample extends VideoSample | AudioSample,
|
|
426
|
+
> {
|
|
427
|
+
constructor(
|
|
428
|
+
public onSample: (sample: MediaSample) => unknown,
|
|
429
|
+
public onError: (error: unknown) => unknown,
|
|
430
|
+
) {}
|
|
431
|
+
|
|
432
|
+
abstract getDecodeQueueSize(): number;
|
|
433
|
+
abstract decode(packet: EncodedPacket): void;
|
|
434
|
+
abstract flush(): Promise<void>;
|
|
435
|
+
abstract close(): void;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Base class for decoded media sample sinks.
|
|
440
|
+
* @group Media sinks
|
|
441
|
+
* @public
|
|
442
|
+
*/
|
|
443
|
+
export abstract class BaseMediaSampleSink<
|
|
444
|
+
MediaSample extends VideoSample | AudioSample,
|
|
445
|
+
> {
|
|
446
|
+
/** @internal */
|
|
447
|
+
abstract _track: InputTrack;
|
|
448
|
+
|
|
449
|
+
/** @internal */
|
|
450
|
+
abstract _createDecoder(
|
|
451
|
+
onSample: (sample: MediaSample) => unknown,
|
|
452
|
+
onError: (error: unknown) => unknown
|
|
453
|
+
): Promise<DecoderWrapper<MediaSample>>;
|
|
454
|
+
/** @internal */
|
|
455
|
+
abstract _createPacketSink(): EncodedPacketSink;
|
|
456
|
+
|
|
457
|
+
/** @internal */
|
|
458
|
+
protected mediaSamplesInRange(
|
|
459
|
+
startTimestamp = -Infinity,
|
|
460
|
+
endTimestamp = Infinity,
|
|
461
|
+
options: PacketRetrievalOptions,
|
|
462
|
+
): AsyncGenerator<MediaSample, void, unknown> {
|
|
463
|
+
validateTimestamp(startTimestamp);
|
|
464
|
+
validateTimestamp(endTimestamp);
|
|
465
|
+
|
|
466
|
+
const sampleQueue: MediaSample[] = [];
|
|
467
|
+
let firstSampleQueued = false;
|
|
468
|
+
let lastSample: MediaSample | null = null;
|
|
469
|
+
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
|
|
470
|
+
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
|
|
471
|
+
let decoderIsFlushed = false;
|
|
472
|
+
let ended = false;
|
|
473
|
+
let terminated = false;
|
|
474
|
+
let decoder: DecoderWrapper<MediaSample> | null = null;
|
|
475
|
+
|
|
476
|
+
// This stores errors that are "out of band" in the sense that they didn't occur in the normal flow of this
|
|
477
|
+
// method but instead in a different context. This error should not go unnoticed and must be bubbled up to
|
|
478
|
+
// the consumer.
|
|
479
|
+
let outOfBandError = null as unknown;
|
|
480
|
+
let hasOutOfBandError = false;
|
|
481
|
+
|
|
482
|
+
const packetRetrievalOptions: PacketRetrievalOptions = {
|
|
483
|
+
...options,
|
|
484
|
+
verifyKeyPackets: true,
|
|
485
|
+
metadataOnly: false,
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// The following is the "pump" process that keeps pumping packets into the decoder
|
|
489
|
+
(async () => {
|
|
490
|
+
decoder = await this._createDecoder((sample) => {
|
|
491
|
+
onQueueDequeue();
|
|
492
|
+
if (sample.timestamp >= endTimestamp) {
|
|
493
|
+
ended = true;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (ended) {
|
|
497
|
+
sample.close();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (lastSample) {
|
|
502
|
+
if (sample.timestamp > startTimestamp) {
|
|
503
|
+
// We don't know ahead of time what the first first is. This is because the first first is the
|
|
504
|
+
// last first whose timestamp is less than or equal to the start timestamp. Therefore we need to
|
|
505
|
+
// wait for the first first after the start timestamp, and then we'll know that the previous
|
|
506
|
+
// first was the first first.
|
|
507
|
+
sampleQueue.push(lastSample);
|
|
508
|
+
firstSampleQueued = true;
|
|
509
|
+
} else {
|
|
510
|
+
lastSample.close();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (sample.timestamp >= startTimestamp) {
|
|
515
|
+
sampleQueue.push(sample);
|
|
516
|
+
firstSampleQueued = true;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
lastSample = firstSampleQueued ? null : sample;
|
|
520
|
+
|
|
521
|
+
if (sampleQueue.length > 0) {
|
|
522
|
+
onQueueNotEmpty();
|
|
523
|
+
({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
|
|
524
|
+
}
|
|
525
|
+
}, (error) => {
|
|
526
|
+
if (!hasOutOfBandError) {
|
|
527
|
+
outOfBandError = error;
|
|
528
|
+
hasOutOfBandError = true;
|
|
529
|
+
onQueueNotEmpty();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const packetSink = this._createPacketSink();
|
|
534
|
+
const keyPacket = await packetSink.getKeyPacket(startTimestamp, packetRetrievalOptions)
|
|
535
|
+
?? await packetSink.getFirstKeyPacket(packetRetrievalOptions);
|
|
536
|
+
|
|
537
|
+
let currentPacket: EncodedPacket | null = keyPacket;
|
|
538
|
+
|
|
539
|
+
// B-frames make it exceedingly difficult to properly define an upper bound for packet iteration if an end
|
|
540
|
+
// timestamp is set, so we just don't do it. The case that makes it especially tricky is when the frames
|
|
541
|
+
// following a key frame have a lower timestamp than the keyframe; something that quite frequently happens
|
|
542
|
+
// in HEVC streams. The price to pay for not upper-bounding the packet iterator is a slight increase in
|
|
543
|
+
// decoder work at the end of the range, but the added correctness and reliability makes this tradeoff worth
|
|
544
|
+
// it.
|
|
545
|
+
const endPacket = undefined;
|
|
546
|
+
|
|
547
|
+
const packets = packetSink.packets(keyPacket ?? undefined, endPacket, packetRetrievalOptions);
|
|
548
|
+
await packets.next(); // Skip the start packet as we already have it
|
|
549
|
+
|
|
550
|
+
while (currentPacket && !ended && !this._track.input._disposed) {
|
|
551
|
+
const maxQueueSize = computeMaxQueueSize(sampleQueue.length);
|
|
552
|
+
if (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize) {
|
|
553
|
+
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
|
554
|
+
await queueDequeue;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
decoder.decode(currentPacket);
|
|
559
|
+
|
|
560
|
+
const packetResult = await packets.next();
|
|
561
|
+
if (packetResult.done) {
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
currentPacket = packetResult.value;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
await packets.return();
|
|
569
|
+
|
|
570
|
+
if (!terminated && !this._track.input._disposed) {
|
|
571
|
+
await decoder.flush();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (!firstSampleQueued && lastSample) {
|
|
575
|
+
sampleQueue.push(lastSample);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
decoderIsFlushed = true;
|
|
579
|
+
onQueueNotEmpty(); // To unstuck (unstick?) the generator
|
|
580
|
+
})().catch((error) => {
|
|
581
|
+
if (!hasOutOfBandError) {
|
|
582
|
+
outOfBandError = error;
|
|
583
|
+
hasOutOfBandError = true;
|
|
584
|
+
onQueueNotEmpty();
|
|
585
|
+
}
|
|
586
|
+
}).finally(() => {
|
|
587
|
+
decoder?.close();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
const track = this._track;
|
|
591
|
+
const closeSamples = () => {
|
|
592
|
+
lastSample?.close();
|
|
593
|
+
for (const sample of sampleQueue) {
|
|
594
|
+
sample.close();
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
async next() {
|
|
600
|
+
while (true) {
|
|
601
|
+
if (track.input._disposed) {
|
|
602
|
+
closeSamples();
|
|
603
|
+
throw new InputDisposedError();
|
|
604
|
+
} else if (terminated) {
|
|
605
|
+
return { value: undefined, done: true };
|
|
606
|
+
} else if (hasOutOfBandError) {
|
|
607
|
+
closeSamples();
|
|
608
|
+
throw outOfBandError;
|
|
609
|
+
} else if (sampleQueue.length > 0) {
|
|
610
|
+
const value = sampleQueue.shift()!;
|
|
611
|
+
onQueueDequeue();
|
|
612
|
+
return { value, done: false };
|
|
613
|
+
} else if (!decoderIsFlushed) {
|
|
614
|
+
await queueNotEmpty;
|
|
615
|
+
} else {
|
|
616
|
+
return { value: undefined, done: true };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
async return() {
|
|
621
|
+
terminated = true;
|
|
622
|
+
ended = true;
|
|
623
|
+
onQueueDequeue();
|
|
624
|
+
onQueueNotEmpty();
|
|
625
|
+
closeSamples();
|
|
626
|
+
|
|
627
|
+
return { value: undefined, done: true };
|
|
628
|
+
},
|
|
629
|
+
async throw(error) {
|
|
630
|
+
throw error;
|
|
631
|
+
},
|
|
632
|
+
[Symbol.asyncIterator]() {
|
|
633
|
+
return this;
|
|
634
|
+
},
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** @internal */
|
|
639
|
+
protected mediaSamplesAtTimestamps(
|
|
640
|
+
timestamps: AnyIterable<number>,
|
|
641
|
+
options: PacketRetrievalOptions,
|
|
642
|
+
): AsyncGenerator<MediaSample | null, void, unknown> {
|
|
643
|
+
validateAnyIterable(timestamps);
|
|
644
|
+
const timestampIterator = toAsyncIterator(timestamps);
|
|
645
|
+
const timestampsOfInterest: number[] = [];
|
|
646
|
+
|
|
647
|
+
const sampleQueue: (MediaSample | null)[] = [];
|
|
648
|
+
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
|
|
649
|
+
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
|
|
650
|
+
let decoderIsFlushed = false;
|
|
651
|
+
let terminated = false;
|
|
652
|
+
let decoder: DecoderWrapper<MediaSample> | null = null;
|
|
653
|
+
|
|
654
|
+
// This stores errors that are "out of band" in the sense that they didn't occur in the normal flow of this
|
|
655
|
+
// method but instead in a different context. This error should not go unnoticed and must be bubbled up to
|
|
656
|
+
// the consumer.
|
|
657
|
+
let outOfBandError = null as unknown;
|
|
658
|
+
let hasOutOfBandError = false;
|
|
659
|
+
|
|
660
|
+
const pushToQueue = (sample: MediaSample | null) => {
|
|
661
|
+
sampleQueue.push(sample);
|
|
662
|
+
onQueueNotEmpty();
|
|
663
|
+
({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const retrievalOptions: PacketRetrievalOptions = {
|
|
667
|
+
...options,
|
|
668
|
+
verifyKeyPackets: true,
|
|
669
|
+
metadataOnly: false,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// The following is the "pump" process that keeps pumping packets into the decoder
|
|
673
|
+
(async () => {
|
|
674
|
+
decoder = await this._createDecoder((sample) => {
|
|
675
|
+
onQueueDequeue();
|
|
676
|
+
|
|
677
|
+
if (terminated) {
|
|
678
|
+
sample.close();
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let sampleUses = 0;
|
|
683
|
+
while (
|
|
684
|
+
timestampsOfInterest.length > 0
|
|
685
|
+
&& sample.timestamp - timestampsOfInterest[0]! > -1e-10 // Give it a little epsilon
|
|
686
|
+
) {
|
|
687
|
+
sampleUses++;
|
|
688
|
+
timestampsOfInterest.shift();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (sampleUses > 0) {
|
|
692
|
+
for (let i = 0; i < sampleUses; i++) {
|
|
693
|
+
// Clone the sample if we need to emit it multiple times
|
|
694
|
+
pushToQueue((i < sampleUses - 1 ? sample.clone() : sample) as MediaSample);
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
sample.close();
|
|
698
|
+
}
|
|
699
|
+
}, (error) => {
|
|
700
|
+
if (!hasOutOfBandError) {
|
|
701
|
+
outOfBandError = error;
|
|
702
|
+
hasOutOfBandError = true;
|
|
703
|
+
onQueueNotEmpty();
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
const packetSink = this._createPacketSink();
|
|
708
|
+
let lastPacket: EncodedPacket | null = null;
|
|
709
|
+
let lastKeyPacket: EncodedPacket | null = null;
|
|
710
|
+
|
|
711
|
+
// The end sequence number (inclusive) in the next batch of packets that will be decoded. The batch starts
|
|
712
|
+
// at the last key frame and goes until this sequence number.
|
|
713
|
+
let maxSequenceNumber = -1;
|
|
714
|
+
|
|
715
|
+
const decodePackets = async () => {
|
|
716
|
+
assert(lastKeyPacket);
|
|
717
|
+
assert(decoder);
|
|
718
|
+
|
|
719
|
+
// Start at the current key packet
|
|
720
|
+
let currentPacket = lastKeyPacket;
|
|
721
|
+
decoder.decode(currentPacket);
|
|
722
|
+
|
|
723
|
+
while (currentPacket.sequenceNumber < maxSequenceNumber) {
|
|
724
|
+
const maxQueueSize = computeMaxQueueSize(sampleQueue.length);
|
|
725
|
+
while (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize && !terminated) {
|
|
726
|
+
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
|
727
|
+
await queueDequeue;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (terminated) {
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const nextPacket = await packetSink.getNextPacket(currentPacket, retrievalOptions);
|
|
735
|
+
assert(nextPacket);
|
|
736
|
+
|
|
737
|
+
decoder.decode(nextPacket);
|
|
738
|
+
currentPacket = nextPacket;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
maxSequenceNumber = -1;
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const flushDecoder = async () => {
|
|
745
|
+
assert(decoder);
|
|
746
|
+
await decoder.flush();
|
|
747
|
+
|
|
748
|
+
// We don't expect this list to have any elements in it anymore, but in case it does, let's emit
|
|
749
|
+
// nulls for every remaining element, then clear it.
|
|
750
|
+
for (let i = 0; i < timestampsOfInterest.length; i++) {
|
|
751
|
+
pushToQueue(null);
|
|
752
|
+
}
|
|
753
|
+
timestampsOfInterest.length = 0;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
for await (const timestamp of timestampIterator) {
|
|
757
|
+
validateTimestamp(timestamp);
|
|
758
|
+
|
|
759
|
+
if (terminated || this._track.input._disposed) {
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const targetPacket = await packetSink.getPacket(timestamp, retrievalOptions);
|
|
764
|
+
const keyPacket = targetPacket && await packetSink.getKeyPacket(timestamp, retrievalOptions);
|
|
765
|
+
|
|
766
|
+
if (!keyPacket) {
|
|
767
|
+
if (maxSequenceNumber !== -1) {
|
|
768
|
+
await decodePackets();
|
|
769
|
+
await flushDecoder();
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
pushToQueue(null);
|
|
773
|
+
lastPacket = null;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Check if the key packet has changed or if we're going back in time
|
|
778
|
+
if (
|
|
779
|
+
lastPacket
|
|
780
|
+
&& (
|
|
781
|
+
keyPacket.sequenceNumber !== lastKeyPacket!.sequenceNumber
|
|
782
|
+
|| targetPacket.timestamp < lastPacket.timestamp
|
|
783
|
+
)
|
|
784
|
+
) {
|
|
785
|
+
await decodePackets();
|
|
786
|
+
await flushDecoder(); // Always flush here, improves decoder compatibility
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
timestampsOfInterest.push(targetPacket.timestamp);
|
|
790
|
+
maxSequenceNumber = Math.max(targetPacket.sequenceNumber, maxSequenceNumber);
|
|
791
|
+
|
|
792
|
+
lastPacket = targetPacket;
|
|
793
|
+
lastKeyPacket = keyPacket;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (!terminated && !this._track.input._disposed) {
|
|
797
|
+
if (maxSequenceNumber !== -1) {
|
|
798
|
+
// We still need to decode packets
|
|
799
|
+
await decodePackets();
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
await flushDecoder();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
decoderIsFlushed = true;
|
|
806
|
+
onQueueNotEmpty(); // To unstuck the generator
|
|
807
|
+
})().catch((error) => {
|
|
808
|
+
if (!hasOutOfBandError) {
|
|
809
|
+
outOfBandError = error;
|
|
810
|
+
hasOutOfBandError = true;
|
|
811
|
+
onQueueNotEmpty();
|
|
812
|
+
}
|
|
813
|
+
}).finally(() => {
|
|
814
|
+
decoder?.close();
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
const track = this._track;
|
|
818
|
+
const closeSamples = () => {
|
|
819
|
+
for (const sample of sampleQueue) {
|
|
820
|
+
sample?.close();
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
return {
|
|
825
|
+
async next() {
|
|
826
|
+
while (true) {
|
|
827
|
+
if (track.input._disposed) {
|
|
828
|
+
closeSamples();
|
|
829
|
+
throw new InputDisposedError();
|
|
830
|
+
} else if (terminated) {
|
|
831
|
+
return { value: undefined, done: true };
|
|
832
|
+
} else if (hasOutOfBandError) {
|
|
833
|
+
closeSamples();
|
|
834
|
+
throw outOfBandError;
|
|
835
|
+
} else if (sampleQueue.length > 0) {
|
|
836
|
+
const value = sampleQueue.shift();
|
|
837
|
+
assert(value !== undefined);
|
|
838
|
+
onQueueDequeue();
|
|
839
|
+
return { value, done: false };
|
|
840
|
+
} else if (!decoderIsFlushed) {
|
|
841
|
+
await queueNotEmpty;
|
|
842
|
+
} else {
|
|
843
|
+
return { value: undefined, done: true };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
async return() {
|
|
848
|
+
terminated = true;
|
|
849
|
+
onQueueDequeue();
|
|
850
|
+
onQueueNotEmpty();
|
|
851
|
+
closeSamples();
|
|
852
|
+
|
|
853
|
+
return { value: undefined, done: true };
|
|
854
|
+
},
|
|
855
|
+
async throw(error) {
|
|
856
|
+
throw error;
|
|
857
|
+
},
|
|
858
|
+
[Symbol.asyncIterator]() {
|
|
859
|
+
return this;
|
|
860
|
+
},
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const computeMaxQueueSize = (decodedSampleQueueSize: number) => {
|
|
866
|
+
// If we have decoded samples lying around, limit the total queue size to a small value (decoded samples can use up
|
|
867
|
+
// a lot of memory). If not, we're fine with a much bigger queue of encoded packets waiting to be decoded. In fact,
|
|
868
|
+
// some decoders only start flushing out decoded chunks when the packet queue is large enough.
|
|
869
|
+
return decodedSampleQueueSize === 0 ? 40 : 8;
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
|
873
|
+
decoder: VideoDecoder | null = null;
|
|
874
|
+
|
|
875
|
+
customDecoder: CustomVideoDecoder | null = null;
|
|
876
|
+
customDecoderCallSerializer = new CallSerializer();
|
|
877
|
+
customDecoderQueueSize = 0;
|
|
878
|
+
|
|
879
|
+
inputTimestamps: number[] = []; // Timestamps input into the decoder, sorted.
|
|
880
|
+
sampleQueue: VideoSample[] = []; // Safari-specific thing, check usage.
|
|
881
|
+
currentPacketIndex = 0;
|
|
882
|
+
raslSkipped = false; // For HEVC stuff
|
|
883
|
+
|
|
884
|
+
// Alpha stuff
|
|
885
|
+
alphaDecoder: VideoDecoder | null = null;
|
|
886
|
+
alphaHadKeyframe = false;
|
|
887
|
+
colorQueue: VideoFrame[] = [];
|
|
888
|
+
alphaQueue: (VideoFrame | null)[] = [];
|
|
889
|
+
merger: ColorAlphaMerger | null = null;
|
|
890
|
+
decodedAlphaChunkCount = 0;
|
|
891
|
+
alphaDecoderQueueSize = 0;
|
|
892
|
+
/** Each value is the number of decoded alpha chunks at which a null alpha frame should be added. */
|
|
893
|
+
nullAlphaFrameQueue: number[] = [];
|
|
894
|
+
currentAlphaPacketIndex = 0;
|
|
895
|
+
alphaRaslSkipped = false; // For HEVC stuff
|
|
896
|
+
finalSamples: { sample: VideoSample | null }[] = [];
|
|
897
|
+
mergeAlphaPromises: Promise<void>[] = [];
|
|
898
|
+
|
|
899
|
+
constructor(
|
|
900
|
+
onSample: (sample: VideoSample) => unknown,
|
|
901
|
+
onError: (error: unknown) => unknown,
|
|
902
|
+
public codec: VideoCodec,
|
|
903
|
+
public decoderConfig: VideoDecoderConfig,
|
|
904
|
+
public rotation: Rotation,
|
|
905
|
+
public timeResolution: number,
|
|
906
|
+
) {
|
|
907
|
+
super(onSample, onError);
|
|
908
|
+
|
|
909
|
+
const MatchingCustomDecoder = customVideoDecoders.find(x => x.supports(codec, decoderConfig));
|
|
910
|
+
if (MatchingCustomDecoder) {
|
|
911
|
+
// @ts-expect-error "Can't create instance of abstract class 🤓"
|
|
912
|
+
this.customDecoder = new MatchingCustomDecoder() as CustomVideoDecoder;
|
|
913
|
+
// @ts-expect-error It's technically readonly
|
|
914
|
+
this.customDecoder.codec = codec;
|
|
915
|
+
// @ts-expect-error It's technically readonly
|
|
916
|
+
this.customDecoder.config = decoderConfig;
|
|
917
|
+
// @ts-expect-error It's technically readonly
|
|
918
|
+
this.customDecoder.onSample = (sample) => {
|
|
919
|
+
if (!(sample instanceof VideoSample)) {
|
|
920
|
+
throw new TypeError('The argument passed to onSample must be a VideoSample.');
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
this.finalizeAndEmitSample(sample);
|
|
924
|
+
};
|
|
925
|
+
// @ts-expect-error It's technically readonly
|
|
926
|
+
this.customDecoder.onError = (error) => {
|
|
927
|
+
onError(error);
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
void this.customDecoderCallSerializer
|
|
931
|
+
.call(() => this.customDecoder!.init())
|
|
932
|
+
.catch(error => onError(error));
|
|
933
|
+
} else {
|
|
934
|
+
const colorHandler = (frame: VideoFrame) => {
|
|
935
|
+
if (this.alphaQueue.length > 0) {
|
|
936
|
+
// Even when no alpha data is present (most of the time), there will be nulls in this queue
|
|
937
|
+
const alphaFrame = this.alphaQueue.shift();
|
|
938
|
+
assert(alphaFrame !== undefined);
|
|
939
|
+
|
|
940
|
+
void this.mergeAlpha(frame, alphaFrame);
|
|
941
|
+
} else {
|
|
942
|
+
this.colorQueue.push(frame);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
if (codec === 'avc' && this.decoderConfig.description && isChromium()) {
|
|
947
|
+
// Chromium has/had a bug with playing interlaced AVC (https://issues.chromium.org/issues/456919096)
|
|
948
|
+
// which can be worked around by requesting that software decoding be used. So, here we peek into the
|
|
949
|
+
// AVC description, if present, and switch to software decoding if we find interlaced content.
|
|
950
|
+
const record = deserializeAvcDecoderConfigurationRecord(toUint8Array(this.decoderConfig.description));
|
|
951
|
+
if (record && record.sequenceParameterSets.length > 0) {
|
|
952
|
+
const sps = parseAvcSps(record.sequenceParameterSets[0]!);
|
|
953
|
+
if (sps && sps.frameMbsOnlyFlag === 0) {
|
|
954
|
+
this.decoderConfig = {
|
|
955
|
+
...this.decoderConfig,
|
|
956
|
+
hardwareAcceleration: 'prefer-software',
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const stack = new Error('Decoding error').stack;
|
|
963
|
+
|
|
964
|
+
this.decoder = new VideoDecoder({
|
|
965
|
+
output: (frame) => {
|
|
966
|
+
try {
|
|
967
|
+
colorHandler(frame);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
this.onError(error);
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
error: (error) => {
|
|
973
|
+
error.stack = stack; // Provide a more useful stack trace, the default one sucks
|
|
974
|
+
this.onError(error);
|
|
975
|
+
},
|
|
976
|
+
});
|
|
977
|
+
this.decoder.configure(this.decoderConfig);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
getDecodeQueueSize() {
|
|
982
|
+
if (this.customDecoder) {
|
|
983
|
+
return this.customDecoderQueueSize;
|
|
984
|
+
} else {
|
|
985
|
+
assert(this.decoder);
|
|
986
|
+
|
|
987
|
+
return Math.max(
|
|
988
|
+
this.decoder.decodeQueueSize,
|
|
989
|
+
this.alphaDecoder?.decodeQueueSize ?? 0,
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
decode(packet: EncodedPacket) {
|
|
995
|
+
if (this.codec === 'hevc' && this.currentPacketIndex > 0 && !this.raslSkipped) {
|
|
996
|
+
if (this.hasHevcRaslPicture(packet.data)) {
|
|
997
|
+
return; // Drop
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
this.raslSkipped = true;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (this.customDecoder) {
|
|
1004
|
+
this.customDecoderQueueSize++;
|
|
1005
|
+
void this.customDecoderCallSerializer
|
|
1006
|
+
.call(() => this.customDecoder!.decode(packet))
|
|
1007
|
+
.catch(error => this.onError(error))
|
|
1008
|
+
.finally(() => this.customDecoderQueueSize--);
|
|
1009
|
+
} else {
|
|
1010
|
+
assert(this.decoder);
|
|
1011
|
+
|
|
1012
|
+
if (!isWebKit()) {
|
|
1013
|
+
insertSorted(this.inputTimestamps, packet.timestamp, x => x);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (isChromium() && this.currentPacketIndex === 0) {
|
|
1017
|
+
if (this.codec === 'avc') {
|
|
1018
|
+
// Workaround for https://issues.chromium.org/issues/470109459
|
|
1019
|
+
const filteredNalUnits: Uint8Array[] = [];
|
|
1020
|
+
let hasFrameData = false;
|
|
1021
|
+
|
|
1022
|
+
for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
|
|
1023
|
+
const type = extractNalUnitTypeForAvc(packet.data[loc.offset]!);
|
|
1024
|
+
hasFrameData ||= type >= 1 && type <= 5;
|
|
1025
|
+
|
|
1026
|
+
if (type === AvcNalUnitType.AUD) {
|
|
1027
|
+
if (hasFrameData) {
|
|
1028
|
+
// Already has actual frame data, so treat an AUD as simply the end of the packet
|
|
1029
|
+
break;
|
|
1030
|
+
} else {
|
|
1031
|
+
// If packets contain an AUD and have NALUs before it, this trips up Chromium's key
|
|
1032
|
+
// frame detector. Clear the NALUs if an AUD is encountered.
|
|
1033
|
+
// https://github.com/Vanilagy/mediabunny/issues/396
|
|
1034
|
+
filteredNalUnits.length = 0;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// These trip up Chromium's key frame detection, so let's strip them
|
|
1039
|
+
if (!(type >= 20 && type <= 31)) {
|
|
1040
|
+
filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
|
|
1045
|
+
packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
|
|
1046
|
+
} else if (this.codec === 'hevc') {
|
|
1047
|
+
// Workaround for https://issues.chromium.org/issues/507611247
|
|
1048
|
+
const sanitizedData = sanitizeHevcPacketForChromium(packet.data, this.decoderConfig);
|
|
1049
|
+
if (sanitizedData) {
|
|
1050
|
+
packet = new EncodedPacket(sanitizedData, packet.type, packet.timestamp, packet.duration);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
this.decoder.decode(packet.toEncodedVideoChunk());
|
|
1056
|
+
this.decodeAlphaData(packet);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
this.currentPacketIndex++;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
decodeAlphaData(packet: EncodedPacket) {
|
|
1063
|
+
if (!packet.sideData.alpha) {
|
|
1064
|
+
// No alpha side data in the packet, most common case
|
|
1065
|
+
this.pushNullAlphaFrame();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (!this.merger) {
|
|
1070
|
+
this.merger = new ColorAlphaMerger();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Check if we need to set up the alpha decoder
|
|
1074
|
+
if (!this.alphaDecoder) {
|
|
1075
|
+
const alphaHandler = (frame: VideoFrame) => {
|
|
1076
|
+
if (this.colorQueue.length > 0) {
|
|
1077
|
+
const colorFrame = this.colorQueue.shift();
|
|
1078
|
+
assert(colorFrame !== undefined);
|
|
1079
|
+
|
|
1080
|
+
void this.mergeAlpha(colorFrame, frame);
|
|
1081
|
+
} else {
|
|
1082
|
+
this.alphaQueue.push(frame);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// Check if any null frames have been queued for this point
|
|
1086
|
+
this.decodedAlphaChunkCount++;
|
|
1087
|
+
while (
|
|
1088
|
+
this.nullAlphaFrameQueue.length > 0
|
|
1089
|
+
&& this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount
|
|
1090
|
+
) {
|
|
1091
|
+
this.nullAlphaFrameQueue.shift();
|
|
1092
|
+
|
|
1093
|
+
if (this.colorQueue.length > 0) {
|
|
1094
|
+
const colorFrame = this.colorQueue.shift();
|
|
1095
|
+
assert(colorFrame !== undefined);
|
|
1096
|
+
|
|
1097
|
+
void this.mergeAlpha(colorFrame, null);
|
|
1098
|
+
} else {
|
|
1099
|
+
this.alphaQueue.push(null);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
this.alphaDecoderQueueSize--;
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const stack = new Error('Decoding error').stack;
|
|
1107
|
+
|
|
1108
|
+
this.alphaDecoder = new VideoDecoder({
|
|
1109
|
+
output: (frame) => {
|
|
1110
|
+
try {
|
|
1111
|
+
alphaHandler(frame);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
this.onError(error);
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
error: (error) => {
|
|
1117
|
+
error.stack = stack; // Provide a more useful stack trace, the default one sucks
|
|
1118
|
+
this.onError(error);
|
|
1119
|
+
},
|
|
1120
|
+
});
|
|
1121
|
+
this.alphaDecoder.configure(this.decoderConfig);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const type = determineVideoPacketType(this.codec, this.decoderConfig, packet.sideData.alpha);
|
|
1125
|
+
|
|
1126
|
+
// Alpha packets might follow a different key frame rhythm than the main packets. Therefore, before we start
|
|
1127
|
+
// decoding, we must first find a packet that's actually a key frame. Until then, we treat the image as opaque.
|
|
1128
|
+
if (!this.alphaHadKeyframe) {
|
|
1129
|
+
this.alphaHadKeyframe = type === 'key';
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if (this.alphaHadKeyframe) {
|
|
1133
|
+
// Same RASL skipping logic as for color, unlikely to be hit (since who uses HEVC with separate alpha??) but
|
|
1134
|
+
// here for symmetry.
|
|
1135
|
+
if (this.codec === 'hevc' && this.currentAlphaPacketIndex > 0 && !this.alphaRaslSkipped) {
|
|
1136
|
+
if (this.hasHevcRaslPicture(packet.sideData.alpha)) {
|
|
1137
|
+
this.pushNullAlphaFrame();
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
this.alphaRaslSkipped = true;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
this.currentAlphaPacketIndex++;
|
|
1145
|
+
this.alphaDecoder.decode(packet.alphaToEncodedVideoChunk(type ?? packet.type));
|
|
1146
|
+
this.alphaDecoderQueueSize++;
|
|
1147
|
+
} else {
|
|
1148
|
+
this.pushNullAlphaFrame();
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
pushNullAlphaFrame() {
|
|
1153
|
+
if (this.alphaDecoderQueueSize === 0) {
|
|
1154
|
+
// Easy
|
|
1155
|
+
this.alphaQueue.push(null);
|
|
1156
|
+
} else {
|
|
1157
|
+
// There are still alpha chunks being decoded, so pushing `null` immediately would result in out-of-order
|
|
1158
|
+
// data and be incorrect. Instead, we need to enqueue a "null frame" for when the current decoder workload
|
|
1159
|
+
// has finished.
|
|
1160
|
+
this.nullAlphaFrameQueue.push(this.decodedAlphaChunkCount + this.alphaDecoderQueueSize);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* If we're using HEVC, we need to make sure to skip any RASL slices that follow a non-IDR key frame such as
|
|
1166
|
+
* CRA_NUT. This is because RASL slices cannot be decoded without data before the CRA_NUT. Browsers behave
|
|
1167
|
+
* differently here: Chromium drops the packets, Safari throws a decoder error. Either way, it's not good
|
|
1168
|
+
* and causes bugs upstream. So, let's take the dropping into our own hands.
|
|
1169
|
+
*/
|
|
1170
|
+
hasHevcRaslPicture(packetData: Uint8Array) {
|
|
1171
|
+
for (const loc of iterateHevcNalUnits(packetData, this.decoderConfig)) {
|
|
1172
|
+
const type = extractNalUnitTypeForHevc(packetData[loc.offset]!);
|
|
1173
|
+
if (type === HevcNalUnitType.RASL_N || type === HevcNalUnitType.RASL_R) {
|
|
1174
|
+
return true;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/** Handler for the WebCodecs VideoDecoder for ironing out browser differences. */
|
|
1182
|
+
sampleHandler(sample: VideoSample) {
|
|
1183
|
+
if (isWebKit()) {
|
|
1184
|
+
// For correct B-frame handling, we don't just hand over the frames directly but instead add them to
|
|
1185
|
+
// a queue, because we want to ensure frames are emitted in presentation order. We flush the queue
|
|
1186
|
+
// each time we receive a frame with a timestamp larger than the highest we've seen so far, as we
|
|
1187
|
+
// can sure that is not a B-frame. Typically, WebCodecs automatically guarantees that frames are
|
|
1188
|
+
// emitted in presentation order, but Safari doesn't always follow this rule.
|
|
1189
|
+
if (this.sampleQueue.length > 0 && (sample.timestamp >= last(this.sampleQueue)!.timestamp)) {
|
|
1190
|
+
for (const sample of this.sampleQueue) {
|
|
1191
|
+
this.finalizeAndEmitSample(sample);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
this.sampleQueue.length = 0;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
insertSorted(this.sampleQueue, sample, x => x.timestamp);
|
|
1198
|
+
} else {
|
|
1199
|
+
// Assign it the next earliest timestamp from the input. We do this because browsers, by spec, are
|
|
1200
|
+
// required to emit decoded frames in presentation order *while* retaining the timestamp of their
|
|
1201
|
+
// originating EncodedVideoChunk. For files with B-frames but no out-of-order timestamps (like a
|
|
1202
|
+
// missing ctts box, for example), this causes a mismatch. We therefore fix the timestamps and
|
|
1203
|
+
// ensure they are sorted by doing this.
|
|
1204
|
+
const timestamp = this.inputTimestamps.shift();
|
|
1205
|
+
|
|
1206
|
+
// There's no way we'd have more decoded frames than encoded packets we passed in. Actually, the
|
|
1207
|
+
// correspondence should be 1:1.
|
|
1208
|
+
assert(timestamp !== undefined);
|
|
1209
|
+
|
|
1210
|
+
sample.setTimestamp(timestamp);
|
|
1211
|
+
this.finalizeAndEmitSample(sample);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
finalizeAndEmitSample(sample: VideoSample) {
|
|
1216
|
+
// Round the timestamps to the time resolution
|
|
1217
|
+
sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution);
|
|
1218
|
+
sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution);
|
|
1219
|
+
sample.setRotation(this.rotation);
|
|
1220
|
+
|
|
1221
|
+
this.onSample(sample);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
async mergeAlpha(color: VideoFrame, alpha: VideoFrame | null) {
|
|
1225
|
+
const resolver = promiseWithResolvers();
|
|
1226
|
+
this.mergeAlphaPromises.push(resolver.promise);
|
|
1227
|
+
|
|
1228
|
+
// Alpha merging is concurrent but the samples must still be emitted in the same order in which the merging
|
|
1229
|
+
// began. Therefore, serialize the results in an array.
|
|
1230
|
+
const result: { sample: VideoSample | null } = { sample: null };
|
|
1231
|
+
this.finalSamples.push(result);
|
|
1232
|
+
|
|
1233
|
+
try {
|
|
1234
|
+
if (!alpha) {
|
|
1235
|
+
// Nothing needs to be merged
|
|
1236
|
+
result.sample = new VideoSample(color);
|
|
1237
|
+
} else {
|
|
1238
|
+
assert(this.merger);
|
|
1239
|
+
|
|
1240
|
+
// The merger takes ownership of the frames, so no need to close them ourselves
|
|
1241
|
+
const finalFrame = await this.merger.merge(color, alpha);
|
|
1242
|
+
result.sample = new VideoSample(finalFrame);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Emit any leading samples that are ready, preserving input order
|
|
1246
|
+
while (this.finalSamples.length > 0 && this.finalSamples[0]!.sample !== null) {
|
|
1247
|
+
const next = this.finalSamples.shift()!;
|
|
1248
|
+
this.sampleHandler(next.sample!);
|
|
1249
|
+
}
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
removeItem(this.finalSamples, result);
|
|
1252
|
+
this.onError(error);
|
|
1253
|
+
} finally {
|
|
1254
|
+
removeItem(this.mergeAlphaPromises, resolver.promise);
|
|
1255
|
+
resolver.resolve();
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async flush() {
|
|
1260
|
+
if (this.customDecoder) {
|
|
1261
|
+
await this.customDecoderCallSerializer.call(() => this.customDecoder!.flush());
|
|
1262
|
+
} else {
|
|
1263
|
+
assert(this.decoder);
|
|
1264
|
+
await Promise.all([
|
|
1265
|
+
this.decoder.flush(),
|
|
1266
|
+
this.alphaDecoder?.flush(),
|
|
1267
|
+
]);
|
|
1268
|
+
await Promise.all(this.mergeAlphaPromises);
|
|
1269
|
+
|
|
1270
|
+
this.colorQueue.forEach(x => x.close());
|
|
1271
|
+
this.colorQueue.length = 0;
|
|
1272
|
+
this.alphaQueue.forEach(x => x?.close());
|
|
1273
|
+
this.alphaQueue.length = 0;
|
|
1274
|
+
|
|
1275
|
+
this.alphaHadKeyframe = false;
|
|
1276
|
+
this.decodedAlphaChunkCount = 0;
|
|
1277
|
+
this.alphaDecoderQueueSize = 0;
|
|
1278
|
+
this.nullAlphaFrameQueue.length = 0;
|
|
1279
|
+
this.currentAlphaPacketIndex = 0;
|
|
1280
|
+
this.alphaRaslSkipped = false;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (isWebKit()) {
|
|
1284
|
+
for (const sample of this.sampleQueue) {
|
|
1285
|
+
this.finalizeAndEmitSample(sample);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
this.sampleQueue.length = 0;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
this.currentPacketIndex = 0;
|
|
1292
|
+
this.raslSkipped = false;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
close() {
|
|
1296
|
+
if (this.customDecoder) {
|
|
1297
|
+
void this.customDecoderCallSerializer.call(() => this.customDecoder!.close());
|
|
1298
|
+
} else {
|
|
1299
|
+
assert(this.decoder);
|
|
1300
|
+
this.decoder.close();
|
|
1301
|
+
this.alphaDecoder?.close();
|
|
1302
|
+
|
|
1303
|
+
this.colorQueue.forEach(x => x.close());
|
|
1304
|
+
this.colorQueue.length = 0;
|
|
1305
|
+
this.alphaQueue.forEach(x => x?.close());
|
|
1306
|
+
this.alphaQueue.length = 0;
|
|
1307
|
+
|
|
1308
|
+
this.merger?.close();
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
for (const sample of this.sampleQueue) {
|
|
1312
|
+
sample.close();
|
|
1313
|
+
}
|
|
1314
|
+
this.sampleQueue.length = 0;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
let mergerWorkerUrl: string | null = null;
|
|
1319
|
+
|
|
1320
|
+
/** Utility class that merges together color and alpha information on the CPU in a pool of workers. */
|
|
1321
|
+
export class ColorAlphaMerger {
|
|
1322
|
+
private workers: Worker[] = [];
|
|
1323
|
+
private nextWorkerIndex = 0;
|
|
1324
|
+
private pendingRequests = new Map<number, ReturnType<typeof promiseWithResolvers<VideoFrame>>>();
|
|
1325
|
+
private nextRequestId = 0;
|
|
1326
|
+
|
|
1327
|
+
merge(color: VideoFrame, alpha: VideoFrame): Promise<VideoFrame> {
|
|
1328
|
+
if (this.workers.length === 0) {
|
|
1329
|
+
if (!mergerWorkerUrl) {
|
|
1330
|
+
const blob = new Blob(
|
|
1331
|
+
[`(${colorAlphaMergerWorkerCode.toString()})()`],
|
|
1332
|
+
{ type: 'application/javascript' },
|
|
1333
|
+
);
|
|
1334
|
+
mergerWorkerUrl = URL.createObjectURL(blob);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const poolSize = clamp(navigator.hardwareConcurrency, 1, 4);
|
|
1338
|
+
for (let i = 0; i < poolSize; i++) {
|
|
1339
|
+
const worker = new Worker(mergerWorkerUrl);
|
|
1340
|
+
|
|
1341
|
+
worker.addEventListener('message', (event: MessageEvent<ColorAlphaMergerWorkerResponse>) => {
|
|
1342
|
+
const data = event.data;
|
|
1343
|
+
const pending = this.pendingRequests.get(data.id);
|
|
1344
|
+
if (!pending) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
this.pendingRequests.delete(data.id);
|
|
1348
|
+
|
|
1349
|
+
if ('error' in data) {
|
|
1350
|
+
pending.reject(new Error(data.error));
|
|
1351
|
+
} else {
|
|
1352
|
+
pending.resolve(data.frame);
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
worker.addEventListener('error', (event) => {
|
|
1357
|
+
const error = new Error(event.message || 'Color/alpha merge worker error.');
|
|
1358
|
+
for (const pending of this.pendingRequests.values()) {
|
|
1359
|
+
pending.reject(error);
|
|
1360
|
+
}
|
|
1361
|
+
this.pendingRequests.clear();
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
this.workers.push(worker);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
const id = this.nextRequestId++;
|
|
1369
|
+
const pending = promiseWithResolvers<VideoFrame>();
|
|
1370
|
+
this.pendingRequests.set(id, pending);
|
|
1371
|
+
|
|
1372
|
+
// Hand the job to the next worker in round-robin fashion
|
|
1373
|
+
const worker = this.workers[this.nextWorkerIndex]!;
|
|
1374
|
+
this.nextWorkerIndex = (this.nextWorkerIndex + 1) % this.workers.length;
|
|
1375
|
+
worker.postMessage({ id, color, alpha }, { transfer: [color, alpha] });
|
|
1376
|
+
|
|
1377
|
+
return pending.promise;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
close() {
|
|
1381
|
+
for (const worker of this.workers) {
|
|
1382
|
+
worker.terminate();
|
|
1383
|
+
}
|
|
1384
|
+
this.workers.length = 0;
|
|
1385
|
+
|
|
1386
|
+
const error = new Error('Color/alpha merger closed.');
|
|
1387
|
+
for (const pending of this.pendingRequests.values()) {
|
|
1388
|
+
pending.reject(error);
|
|
1389
|
+
}
|
|
1390
|
+
this.pendingRequests.clear();
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
type ColorAlphaMergerWorkerRequest = {
|
|
1395
|
+
id: number;
|
|
1396
|
+
color: VideoFrame;
|
|
1397
|
+
alpha: VideoFrame;
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
type ColorAlphaMergerWorkerResponse =
|
|
1401
|
+
| { id: number; frame: VideoFrame }
|
|
1402
|
+
| { id: number; error: string };
|
|
1403
|
+
|
|
1404
|
+
const colorAlphaMergerWorkerCode = () => {
|
|
1405
|
+
// These buffers are reused across frames as long as the size matches, since consecutive frames usually share
|
|
1406
|
+
// dimensions
|
|
1407
|
+
let cpuAlphaBuffer: Uint8Array | null = null;
|
|
1408
|
+
let cpuColorBuffer: Uint8Array | null = null;
|
|
1409
|
+
|
|
1410
|
+
// Serialize execution internally so concurrent requests don't race on the shared cpu*Buffer state.
|
|
1411
|
+
let chain: Promise<void> = Promise.resolve();
|
|
1412
|
+
self.addEventListener('message', (event: MessageEvent<ColorAlphaMergerWorkerRequest>) => {
|
|
1413
|
+
const { id, color, alpha } = event.data;
|
|
1414
|
+
chain = chain.then(async () => {
|
|
1415
|
+
try {
|
|
1416
|
+
const frame = await merge(color, alpha);
|
|
1417
|
+
self.postMessage({ id, frame }, { transfer: [frame] });
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
self.postMessage({ id, error: (error as Error).message });
|
|
1420
|
+
} finally {
|
|
1421
|
+
// We took ownership of the inputs via transfer; close them now that the merge (or its error) is done.
|
|
1422
|
+
color.close();
|
|
1423
|
+
alpha.close();
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
const merge = async (color: VideoFrame, alpha: VideoFrame): Promise<VideoFrame> => {
|
|
1429
|
+
const format = color.format as VideoSamplePixelFormat | null;
|
|
1430
|
+
const alphaFormat = alpha.format as VideoSamplePixelFormat | null;
|
|
1431
|
+
if (!format || !alphaFormat) {
|
|
1432
|
+
throw new Error('CPU color/alpha merging requires a known VideoFrame format.');
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// The alpha frame must have the same bit depth as the color frame
|
|
1436
|
+
const colorIs10 = format.includes('P10');
|
|
1437
|
+
const colorIs12 = format.includes('P12');
|
|
1438
|
+
const alphaIs10 = alphaFormat.includes('P10');
|
|
1439
|
+
const alphaIs12 = alphaFormat.includes('P12');
|
|
1440
|
+
if (alphaIs10 !== colorIs10 || alphaIs12 !== colorIs12) {
|
|
1441
|
+
throw new Error(
|
|
1442
|
+
`CPU color/alpha merging requires the alpha frame to have the same bit depth as the color frame`
|
|
1443
|
+
+ ` (color: '${format}', alpha: '${alphaFormat}').`,
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (format === 'RGBX' || format === 'RGBA' || format === 'BGRX' || format === 'BGRA') {
|
|
1448
|
+
return await mergeInterleavedRgba(color, alpha, format);
|
|
1449
|
+
} else if (
|
|
1450
|
+
format === 'I420' || format === 'I420P10' || format === 'I420P12'
|
|
1451
|
+
|| format === 'I422' || format === 'I422P10' || format === 'I422P12'
|
|
1452
|
+
|| format === 'I444' || format === 'I444P10' || format === 'I444P12'
|
|
1453
|
+
) {
|
|
1454
|
+
return await mergePlanarYuv(color, alpha, format);
|
|
1455
|
+
} else if (format === 'NV12') {
|
|
1456
|
+
return await mergeNv12(color, alpha);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
throw new Error(`CPU color/alpha merging does not support format '${format}'.`);
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
const mergeInterleavedRgba = async (
|
|
1463
|
+
color: VideoFrame,
|
|
1464
|
+
alpha: VideoFrame,
|
|
1465
|
+
format: 'RGBX' | 'RGBA' | 'BGRX' | 'BGRA',
|
|
1466
|
+
): Promise<VideoFrame> => {
|
|
1467
|
+
const width = color.visibleRect?.width ?? color.codedWidth;
|
|
1468
|
+
const height = color.visibleRect?.height ?? color.codedHeight;
|
|
1469
|
+
|
|
1470
|
+
const pixelCount = width * height;
|
|
1471
|
+
const output = new Uint8Array(pixelCount * 4);
|
|
1472
|
+
|
|
1473
|
+
// Color goes straight into the output buffer via copyTo, no intermediate copy needed
|
|
1474
|
+
await color.copyTo(output);
|
|
1475
|
+
|
|
1476
|
+
// And now add the alpha data
|
|
1477
|
+
const alphaY = await readAlpha(alpha, width, height, 1);
|
|
1478
|
+
for (let i = 0, j = 3; i < pixelCount; i++, j += 4) {
|
|
1479
|
+
output[j] = alphaY[i]!;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const outputFormat = (format === 'RGBX' || format === 'RGBA') ? 'RGBA' : 'BGRA';
|
|
1483
|
+
const init = {
|
|
1484
|
+
format: outputFormat,
|
|
1485
|
+
codedWidth: width,
|
|
1486
|
+
codedHeight: height,
|
|
1487
|
+
timestamp: color.timestamp,
|
|
1488
|
+
duration: color.duration ?? undefined,
|
|
1489
|
+
transfer: [output.buffer],
|
|
1490
|
+
} as const;
|
|
1491
|
+
|
|
1492
|
+
return new VideoFrame(output, init);
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
const mergePlanarYuv = async (
|
|
1496
|
+
color: VideoFrame,
|
|
1497
|
+
alpha: VideoFrame,
|
|
1498
|
+
format:
|
|
1499
|
+
| 'I420' | 'I420P10' | 'I420P12'
|
|
1500
|
+
| 'I422' | 'I422P10' | 'I422P12'
|
|
1501
|
+
| 'I444' | 'I444P10' | 'I444P12',
|
|
1502
|
+
): Promise<VideoFrame> => {
|
|
1503
|
+
const width = color.visibleRect?.width ?? color.codedWidth;
|
|
1504
|
+
const height = color.visibleRect?.height ?? color.codedHeight;
|
|
1505
|
+
|
|
1506
|
+
const is10 = format.includes('P10');
|
|
1507
|
+
const is12 = format.includes('P12');
|
|
1508
|
+
const bytesPerSample = (is10 || is12) ? 2 : 1;
|
|
1509
|
+
|
|
1510
|
+
let chromaW: number;
|
|
1511
|
+
let chromaH: number;
|
|
1512
|
+
if (format.startsWith('I420')) {
|
|
1513
|
+
chromaW = Math.ceil(width / 2);
|
|
1514
|
+
chromaH = Math.ceil(height / 2);
|
|
1515
|
+
} else if (format.startsWith('I422')) {
|
|
1516
|
+
chromaW = Math.ceil(width / 2);
|
|
1517
|
+
chromaH = height;
|
|
1518
|
+
} else {
|
|
1519
|
+
chromaW = width;
|
|
1520
|
+
chromaH = height;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
const ySamples = width * height;
|
|
1524
|
+
const uvSamples = chromaW * chromaH;
|
|
1525
|
+
const yBytes = ySamples * bytesPerSample;
|
|
1526
|
+
const uvBytes = uvSamples * bytesPerSample;
|
|
1527
|
+
const aBytes = ySamples * bytesPerSample;
|
|
1528
|
+
|
|
1529
|
+
const outputBytes = yBytes + 2 * uvBytes + aBytes;
|
|
1530
|
+
const output = new Uint8Array(outputBytes);
|
|
1531
|
+
|
|
1532
|
+
// Write color planes directly into the output buffer via copyTo, no intermediate copy
|
|
1533
|
+
await color.copyTo(output);
|
|
1534
|
+
|
|
1535
|
+
const alphaY = await readAlpha(alpha, width, height, bytesPerSample);
|
|
1536
|
+
const aOffset = yBytes + 2 * uvBytes;
|
|
1537
|
+
output.set(alphaY, aOffset);
|
|
1538
|
+
|
|
1539
|
+
const outputFormat = (format.slice(0, 4) + 'A' + format.slice(4)) as VideoPixelFormat;
|
|
1540
|
+
|
|
1541
|
+
const init = {
|
|
1542
|
+
format: outputFormat,
|
|
1543
|
+
codedWidth: width,
|
|
1544
|
+
codedHeight: height,
|
|
1545
|
+
timestamp: color.timestamp,
|
|
1546
|
+
duration: color.duration ?? undefined,
|
|
1547
|
+
transfer: [output.buffer],
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
return new VideoFrame(output, init);
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
const mergeNv12 = async (
|
|
1554
|
+
color: VideoFrame,
|
|
1555
|
+
alpha: VideoFrame,
|
|
1556
|
+
): Promise<VideoFrame> => {
|
|
1557
|
+
const width = color.visibleRect?.width ?? color.codedWidth;
|
|
1558
|
+
const height = color.visibleRect?.height ?? color.codedHeight;
|
|
1559
|
+
|
|
1560
|
+
const ySize = width * height;
|
|
1561
|
+
const chromaW = Math.ceil(width / 2);
|
|
1562
|
+
const chromaH = Math.ceil(height / 2);
|
|
1563
|
+
const uvSize = chromaW * chromaH;
|
|
1564
|
+
|
|
1565
|
+
const sourceSize = color.allocationSize();
|
|
1566
|
+
if (!cpuColorBuffer || cpuColorBuffer.byteLength !== sourceSize) {
|
|
1567
|
+
cpuColorBuffer = new Uint8Array(sourceSize);
|
|
1568
|
+
}
|
|
1569
|
+
await color.copyTo(cpuColorBuffer);
|
|
1570
|
+
|
|
1571
|
+
const output = new Uint8Array(ySize + 2 * uvSize + ySize);
|
|
1572
|
+
|
|
1573
|
+
// Y plane copies straight over
|
|
1574
|
+
output.set(cpuColorBuffer.subarray(0, ySize), 0);
|
|
1575
|
+
|
|
1576
|
+
// Deinterleave the UV plane into separate U and V planes
|
|
1577
|
+
const uOffset = ySize;
|
|
1578
|
+
const vOffset = ySize + uvSize;
|
|
1579
|
+
const uvStart = ySize;
|
|
1580
|
+
for (let i = 0; i < uvSize; i++) {
|
|
1581
|
+
output[uOffset + i] = cpuColorBuffer[uvStart + i * 2]!;
|
|
1582
|
+
output[vOffset + i] = cpuColorBuffer[uvStart + i * 2 + 1]!;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const alphaY = await readAlpha(alpha, width, height, 1);
|
|
1586
|
+
output.set(alphaY, ySize + 2 * uvSize);
|
|
1587
|
+
|
|
1588
|
+
const init = {
|
|
1589
|
+
format: 'I420A',
|
|
1590
|
+
codedWidth: width,
|
|
1591
|
+
codedHeight: height,
|
|
1592
|
+
timestamp: color.timestamp,
|
|
1593
|
+
duration: color.duration ?? undefined,
|
|
1594
|
+
transfer: [output.buffer],
|
|
1595
|
+
} as const;
|
|
1596
|
+
|
|
1597
|
+
return new VideoFrame(output, init);
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
const readAlpha = async (alpha: VideoFrame, width: number, height: number, bytesPerSample: number) => {
|
|
1601
|
+
const size = alpha.allocationSize();
|
|
1602
|
+
if (!cpuAlphaBuffer || cpuAlphaBuffer.byteLength !== size) {
|
|
1603
|
+
cpuAlphaBuffer = new Uint8Array(size);
|
|
1604
|
+
}
|
|
1605
|
+
await alpha.copyTo(cpuAlphaBuffer);
|
|
1606
|
+
|
|
1607
|
+
const format = alpha.format;
|
|
1608
|
+
if (format === 'RGBA' || format === 'BGRA' || format === 'RGBX' || format === 'BGRX') {
|
|
1609
|
+
// Pack alpha data tightly. Assume alpha is stored in RGB, so sample just from R for simplicity.
|
|
1610
|
+
const rOffset = (format === 'RGBA' || format === 'RGBX') ? 0 : 2;
|
|
1611
|
+
const pixelCount = width * height;
|
|
1612
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
1613
|
+
cpuAlphaBuffer[i] = cpuAlphaBuffer[i * 4 + rOffset]!;
|
|
1614
|
+
}
|
|
1615
|
+
return cpuAlphaBuffer.subarray(0, pixelCount);
|
|
1616
|
+
} else {
|
|
1617
|
+
// For Y-plane-first formats (I*** and NV12), the leading width*height samples are the Y plane
|
|
1618
|
+
return cpuAlphaBuffer.subarray(0, width * height * bytesPerSample);
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* Describes additional decoder preferences for video sinks.
|
|
1625
|
+
* @group Media sinks
|
|
1626
|
+
* @public
|
|
1627
|
+
*/
|
|
1628
|
+
export type VideoSinkDecoderOptions = {
|
|
1629
|
+
/**
|
|
1630
|
+
* A hint that configures the hardware acceleration method of the decoder. This is best left on `'no-preference'`,
|
|
1631
|
+
* the default.
|
|
1632
|
+
*/
|
|
1633
|
+
hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';
|
|
1634
|
+
/**
|
|
1635
|
+
* Hint that the selected decoder should be configured to minimize the number of packets that have to be decoded
|
|
1636
|
+
* before video frames are output.
|
|
1637
|
+
*/
|
|
1638
|
+
optimizeForLatency?: boolean;
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
const validateVideoSinkDecoderOptions = (decoderOptions: VideoSinkDecoderOptions) => {
|
|
1642
|
+
if (!decoderOptions || typeof decoderOptions !== 'object') {
|
|
1643
|
+
throw new TypeError('decoderOptions must be an object.');
|
|
1644
|
+
}
|
|
1645
|
+
if (
|
|
1646
|
+
decoderOptions.hardwareAcceleration !== undefined
|
|
1647
|
+
&& !['no-preference', 'prefer-hardware', 'prefer-software'].includes(decoderOptions.hardwareAcceleration)
|
|
1648
|
+
) {
|
|
1649
|
+
throw new TypeError(
|
|
1650
|
+
'decoderOptions.hardwareAcceleration, when provided, must be \'no-preference\', \'prefer-hardware\' or'
|
|
1651
|
+
+ ' \'prefer-software\'.',
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
if (decoderOptions.optimizeForLatency !== undefined && typeof decoderOptions.optimizeForLatency !== 'boolean') {
|
|
1655
|
+
throw new TypeError('decoderOptions.optimizeForLatency, when provided, must be a boolean.');
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
/**
|
|
1660
|
+
* A sink that retrieves decoded video samples (video frames) from a video track.
|
|
1661
|
+
* @group Media sinks
|
|
1662
|
+
* @public
|
|
1663
|
+
*/
|
|
1664
|
+
export class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
|
|
1665
|
+
/** @internal */
|
|
1666
|
+
_track: InputVideoTrack;
|
|
1667
|
+
/** @internal */
|
|
1668
|
+
_decoderOptions: VideoSinkDecoderOptions;
|
|
1669
|
+
|
|
1670
|
+
/** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */
|
|
1671
|
+
constructor(videoTrack: InputVideoTrack, decoderOptions: VideoSinkDecoderOptions = {}) {
|
|
1672
|
+
if (!(videoTrack instanceof InputVideoTrack)) {
|
|
1673
|
+
throw new TypeError('videoTrack must be an InputVideoTrack.');
|
|
1674
|
+
}
|
|
1675
|
+
validateVideoSinkDecoderOptions(decoderOptions);
|
|
1676
|
+
|
|
1677
|
+
super();
|
|
1678
|
+
|
|
1679
|
+
this._track = videoTrack;
|
|
1680
|
+
this._decoderOptions = decoderOptions;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/** @internal */
|
|
1684
|
+
async _createDecoder(
|
|
1685
|
+
onSample: (sample: VideoSample) => unknown,
|
|
1686
|
+
onError: (error: unknown) => unknown,
|
|
1687
|
+
) {
|
|
1688
|
+
if (!(await this._track.canDecode())) {
|
|
1689
|
+
throw new Error(
|
|
1690
|
+
'This video track cannot be decoded by this browser. Make sure to check decodability before using'
|
|
1691
|
+
+ ' a track.',
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const codec = await this._track.getCodec();
|
|
1696
|
+
const rotation = await this._track.getRotation();
|
|
1697
|
+
let decoderConfig = await this._track.getDecoderConfig();
|
|
1698
|
+
const timeResolution = await this._track.getTimeResolution();
|
|
1699
|
+
assert(codec && decoderConfig);
|
|
1700
|
+
|
|
1701
|
+
decoderConfig = {
|
|
1702
|
+
...decoderConfig,
|
|
1703
|
+
hardwareAcceleration: this._decoderOptions.hardwareAcceleration,
|
|
1704
|
+
optimizeForLatency: this._decoderOptions.optimizeForLatency,
|
|
1705
|
+
};
|
|
1706
|
+
|
|
1707
|
+
return new VideoDecoderWrapper(onSample, onError, codec, decoderConfig, rotation, timeResolution);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/** @internal */
|
|
1711
|
+
_createPacketSink() {
|
|
1712
|
+
return new EncodedPacketSink(this._track);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* Retrieves the video sample (frame) corresponding to the given timestamp, in seconds. More specifically, returns
|
|
1717
|
+
* the last video sample (in presentation order) with a start timestamp less than or equal to the given timestamp.
|
|
1718
|
+
* Returns null if the timestamp is before the track's first timestamp.
|
|
1719
|
+
*
|
|
1720
|
+
* @param timestamp - The timestamp used for retrieval, in seconds.
|
|
1721
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
1722
|
+
*/
|
|
1723
|
+
async getSample(timestamp: number, options: PacketRetrievalOptions = {}) {
|
|
1724
|
+
validateTimestamp(timestamp);
|
|
1725
|
+
|
|
1726
|
+
for await (const sample of this.mediaSamplesAtTimestamps([timestamp], options)) {
|
|
1727
|
+
return sample;
|
|
1728
|
+
}
|
|
1729
|
+
throw new Error('Internal error: Iterator returned nothing.');
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
/**
|
|
1733
|
+
* Creates an async iterator that yields the video samples (frames) of this track in presentation order. This method
|
|
1734
|
+
* will intelligently pre-decode a few frames ahead to enable fast iteration.
|
|
1735
|
+
*
|
|
1736
|
+
* @param startTimestamp - The timestamp in seconds at which to start yielding samples (inclusive).
|
|
1737
|
+
* @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive).
|
|
1738
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
1739
|
+
*/
|
|
1740
|
+
samples(startTimestamp?: number, endTimestamp?: number, options: PacketRetrievalOptions = {}) {
|
|
1741
|
+
return this.mediaSamplesInRange(startTimestamp, endTimestamp, options);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/**
|
|
1745
|
+
* Creates an async iterator that yields a video sample (frame) for each timestamp in the argument. This method
|
|
1746
|
+
* uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most
|
|
1747
|
+
* once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may
|
|
1748
|
+
* yield null if no frame is available for a given timestamp.
|
|
1749
|
+
*
|
|
1750
|
+
* This method is good for sparse access of media data. If you want primarily sequential media access, prefer
|
|
1751
|
+
* {@link VideoSampleSink.samples} instead.
|
|
1752
|
+
*
|
|
1753
|
+
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
1754
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
1755
|
+
*/
|
|
1756
|
+
samplesAtTimestamps(timestamps: AnyIterable<number>, options: PacketRetrievalOptions = {}) {
|
|
1757
|
+
return this.mediaSamplesAtTimestamps(timestamps, options);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
/**
|
|
1762
|
+
* A canvas with additional timing information (timestamp & duration).
|
|
1763
|
+
* @group Media sinks
|
|
1764
|
+
* @public
|
|
1765
|
+
*/
|
|
1766
|
+
export type WrappedCanvas = {
|
|
1767
|
+
/** A canvas element or offscreen canvas. */
|
|
1768
|
+
canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
1769
|
+
/** The timestamp of the corresponding video sample, in seconds. */
|
|
1770
|
+
timestamp: number;
|
|
1771
|
+
/** The duration of the corresponding video sample, in seconds. */
|
|
1772
|
+
duration: number;
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* Options for constructing a CanvasSink.
|
|
1777
|
+
* @group Media sinks
|
|
1778
|
+
* @public
|
|
1779
|
+
*/
|
|
1780
|
+
export type CanvasSinkOptions = {
|
|
1781
|
+
/**
|
|
1782
|
+
* Whether the output canvases should have transparency instead of a black background. Defaults to `false`. Set
|
|
1783
|
+
* this to `true` when using this sink to read transparent videos.
|
|
1784
|
+
*/
|
|
1785
|
+
alpha?: boolean;
|
|
1786
|
+
/**
|
|
1787
|
+
* The width of the output canvas in pixels, defaulting to the display width of the video track. If height is not
|
|
1788
|
+
* set, it will be deduced automatically based on aspect ratio.
|
|
1789
|
+
*/
|
|
1790
|
+
width?: number;
|
|
1791
|
+
/**
|
|
1792
|
+
* The height of the output canvas in pixels, defaulting to the display height of the video track. If width is not
|
|
1793
|
+
* set, it will be deduced automatically based on aspect ratio.
|
|
1794
|
+
*/
|
|
1795
|
+
height?: number;
|
|
1796
|
+
/**
|
|
1797
|
+
* The fitting algorithm in case both width and height are set.
|
|
1798
|
+
*
|
|
1799
|
+
* - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio.
|
|
1800
|
+
* - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to
|
|
1801
|
+
* letterboxing.
|
|
1802
|
+
* - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio.
|
|
1803
|
+
*/
|
|
1804
|
+
fit?: 'fill' | 'contain' | 'cover';
|
|
1805
|
+
/**
|
|
1806
|
+
* The clockwise rotation by which to rotate the raw video frame. Defaults to the rotation set in the file metadata.
|
|
1807
|
+
* Rotation is applied before resizing.
|
|
1808
|
+
*/
|
|
1809
|
+
rotation?: Rotation;
|
|
1810
|
+
/**
|
|
1811
|
+
* Specifies the rectangular region of the input video to crop to. The crop region will automatically be clamped to
|
|
1812
|
+
* the dimensions of the input video track. Cropping is performed after rotation but before resizing. The crop
|
|
1813
|
+
* region is in the _display pixel space_ of the underlying video data.
|
|
1814
|
+
*/
|
|
1815
|
+
crop?: CropRectangle;
|
|
1816
|
+
/**
|
|
1817
|
+
* When set, specifies the number of canvases in the pool. These canvases will be reused in a ring buffer /
|
|
1818
|
+
* round-robin type fashion. This keeps the amount of allocated VRAM constant and relieves the browser from
|
|
1819
|
+
* constantly allocating/deallocating canvases. A pool size of 0 or `undefined` disables the pool and means a new
|
|
1820
|
+
* canvas is created each time.
|
|
1821
|
+
*/
|
|
1822
|
+
poolSize?: number;
|
|
1823
|
+
/** Additional preferences for the underlying video decoder. */
|
|
1824
|
+
decoderOptions?: VideoSinkDecoderOptions;
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* A sink that renders video samples (frames) of the given video track to canvases. This is often more useful than
|
|
1829
|
+
* directly retrieving frames, as it comes with common preprocessing steps such as resizing or applying rotation
|
|
1830
|
+
* metadata.
|
|
1831
|
+
*
|
|
1832
|
+
* This sink will yield `HTMLCanvasElement`s when in a DOM context, and `OffscreenCanvas`es otherwise.
|
|
1833
|
+
*
|
|
1834
|
+
* @group Media sinks
|
|
1835
|
+
* @public
|
|
1836
|
+
*/
|
|
1837
|
+
export class CanvasSink {
|
|
1838
|
+
/** @internal */
|
|
1839
|
+
_videoTrack: InputVideoTrack;
|
|
1840
|
+
/** @internal */
|
|
1841
|
+
_alpha: boolean;
|
|
1842
|
+
/** @internal */
|
|
1843
|
+
_width!: number;
|
|
1844
|
+
/** @internal */
|
|
1845
|
+
_height!: number;
|
|
1846
|
+
/** @internal */
|
|
1847
|
+
_options: CanvasSinkOptions;
|
|
1848
|
+
/** @internal */
|
|
1849
|
+
_fit: 'fill' | 'contain' | 'cover';
|
|
1850
|
+
/** @internal */
|
|
1851
|
+
_rotation: Rotation = 0;
|
|
1852
|
+
/** @internal */
|
|
1853
|
+
_crop?: { left: number; top: number; width: number; height: number };
|
|
1854
|
+
/** @internal */
|
|
1855
|
+
_initPromise: Promise<void> | null = null;
|
|
1856
|
+
/** @internal */
|
|
1857
|
+
_videoSampleSink: VideoSampleSink;
|
|
1858
|
+
/** @internal */
|
|
1859
|
+
_canvasPool: (HTMLCanvasElement | OffscreenCanvas | null)[];
|
|
1860
|
+
/** @internal */
|
|
1861
|
+
_nextCanvasIndex = 0;
|
|
1862
|
+
|
|
1863
|
+
/** Creates a new {@link CanvasSink} for the given {@link InputVideoTrack}. */
|
|
1864
|
+
constructor(videoTrack: InputVideoTrack, options: CanvasSinkOptions = {}) {
|
|
1865
|
+
if (!(videoTrack instanceof InputVideoTrack)) {
|
|
1866
|
+
throw new TypeError('videoTrack must be an InputVideoTrack.');
|
|
1867
|
+
}
|
|
1868
|
+
if (options && typeof options !== 'object') {
|
|
1869
|
+
throw new TypeError('options must be an object.');
|
|
1870
|
+
}
|
|
1871
|
+
if (options.alpha !== undefined && typeof options.alpha !== 'boolean') {
|
|
1872
|
+
throw new TypeError('options.alpha, when provided, must be a boolean.');
|
|
1873
|
+
}
|
|
1874
|
+
if (options.width !== undefined && (!Number.isInteger(options.width) || options.width <= 0)) {
|
|
1875
|
+
throw new TypeError('options.width, when defined, must be a positive integer.');
|
|
1876
|
+
}
|
|
1877
|
+
if (options.height !== undefined && (!Number.isInteger(options.height) || options.height <= 0)) {
|
|
1878
|
+
throw new TypeError('options.height, when defined, must be a positive integer.');
|
|
1879
|
+
}
|
|
1880
|
+
if (options.fit !== undefined && !['fill', 'contain', 'cover'].includes(options.fit)) {
|
|
1881
|
+
throw new TypeError('options.fit, when provided, must be one of "fill", "contain", or "cover".');
|
|
1882
|
+
}
|
|
1883
|
+
if (
|
|
1884
|
+
options.width !== undefined
|
|
1885
|
+
&& options.height !== undefined
|
|
1886
|
+
&& options.fit === undefined
|
|
1887
|
+
) {
|
|
1888
|
+
throw new TypeError(
|
|
1889
|
+
'When both options.width and options.height are provided, options.fit must also be provided.',
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
if (options.rotation !== undefined && ![0, 90, 180, 270].includes(options.rotation)) {
|
|
1893
|
+
throw new TypeError('options.rotation, when provided, must be 0, 90, 180 or 270.');
|
|
1894
|
+
}
|
|
1895
|
+
if (options.crop !== undefined) {
|
|
1896
|
+
validateCropRectangle(options.crop, 'options.');
|
|
1897
|
+
}
|
|
1898
|
+
if (
|
|
1899
|
+
options.poolSize !== undefined
|
|
1900
|
+
&& (typeof options.poolSize !== 'number' || !Number.isInteger(options.poolSize) || options.poolSize < 0)
|
|
1901
|
+
) {
|
|
1902
|
+
throw new TypeError('poolSize must be a non-negative integer.');
|
|
1903
|
+
}
|
|
1904
|
+
if (options.decoderOptions !== undefined) {
|
|
1905
|
+
validateVideoSinkDecoderOptions(options.decoderOptions);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
this._videoTrack = videoTrack;
|
|
1909
|
+
this._alpha = options.alpha ?? false;
|
|
1910
|
+
this._options = options;
|
|
1911
|
+
this._fit = options.fit ?? 'fill';
|
|
1912
|
+
this._videoSampleSink = new VideoSampleSink(videoTrack, options.decoderOptions);
|
|
1913
|
+
this._canvasPool = Array.from({ length: options.poolSize ?? 0 }, () => null);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
/** @internal */
|
|
1917
|
+
_ensureInit() {
|
|
1918
|
+
return this._initPromise ??= (async () => {
|
|
1919
|
+
const options = this._options;
|
|
1920
|
+
const videoTrack = this._videoTrack;
|
|
1921
|
+
|
|
1922
|
+
const rotation = options.rotation ?? await videoTrack.getRotation();
|
|
1923
|
+
const squarePixelWidth = await videoTrack.getSquarePixelWidth();
|
|
1924
|
+
const squarePixelHeight = await videoTrack.getSquarePixelHeight();
|
|
1925
|
+
|
|
1926
|
+
const [rotatedWidth, rotatedHeight] = rotation % 180 === 0
|
|
1927
|
+
? [squarePixelWidth, squarePixelHeight]
|
|
1928
|
+
: [squarePixelHeight, squarePixelWidth];
|
|
1929
|
+
|
|
1930
|
+
let crop = options.crop;
|
|
1931
|
+
if (crop) {
|
|
1932
|
+
crop = clampCropRectangle(crop, rotatedWidth, rotatedHeight);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
let [width, height] = crop
|
|
1936
|
+
? [crop.width, crop.height]
|
|
1937
|
+
: [rotatedWidth, rotatedHeight];
|
|
1938
|
+
const originalAspectRatio = width / height;
|
|
1939
|
+
|
|
1940
|
+
// If width and height aren't defined together, deduce the missing value using the aspect ratio
|
|
1941
|
+
if (options.width !== undefined && options.height === undefined) {
|
|
1942
|
+
width = options.width;
|
|
1943
|
+
height = Math.round(width / originalAspectRatio);
|
|
1944
|
+
} else if (options.width === undefined && options.height !== undefined) {
|
|
1945
|
+
height = options.height;
|
|
1946
|
+
width = Math.round(height * originalAspectRatio);
|
|
1947
|
+
} else if (options.width !== undefined && options.height !== undefined) {
|
|
1948
|
+
width = options.width;
|
|
1949
|
+
height = options.height;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
this._width = width;
|
|
1953
|
+
this._height = height;
|
|
1954
|
+
this._rotation = rotation;
|
|
1955
|
+
this._crop = crop;
|
|
1956
|
+
})();
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/** @internal */
|
|
1960
|
+
_videoSampleToWrappedCanvas(sample: VideoSample): WrappedCanvas {
|
|
1961
|
+
const width = this._width;
|
|
1962
|
+
const height = this._height;
|
|
1963
|
+
let canvas = this._canvasPool[this._nextCanvasIndex];
|
|
1964
|
+
let canvasIsNew = false;
|
|
1965
|
+
|
|
1966
|
+
if (!canvas) {
|
|
1967
|
+
if (typeof document !== 'undefined') {
|
|
1968
|
+
// Prefer an HTMLCanvasElement
|
|
1969
|
+
canvas = document.createElement('canvas');
|
|
1970
|
+
canvas.width = width;
|
|
1971
|
+
canvas.height = height;
|
|
1972
|
+
} else {
|
|
1973
|
+
canvas = new OffscreenCanvas(width, height);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
if (this._canvasPool.length > 0) {
|
|
1977
|
+
this._canvasPool[this._nextCanvasIndex] = canvas;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
canvasIsNew = true;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
if (this._canvasPool.length > 0) {
|
|
1984
|
+
this._nextCanvasIndex = (this._nextCanvasIndex + 1) % this._canvasPool.length;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
const context = canvas.getContext('2d', {
|
|
1988
|
+
alpha: this._alpha || isFirefox(), // Firefox has VideoFrame glitches with opaque canvases
|
|
1989
|
+
}) as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
1990
|
+
assert(context);
|
|
1991
|
+
|
|
1992
|
+
context.imageSmoothingQuality = 'high';
|
|
1993
|
+
|
|
1994
|
+
if (!canvasIsNew) {
|
|
1995
|
+
if (!this._alpha && isFirefox()) {
|
|
1996
|
+
context.fillStyle = 'black';
|
|
1997
|
+
context.fillRect(0, 0, width, height);
|
|
1998
|
+
} else {
|
|
1999
|
+
context.clearRect(0, 0, width, height);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
sample.drawWithFit(context, {
|
|
2004
|
+
fit: this._fit,
|
|
2005
|
+
rotation: this._rotation,
|
|
2006
|
+
crop: this._crop,
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
const result = {
|
|
2010
|
+
canvas,
|
|
2011
|
+
timestamp: sample.timestamp,
|
|
2012
|
+
duration: sample.duration,
|
|
2013
|
+
};
|
|
2014
|
+
|
|
2015
|
+
sample.close();
|
|
2016
|
+
return result;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
/**
|
|
2020
|
+
* Retrieves a canvas with the video frame corresponding to the given timestamp, in seconds. More specifically,
|
|
2021
|
+
* returns the last video frame (in presentation order) with a start timestamp less than or equal to the given
|
|
2022
|
+
* timestamp. Returns null if the timestamp is before the track's first timestamp.
|
|
2023
|
+
*
|
|
2024
|
+
* @param timestamp - The timestamp used for retrieval, in seconds.
|
|
2025
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2026
|
+
*/
|
|
2027
|
+
async getCanvas(timestamp: number, options?: PacketRetrievalOptions) {
|
|
2028
|
+
validateTimestamp(timestamp);
|
|
2029
|
+
await this._ensureInit();
|
|
2030
|
+
|
|
2031
|
+
const sample = await this._videoSampleSink.getSample(timestamp, options);
|
|
2032
|
+
return sample && this._videoSampleToWrappedCanvas(sample);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* Creates an async iterator that yields canvases with the video frames of this track in presentation order. This
|
|
2037
|
+
* method will intelligently pre-decode a few frames ahead to enable fast iteration.
|
|
2038
|
+
*
|
|
2039
|
+
* @param startTimestamp - The timestamp in seconds at which to start yielding canvases (inclusive).
|
|
2040
|
+
* @param endTimestamp - The timestamp in seconds at which to stop yielding canvases (exclusive).
|
|
2041
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2042
|
+
*/
|
|
2043
|
+
async* canvases(startTimestamp?: number, endTimestamp?: number, options?: PacketRetrievalOptions) {
|
|
2044
|
+
await this._ensureInit();
|
|
2045
|
+
yield* mapAsyncGenerator(
|
|
2046
|
+
this._videoSampleSink.samples(startTimestamp, endTimestamp, options),
|
|
2047
|
+
sample => this._videoSampleToWrappedCanvas(sample),
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* Creates an async iterator that yields a canvas for each timestamp in the argument. This method uses an optimized
|
|
2053
|
+
* decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most once, and is
|
|
2054
|
+
* therefore more efficient than manually getting the canvas for every timestamp. The iterator may yield null if
|
|
2055
|
+
* no frame is available for a given timestamp.
|
|
2056
|
+
*
|
|
2057
|
+
* This method is good for sparse access of media data. If you want primarily sequential media access, prefer
|
|
2058
|
+
* {@link CanvasSink.canvases} instead.
|
|
2059
|
+
*
|
|
2060
|
+
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
2061
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2062
|
+
*/
|
|
2063
|
+
async* canvasesAtTimestamps(timestamps: AnyIterable<number>, options?: PacketRetrievalOptions) {
|
|
2064
|
+
await this._ensureInit();
|
|
2065
|
+
yield* mapAsyncGenerator(
|
|
2066
|
+
this._videoSampleSink.samplesAtTimestamps(timestamps, options),
|
|
2067
|
+
sample => sample && this._videoSampleToWrappedCanvas(sample),
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|
2073
|
+
decoder: AudioDecoder | null = null;
|
|
2074
|
+
|
|
2075
|
+
customDecoder: CustomAudioDecoder | null = null;
|
|
2076
|
+
customDecoderCallSerializer = new CallSerializer();
|
|
2077
|
+
customDecoderQueueSize = 0;
|
|
2078
|
+
|
|
2079
|
+
// Internal state to accumulate a precise current timestamp based on audio durations, not the (potentially
|
|
2080
|
+
// inaccurate) packet timestamps.
|
|
2081
|
+
currentTimestamp: number | null = null;
|
|
2082
|
+
// Chromium does not respect negative packet timestamps, so we must do the fixin' ourselves
|
|
2083
|
+
expectedFirstTimestamp: number | null = null;
|
|
2084
|
+
timestampOffset = 0;
|
|
2085
|
+
|
|
2086
|
+
constructor(
|
|
2087
|
+
onSample: (sample: AudioSample) => unknown,
|
|
2088
|
+
onError: (error: unknown) => unknown,
|
|
2089
|
+
codec: AudioCodec,
|
|
2090
|
+
decoderConfig: AudioDecoderConfig,
|
|
2091
|
+
) {
|
|
2092
|
+
super(onSample, onError);
|
|
2093
|
+
|
|
2094
|
+
const sampleHandler = (sample: AudioSample) => {
|
|
2095
|
+
let sampleTimestamp = sample.timestamp;
|
|
2096
|
+
|
|
2097
|
+
if (this.expectedFirstTimestamp && this.currentTimestamp === null) {
|
|
2098
|
+
this.timestampOffset = this.expectedFirstTimestamp - sampleTimestamp; ;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
sampleTimestamp += this.timestampOffset;
|
|
2102
|
+
|
|
2103
|
+
if (
|
|
2104
|
+
this.currentTimestamp === null
|
|
2105
|
+
|| Math.abs(sampleTimestamp - this.currentTimestamp) >= sample.duration
|
|
2106
|
+
) {
|
|
2107
|
+
// We need to sync with the sample timestamp again
|
|
2108
|
+
this.currentTimestamp = sampleTimestamp;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
const preciseTimestamp = this.currentTimestamp;
|
|
2112
|
+
this.currentTimestamp += sample.duration;
|
|
2113
|
+
|
|
2114
|
+
if (sample.numberOfFrames === 0) {
|
|
2115
|
+
// We skip zero-data (empty) AudioSamples. These are sometimes emitted, for example, by Firefox when it
|
|
2116
|
+
// decodes Vorbis (at the start).
|
|
2117
|
+
sample.close();
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// Round the timestamp to the sample rate
|
|
2122
|
+
const sampleRate = decoderConfig.sampleRate;
|
|
2123
|
+
sample.setTimestamp(Math.round(preciseTimestamp * sampleRate) / sampleRate);
|
|
2124
|
+
|
|
2125
|
+
onSample(sample);
|
|
2126
|
+
};
|
|
2127
|
+
|
|
2128
|
+
const MatchingCustomDecoder = customAudioDecoders.find(x => x.supports(codec, decoderConfig));
|
|
2129
|
+
if (MatchingCustomDecoder) {
|
|
2130
|
+
// @ts-expect-error "Can't create instance of abstract class 🤓"
|
|
2131
|
+
this.customDecoder = new MatchingCustomDecoder() as CustomAudioDecoder;
|
|
2132
|
+
// @ts-expect-error It's technically readonly
|
|
2133
|
+
this.customDecoder.codec = codec;
|
|
2134
|
+
// @ts-expect-error It's technically readonly
|
|
2135
|
+
this.customDecoder.config = decoderConfig;
|
|
2136
|
+
// @ts-expect-error It's technically readonly
|
|
2137
|
+
this.customDecoder.onSample = (sample) => {
|
|
2138
|
+
if (!(sample instanceof AudioSample)) {
|
|
2139
|
+
throw new TypeError('The argument passed to onSample must be an AudioSample.');
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
sampleHandler(sample);
|
|
2143
|
+
};
|
|
2144
|
+
// @ts-expect-error It's technically readonly
|
|
2145
|
+
this.customDecoder.onError = (error) => {
|
|
2146
|
+
onError(error);
|
|
2147
|
+
};
|
|
2148
|
+
|
|
2149
|
+
void this.customDecoderCallSerializer
|
|
2150
|
+
.call(() => this.customDecoder!.init())
|
|
2151
|
+
.catch(error => onError(error));
|
|
2152
|
+
} else {
|
|
2153
|
+
const stack = new Error('Decoding error').stack;
|
|
2154
|
+
|
|
2155
|
+
this.decoder = new AudioDecoder({
|
|
2156
|
+
output: (data) => {
|
|
2157
|
+
try {
|
|
2158
|
+
sampleHandler(new AudioSample(data));
|
|
2159
|
+
} catch (error) {
|
|
2160
|
+
this.onError(error);
|
|
2161
|
+
}
|
|
2162
|
+
},
|
|
2163
|
+
error: (error) => {
|
|
2164
|
+
error.stack = stack; // Provide a more useful stack trace, the default one sucks
|
|
2165
|
+
this.onError(error);
|
|
2166
|
+
},
|
|
2167
|
+
});
|
|
2168
|
+
this.decoder.configure(decoderConfig);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
getDecodeQueueSize() {
|
|
2173
|
+
if (this.customDecoder) {
|
|
2174
|
+
return this.customDecoderQueueSize;
|
|
2175
|
+
} else {
|
|
2176
|
+
assert(this.decoder);
|
|
2177
|
+
return this.decoder.decodeQueueSize;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
decode(packet: EncodedPacket) {
|
|
2182
|
+
if (this.customDecoder) {
|
|
2183
|
+
this.customDecoderQueueSize++;
|
|
2184
|
+
void this.customDecoderCallSerializer
|
|
2185
|
+
.call(() => this.customDecoder!.decode(packet))
|
|
2186
|
+
.catch(error => this.onError(error))
|
|
2187
|
+
.finally(() => this.customDecoderQueueSize--);
|
|
2188
|
+
} else {
|
|
2189
|
+
assert(this.decoder);
|
|
2190
|
+
|
|
2191
|
+
this.expectedFirstTimestamp ??= packet.timestamp;
|
|
2192
|
+
this.decoder.decode(packet.toEncodedAudioChunk());
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
async flush() {
|
|
2197
|
+
if (this.customDecoder) {
|
|
2198
|
+
await this.customDecoderCallSerializer.call(() => this.customDecoder!.flush());
|
|
2199
|
+
} else {
|
|
2200
|
+
assert(this.decoder);
|
|
2201
|
+
await this.decoder.flush();
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
this.currentTimestamp = null;
|
|
2205
|
+
this.expectedFirstTimestamp = null;
|
|
2206
|
+
this.timestampOffset = 0;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
close() {
|
|
2210
|
+
if (this.customDecoder) {
|
|
2211
|
+
void this.customDecoderCallSerializer.call(() => this.customDecoder!.close());
|
|
2212
|
+
} else {
|
|
2213
|
+
assert(this.decoder);
|
|
2214
|
+
this.decoder.close();
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// There are a lot of PCM variants not natively supported by the browser and by AudioData. Therefore we need a simple
|
|
2220
|
+
// decoder that maps any input PCM format into a PCM format supported by the browser.
|
|
2221
|
+
class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|
2222
|
+
codec: PcmAudioCodec;
|
|
2223
|
+
|
|
2224
|
+
inputSampleSize: 1 | 2 | 3 | 4 | 8;
|
|
2225
|
+
readInputValue: (view: DataView, byteOffset: number) => number;
|
|
2226
|
+
|
|
2227
|
+
outputSampleSize: 1 | 2 | 4;
|
|
2228
|
+
outputFormat: 'u8' | 's16' | 's32' | 'f32';
|
|
2229
|
+
writeOutputValue: (view: DataView, byteOffset: number, value: number) => void;
|
|
2230
|
+
|
|
2231
|
+
// Internal state to accumulate a precise current timestamp based on audio durations, not the (potentially
|
|
2232
|
+
// inaccurate) packet timestamps.
|
|
2233
|
+
currentTimestamp: number | null = null;
|
|
2234
|
+
|
|
2235
|
+
constructor(
|
|
2236
|
+
onSample: (sample: AudioSample) => unknown,
|
|
2237
|
+
onError: (error: unknown) => unknown,
|
|
2238
|
+
public decoderConfig: AudioDecoderConfig,
|
|
2239
|
+
) {
|
|
2240
|
+
super(onSample, onError);
|
|
2241
|
+
|
|
2242
|
+
assert((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec));
|
|
2243
|
+
this.codec = decoderConfig.codec as PcmAudioCodec;
|
|
2244
|
+
|
|
2245
|
+
const { dataType, sampleSize, littleEndian } = parsePcmCodec(this.codec);
|
|
2246
|
+
this.inputSampleSize = sampleSize;
|
|
2247
|
+
|
|
2248
|
+
switch (sampleSize) {
|
|
2249
|
+
case 1: {
|
|
2250
|
+
if (dataType === 'unsigned') {
|
|
2251
|
+
this.readInputValue = (view, byteOffset) => view.getUint8(byteOffset) - 2 ** 7;
|
|
2252
|
+
} else if (dataType === 'signed') {
|
|
2253
|
+
this.readInputValue = (view, byteOffset) => view.getInt8(byteOffset);
|
|
2254
|
+
} else if (dataType === 'ulaw') {
|
|
2255
|
+
this.readInputValue = (view, byteOffset) => fromUlaw(view.getUint8(byteOffset));
|
|
2256
|
+
} else if (dataType === 'alaw') {
|
|
2257
|
+
this.readInputValue = (view, byteOffset) => fromAlaw(view.getUint8(byteOffset));
|
|
2258
|
+
} else {
|
|
2259
|
+
assert(false);
|
|
2260
|
+
}
|
|
2261
|
+
}; break;
|
|
2262
|
+
case 2: {
|
|
2263
|
+
if (dataType === 'unsigned') {
|
|
2264
|
+
this.readInputValue = (view, byteOffset) => view.getUint16(byteOffset, littleEndian) - 2 ** 15;
|
|
2265
|
+
} else if (dataType === 'signed') {
|
|
2266
|
+
this.readInputValue = (view, byteOffset) => view.getInt16(byteOffset, littleEndian);
|
|
2267
|
+
} else {
|
|
2268
|
+
assert(false);
|
|
2269
|
+
}
|
|
2270
|
+
}; break;
|
|
2271
|
+
case 3: {
|
|
2272
|
+
if (dataType === 'unsigned') {
|
|
2273
|
+
this.readInputValue = (view, byteOffset) => getUint24(view, byteOffset, littleEndian) - 2 ** 23;
|
|
2274
|
+
} else if (dataType === 'signed') {
|
|
2275
|
+
this.readInputValue = (view, byteOffset) => getInt24(view, byteOffset, littleEndian);
|
|
2276
|
+
} else {
|
|
2277
|
+
assert(false);
|
|
2278
|
+
}
|
|
2279
|
+
}; break;
|
|
2280
|
+
case 4: {
|
|
2281
|
+
if (dataType === 'unsigned') {
|
|
2282
|
+
this.readInputValue = (view, byteOffset) => view.getUint32(byteOffset, littleEndian) - 2 ** 31;
|
|
2283
|
+
} else if (dataType === 'signed') {
|
|
2284
|
+
this.readInputValue = (view, byteOffset) => view.getInt32(byteOffset, littleEndian);
|
|
2285
|
+
} else if (dataType === 'float') {
|
|
2286
|
+
this.readInputValue = (view, byteOffset) => view.getFloat32(byteOffset, littleEndian);
|
|
2287
|
+
} else {
|
|
2288
|
+
assert(false);
|
|
2289
|
+
}
|
|
2290
|
+
}; break;
|
|
2291
|
+
case 8: {
|
|
2292
|
+
if (dataType === 'float') {
|
|
2293
|
+
this.readInputValue = (view, byteOffset) => view.getFloat64(byteOffset, littleEndian);
|
|
2294
|
+
} else {
|
|
2295
|
+
assert(false);
|
|
2296
|
+
}
|
|
2297
|
+
}; break;
|
|
2298
|
+
default: {
|
|
2299
|
+
assertNever(sampleSize);
|
|
2300
|
+
assert(false);
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
switch (sampleSize) {
|
|
2305
|
+
case 1: {
|
|
2306
|
+
if (dataType === 'ulaw' || dataType === 'alaw') {
|
|
2307
|
+
this.outputSampleSize = 2;
|
|
2308
|
+
this.outputFormat = 's16';
|
|
2309
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setInt16(byteOffset, value, true);
|
|
2310
|
+
} else {
|
|
2311
|
+
this.outputSampleSize = 1;
|
|
2312
|
+
this.outputFormat = 'u8';
|
|
2313
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setUint8(byteOffset, value + 2 ** 7);
|
|
2314
|
+
}
|
|
2315
|
+
}; break;
|
|
2316
|
+
case 2: {
|
|
2317
|
+
this.outputSampleSize = 2;
|
|
2318
|
+
this.outputFormat = 's16';
|
|
2319
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setInt16(byteOffset, value, true);
|
|
2320
|
+
}; break;
|
|
2321
|
+
case 3: {
|
|
2322
|
+
this.outputSampleSize = 4;
|
|
2323
|
+
this.outputFormat = 's32';
|
|
2324
|
+
// From https://www.w3.org/TR/webcodecs:
|
|
2325
|
+
// AudioData containing 24-bit samples SHOULD store those samples in s32 or f32. When samples are
|
|
2326
|
+
// stored in s32, each sample MUST be left-shifted by 8 bits.
|
|
2327
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value << 8, true);
|
|
2328
|
+
}; break;
|
|
2329
|
+
case 4: {
|
|
2330
|
+
this.outputSampleSize = 4;
|
|
2331
|
+
|
|
2332
|
+
if (dataType === 'float') {
|
|
2333
|
+
this.outputFormat = 'f32';
|
|
2334
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setFloat32(byteOffset, value, true);
|
|
2335
|
+
} else {
|
|
2336
|
+
this.outputFormat = 's32';
|
|
2337
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setInt32(byteOffset, value, true);
|
|
2338
|
+
}
|
|
2339
|
+
}; break;
|
|
2340
|
+
case 8: {
|
|
2341
|
+
this.outputSampleSize = 4;
|
|
2342
|
+
|
|
2343
|
+
this.outputFormat = 'f32';
|
|
2344
|
+
this.writeOutputValue = (view, byteOffset, value) => view.setFloat32(byteOffset, value, true);
|
|
2345
|
+
}; break;
|
|
2346
|
+
default: {
|
|
2347
|
+
assertNever(sampleSize);
|
|
2348
|
+
assert(false);
|
|
2349
|
+
};
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
getDecodeQueueSize() {
|
|
2354
|
+
return 0;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
decode(packet: EncodedPacket) {
|
|
2358
|
+
const inputView = toDataView(packet.data);
|
|
2359
|
+
|
|
2360
|
+
const numberOfFrames = packet.byteLength / this.decoderConfig.numberOfChannels / this.inputSampleSize;
|
|
2361
|
+
|
|
2362
|
+
const outputBufferSize = numberOfFrames * this.decoderConfig.numberOfChannels * this.outputSampleSize;
|
|
2363
|
+
const outputBuffer = new ArrayBuffer(outputBufferSize);
|
|
2364
|
+
const outputView = new DataView(outputBuffer);
|
|
2365
|
+
|
|
2366
|
+
for (let i = 0; i < numberOfFrames * this.decoderConfig.numberOfChannels; i++) {
|
|
2367
|
+
const inputIndex = i * this.inputSampleSize;
|
|
2368
|
+
const outputIndex = i * this.outputSampleSize;
|
|
2369
|
+
|
|
2370
|
+
const value = this.readInputValue(inputView, inputIndex);
|
|
2371
|
+
this.writeOutputValue(outputView, outputIndex, value);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
const preciseDuration = numberOfFrames / this.decoderConfig.sampleRate;
|
|
2375
|
+
if (this.currentTimestamp === null || Math.abs(packet.timestamp - this.currentTimestamp) >= preciseDuration) {
|
|
2376
|
+
// We need to sync with the packet timestamp again
|
|
2377
|
+
this.currentTimestamp = packet.timestamp;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
const preciseTimestamp = this.currentTimestamp;
|
|
2381
|
+
this.currentTimestamp += preciseDuration;
|
|
2382
|
+
|
|
2383
|
+
const audioSample = new AudioSample({
|
|
2384
|
+
format: this.outputFormat,
|
|
2385
|
+
data: outputBuffer,
|
|
2386
|
+
numberOfChannels: this.decoderConfig.numberOfChannels,
|
|
2387
|
+
sampleRate: this.decoderConfig.sampleRate,
|
|
2388
|
+
numberOfFrames,
|
|
2389
|
+
timestamp: preciseTimestamp,
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
this.onSample(audioSample);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
async flush() {
|
|
2396
|
+
// Do nothing
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
close() {
|
|
2400
|
+
// Do nothing
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
/**
|
|
2405
|
+
* Sink for retrieving decoded audio samples from an audio track.
|
|
2406
|
+
* @group Media sinks
|
|
2407
|
+
* @public
|
|
2408
|
+
*/
|
|
2409
|
+
export class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
|
|
2410
|
+
/** @internal */
|
|
2411
|
+
_track: InputAudioTrack;
|
|
2412
|
+
|
|
2413
|
+
/** Creates a new {@link AudioSampleSink} for the given {@link InputAudioTrack}. */
|
|
2414
|
+
constructor(audioTrack: InputAudioTrack) {
|
|
2415
|
+
if (!(audioTrack instanceof InputAudioTrack)) {
|
|
2416
|
+
throw new TypeError('audioTrack must be an InputAudioTrack.');
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
super();
|
|
2420
|
+
|
|
2421
|
+
this._track = audioTrack;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
/** @internal */
|
|
2425
|
+
async _createDecoder(
|
|
2426
|
+
onSample: (sample: AudioSample) => unknown,
|
|
2427
|
+
onError: (error: unknown) => unknown,
|
|
2428
|
+
) {
|
|
2429
|
+
if (!(await this._track.canDecode())) {
|
|
2430
|
+
throw new Error(
|
|
2431
|
+
'This audio track cannot be decoded by this browser. Make sure to check decodability before using'
|
|
2432
|
+
+ ' a track.',
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
const codec = await this._track.getCodec();
|
|
2437
|
+
const decoderConfig = await this._track.getDecoderConfig();
|
|
2438
|
+
assert(codec && decoderConfig);
|
|
2439
|
+
|
|
2440
|
+
if ((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec)) {
|
|
2441
|
+
return new PcmAudioDecoderWrapper(onSample, onError, decoderConfig);
|
|
2442
|
+
} else {
|
|
2443
|
+
return new AudioDecoderWrapper(onSample, onError, codec, decoderConfig);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/** @internal */
|
|
2448
|
+
_createPacketSink() {
|
|
2449
|
+
return new EncodedPacketSink(this._track);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
/**
|
|
2453
|
+
* Retrieves the audio sample corresponding to the given timestamp, in seconds. More specifically, returns
|
|
2454
|
+
* the last audio sample (in presentation order) with a start timestamp less than or equal to the given timestamp.
|
|
2455
|
+
* Returns null if the timestamp is before the track's first timestamp.
|
|
2456
|
+
*
|
|
2457
|
+
* @param timestamp - The timestamp used for retrieval, in seconds.
|
|
2458
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2459
|
+
*/
|
|
2460
|
+
async getSample(timestamp: number, options: PacketRetrievalOptions = {}) {
|
|
2461
|
+
validateTimestamp(timestamp);
|
|
2462
|
+
|
|
2463
|
+
for await (const sample of this.mediaSamplesAtTimestamps([timestamp], options)) {
|
|
2464
|
+
return sample;
|
|
2465
|
+
}
|
|
2466
|
+
throw new Error('Internal error: Iterator returned nothing.');
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
/**
|
|
2470
|
+
* Creates an async iterator that yields the audio samples of this track in presentation order. This method
|
|
2471
|
+
* will intelligently pre-decode a few samples ahead to enable fast iteration.
|
|
2472
|
+
*
|
|
2473
|
+
* @param startTimestamp - The timestamp in seconds at which to start yielding samples (inclusive).
|
|
2474
|
+
* @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive).
|
|
2475
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2476
|
+
*/
|
|
2477
|
+
samples(startTimestamp?: number, endTimestamp?: number, options: PacketRetrievalOptions = {}) {
|
|
2478
|
+
return this.mediaSamplesInRange(startTimestamp, endTimestamp, options);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
/**
|
|
2482
|
+
* Creates an async iterator that yields an audio sample for each timestamp in the argument. This method
|
|
2483
|
+
* uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most
|
|
2484
|
+
* once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may
|
|
2485
|
+
* yield null if no sample is available for a given timestamp.
|
|
2486
|
+
*
|
|
2487
|
+
* This method is good for sparse access of media data. If you want primarily sequential media access, prefer
|
|
2488
|
+
* {@link AudioSampleSink.samples} instead.
|
|
2489
|
+
*
|
|
2490
|
+
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
2491
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2492
|
+
*/
|
|
2493
|
+
samplesAtTimestamps(timestamps: AnyIterable<number>, options: PacketRetrievalOptions = {}) {
|
|
2494
|
+
return this.mediaSamplesAtTimestamps(timestamps, options);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* An AudioBuffer with additional timing information (timestamp & duration).
|
|
2500
|
+
* @group Media sinks
|
|
2501
|
+
* @public
|
|
2502
|
+
*/
|
|
2503
|
+
export type WrappedAudioBuffer = {
|
|
2504
|
+
/** An AudioBuffer. */
|
|
2505
|
+
buffer: AudioBuffer;
|
|
2506
|
+
/** The timestamp of the corresponding audio sample, in seconds. */
|
|
2507
|
+
timestamp: number;
|
|
2508
|
+
/** The duration of the corresponding audio sample, in seconds. */
|
|
2509
|
+
duration: number;
|
|
2510
|
+
};
|
|
2511
|
+
|
|
2512
|
+
/**
|
|
2513
|
+
* A sink that retrieves decoded audio samples from an audio track and converts them to `AudioBuffer` instances. This is
|
|
2514
|
+
* often more useful than directly retrieving audio samples, as audio buffers can be directly used with the
|
|
2515
|
+
* Web Audio API.
|
|
2516
|
+
* @group Media sinks
|
|
2517
|
+
* @public
|
|
2518
|
+
*/
|
|
2519
|
+
export class AudioBufferSink {
|
|
2520
|
+
/** @internal */
|
|
2521
|
+
_audioSampleSink: AudioSampleSink;
|
|
2522
|
+
|
|
2523
|
+
/** Creates a new {@link AudioBufferSink} for the given {@link InputAudioTrack}. */
|
|
2524
|
+
constructor(audioTrack: InputAudioTrack) {
|
|
2525
|
+
if (!(audioTrack instanceof InputAudioTrack)) {
|
|
2526
|
+
throw new TypeError('audioTrack must be an InputAudioTrack.');
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
this._audioSampleSink = new AudioSampleSink(audioTrack);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/** @internal */
|
|
2533
|
+
_audioSampleToWrappedArrayBuffer(sample: AudioSample): WrappedAudioBuffer {
|
|
2534
|
+
const result: WrappedAudioBuffer = {
|
|
2535
|
+
buffer: sample.toAudioBuffer(),
|
|
2536
|
+
timestamp: sample.timestamp,
|
|
2537
|
+
duration: sample.duration,
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2540
|
+
sample.close();
|
|
2541
|
+
return result;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
/**
|
|
2545
|
+
* Retrieves the audio buffer corresponding to the given timestamp, in seconds. More specifically, returns
|
|
2546
|
+
* the last audio buffer (in presentation order) with a start timestamp less than or equal to the given timestamp.
|
|
2547
|
+
* Returns null if the timestamp is before the track's first timestamp.
|
|
2548
|
+
*
|
|
2549
|
+
* @param timestamp - The timestamp used for retrieval, in seconds.
|
|
2550
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2551
|
+
*/
|
|
2552
|
+
async getBuffer(timestamp: number, options?: PacketRetrievalOptions) {
|
|
2553
|
+
validateTimestamp(timestamp);
|
|
2554
|
+
|
|
2555
|
+
const data = await this._audioSampleSink.getSample(timestamp, options);
|
|
2556
|
+
return data && this._audioSampleToWrappedArrayBuffer(data);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
/**
|
|
2560
|
+
* Creates an async iterator that yields audio buffers of this track in presentation order. This method
|
|
2561
|
+
* will intelligently pre-decode a few buffers ahead to enable fast iteration.
|
|
2562
|
+
*
|
|
2563
|
+
* @param startTimestamp - The timestamp in seconds at which to start yielding buffers (inclusive).
|
|
2564
|
+
* @param endTimestamp - The timestamp in seconds at which to stop yielding buffers (exclusive).
|
|
2565
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2566
|
+
*/
|
|
2567
|
+
buffers(startTimestamp?: number, endTimestamp?: number, options?: PacketRetrievalOptions) {
|
|
2568
|
+
return mapAsyncGenerator(
|
|
2569
|
+
this._audioSampleSink.samples(startTimestamp, endTimestamp, options),
|
|
2570
|
+
data => this._audioSampleToWrappedArrayBuffer(data),
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
/**
|
|
2575
|
+
* Creates an async iterator that yields an audio buffer for each timestamp in the argument. This method
|
|
2576
|
+
* uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most
|
|
2577
|
+
* once, and is therefore more efficient than manually getting the buffer for every timestamp. The iterator may
|
|
2578
|
+
* yield null if no buffer is available for a given timestamp.
|
|
2579
|
+
*
|
|
2580
|
+
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
2581
|
+
* @param options - Options used for the underlying packet retrieval.
|
|
2582
|
+
*/
|
|
2583
|
+
buffersAtTimestamps(timestamps: AnyIterable<number>, options?: PacketRetrievalOptions) {
|
|
2584
|
+
return mapAsyncGenerator(
|
|
2585
|
+
this._audioSampleSink.samplesAtTimestamps(timestamps, options),
|
|
2586
|
+
data => data && this._audioSampleToWrappedArrayBuffer(data),
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
}
|