@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.
Files changed (949) hide show
  1. package/DEPLOY.md +87 -0
  2. package/README.md +243 -0
  3. package/RELEASE.md +164 -0
  4. package/SETUP.md +60 -0
  5. package/THIRD_PARTY_NOTICES.md +18 -0
  6. package/bin/osmo.mjs +12 -0
  7. package/dist/osmo.js +7817 -0
  8. package/node_modules/@esbuild/darwin-arm64/README.md +3 -0
  9. package/node_modules/@esbuild/darwin-arm64/bin/esbuild +0 -0
  10. package/node_modules/@esbuild/darwin-arm64/package.json +20 -0
  11. package/node_modules/esbuild/LICENSE.md +21 -0
  12. package/node_modules/esbuild/README.md +3 -0
  13. package/node_modules/esbuild/bin/esbuild +223 -0
  14. package/node_modules/esbuild/install.js +289 -0
  15. package/node_modules/esbuild/lib/main.d.ts +716 -0
  16. package/node_modules/esbuild/lib/main.js +2532 -0
  17. package/node_modules/esbuild/package.json +49 -0
  18. package/node_modules/fflate/LICENSE +21 -0
  19. package/node_modules/fflate/esm/index.mjs +2710 -0
  20. package/node_modules/fflate/package.json +128 -0
  21. package/node_modules/typescript/LICENSE.txt +55 -0
  22. package/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
  23. package/node_modules/typescript/lib/lib.d.ts +22 -0
  24. package/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
  25. package/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
  26. package/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
  27. package/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
  28. package/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
  29. package/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
  30. package/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
  31. package/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
  32. package/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
  33. package/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
  34. package/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
  35. package/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
  36. package/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
  37. package/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
  38. package/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
  39. package/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
  40. package/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
  41. package/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
  42. package/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
  43. package/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
  44. package/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
  45. package/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
  46. package/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
  47. package/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
  48. package/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
  49. package/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
  50. package/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
  51. package/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
  52. package/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
  53. package/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
  54. package/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
  55. package/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
  56. package/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
  57. package/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
  58. package/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
  59. package/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
  60. package/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
  61. package/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
  62. package/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
  63. package/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
  64. package/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
  65. package/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
  66. package/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
  67. package/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
  68. package/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
  69. package/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
  70. package/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
  71. package/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
  72. package/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
  73. package/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
  74. package/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
  75. package/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
  76. package/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
  77. package/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
  78. package/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
  79. package/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
  80. package/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
  81. package/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
  82. package/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
  83. package/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
  84. package/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
  85. package/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
  86. package/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
  87. package/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
  88. package/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
  89. package/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
  90. package/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
  91. package/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
  92. package/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
  93. package/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
  94. package/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
  95. package/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
  96. package/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
  97. package/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
  98. package/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
  99. package/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
  100. package/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
  101. package/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
  102. package/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
  103. package/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
  104. package/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
  105. package/node_modules/typescript/lib/lib.es6.d.ts +23 -0
  106. package/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
  107. package/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
  108. package/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
  109. package/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
  110. package/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
  111. package/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
  112. package/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
  113. package/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
  114. package/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
  115. package/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
  116. package/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
  117. package/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
  118. package/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
  119. package/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
  120. package/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
  121. package/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
  122. package/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
  123. package/node_modules/typescript/lib/typescript.js +382 -0
  124. package/node_modules/typescript/package.json +57 -0
  125. package/package.json +54 -0
  126. package/release.json +136 -0
  127. package/runtime/apps/native/tools/compile.mjs +217 -0
  128. package/runtime/packages/scene/scripts/validate-strict.mjs +246 -0
  129. package/runtime/packages/scene/src/clip.d.mts +14 -0
  130. package/runtime/packages/scene/src/clip.mjs +15 -0
  131. package/runtime/packages/scene/src/color-value.d.mts +5 -0
  132. package/runtime/packages/scene/src/color-value.mjs +19 -0
  133. package/runtime/packages/scene/src/color.ts +1 -0
  134. package/runtime/packages/scene/src/effects.ts +213 -0
  135. package/runtime/packages/scene/src/elements.ts +491 -0
  136. package/runtime/packages/scene/src/evaluate.ts +919 -0
  137. package/runtime/packages/scene/src/index.ts +69 -0
  138. package/runtime/packages/scene/src/jsx-runtime.ts +73 -0
  139. package/runtime/packages/scene/src/knob-schema.ts +115 -0
  140. package/runtime/packages/scene/src/knobs.ts +118 -0
  141. package/runtime/packages/scene/src/mask.d.mts +6 -0
  142. package/runtime/packages/scene/src/mask.mjs +17 -0
  143. package/runtime/packages/scene/src/metadata.d.mts +23 -0
  144. package/runtime/packages/scene/src/metadata.mjs +44 -0
  145. package/runtime/packages/scene/src/motion-sampling.d.mts +2 -0
  146. package/runtime/packages/scene/src/motion-sampling.mjs +50 -0
  147. package/runtime/packages/scene/src/motion.ts +121 -0
  148. package/runtime/packages/scene/src/shader.ts +57 -0
  149. package/src/resource-examples/example-image/landscape.svg +7 -0
  150. package/src/resource-examples/example-image/resource.json +8 -0
  151. package/src/resource-examples/example-model/gem.glb +0 -0
  152. package/src/resource-examples/example-model/preview.svg +1 -0
  153. package/src/resource-examples/example-model/resource.json +7 -0
  154. package/src/resource-examples/example-scene/resource.json +1 -0
  155. package/src/resource-examples/example-scene/scene.tsx +8 -0
  156. package/src/resource-examples/example-shader/effect.wgsl +7 -0
  157. package/src/resource-examples/example-shader/preview.svg +1 -0
  158. package/src/resource-examples/example-shader/resource.json +27 -0
  159. package/src/resource-examples/example-shader/tint.wgsl +7 -0
  160. package/src/resource-examples/example-video/ambient.mp4 +0 -0
  161. package/src/resource-examples/example-video/resource.json +9 -0
  162. package/src/starter/scene.tsx +20 -0
  163. package/studio/index.html +2 -0
  164. package/studio/public/check-circle-filled.svg +3 -0
  165. package/studio/public/comment-cursor/active-outer.svg +3 -0
  166. package/studio/public/comment-cursor/default.svg +6 -0
  167. package/studio/public/osmo-studio-logo.svg +12 -0
  168. package/studio/public/pencil-simple.svg +5 -0
  169. package/studio/src/active-outer-TVYUEURE.svg +3 -0
  170. package/studio/src/app.css +4 -0
  171. package/studio/src/app.js +705 -0
  172. package/studio/src/default-6CTRH4V5.svg +6 -0
  173. package/studio/src/engine_bg.wasm +0 -0
  174. package/studio/src/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 +0 -0
  175. package/studio/src/inter-cyrillic-wght-normal-JEOLYBOO.woff2 +0 -0
  176. package/studio/src/inter-greek-ext-wght-normal-EOVOK2B5.woff2 +0 -0
  177. package/studio/src/inter-greek-wght-normal-IRE366VL.woff2 +0 -0
  178. package/studio/src/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
  179. package/studio/src/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
  180. package/studio/src/inter-vietnamese-wght-normal-CE5GGD3W.woff2 +0 -0
  181. package/studio/src/osmo-studio-logo-D5R2V77B.svg +12 -0
  182. package/third-party/cargo/adler2@2.0.1/Cargo.toml +91 -0
  183. package/third-party/cargo/adler2@2.0.1/LICENSE-0BSD +12 -0
  184. package/third-party/cargo/adler2@2.0.1/LICENSE-APACHE +201 -0
  185. package/third-party/cargo/adler2@2.0.1/LICENSE-MIT +23 -0
  186. package/third-party/cargo/allocator-api2@0.2.21/Cargo.toml +48 -0
  187. package/third-party/cargo/allocator-api2@0.2.21/LICENSE-APACHE +176 -0
  188. package/third-party/cargo/allocator-api2@0.2.21/LICENSE-MIT +23 -0
  189. package/third-party/cargo/arrayref@0.3.9/Cargo.toml +23 -0
  190. package/third-party/cargo/arrayref@0.3.9/LICENSE +26 -0
  191. package/third-party/cargo/arrayvec@0.7.8/Cargo.toml +112 -0
  192. package/third-party/cargo/arrayvec@0.7.8/LICENSE-APACHE +201 -0
  193. package/third-party/cargo/arrayvec@0.7.8/LICENSE-MIT +25 -0
  194. package/third-party/cargo/ascii@1.1.0/Cargo.toml +41 -0
  195. package/third-party/cargo/ascii@1.1.0/LICENSE-APACHE +201 -0
  196. package/third-party/cargo/ascii@1.1.0/LICENSE-MIT +22 -0
  197. package/third-party/cargo/ash@0.38.0+1.3.281/Cargo.toml +66 -0
  198. package/third-party/cargo/ash@0.38.0+1.3.281/LICENSE-APACHE +67 -0
  199. package/third-party/cargo/ash@0.38.0+1.3.281/LICENSE-MIT +25 -0
  200. package/third-party/cargo/autocfg@1.5.1/Cargo.toml +75 -0
  201. package/third-party/cargo/autocfg@1.5.1/LICENSE-APACHE +201 -0
  202. package/third-party/cargo/autocfg@1.5.1/LICENSE-MIT +25 -0
  203. package/third-party/cargo/base64@0.13.1/Cargo.toml +53 -0
  204. package/third-party/cargo/base64@0.13.1/LICENSE-APACHE +201 -0
  205. package/third-party/cargo/base64@0.13.1/LICENSE-MIT +21 -0
  206. package/third-party/cargo/base64@0.22.1/Cargo.toml +85 -0
  207. package/third-party/cargo/base64@0.22.1/LICENSE-APACHE +201 -0
  208. package/third-party/cargo/base64@0.22.1/LICENSE-MIT +21 -0
  209. package/third-party/cargo/bit-set@0.10.0/Cargo.toml +108 -0
  210. package/third-party/cargo/bit-set@0.10.0/LICENSE-APACHE +201 -0
  211. package/third-party/cargo/bit-set@0.10.0/LICENSE-MIT +25 -0
  212. package/third-party/cargo/bit-vec@0.9.1/Cargo.toml +94 -0
  213. package/third-party/cargo/bit-vec@0.9.1/LICENSE-APACHE +201 -0
  214. package/third-party/cargo/bit-vec@0.9.1/LICENSE-MIT +25 -0
  215. package/third-party/cargo/bitflags@2.13.0/Cargo.toml +120 -0
  216. package/third-party/cargo/bitflags@2.13.0/LICENSE-APACHE +201 -0
  217. package/third-party/cargo/bitflags@2.13.0/LICENSE-MIT +25 -0
  218. package/third-party/cargo/block2@0.6.2/Cargo.toml +115 -0
  219. package/third-party/cargo/block2@0.6.2/MIT-terms.txt +22 -0
  220. package/third-party/cargo/block2@0.6.2/madsmtm-objc2-b4167b582b2f-LICENSE.md +34 -0
  221. package/third-party/cargo/bumpalo@3.20.3/Cargo.toml +99 -0
  222. package/third-party/cargo/bumpalo@3.20.3/LICENSE-APACHE +201 -0
  223. package/third-party/cargo/bumpalo@3.20.3/LICENSE-MIT +25 -0
  224. package/third-party/cargo/bytemuck@1.25.1/Cargo.toml +153 -0
  225. package/third-party/cargo/bytemuck@1.25.1/LICENSE-APACHE +61 -0
  226. package/third-party/cargo/bytemuck@1.25.1/LICENSE-MIT +9 -0
  227. package/third-party/cargo/bytemuck@1.25.1/LICENSE-ZLIB +11 -0
  228. package/third-party/cargo/bytemuck_derive@1.11.0/Cargo.toml +60 -0
  229. package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-APACHE +61 -0
  230. package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-MIT +9 -0
  231. package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-ZLIB +11 -0
  232. package/third-party/cargo/byteorder-lite@0.1.0/Cargo.toml +57 -0
  233. package/third-party/cargo/byteorder-lite@0.1.0/LICENSE-MIT +21 -0
  234. package/third-party/cargo/byteorder-lite@0.1.0/UNLICENSE +24 -0
  235. package/third-party/cargo/byteorder@1.5.0/COPYING +3 -0
  236. package/third-party/cargo/byteorder@1.5.0/Cargo.toml +54 -0
  237. package/third-party/cargo/byteorder@1.5.0/LICENSE-MIT +21 -0
  238. package/third-party/cargo/byteorder@1.5.0/UNLICENSE +24 -0
  239. package/third-party/cargo/cfg-if@1.0.4/Cargo.toml +47 -0
  240. package/third-party/cargo/cfg-if@1.0.4/LICENSE-APACHE +201 -0
  241. package/third-party/cargo/cfg-if@1.0.4/LICENSE-MIT +25 -0
  242. package/third-party/cargo/cfg_aliases@0.2.1/Cargo.toml +43 -0
  243. package/third-party/cargo/cfg_aliases@0.2.1/LICENSE +9 -0
  244. package/third-party/cargo/chunked_transfer@1.5.0/Cargo.toml +27 -0
  245. package/third-party/cargo/chunked_transfer@1.5.0/LICENSE-APACHE +201 -0
  246. package/third-party/cargo/chunked_transfer@1.5.0/LICENSE-MIT +22 -0
  247. package/third-party/cargo/codespan-reporting@0.13.1/Cargo.toml +115 -0
  248. package/third-party/cargo/codespan-reporting@0.13.1/LICENSE +201 -0
  249. package/third-party/cargo/color_quant@1.1.0/Cargo.toml +20 -0
  250. package/third-party/cargo/color_quant@1.1.0/LICENSE +21 -0
  251. package/third-party/cargo/console_error_panic_hook@0.1.7/Cargo.toml +27 -0
  252. package/third-party/cargo/console_error_panic_hook@0.1.7/LICENSE-APACHE +201 -0
  253. package/third-party/cargo/console_error_panic_hook@0.1.7/LICENSE-MIT +25 -0
  254. package/third-party/cargo/core_maths@0.1.1/Cargo.toml +37 -0
  255. package/third-party/cargo/core_maths@0.1.1/LICENSE +21 -0
  256. package/third-party/cargo/cosmic-text@0.19.0/Cargo.toml +214 -0
  257. package/third-party/cargo/cosmic-text@0.19.0/LICENSE-APACHE +201 -0
  258. package/third-party/cargo/cosmic-text@0.19.0/LICENSE-MIT +21 -0
  259. package/third-party/cargo/crc32fast@1.5.0/Cargo.toml +68 -0
  260. package/third-party/cargo/crc32fast@1.5.0/LICENSE-APACHE +202 -0
  261. package/third-party/cargo/crc32fast@1.5.0/LICENSE-MIT +21 -0
  262. package/third-party/cargo/data-url@0.3.2/Cargo.toml +63 -0
  263. package/third-party/cargo/data-url@0.3.2/LICENSE-APACHE +201 -0
  264. package/third-party/cargo/data-url@0.3.2/LICENSE-MIT +25 -0
  265. package/third-party/cargo/dispatch2@0.3.1/Cargo.toml +122 -0
  266. package/third-party/cargo/dispatch2@0.3.1/MIT-terms.txt +22 -0
  267. package/third-party/cargo/dispatch2@0.3.1/madsmtm-objc2-8852b424193c-LICENSE.md +34 -0
  268. package/third-party/cargo/dlib@0.5.3/Cargo.toml +43 -0
  269. package/third-party/cargo/dlib@0.5.3/LICENSE.txt +19 -0
  270. package/third-party/cargo/document-features@0.2.12/Cargo.toml +51 -0
  271. package/third-party/cargo/document-features@0.2.12/LICENSE-APACHE +73 -0
  272. package/third-party/cargo/document-features@0.2.12/LICENSE-MIT +19 -0
  273. package/third-party/cargo/equivalent@1.0.2/Cargo.toml +37 -0
  274. package/third-party/cargo/equivalent@1.0.2/LICENSE-APACHE +201 -0
  275. package/third-party/cargo/equivalent@1.0.2/LICENSE-MIT +25 -0
  276. package/third-party/cargo/errno@0.3.14/Cargo.toml +67 -0
  277. package/third-party/cargo/errno@0.3.14/LICENSE-APACHE +201 -0
  278. package/third-party/cargo/errno@0.3.14/LICENSE-MIT +25 -0
  279. package/third-party/cargo/etagere@0.3.0/Cargo.toml +57 -0
  280. package/third-party/cargo/etagere@0.3.0/LICENSE-APACHE +13 -0
  281. package/third-party/cargo/etagere@0.3.0/LICENSE-MIT +20 -0
  282. package/third-party/cargo/euclid@0.22.14/COPYRIGHT +5 -0
  283. package/third-party/cargo/euclid@0.22.14/Cargo.toml +66 -0
  284. package/third-party/cargo/euclid@0.22.14/LICENSE-APACHE +201 -0
  285. package/third-party/cargo/euclid@0.22.14/LICENSE-MIT +25 -0
  286. package/third-party/cargo/fastrand@2.5.0/Cargo.toml +89 -0
  287. package/third-party/cargo/fastrand@2.5.0/LICENSE-APACHE +201 -0
  288. package/third-party/cargo/fastrand@2.5.0/LICENSE-MIT +23 -0
  289. package/third-party/cargo/fdeflate@0.3.7/Cargo.toml +55 -0
  290. package/third-party/cargo/fdeflate@0.3.7/LICENSE-APACHE +177 -0
  291. package/third-party/cargo/fdeflate@0.3.7/LICENSE-MIT +25 -0
  292. package/third-party/cargo/flate2@1.1.9/Cargo.toml +274 -0
  293. package/third-party/cargo/flate2@1.1.9/LICENSE-APACHE +201 -0
  294. package/third-party/cargo/flate2@1.1.9/LICENSE-MIT +25 -0
  295. package/third-party/cargo/float-cmp@0.9.0/Cargo.toml +39 -0
  296. package/third-party/cargo/float-cmp@0.9.0/LICENSE +19 -0
  297. package/third-party/cargo/float_next_after@1.0.0/Cargo.toml +43 -0
  298. package/third-party/cargo/float_next_after@1.0.0/LICENSE +23 -0
  299. package/third-party/cargo/foldhash@0.2.0/Cargo.toml +80 -0
  300. package/third-party/cargo/foldhash@0.2.0/LICENSE +19 -0
  301. package/third-party/cargo/font-types@0.11.3/Cargo.toml +55 -0
  302. package/third-party/cargo/font-types@0.11.3/LICENSE-APACHE +67 -0
  303. package/third-party/cargo/font-types@0.11.3/LICENSE-MIT +25 -0
  304. package/third-party/cargo/fontdb@0.23.0/Cargo.toml +83 -0
  305. package/third-party/cargo/fontdb@0.23.0/LICENSE +21 -0
  306. package/third-party/cargo/futures-core@0.3.32/Cargo.toml +66 -0
  307. package/third-party/cargo/futures-core@0.3.32/LICENSE-APACHE +202 -0
  308. package/third-party/cargo/futures-core@0.3.32/LICENSE-MIT +26 -0
  309. package/third-party/cargo/futures-task@0.3.32/Cargo.toml +58 -0
  310. package/third-party/cargo/futures-task@0.3.32/LICENSE-APACHE +202 -0
  311. package/third-party/cargo/futures-task@0.3.32/LICENSE-MIT +26 -0
  312. package/third-party/cargo/futures-util@0.3.32/Cargo.toml +181 -0
  313. package/third-party/cargo/futures-util@0.3.32/LICENSE-APACHE +202 -0
  314. package/third-party/cargo/futures-util@0.3.32/LICENSE-MIT +26 -0
  315. package/third-party/cargo/getrandom@0.4.3/Cargo.toml +165 -0
  316. package/third-party/cargo/getrandom@0.4.3/LICENSE-APACHE +201 -0
  317. package/third-party/cargo/getrandom@0.4.3/LICENSE-MIT +26 -0
  318. package/third-party/cargo/gif@0.14.2/Cargo.toml +84 -0
  319. package/third-party/cargo/gif@0.14.2/LICENSE-APACHE +201 -0
  320. package/third-party/cargo/gif@0.14.2/LICENSE-MIT +21 -0
  321. package/third-party/cargo/glam@0.33.2/Cargo.toml +354 -0
  322. package/third-party/cargo/glam@0.33.2/LICENSE-APACHE +201 -0
  323. package/third-party/cargo/glam@0.33.2/LICENSE-MIT +23 -0
  324. package/third-party/cargo/glow@0.17.0/Cargo.toml +122 -0
  325. package/third-party/cargo/glow@0.17.0/LICENSE-APACHE +201 -0
  326. package/third-party/cargo/glow@0.17.0/LICENSE-MIT +23 -0
  327. package/third-party/cargo/glow@0.17.0/LICENSE-ZLIB +15 -0
  328. package/third-party/cargo/gltf-derive@1.4.1/Cargo.toml +34 -0
  329. package/third-party/cargo/gltf-derive@1.4.1/LICENSE-APACHE +202 -0
  330. package/third-party/cargo/gltf-derive@1.4.1/LICENSE-MIT +26 -0
  331. package/third-party/cargo/gltf-json@1.4.1/Cargo.toml +50 -0
  332. package/third-party/cargo/gltf-json@1.4.1/LICENSE-APACHE +202 -0
  333. package/third-party/cargo/gltf-json@1.4.1/LICENSE-MIT +26 -0
  334. package/third-party/cargo/gltf@1.4.1/Cargo.toml +122 -0
  335. package/third-party/cargo/gltf@1.4.1/LICENSE-APACHE +202 -0
  336. package/third-party/cargo/gltf@1.4.1/LICENSE-MIT +26 -0
  337. package/third-party/cargo/glyphon@0.12.0/Cargo.toml +35 -0
  338. package/third-party/cargo/glyphon@0.12.0/LICENSE-APACHE +201 -0
  339. package/third-party/cargo/glyphon@0.12.0/LICENSE-MIT +23 -0
  340. package/third-party/cargo/glyphon@0.12.0/LICENSE-ZLIB +15 -0
  341. package/third-party/cargo/gpu-allocator@0.28.0/Cargo.toml +180 -0
  342. package/third-party/cargo/gpu-allocator@0.28.0/LICENSE-APACHE +201 -0
  343. package/third-party/cargo/gpu-allocator@0.28.0/LICENSE-MIT +7 -0
  344. package/third-party/cargo/grid@1.0.1/Cargo.toml +77 -0
  345. package/third-party/cargo/grid@1.0.1/LICENSE +21 -0
  346. package/third-party/cargo/half@2.7.1/Cargo.toml +137 -0
  347. package/third-party/cargo/half@2.7.1/LICENSE-APACHE +176 -0
  348. package/third-party/cargo/half@2.7.1/LICENSE-MIT +19 -0
  349. package/third-party/cargo/harfrust@0.5.2/Cargo.toml +115 -0
  350. package/third-party/cargo/harfrust@0.5.2/harfbuzz-harfrust-efdae3142ab7-LICENSE +22 -0
  351. package/third-party/cargo/hashbrown@0.16.1/Cargo.toml +180 -0
  352. package/third-party/cargo/hashbrown@0.16.1/LICENSE-APACHE +201 -0
  353. package/third-party/cargo/hashbrown@0.16.1/LICENSE-MIT +25 -0
  354. package/third-party/cargo/hashbrown@0.17.1/Cargo.toml +201 -0
  355. package/third-party/cargo/hashbrown@0.17.1/LICENSE-APACHE +201 -0
  356. package/third-party/cargo/hashbrown@0.17.1/LICENSE-MIT +25 -0
  357. package/third-party/cargo/httpdate@1.0.3/Cargo.toml +35 -0
  358. package/third-party/cargo/httpdate@1.0.3/LICENSE-APACHE +201 -0
  359. package/third-party/cargo/httpdate@1.0.3/LICENSE-MIT +19 -0
  360. package/third-party/cargo/image-webp@0.2.4/Cargo.toml +63 -0
  361. package/third-party/cargo/image-webp@0.2.4/LICENSE-APACHE +177 -0
  362. package/third-party/cargo/image-webp@0.2.4/LICENSE-MIT +25 -0
  363. package/third-party/cargo/image@0.25.10/Cargo.toml +248 -0
  364. package/third-party/cargo/image@0.25.10/LICENSE-APACHE +177 -0
  365. package/third-party/cargo/image@0.25.10/LICENSE-MIT +25 -0
  366. package/third-party/cargo/imagesize@0.14.0/Cargo.toml +104 -0
  367. package/third-party/cargo/imagesize@0.14.0/LICENSE +21 -0
  368. package/third-party/cargo/indexmap@2.14.0/Cargo.toml +167 -0
  369. package/third-party/cargo/indexmap@2.14.0/LICENSE-APACHE +201 -0
  370. package/third-party/cargo/indexmap@2.14.0/LICENSE-MIT +25 -0
  371. package/third-party/cargo/inflections@1.1.1/Cargo.toml +21 -0
  372. package/third-party/cargo/inflections@1.1.1/LICENSE +20 -0
  373. package/third-party/cargo/itoa@1.0.18/Cargo.toml +66 -0
  374. package/third-party/cargo/itoa@1.0.18/LICENSE-APACHE +176 -0
  375. package/third-party/cargo/itoa@1.0.18/LICENSE-MIT +23 -0
  376. package/third-party/cargo/js-sys@0.3.103/Cargo.toml +98 -0
  377. package/third-party/cargo/js-sys@0.3.103/LICENSE-APACHE +201 -0
  378. package/third-party/cargo/js-sys@0.3.103/LICENSE-MIT +25 -0
  379. package/third-party/cargo/khronos-egl@6.0.0/Cargo.toml +82 -0
  380. package/third-party/cargo/khronos-egl@6.0.0/LICENSE-APACHE +201 -0
  381. package/third-party/cargo/khronos-egl@6.0.0/LICENSE-MIT +23 -0
  382. package/third-party/cargo/kurbo@0.13.1/Cargo.toml +230 -0
  383. package/third-party/cargo/kurbo@0.13.1/LICENSE-APACHE +202 -0
  384. package/third-party/cargo/kurbo@0.13.1/LICENSE-MIT +25 -0
  385. package/third-party/cargo/lazy_static@1.5.0/Cargo.toml +46 -0
  386. package/third-party/cargo/lazy_static@1.5.0/LICENSE-APACHE +201 -0
  387. package/third-party/cargo/lazy_static@1.5.0/LICENSE-MIT +25 -0
  388. package/third-party/cargo/libc@0.2.186/Cargo.toml +229 -0
  389. package/third-party/cargo/libc@0.2.186/LICENSE-APACHE +176 -0
  390. package/third-party/cargo/libc@0.2.186/LICENSE-MIT +25 -0
  391. package/third-party/cargo/libloading@0.8.9/Cargo.toml +90 -0
  392. package/third-party/cargo/libloading@0.8.9/LICENSE +12 -0
  393. package/third-party/cargo/libm@0.2.16/Cargo.toml +61 -0
  394. package/third-party/cargo/libm@0.2.16/LICENSE.txt +258 -0
  395. package/third-party/cargo/linebender_resource_handle@0.1.1/Cargo.toml +107 -0
  396. package/third-party/cargo/linebender_resource_handle@0.1.1/LICENSE-APACHE +176 -0
  397. package/third-party/cargo/linebender_resource_handle@0.1.1/LICENSE-MIT +19 -0
  398. package/third-party/cargo/litrs@1.0.0/Cargo.toml +63 -0
  399. package/third-party/cargo/litrs@1.0.0/LICENSE-APACHE +176 -0
  400. package/third-party/cargo/litrs@1.0.0/LICENSE-MIT +25 -0
  401. package/third-party/cargo/lock_api@0.4.14/Cargo.toml +68 -0
  402. package/third-party/cargo/lock_api@0.4.14/LICENSE-APACHE +201 -0
  403. package/third-party/cargo/lock_api@0.4.14/LICENSE-MIT +25 -0
  404. package/third-party/cargo/log@0.4.33/Cargo.toml +152 -0
  405. package/third-party/cargo/log@0.4.33/LICENSE-APACHE +201 -0
  406. package/third-party/cargo/log@0.4.33/LICENSE-MIT +25 -0
  407. package/third-party/cargo/lru@0.16.4/Cargo.toml +47 -0
  408. package/third-party/cargo/lru@0.16.4/LICENSE +21 -0
  409. package/third-party/cargo/lyon_geom@1.0.19/Cargo.toml +69 -0
  410. package/third-party/cargo/lyon_geom@1.0.19/nical-lyon-56219c95c658-LICENSE-APACHE +13 -0
  411. package/third-party/cargo/lyon_geom@1.0.19/nical-lyon-56219c95c658-LICENSE-MIT +20 -0
  412. package/third-party/cargo/lyon_path@1.0.19/Cargo.toml +58 -0
  413. package/third-party/cargo/lyon_path@1.0.19/nical-lyon-56219c95c658-LICENSE-APACHE +13 -0
  414. package/third-party/cargo/lyon_path@1.0.19/nical-lyon-56219c95c658-LICENSE-MIT +20 -0
  415. package/third-party/cargo/lyon_tessellation@1.0.20/Cargo.toml +71 -0
  416. package/third-party/cargo/lyon_tessellation@1.0.20/nical-lyon-7fa75ea8eea3-LICENSE-APACHE +13 -0
  417. package/third-party/cargo/lyon_tessellation@1.0.20/nical-lyon-7fa75ea8eea3-LICENSE-MIT +20 -0
  418. package/third-party/cargo/memchr@2.8.3/COPYING +3 -0
  419. package/third-party/cargo/memchr@2.8.3/Cargo.toml +89 -0
  420. package/third-party/cargo/memchr@2.8.3/LICENSE-MIT +21 -0
  421. package/third-party/cargo/memchr@2.8.3/UNLICENSE +24 -0
  422. package/third-party/cargo/memmap2@0.9.11/Cargo.toml +59 -0
  423. package/third-party/cargo/memmap2@0.9.11/LICENSE-APACHE +201 -0
  424. package/third-party/cargo/memmap2@0.9.11/LICENSE-MIT +26 -0
  425. package/third-party/cargo/miniz_oxide@0.8.9/Cargo.toml +91 -0
  426. package/third-party/cargo/miniz_oxide@0.8.9/LICENSE +25 -0
  427. package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-APACHE.md +177 -0
  428. package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-MIT.md +24 -0
  429. package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-ZLIB.md +14 -0
  430. package/third-party/cargo/moxcms@0.8.1/Cargo.toml +113 -0
  431. package/third-party/cargo/moxcms@0.8.1/LICENSE-APACHE.md +201 -0
  432. package/third-party/cargo/moxcms@0.8.1/LICENSE.md +26 -0
  433. package/third-party/cargo/naga-types@30.0.0/Cargo.toml +94 -0
  434. package/third-party/cargo/naga-types@30.0.0/LICENSE.APACHE +176 -0
  435. package/third-party/cargo/naga-types@30.0.0/LICENSE.MIT +21 -0
  436. package/third-party/cargo/naga@30.0.0/Cargo.toml +246 -0
  437. package/third-party/cargo/naga@30.0.0/LICENSE.APACHE +176 -0
  438. package/third-party/cargo/naga@30.0.0/LICENSE.MIT +21 -0
  439. package/third-party/cargo/num-traits@0.2.19/Cargo.toml +54 -0
  440. package/third-party/cargo/num-traits@0.2.19/LICENSE-APACHE +201 -0
  441. package/third-party/cargo/num-traits@0.2.19/LICENSE-MIT +25 -0
  442. package/third-party/cargo/objc2-core-foundation@0.3.2/Cargo.toml +221 -0
  443. package/third-party/cargo/objc2-core-foundation@0.3.2/MIT-terms.txt +22 -0
  444. package/third-party/cargo/objc2-core-foundation@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
  445. package/third-party/cargo/objc2-core-graphics@0.3.2/Cargo.toml +331 -0
  446. package/third-party/cargo/objc2-core-graphics@0.3.2/MIT-terms.txt +22 -0
  447. package/third-party/cargo/objc2-core-graphics@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
  448. package/third-party/cargo/objc2-encode@4.1.0/Cargo.toml +81 -0
  449. package/third-party/cargo/objc2-encode@4.1.0/MIT-terms.txt +22 -0
  450. package/third-party/cargo/objc2-encode@4.1.0/madsmtm-objc2-8d214f547736-LICENSE.md +34 -0
  451. package/third-party/cargo/objc2-foundation@0.3.2/Cargo.toml +529 -0
  452. package/third-party/cargo/objc2-foundation@0.3.2/MIT-terms.txt +22 -0
  453. package/third-party/cargo/objc2-foundation@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
  454. package/third-party/cargo/objc2-io-surface@0.3.2/Cargo.toml +139 -0
  455. package/third-party/cargo/objc2-io-surface@0.3.2/MIT-terms.txt +22 -0
  456. package/third-party/cargo/objc2-io-surface@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
  457. package/third-party/cargo/objc2-metal@0.3.2/Cargo.toml +601 -0
  458. package/third-party/cargo/objc2-metal@0.3.2/MIT-terms.txt +22 -0
  459. package/third-party/cargo/objc2-metal@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
  460. package/third-party/cargo/objc2-quartz-core@0.3.2/Cargo.toml +302 -0
  461. package/third-party/cargo/objc2-quartz-core@0.3.2/MIT-terms.txt +22 -0
  462. package/third-party/cargo/objc2-quartz-core@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
  463. package/third-party/cargo/objc2@0.6.4/Cargo.toml +232 -0
  464. package/third-party/cargo/objc2@0.6.4/MIT-terms.txt +22 -0
  465. package/third-party/cargo/objc2@0.6.4/madsmtm-objc2-8852b424193c-LICENSE.md +34 -0
  466. package/third-party/cargo/once_cell@1.21.4/Cargo.toml +119 -0
  467. package/third-party/cargo/once_cell@1.21.4/LICENSE-APACHE +201 -0
  468. package/third-party/cargo/once_cell@1.21.4/LICENSE-MIT +23 -0
  469. package/third-party/cargo/ordered-float@5.3.0/Cargo.toml +162 -0
  470. package/third-party/cargo/ordered-float@5.3.0/LICENSE-MIT +25 -0
  471. package/third-party/cargo/parking_lot@0.12.5/Cargo.toml +87 -0
  472. package/third-party/cargo/parking_lot@0.12.5/LICENSE-APACHE +201 -0
  473. package/third-party/cargo/parking_lot@0.12.5/LICENSE-MIT +25 -0
  474. package/third-party/cargo/parking_lot_core@0.9.12/Cargo.toml +72 -0
  475. package/third-party/cargo/parking_lot_core@0.9.12/LICENSE-APACHE +201 -0
  476. package/third-party/cargo/parking_lot_core@0.9.12/LICENSE-MIT +25 -0
  477. package/third-party/cargo/pico-args@0.5.0/Cargo.toml +32 -0
  478. package/third-party/cargo/pico-args@0.5.0/LICENSE +20 -0
  479. package/third-party/cargo/pin-project-lite@0.2.17/Cargo.toml +198 -0
  480. package/third-party/cargo/pin-project-lite@0.2.17/LICENSE-APACHE +177 -0
  481. package/third-party/cargo/pin-project-lite@0.2.17/LICENSE-MIT +23 -0
  482. package/third-party/cargo/pkg-config@0.3.33/Cargo.toml +43 -0
  483. package/third-party/cargo/pkg-config@0.3.33/LICENSE-APACHE +201 -0
  484. package/third-party/cargo/pkg-config@0.3.33/LICENSE-MIT +25 -0
  485. package/third-party/cargo/png@0.18.1/Cargo.toml +130 -0
  486. package/third-party/cargo/png@0.18.1/LICENSE-APACHE +201 -0
  487. package/third-party/cargo/png@0.18.1/LICENSE-MIT +25 -0
  488. package/third-party/cargo/pollster@0.4.0/Cargo.toml +53 -0
  489. package/third-party/cargo/pollster@0.4.0/LICENSE-APACHE +201 -0
  490. package/third-party/cargo/pollster@0.4.0/LICENSE-MIT +25 -0
  491. package/third-party/cargo/polycool@0.4.0/Cargo.toml +83 -0
  492. package/third-party/cargo/polycool@0.4.0/LICENSE-APACHE +202 -0
  493. package/third-party/cargo/polycool@0.4.0/LICENSE-MIT +25 -0
  494. package/third-party/cargo/pp-rs@0.2.1/Cargo.toml +23 -0
  495. package/third-party/cargo/pp-rs@0.2.1/LICENSE +29 -0
  496. package/third-party/cargo/presser@0.3.1/Cargo.toml +58 -0
  497. package/third-party/cargo/presser@0.3.1/LICENSE-APACHE +201 -0
  498. package/third-party/cargo/presser@0.3.1/LICENSE-MIT +25 -0
  499. package/third-party/cargo/proc-macro2@1.0.106/Cargo.toml +105 -0
  500. package/third-party/cargo/proc-macro2@1.0.106/LICENSE-APACHE +176 -0
  501. package/third-party/cargo/proc-macro2@1.0.106/LICENSE-MIT +23 -0
  502. package/third-party/cargo/profiling@1.0.18/Cargo.toml +96 -0
  503. package/third-party/cargo/profiling@1.0.18/aclysma-profiling-8271551172eb-LICENSE-APACHE +201 -0
  504. package/third-party/cargo/profiling@1.0.18/aclysma-profiling-8271551172eb-LICENSE-MIT +21 -0
  505. package/third-party/cargo/pxfm@0.1.30/Cargo.toml +53 -0
  506. package/third-party/cargo/pxfm@0.1.30/LICENSE-APACHE.md +201 -0
  507. package/third-party/cargo/pxfm@0.1.30/LICENSE.md +26 -0
  508. package/third-party/cargo/quick-error@2.0.1/Cargo.toml +24 -0
  509. package/third-party/cargo/quick-error@2.0.1/LICENSE-APACHE +202 -0
  510. package/third-party/cargo/quick-error@2.0.1/LICENSE-MIT +19 -0
  511. package/third-party/cargo/quote@1.0.46/Cargo.toml +70 -0
  512. package/third-party/cargo/quote@1.0.46/LICENSE-APACHE +176 -0
  513. package/third-party/cargo/quote@1.0.46/LICENSE-MIT +23 -0
  514. package/third-party/cargo/rangemap@1.7.1/Cargo.toml +91 -0
  515. package/third-party/cargo/rangemap@1.7.1/LICENSE-APACHE +191 -0
  516. package/third-party/cargo/rangemap@1.7.1/LICENSE-MIT +7 -0
  517. package/third-party/cargo/raw-window-handle@0.6.2/Cargo.toml +56 -0
  518. package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-APACHE.md +177 -0
  519. package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-MIT.md +21 -0
  520. package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-ZLIB.md +11 -0
  521. package/third-party/cargo/raw-window-metal@1.1.0/Cargo.toml +111 -0
  522. package/third-party/cargo/raw-window-metal@1.1.0/LICENSE-APACHE +202 -0
  523. package/third-party/cargo/raw-window-metal@1.1.0/LICENSE-MIT +17 -0
  524. package/third-party/cargo/read-fonts@0.37.0/Cargo.toml +95 -0
  525. package/third-party/cargo/read-fonts@0.37.0/LICENSE-APACHE +67 -0
  526. package/third-party/cargo/read-fonts@0.37.0/LICENSE-MIT +25 -0
  527. package/third-party/cargo/read-fonts@0.39.2/Cargo.toml +96 -0
  528. package/third-party/cargo/read-fonts@0.39.2/LICENSE-APACHE +67 -0
  529. package/third-party/cargo/read-fonts@0.39.2/LICENSE-MIT +25 -0
  530. package/third-party/cargo/renderdoc-sys@1.1.0/Cargo.toml +39 -0
  531. package/third-party/cargo/renderdoc-sys@1.1.0/LICENSE-APACHE +201 -0
  532. package/third-party/cargo/renderdoc-sys@1.1.0/LICENSE-MIT +25 -0
  533. package/third-party/cargo/resvg@0.47.0/Cargo.toml +112 -0
  534. package/third-party/cargo/resvg@0.47.0/LICENSE-APACHE +176 -0
  535. package/third-party/cargo/resvg@0.47.0/LICENSE-MIT +25 -0
  536. package/third-party/cargo/rgb@0.8.53/Cargo.toml +110 -0
  537. package/third-party/cargo/rgb@0.8.53/LICENSE +21 -0
  538. package/third-party/cargo/roxmltree@0.21.1/Cargo.toml +75 -0
  539. package/third-party/cargo/roxmltree@0.21.1/LICENSE-APACHE +201 -0
  540. package/third-party/cargo/roxmltree@0.21.1/LICENSE-MIT +21 -0
  541. package/third-party/cargo/rustc-hash@1.1.0/Cargo.toml +25 -0
  542. package/third-party/cargo/rustc-hash@1.1.0/LICENSE-APACHE +201 -0
  543. package/third-party/cargo/rustc-hash@1.1.0/LICENSE-MIT +23 -0
  544. package/third-party/cargo/rustc-hash@2.1.3/Cargo.toml +50 -0
  545. package/third-party/cargo/rustc-hash@2.1.3/LICENSE-APACHE +176 -0
  546. package/third-party/cargo/rustc-hash@2.1.3/LICENSE-MIT +23 -0
  547. package/third-party/cargo/rustix@1.1.4/COPYRIGHT +29 -0
  548. package/third-party/cargo/rustix@1.1.4/Cargo.toml +292 -0
  549. package/third-party/cargo/rustix@1.1.4/LICENSE-APACHE +201 -0
  550. package/third-party/cargo/rustix@1.1.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
  551. package/third-party/cargo/rustix@1.1.4/LICENSE-MIT +23 -0
  552. package/third-party/cargo/rustversion@1.0.23/Cargo.toml +69 -0
  553. package/third-party/cargo/rustversion@1.0.23/LICENSE-APACHE +176 -0
  554. package/third-party/cargo/rustversion@1.0.23/LICENSE-MIT +23 -0
  555. package/third-party/cargo/rustybuzz@0.20.1/Cargo.toml +110 -0
  556. package/third-party/cargo/rustybuzz@0.20.1/LICENSE +22 -0
  557. package/third-party/cargo/scopeguard@1.2.0/Cargo.toml +43 -0
  558. package/third-party/cargo/scopeguard@1.2.0/LICENSE-APACHE +201 -0
  559. package/third-party/cargo/scopeguard@1.2.0/LICENSE-MIT +25 -0
  560. package/third-party/cargo/self_cell@1.2.2/Cargo.toml +33 -0
  561. package/third-party/cargo/self_cell@1.2.2/LICENSE-APACHE +201 -0
  562. package/third-party/cargo/self_cell@1.2.2/LICENSE-GPLv2 +339 -0
  563. package/third-party/cargo/self_cell@1.3.0/Cargo.toml +33 -0
  564. package/third-party/cargo/self_cell@1.3.0/LICENSE-APACHE +201 -0
  565. package/third-party/cargo/self_cell@1.3.0/LICENSE-GPLv2 +339 -0
  566. package/third-party/cargo/serde@1.0.228/Cargo.toml +84 -0
  567. package/third-party/cargo/serde@1.0.228/LICENSE-APACHE +176 -0
  568. package/third-party/cargo/serde@1.0.228/LICENSE-MIT +23 -0
  569. package/third-party/cargo/serde_core@1.0.228/Cargo.toml +87 -0
  570. package/third-party/cargo/serde_core@1.0.228/LICENSE-APACHE +176 -0
  571. package/third-party/cargo/serde_core@1.0.228/LICENSE-MIT +23 -0
  572. package/third-party/cargo/serde_derive@1.0.228/Cargo.toml +87 -0
  573. package/third-party/cargo/serde_derive@1.0.228/LICENSE-APACHE +176 -0
  574. package/third-party/cargo/serde_derive@1.0.228/LICENSE-MIT +23 -0
  575. package/third-party/cargo/serde_json@1.0.150/Cargo.toml +162 -0
  576. package/third-party/cargo/serde_json@1.0.150/LICENSE-APACHE +176 -0
  577. package/third-party/cargo/serde_json@1.0.150/LICENSE-MIT +23 -0
  578. package/third-party/cargo/simd-adler32@0.3.10/Cargo.toml +68 -0
  579. package/third-party/cargo/simd-adler32@0.3.10/LICENSE.md +21 -0
  580. package/third-party/cargo/simplecss@0.2.2/Cargo.toml +146 -0
  581. package/third-party/cargo/simplecss@0.2.2/LICENSE-APACHE +201 -0
  582. package/third-party/cargo/simplecss@0.2.2/LICENSE-MIT +21 -0
  583. package/third-party/cargo/siphasher@1.0.3/COPYING +7 -0
  584. package/third-party/cargo/siphasher@1.0.3/Cargo.toml +64 -0
  585. package/third-party/cargo/skrifa@0.40.0/Cargo.toml +86 -0
  586. package/third-party/cargo/skrifa@0.40.0/LICENSE-APACHE +67 -0
  587. package/third-party/cargo/skrifa@0.40.0/LICENSE-MIT +25 -0
  588. package/third-party/cargo/skrifa@0.42.1/Cargo.toml +86 -0
  589. package/third-party/cargo/skrifa@0.42.1/LICENSE-APACHE +67 -0
  590. package/third-party/cargo/skrifa@0.42.1/LICENSE-MIT +25 -0
  591. package/third-party/cargo/slab@0.4.12/Cargo.toml +67 -0
  592. package/third-party/cargo/slab@0.4.12/LICENSE +25 -0
  593. package/third-party/cargo/slotmap@1.1.1/Cargo.toml +83 -0
  594. package/third-party/cargo/slotmap@1.1.1/LICENSE +19 -0
  595. package/third-party/cargo/smallvec@1.15.2/Cargo.toml +103 -0
  596. package/third-party/cargo/smallvec@1.15.2/LICENSE-APACHE +201 -0
  597. package/third-party/cargo/smallvec@1.15.2/LICENSE-MIT +25 -0
  598. package/third-party/cargo/smol_str@0.3.6/Cargo.toml +155 -0
  599. package/third-party/cargo/smol_str@0.3.6/LICENSE-APACHE +201 -0
  600. package/third-party/cargo/smol_str@0.3.6/LICENSE-MIT +23 -0
  601. package/third-party/cargo/spirv@0.4.0+sdk-1.4.341.0/Cargo.toml +56 -0
  602. package/third-party/cargo/spirv@0.4.0+sdk-1.4.341.0/gfx-rs-rspirv-8afc3d0ac8e1-LICENSE +202 -0
  603. package/third-party/cargo/static_assertions@1.1.0/Cargo.toml +39 -0
  604. package/third-party/cargo/static_assertions@1.1.0/LICENSE-APACHE +202 -0
  605. package/third-party/cargo/static_assertions@1.1.0/LICENSE-MIT +21 -0
  606. package/third-party/cargo/strict-num@0.1.1/Cargo.toml +35 -0
  607. package/third-party/cargo/strict-num@0.1.1/LICENSE +19 -0
  608. package/third-party/cargo/svgtypes@0.16.1/Cargo.toml +57 -0
  609. package/third-party/cargo/svgtypes@0.16.1/LICENSE-APACHE +201 -0
  610. package/third-party/cargo/svgtypes@0.16.1/LICENSE-MIT +20 -0
  611. package/third-party/cargo/swash@0.2.9/Cargo.toml +93 -0
  612. package/third-party/cargo/swash@0.2.9/LICENSE-APACHE +201 -0
  613. package/third-party/cargo/swash@0.2.9/LICENSE-MIT +25 -0
  614. package/third-party/cargo/syn@2.0.118/Cargo.toml +272 -0
  615. package/third-party/cargo/syn@2.0.118/LICENSE-APACHE +176 -0
  616. package/third-party/cargo/syn@2.0.118/LICENSE-MIT +23 -0
  617. package/third-party/cargo/sys-locale@0.3.2/Cargo.toml +73 -0
  618. package/third-party/cargo/sys-locale@0.3.2/LICENSE-APACHE +201 -0
  619. package/third-party/cargo/sys-locale@0.3.2/LICENSE-MIT +21 -0
  620. package/third-party/cargo/taffy@0.12.1/Cargo.toml +171 -0
  621. package/third-party/cargo/taffy@0.12.1/DioxusLabs-taffy-1c9e37a0a1cf-LICENSE.md +25 -0
  622. package/third-party/cargo/tempfile@3.27.0/Cargo.toml +105 -0
  623. package/third-party/cargo/tempfile@3.27.0/LICENSE-APACHE +201 -0
  624. package/third-party/cargo/tempfile@3.27.0/LICENSE-MIT +25 -0
  625. package/third-party/cargo/termcolor@1.4.1/COPYING +3 -0
  626. package/third-party/cargo/termcolor@1.4.1/Cargo.toml +40 -0
  627. package/third-party/cargo/termcolor@1.4.1/LICENSE-MIT +21 -0
  628. package/third-party/cargo/termcolor@1.4.1/UNLICENSE +24 -0
  629. package/third-party/cargo/thiserror-impl@2.0.18/Cargo.toml +52 -0
  630. package/third-party/cargo/thiserror-impl@2.0.18/LICENSE-APACHE +176 -0
  631. package/third-party/cargo/thiserror-impl@2.0.18/LICENSE-MIT +23 -0
  632. package/third-party/cargo/thiserror@2.0.18/Cargo.toml +119 -0
  633. package/third-party/cargo/thiserror@2.0.18/LICENSE-APACHE +176 -0
  634. package/third-party/cargo/thiserror@2.0.18/LICENSE-MIT +23 -0
  635. package/third-party/cargo/tiny-skia-path@0.12.0/Cargo.toml +58 -0
  636. package/third-party/cargo/tiny-skia-path@0.12.0/LICENSE +30 -0
  637. package/third-party/cargo/tiny-skia@0.12.0/Cargo.toml +116 -0
  638. package/third-party/cargo/tiny-skia@0.12.0/LICENSE +30 -0
  639. package/third-party/cargo/tiny_http@0.12.0/Cargo.toml +82 -0
  640. package/third-party/cargo/tiny_http@0.12.0/LICENSE-APACHE +201 -0
  641. package/third-party/cargo/tiny_http@0.12.0/LICENSE-MIT +25 -0
  642. package/third-party/cargo/tinyvec@1.12.0/Cargo.toml +184 -0
  643. package/third-party/cargo/tinyvec@1.12.0/LICENSE-APACHE.md +202 -0
  644. package/third-party/cargo/tinyvec@1.12.0/LICENSE-MIT.md +5 -0
  645. package/third-party/cargo/tinyvec@1.12.0/LICENSE-ZLIB.md +11 -0
  646. package/third-party/cargo/tinyvec_macros@0.1.1/Cargo.toml +19 -0
  647. package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-APACHE.md +202 -0
  648. package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-MIT.md +21 -0
  649. package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-ZLIB.md +20 -0
  650. package/third-party/cargo/ttf-parser@0.25.1/Cargo.toml +101 -0
  651. package/third-party/cargo/ttf-parser@0.25.1/LICENSE-APACHE +201 -0
  652. package/third-party/cargo/ttf-parser@0.25.1/LICENSE-MIT +20 -0
  653. package/third-party/cargo/unicode-bidi-mirroring@0.4.0/Cargo.toml +22 -0
  654. package/third-party/cargo/unicode-bidi-mirroring@0.4.0/LICENSE-APACHE +201 -0
  655. package/third-party/cargo/unicode-bidi-mirroring@0.4.0/LICENSE-MIT +21 -0
  656. package/third-party/cargo/unicode-bidi@0.3.18/COPYRIGHT +8 -0
  657. package/third-party/cargo/unicode-bidi@0.3.18/Cargo.toml +90 -0
  658. package/third-party/cargo/unicode-bidi@0.3.18/LICENSE-APACHE +201 -0
  659. package/third-party/cargo/unicode-bidi@0.3.18/LICENSE-MIT +25 -0
  660. package/third-party/cargo/unicode-ccc@0.4.0/Cargo.toml +22 -0
  661. package/third-party/cargo/unicode-ccc@0.4.0/LICENSE-APACHE +201 -0
  662. package/third-party/cargo/unicode-ccc@0.4.0/LICENSE-MIT +21 -0
  663. package/third-party/cargo/unicode-ident@1.0.24/Cargo.toml +84 -0
  664. package/third-party/cargo/unicode-ident@1.0.24/LICENSE-APACHE +176 -0
  665. package/third-party/cargo/unicode-ident@1.0.24/LICENSE-MIT +23 -0
  666. package/third-party/cargo/unicode-ident@1.0.24/LICENSE-UNICODE +39 -0
  667. package/third-party/cargo/unicode-linebreak@0.1.5/Cargo.toml +32 -0
  668. package/third-party/cargo/unicode-linebreak@0.1.5/LICENSE +201 -0
  669. package/third-party/cargo/unicode-properties@0.1.4/COPYRIGHT +7 -0
  670. package/third-party/cargo/unicode-properties@0.1.4/Cargo.toml +65 -0
  671. package/third-party/cargo/unicode-properties@0.1.4/LICENSE-APACHE +201 -0
  672. package/third-party/cargo/unicode-properties@0.1.4/LICENSE-MIT +25 -0
  673. package/third-party/cargo/unicode-script@0.5.8/Cargo.toml +63 -0
  674. package/third-party/cargo/unicode-script@0.5.8/LICENSE-APACHE +201 -0
  675. package/third-party/cargo/unicode-script@0.5.8/LICENSE-MIT +27 -0
  676. package/third-party/cargo/unicode-segmentation@1.13.3/COPYRIGHT +7 -0
  677. package/third-party/cargo/unicode-segmentation@1.13.3/Cargo.toml +91 -0
  678. package/third-party/cargo/unicode-segmentation@1.13.3/LICENSE-APACHE +201 -0
  679. package/third-party/cargo/unicode-segmentation@1.13.3/LICENSE-MIT +25 -0
  680. package/third-party/cargo/unicode-vo@0.1.0/Cargo.toml +22 -0
  681. package/third-party/cargo/unicode-vo@0.1.0/LICENSE-APACHE +201 -0
  682. package/third-party/cargo/unicode-vo@0.1.0/LICENSE-MIT +21 -0
  683. package/third-party/cargo/unicode-width@0.2.2/COPYRIGHT +7 -0
  684. package/third-party/cargo/unicode-width@0.2.2/Cargo.toml +77 -0
  685. package/third-party/cargo/unicode-width@0.2.2/LICENSE-APACHE +201 -0
  686. package/third-party/cargo/unicode-width@0.2.2/LICENSE-MIT +25 -0
  687. package/third-party/cargo/unicode-xid@0.2.6/COPYRIGHT +7 -0
  688. package/third-party/cargo/unicode-xid@0.2.6/Cargo.toml +65 -0
  689. package/third-party/cargo/unicode-xid@0.2.6/LICENSE-APACHE +201 -0
  690. package/third-party/cargo/unicode-xid@0.2.6/LICENSE-MIT +25 -0
  691. package/third-party/cargo/urlencoding@2.1.3/Cargo.toml +41 -0
  692. package/third-party/cargo/urlencoding@2.1.3/LICENSE +20 -0
  693. package/third-party/cargo/usvg@0.47.0/Cargo.toml +138 -0
  694. package/third-party/cargo/usvg@0.47.0/LICENSE-APACHE +176 -0
  695. package/third-party/cargo/usvg@0.47.0/LICENSE-MIT +25 -0
  696. package/third-party/cargo/version_check@0.9.5/Cargo.toml +38 -0
  697. package/third-party/cargo/version_check@0.9.5/LICENSE-APACHE +201 -0
  698. package/third-party/cargo/version_check@0.9.5/LICENSE-MIT +19 -0
  699. package/third-party/cargo/wasm-bindgen-futures@0.4.76/Cargo.toml +88 -0
  700. package/third-party/cargo/wasm-bindgen-futures@0.4.76/LICENSE-APACHE +201 -0
  701. package/third-party/cargo/wasm-bindgen-futures@0.4.76/LICENSE-MIT +25 -0
  702. package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/Cargo.toml +83 -0
  703. package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/LICENSE-APACHE +201 -0
  704. package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/LICENSE-MIT +25 -0
  705. package/third-party/cargo/wasm-bindgen-macro@0.2.126/Cargo.toml +73 -0
  706. package/third-party/cargo/wasm-bindgen-macro@0.2.126/LICENSE-APACHE +201 -0
  707. package/third-party/cargo/wasm-bindgen-macro@0.2.126/LICENSE-MIT +25 -0
  708. package/third-party/cargo/wasm-bindgen-shared@0.2.126/Cargo.toml +66 -0
  709. package/third-party/cargo/wasm-bindgen-shared@0.2.126/LICENSE-APACHE +201 -0
  710. package/third-party/cargo/wasm-bindgen-shared@0.2.126/LICENSE-MIT +25 -0
  711. package/third-party/cargo/wasm-bindgen@0.2.126/Cargo.toml +122 -0
  712. package/third-party/cargo/wasm-bindgen@0.2.126/LICENSE-APACHE +201 -0
  713. package/third-party/cargo/wasm-bindgen@0.2.126/LICENSE-MIT +25 -0
  714. package/third-party/cargo/wayland-sys@0.31.11/Cargo.toml +81 -0
  715. package/third-party/cargo/wayland-sys@0.31.11/LICENSE.txt +19 -0
  716. package/third-party/cargo/web-sys@0.3.103/Cargo.toml +2823 -0
  717. package/third-party/cargo/web-sys@0.3.103/LICENSE-APACHE +201 -0
  718. package/third-party/cargo/web-sys@0.3.103/LICENSE-MIT +25 -0
  719. package/third-party/cargo/weezl@0.1.12/Cargo.toml +120 -0
  720. package/third-party/cargo/weezl@0.1.12/LICENSE-APACHE +201 -0
  721. package/third-party/cargo/weezl@0.1.12/LICENSE-MIT +22 -0
  722. package/third-party/cargo/wgpu-core-deps-apple@30.0.0/Cargo.toml +53 -0
  723. package/third-party/cargo/wgpu-core-deps-apple@30.0.0/LICENSE.APACHE +176 -0
  724. package/third-party/cargo/wgpu-core-deps-apple@30.0.0/LICENSE.MIT +21 -0
  725. package/third-party/cargo/wgpu-core@30.0.0/Cargo.toml +237 -0
  726. package/third-party/cargo/wgpu-core@30.0.0/LICENSE.APACHE +176 -0
  727. package/third-party/cargo/wgpu-core@30.0.0/LICENSE.MIT +21 -0
  728. package/third-party/cargo/wgpu-hal@30.0.0/Cargo.toml +541 -0
  729. package/third-party/cargo/wgpu-hal@30.0.0/LICENSE.APACHE +176 -0
  730. package/third-party/cargo/wgpu-hal@30.0.0/LICENSE.MIT +21 -0
  731. package/third-party/cargo/wgpu-naga-bridge@30.0.0/Cargo.toml +46 -0
  732. package/third-party/cargo/wgpu-naga-bridge@30.0.0/LICENSE.APACHE +176 -0
  733. package/third-party/cargo/wgpu-naga-bridge@30.0.0/LICENSE.MIT +21 -0
  734. package/third-party/cargo/wgpu-types@30.0.0/Cargo.toml +161 -0
  735. package/third-party/cargo/wgpu-types@30.0.0/LICENSE.APACHE +176 -0
  736. package/third-party/cargo/wgpu-types@30.0.0/LICENSE.MIT +21 -0
  737. package/third-party/cargo/wgpu@30.0.0/Cargo.toml +278 -0
  738. package/third-party/cargo/wgpu@30.0.0/LICENSE.APACHE +176 -0
  739. package/third-party/cargo/wgpu@30.0.0/LICENSE.MIT +21 -0
  740. package/third-party/cargo/xmlwriter@0.1.0/Cargo.toml +23 -0
  741. package/third-party/cargo/xmlwriter@0.1.0/LICENSE +21 -0
  742. package/third-party/cargo/yazi@0.2.1/Cargo.toml +45 -0
  743. package/third-party/cargo/yazi@0.2.1/LICENSE-APACHE +201 -0
  744. package/third-party/cargo/yazi@0.2.1/LICENSE-MIT +19 -0
  745. package/third-party/cargo/zeno@0.3.3/Cargo.toml +65 -0
  746. package/third-party/cargo/zeno@0.3.3/LICENSE-APACHE +201 -0
  747. package/third-party/cargo/zeno@0.3.3/LICENSE-MIT +25 -0
  748. package/third-party/cargo/zerocopy-derive@0.8.54/Cargo.toml +209 -0
  749. package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-APACHE +202 -0
  750. package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-BSD +24 -0
  751. package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-MIT +26 -0
  752. package/third-party/cargo/zerocopy@0.8.54/Cargo.toml +419 -0
  753. package/third-party/cargo/zerocopy@0.8.54/LICENSE-APACHE +202 -0
  754. package/third-party/cargo/zerocopy@0.8.54/LICENSE-BSD +24 -0
  755. package/third-party/cargo/zerocopy@0.8.54/LICENSE-MIT +26 -0
  756. package/third-party/cargo/zmij@1.0.23/Cargo.toml +90 -0
  757. package/third-party/cargo/zmij@1.0.23/LICENSE-MIT +23 -0
  758. package/third-party/cargo/zune-core@0.5.1/Cargo.toml +47 -0
  759. package/third-party/cargo/zune-core@0.5.1/LICENSE-APACHE +201 -0
  760. package/third-party/cargo/zune-core@0.5.1/LICENSE-MIT +21 -0
  761. package/third-party/cargo/zune-core@0.5.1/LICENSE-ZLIB +19 -0
  762. package/third-party/cargo/zune-jpeg@0.5.15/Cargo.toml +67 -0
  763. package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-APACHE +201 -0
  764. package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-MIT +21 -0
  765. package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-ZLIB +19 -0
  766. package/third-party/inventory.json +3614 -0
  767. package/third-party/npm/@esbuild__darwin-arm64@0.27.4/esbuild-0.27.4-LICENSE.md +21 -0
  768. package/third-party/npm/@esbuild__darwin-arm64@0.27.4/go-LICENSE +27 -0
  769. package/third-party/npm/@esbuild__darwin-arm64@0.27.4/golang-x-sys-c0bba94af5f8-LICENSE +27 -0
  770. package/third-party/npm/@esbuild__darwin-arm64@0.27.4/package.json +20 -0
  771. package/third-party/npm/@floating-ui__core@1.7.5/LICENSE +20 -0
  772. package/third-party/npm/@floating-ui__core@1.7.5/package.json +63 -0
  773. package/third-party/npm/@floating-ui__dom@1.7.6/LICENSE +20 -0
  774. package/third-party/npm/@floating-ui__dom@1.7.6/package.json +71 -0
  775. package/third-party/npm/@floating-ui__react-dom@2.1.8/LICENSE +20 -0
  776. package/third-party/npm/@floating-ui__react-dom@2.1.8/package.json +76 -0
  777. package/third-party/npm/@floating-ui__utils@0.2.11/LICENSE +20 -0
  778. package/third-party/npm/@floating-ui__utils@0.2.11/package.json +70 -0
  779. package/third-party/npm/@radix-ui__primitive@1.1.7/LICENSE +21 -0
  780. package/third-party/npm/@radix-ui__primitive@1.1.7/package.json +79 -0
  781. package/third-party/npm/@radix-ui__react-arrow@1.1.15/LICENSE +21 -0
  782. package/third-party/npm/@radix-ui__react-arrow@1.1.15/package.json +68 -0
  783. package/third-party/npm/@radix-ui__react-collection@1.1.15/LICENSE +21 -0
  784. package/third-party/npm/@radix-ui__react-collection@1.1.15/package.json +71 -0
  785. package/third-party/npm/@radix-ui__react-compose-refs@1.1.5/LICENSE +21 -0
  786. package/third-party/npm/@radix-ui__react-compose-refs@1.1.5/package.json +60 -0
  787. package/third-party/npm/@radix-ui__react-context-menu@2.3.7/LICENSE +21 -0
  788. package/third-party/npm/@radix-ui__react-context-menu@2.3.7/package.json +73 -0
  789. package/third-party/npm/@radix-ui__react-context@1.2.2/LICENSE +21 -0
  790. package/third-party/npm/@radix-ui__react-context@1.2.2/package.json +60 -0
  791. package/third-party/npm/@radix-ui__react-direction@1.1.4/LICENSE +21 -0
  792. package/third-party/npm/@radix-ui__react-direction@1.1.4/package.json +60 -0
  793. package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.19/LICENSE +21 -0
  794. package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.19/package.json +74 -0
  795. package/third-party/npm/@radix-ui__react-focus-guards@1.1.6/LICENSE +21 -0
  796. package/third-party/npm/@radix-ui__react-focus-guards@1.1.6/package.json +60 -0
  797. package/third-party/npm/@radix-ui__react-focus-scope@1.1.16/LICENSE +21 -0
  798. package/third-party/npm/@radix-ui__react-focus-scope@1.1.16/package.json +71 -0
  799. package/third-party/npm/@radix-ui__react-id@1.1.4/LICENSE +21 -0
  800. package/third-party/npm/@radix-ui__react-id@1.1.4/package.json +63 -0
  801. package/third-party/npm/@radix-ui__react-menu@2.1.24/LICENSE +21 -0
  802. package/third-party/npm/@radix-ui__react-menu@2.1.24/package.json +86 -0
  803. package/third-party/npm/@radix-ui__react-popper@1.3.7/LICENSE +21 -0
  804. package/third-party/npm/@radix-ui__react-popper@1.3.7/package.json +78 -0
  805. package/third-party/npm/@radix-ui__react-portal@1.1.17/LICENSE +21 -0
  806. package/third-party/npm/@radix-ui__react-portal@1.1.17/package.json +69 -0
  807. package/third-party/npm/@radix-ui__react-presence@1.1.10/LICENSE +21 -0
  808. package/third-party/npm/@radix-ui__react-presence@1.1.10/package.json +68 -0
  809. package/third-party/npm/@radix-ui__react-primitive@2.1.10/LICENSE +21 -0
  810. package/third-party/npm/@radix-ui__react-primitive@2.1.10/package.json +68 -0
  811. package/third-party/npm/@radix-ui__react-roving-focus@1.1.19/LICENSE +21 -0
  812. package/third-party/npm/@radix-ui__react-roving-focus@1.1.19/package.json +78 -0
  813. package/third-party/npm/@radix-ui__react-slot@1.3.3/LICENSE +21 -0
  814. package/third-party/npm/@radix-ui__react-slot@1.3.3/package.json +63 -0
  815. package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.4/LICENSE +21 -0
  816. package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.4/package.json +60 -0
  817. package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.6/LICENSE +21 -0
  818. package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.6/package.json +65 -0
  819. package/third-party/npm/@radix-ui__react-use-effect-event@0.0.5/LICENSE +21 -0
  820. package/third-party/npm/@radix-ui__react-use-effect-event@0.0.5/package.json +65 -0
  821. package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.3/LICENSE +21 -0
  822. package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.3/package.json +61 -0
  823. package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.4/LICENSE +21 -0
  824. package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.4/package.json +60 -0
  825. package/third-party/npm/@radix-ui__react-use-size@1.1.4/LICENSE +21 -0
  826. package/third-party/npm/@radix-ui__react-use-size@1.1.4/package.json +63 -0
  827. package/third-party/npm/aria-hidden@1.2.6/LICENSE +21 -0
  828. package/third-party/npm/aria-hidden@1.2.6/package.json +71 -0
  829. package/third-party/npm/detect-node-es@1.1.0/LICENSE +21 -0
  830. package/third-party/npm/detect-node-es@1.1.0/package.json +29 -0
  831. package/third-party/npm/esbuild@0.27.4/LICENSE.md +21 -0
  832. package/third-party/npm/esbuild@0.27.4/package.json +49 -0
  833. package/third-party/npm/fflate@0.8.3/LICENSE +21 -0
  834. package/third-party/npm/fflate@0.8.3/package.json +128 -0
  835. package/third-party/npm/get-nonce@1.0.1/LICENSE +21 -0
  836. package/third-party/npm/get-nonce@1.0.1/package.json +68 -0
  837. package/third-party/npm/mediabunny@1.52.2/LICENSE +373 -0
  838. package/third-party/npm/mediabunny@1.52.2/package.json +136 -0
  839. package/third-party/npm/mediabunny@1.52.2/source/LICENSE +373 -0
  840. package/third-party/npm/mediabunny@1.52.2/source/package.json +136 -0
  841. package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-demuxer.ts +400 -0
  842. package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-muxer.ts +124 -0
  843. package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-reader.ts +85 -0
  844. package/third-party/npm/mediabunny@1.52.2/source/src/aes.ts +307 -0
  845. package/third-party/npm/mediabunny@1.52.2/source/src/codec-data.ts +3178 -0
  846. package/third-party/npm/mediabunny@1.52.2/source/src/codec.ts +1154 -0
  847. package/third-party/npm/mediabunny@1.52.2/source/src/conversion.ts +2042 -0
  848. package/third-party/npm/mediabunny@1.52.2/source/src/custom-coder.ts +212 -0
  849. package/third-party/npm/mediabunny@1.52.2/source/src/decode.ts +263 -0
  850. package/third-party/npm/mediabunny@1.52.2/source/src/demuxer.ts +43 -0
  851. package/third-party/npm/mediabunny@1.52.2/source/src/encode.ts +1412 -0
  852. package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-demuxer.ts +813 -0
  853. package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-misc.ts +165 -0
  854. package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-muxer.ts +350 -0
  855. package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-demuxer.ts +995 -0
  856. package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-misc.ts +81 -0
  857. package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-muxer.ts +1529 -0
  858. package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-segmented-input.ts +772 -0
  859. package/third-party/npm/mediabunny@1.52.2/source/src/id3.ts +972 -0
  860. package/third-party/npm/mediabunny@1.52.2/source/src/index.ts +319 -0
  861. package/third-party/npm/mediabunny@1.52.2/source/src/input-format.ts +819 -0
  862. package/third-party/npm/mediabunny@1.52.2/source/src/input-track.ts +1141 -0
  863. package/third-party/npm/mediabunny@1.52.2/source/src/input.ts +571 -0
  864. package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-boxes.ts +1911 -0
  865. package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-demuxer.ts +3953 -0
  866. package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-misc.ts +90 -0
  867. package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-muxer.ts +1595 -0
  868. package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-reader.ts +91 -0
  869. package/third-party/npm/mediabunny@1.52.2/source/src/logging.ts +120 -0
  870. package/third-party/npm/mediabunny@1.52.2/source/src/matroska/ebml.ts +770 -0
  871. package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-demuxer.ts +2598 -0
  872. package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-misc.ts +29 -0
  873. package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-muxer.ts +1360 -0
  874. package/third-party/npm/mediabunny@1.52.2/source/src/media-sink.ts +2589 -0
  875. package/third-party/npm/mediabunny@1.52.2/source/src/media-source.ts +3041 -0
  876. package/third-party/npm/mediabunny@1.52.2/source/src/metadata.ts +335 -0
  877. package/third-party/npm/mediabunny@1.52.2/source/src/misc.ts +1336 -0
  878. package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-demuxer.ts +469 -0
  879. package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-muxer.ts +169 -0
  880. package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-reader.ts +61 -0
  881. package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-writer.ts +121 -0
  882. package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-demuxer.ts +2519 -0
  883. package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-misc.ts +32 -0
  884. package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-muxer.ts +842 -0
  885. package/third-party/npm/mediabunny@1.52.2/source/src/muxer.ts +80 -0
  886. package/third-party/npm/mediabunny@1.52.2/source/src/node.ts +11 -0
  887. package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-demuxer.ts +1087 -0
  888. package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-misc.ts +116 -0
  889. package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-muxer.ts +527 -0
  890. package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-reader.ts +93 -0
  891. package/third-party/npm/mediabunny@1.52.2/source/src/output-format.ts +1425 -0
  892. package/third-party/npm/mediabunny@1.52.2/source/src/output.ts +940 -0
  893. package/third-party/npm/mediabunny@1.52.2/source/src/packet.ts +287 -0
  894. package/third-party/npm/mediabunny@1.52.2/source/src/pcm.ts +112 -0
  895. package/third-party/npm/mediabunny@1.52.2/source/src/reader.ts +391 -0
  896. package/third-party/npm/mediabunny@1.52.2/source/src/resample.ts +287 -0
  897. package/third-party/npm/mediabunny@1.52.2/source/src/sample.ts +3231 -0
  898. package/third-party/npm/mediabunny@1.52.2/source/src/segmented-input.ts +575 -0
  899. package/third-party/npm/mediabunny@1.52.2/source/src/source.ts +2588 -0
  900. package/third-party/npm/mediabunny@1.52.2/source/src/subtitles.ts +131 -0
  901. package/third-party/npm/mediabunny@1.52.2/source/src/target.ts +871 -0
  902. package/third-party/npm/mediabunny@1.52.2/source/src/tsconfig.json +16 -0
  903. package/third-party/npm/mediabunny@1.52.2/source/src/wave/riff-writer.ts +36 -0
  904. package/third-party/npm/mediabunny@1.52.2/source/src/wave/wave-demuxer.ts +583 -0
  905. package/third-party/npm/mediabunny@1.52.2/source/src/wave/wave-muxer.ts +375 -0
  906. package/third-party/npm/mediabunny@1.52.2/source/src/writer.ts +128 -0
  907. package/third-party/npm/react-dom@19.3.0/LICENSE +21 -0
  908. package/third-party/npm/react-dom@19.3.0/package.json +117 -0
  909. package/third-party/npm/react-remove-scroll-bar@2.3.8/package.json +85 -0
  910. package/third-party/npm/react-remove-scroll-bar@2.3.8/react-remove-scroll-bar-LICENSE +21 -0
  911. package/third-party/npm/react-remove-scroll@2.7.2/LICENSE +21 -0
  912. package/third-party/npm/react-remove-scroll@2.7.2/package.json +98 -0
  913. package/third-party/npm/react-style-singleton@2.2.3/LICENSE +21 -0
  914. package/third-party/npm/react-style-singleton@2.2.3/package.json +90 -0
  915. package/third-party/npm/react@19.3.0/LICENSE +21 -0
  916. package/third-party/npm/react@19.3.0/package.json +51 -0
  917. package/third-party/npm/scheduler@0.28.0/LICENSE +21 -0
  918. package/third-party/npm/scheduler@0.28.0/package.json +27 -0
  919. package/third-party/npm/tslib@2.8.1/LICENSE.txt +12 -0
  920. package/third-party/npm/tslib@2.8.1/package.json +47 -0
  921. package/third-party/npm/typescript@5.9.3/LICENSE.txt +55 -0
  922. package/third-party/npm/typescript@5.9.3/ThirdPartyNoticeText.txt +193 -0
  923. package/third-party/npm/typescript@5.9.3/package.json +120 -0
  924. package/third-party/npm/use-callback-ref@1.3.3/LICENSE +21 -0
  925. package/third-party/npm/use-callback-ref@1.3.3/package.json +85 -0
  926. package/third-party/npm/use-sidecar@1.1.3/LICENSE +21 -0
  927. package/third-party/npm/use-sidecar@1.1.3/package.json +93 -0
  928. package/third-party/packages/render-engine/assets/OFL.txt +93 -0
  929. package/third-party/packages/studio/public/fonts/OFL.txt +93 -0
  930. package/third-party/packages/studio/vendor/osmo-icons/LICENSE +21 -0
  931. package/third-party/packages/studio/vendor/shadcn/LICENSE +21 -0
  932. package/third-party/repository-notices.md +41 -0
  933. package/third-party/rust-standard-library/COPYRIGHT-library.html +5556 -0
  934. package/third-party/rust-standard-library/licenses/Apache-2.0.txt +73 -0
  935. package/third-party/rust-standard-library/licenses/BSD-2-Clause.txt +9 -0
  936. package/third-party/rust-standard-library/licenses/CC-BY-SA-4.0.txt +427 -0
  937. package/third-party/rust-standard-library/licenses/GCC-exception-3.1.txt +30 -0
  938. package/third-party/rust-standard-library/licenses/GPL-2.0-only.txt +133 -0
  939. package/third-party/rust-standard-library/licenses/GPL-3.0-or-later.txt +202 -0
  940. package/third-party/rust-standard-library/licenses/ISC.txt +7 -0
  941. package/third-party/rust-standard-library/licenses/LLVM-exception.txt +15 -0
  942. package/third-party/rust-standard-library/licenses/MIT.txt +9 -0
  943. package/third-party/rust-standard-library/licenses/NCSA.txt +29 -0
  944. package/third-party/rust-standard-library/licenses/OFL-1.1.txt +43 -0
  945. package/third-party/rust-standard-library/licenses/Unicode-3.0.txt +39 -0
  946. package/third-party/rust-standard-library/version.txt +1 -0
  947. package/vendor/osmo-lite +0 -0
  948. package/vendor/osmo-scene-0.1.0.tgz +0 -0
  949. package/vendor/platform.json +1 -0
@@ -0,0 +1,3231 @@
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 {
10
+ assert,
11
+ clamp,
12
+ COLOR_PRIMARIES_MAP,
13
+ isAllowSharedBufferSource,
14
+ MATRIX_COEFFICIENTS_MAP,
15
+ Rotation,
16
+ SECOND_TO_MICROSECOND_FACTOR,
17
+ toDataView,
18
+ toUint8Array,
19
+ SetRequired,
20
+ TRANSFER_CHARACTERISTICS_MAP,
21
+ isFirefox,
22
+ polyfillSymbolDispose,
23
+ assertNever,
24
+ isWebKit,
25
+ Rational,
26
+ simplifyRational,
27
+ Rectangle,
28
+ validateRectangle,
29
+ normalizeRotation,
30
+ roundToMultiple,
31
+ arrayArgmin,
32
+ MaybePromise,
33
+ DeepReadonly,
34
+ } from './misc';
35
+ import { Logging } from './logging';
36
+
37
+ polyfillSymbolDispose();
38
+
39
+ type FinalizationRegistryValue = {
40
+ type: 'video';
41
+ data: VideoFrame | OffscreenCanvas | Uint8Array | VideoSampleResource;
42
+ } | {
43
+ type: 'audio';
44
+ data: AudioData | Uint8Array | AudioSampleResource;
45
+ };
46
+
47
+ // Let's manually handle logging the garbage collection errors that are typically logged by the browser. This way, they
48
+ // also kick for audio samples (which is normally not the case), making sure any incorrect code is quickly caught.
49
+ let lastVideoGcErrorLog = -Infinity;
50
+ let lastAudioGcErrorLog = -Infinity;
51
+ let finalizationRegistry: FinalizationRegistry<FinalizationRegistryValue> | null = null;
52
+ if (typeof FinalizationRegistry !== 'undefined') {
53
+ finalizationRegistry = new FinalizationRegistry<FinalizationRegistryValue>((value) => {
54
+ const now = performance.now();
55
+
56
+ if (value.type === 'video') {
57
+ if (now - lastVideoGcErrorLog >= 1000) {
58
+ // This error is annoying but oh so important
59
+ Logging._error(
60
+ `A VideoSample was garbage collected without first being closed. For proper resource management,`
61
+ + ` make sure to call close() on all your VideoSamples as soon as you're done using them.`,
62
+ );
63
+
64
+ lastVideoGcErrorLog = now;
65
+ }
66
+
67
+ if (typeof VideoFrame !== 'undefined' && value.data instanceof VideoFrame) {
68
+ value.data.close(); // Prevent the browser error since we're logging our own
69
+ }
70
+ } else {
71
+ if (now - lastAudioGcErrorLog >= 1000) {
72
+ Logging._error(
73
+ `An AudioSample was garbage collected without first being closed. For proper resource management,`
74
+ + ` make sure to call close() on all your AudioSamples as soon as you're done using them.`,
75
+ );
76
+
77
+ lastAudioGcErrorLog = now;
78
+ }
79
+
80
+ if (typeof AudioData !== 'undefined' && value.data instanceof AudioData) {
81
+ value.data.close();
82
+ }
83
+ }
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Abstract base class for custom video sample resources. Implement this class to provide custom backing
89
+ * for VideoSample instances.
90
+ * @group Samples
91
+ * @public
92
+ */
93
+ export abstract class VideoSampleResource {
94
+ /** @internal */
95
+ _referenceCount: number = 0;
96
+ /** @internal */
97
+ _lastAllocationBuffer: ArrayBuffer | null = null;
98
+
99
+ /**
100
+ * Returns the internal pixel format in which the frame is stored.
101
+ * [See pixel formats](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame/format)
102
+ */
103
+ abstract getFormat(): VideoSamplePixelFormat | null;
104
+
105
+ /** Returns the width of the frame in pixels. */
106
+ abstract getCodedWidth(): number;
107
+
108
+ /** Returns the height of the frame in pixels. */
109
+ abstract getCodedHeight(): number;
110
+
111
+ /** Returns the width of the frame in square pixels, respecting pixel aspect ratio. */
112
+ abstract getSquarePixelWidth(): number;
113
+
114
+ /** Returns the height of the frame in square pixels, respecting pixel aspect ratio. */
115
+ abstract getSquarePixelHeight(): number;
116
+
117
+ /** Returns the color space of the frame. */
118
+ abstract getColorSpace(): VideoSampleColorSpace;
119
+
120
+ /**
121
+ * Closes this resource, releasing held resources. Called automatically when the last {@link VideoSample} using this
122
+ * resource is closed.
123
+ */
124
+ abstract close(): void;
125
+
126
+ /**
127
+ * Returns the data planes that hold the video data for this sample. The returned planes and data must be in the
128
+ * format returned by `getFormat()`.
129
+ */
130
+ abstract getDataPlanes(): MaybePromise<VideoDataPlane[]>;
131
+
132
+ /**
133
+ * Returns a new RGB {@link VideoSample} that contains the same content as this sample. The provided `init` object
134
+ * must be used to set the metadata of this new video sample. When converting from a non-RGB format to RGB, the
135
+ * conversion must respect `colorSpace`.
136
+ */
137
+ abstract toRgbSample(
138
+ init: SetRequired<VideoSampleInit, 'timestamp'>,
139
+ colorSpace: PredefinedColorSpace,
140
+ ): MaybePromise<VideoSample>;
141
+ }
142
+
143
+ /**
144
+ * Describes a single data plane of a video frame.
145
+ * @group Samples
146
+ * @public
147
+ */
148
+ export type VideoDataPlane = {
149
+ /** The data of the plane. */
150
+ data: Uint8Array;
151
+ /** The stride of the plane, in bytes. This is the distance in bytes between the start of each row of pixels. */
152
+ stride: number;
153
+ };
154
+
155
+ /**
156
+ * The list of {@link VideoSample} pixel formats.
157
+ * @group Samples
158
+ * @public
159
+ */
160
+ export const VIDEO_SAMPLE_PIXEL_FORMATS = [
161
+ // 4:2:0 Y, U, V
162
+ 'I420',
163
+ 'I420P10',
164
+ 'I420P12',
165
+ // 4:2:0 Y, U, V, A
166
+ 'I420A',
167
+ 'I420AP10',
168
+ 'I420AP12',
169
+ // 4:2:2 Y, U, V
170
+ 'I422',
171
+ 'I422P10',
172
+ 'I422P12',
173
+ // 4:2:2 Y, U, V, A
174
+ 'I422A',
175
+ 'I422AP10',
176
+ 'I422AP12',
177
+ // 4:4:4 Y, U, V
178
+ 'I444',
179
+ 'I444P10',
180
+ 'I444P12',
181
+ // 4:4:4 Y, U, V, A
182
+ 'I444A',
183
+ 'I444AP10',
184
+ 'I444AP12',
185
+ // 4:2:0 Y, UV
186
+ 'NV12',
187
+ // 4:4:4 RGBA
188
+ 'RGBA',
189
+ // 4:4:4 RGBX (opaque)
190
+ 'RGBX',
191
+ // 4:4:4 BGRA
192
+ 'BGRA',
193
+ // 4:4:4 BGRX (opaque)
194
+ 'BGRX',
195
+ ] as const;
196
+ const VIDEO_SAMPLE_PIXEL_FORMATS_SET = new Set(VIDEO_SAMPLE_PIXEL_FORMATS);
197
+
198
+ /**
199
+ * The internal pixel format with which a {@link VideoSample} is stored.
200
+ * [See pixel formats](https://www.w3.org/TR/webcodecs/#pixel-format) for more.
201
+ * @group Samples
202
+ * @public
203
+ */
204
+ export type VideoSamplePixelFormat = typeof VIDEO_SAMPLE_PIXEL_FORMATS[number];
205
+
206
+ /**
207
+ * Metadata used for VideoSample initialization.
208
+ * @group Samples
209
+ * @public
210
+ */
211
+ export type VideoSampleInit = {
212
+ /**
213
+ * The internal pixel format in which the frame is stored.
214
+ * [See pixel formats](https://www.w3.org/TR/webcodecs/#pixel-format)
215
+ */
216
+ format?: VideoSamplePixelFormat;
217
+ /** The width of the frame in pixels. */
218
+ codedWidth?: number;
219
+ /** The height of the frame in pixels. */
220
+ codedHeight?: number;
221
+ /** The rotation of the frame in degrees, clockwise. */
222
+ rotation?: Rotation;
223
+ /** The presentation timestamp of the frame in seconds. */
224
+ timestamp?: number;
225
+ /** The duration of the frame in seconds. */
226
+ duration?: number;
227
+ /** The color space of the frame. */
228
+ colorSpace?: VideoColorSpaceInit;
229
+ /** The byte layout of the planes of the frame. */
230
+ layout?: PlaneLayout[];
231
+ /** Visible region in the coded frame. When omitted, the rect defaults to `(0, 0, codedWidth, codedHeight)`. */
232
+ visibleRect?: Rectangle | undefined;
233
+ /** Width of the frame in pixels after applying aspect ratio adjustments and rotation. */
234
+ displayWidth?: number | undefined;
235
+ /** Height of the frame in pixels after applying aspect ratio adjustments and rotation. */
236
+ displayHeight?: number | undefined;
237
+ /** The encode options to use when this sample is passed to an encoder. */
238
+ encodeOptions?: DeepReadonly<VideoEncoderEncodeOptions>;
239
+
240
+ /** @internal */
241
+ _doNotCopy?: boolean;
242
+ };
243
+
244
+ /**
245
+ * Represents a raw, unencoded video sample (frame). Mainly used as an expressive wrapper around WebCodecs API's
246
+ * [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame), but can also be used standalone.
247
+ * @group Samples
248
+ * @public
249
+ */
250
+ export class VideoSample implements Disposable {
251
+ /** @internal */
252
+ _data!: VideoFrame | OffscreenCanvas | Uint8Array | VideoSampleResource | null;
253
+ /**
254
+ * Used for the ArrayBuffer-backed case.
255
+ * @internal
256
+ */
257
+ _layout!: PlaneLayout[] | null;
258
+ /** @internal */
259
+ _closed: boolean = false;
260
+
261
+ /**
262
+ * The internal pixel format in which the frame is stored. Will be `null` if it's using an arbitrary internal
263
+ * format not representable by `VideoSamplePixelFormat`.
264
+ * [See pixel formats](https://www.w3.org/TR/webcodecs/#pixel-format)
265
+ */
266
+ readonly format!: VideoSamplePixelFormat | null;
267
+ /** The visible region of the frame in the coded pixel grid. */
268
+ readonly visibleRect!: Rectangle;
269
+ /** The width of the frame in square pixels (respecting pixel aspect ratio), before rotation is applied. */
270
+ readonly squarePixelWidth!: number;
271
+ /** The height of the frame in square pixels (respecting pixel aspect ratio), before rotation is applied. */
272
+ readonly squarePixelHeight!: number;
273
+ /** The rotation of the frame in degrees, clockwise. */
274
+ readonly rotation!: Rotation;
275
+ /**
276
+ * The pixel aspect ratio of the frame, as a rational number in its reduced form. Most videos use
277
+ * square pixels (1:1).
278
+ */
279
+ readonly pixelAspectRatio!: Rational;
280
+ /**
281
+ * The presentation timestamp of the frame in seconds. May be negative. Frames with negative end timestamps should
282
+ * not be presented.
283
+ */
284
+ readonly timestamp!: number;
285
+ /** The duration of the frame in seconds. */
286
+ readonly duration!: number;
287
+ /** The color space of the frame. */
288
+ readonly colorSpace!: VideoSampleColorSpace;
289
+ /** The encode options to use when this sample is passed to an encoder. */
290
+ readonly encodeOptions!: DeepReadonly<VideoEncoderEncodeOptions>;
291
+
292
+ /** The width of the frame in pixels. */
293
+ get codedWidth() {
294
+ // This is wrong, but the fix is a v2 thing
295
+ return this.visibleRect.width;
296
+ }
297
+
298
+ /** The height of the frame in pixels. */
299
+ get codedHeight() {
300
+ // Same here
301
+ return this.visibleRect.height;
302
+ }
303
+
304
+ /** The display width of the frame in pixels, after aspect ratio adjustment and rotation. */
305
+ get displayWidth() {
306
+ return this.rotation % 180 === 0 ? this.squarePixelWidth : this.squarePixelHeight;
307
+ }
308
+
309
+ /** The display height of the frame in pixels, after aspect ratio adjustment and rotation. */
310
+ get displayHeight() {
311
+ return this.rotation % 180 === 0 ? this.squarePixelHeight : this.squarePixelWidth;
312
+ }
313
+
314
+ /** The presentation timestamp of the frame in microseconds. */
315
+ get microsecondTimestamp() {
316
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
317
+ }
318
+
319
+ /** The duration of the frame in microseconds. */
320
+ get microsecondDuration() {
321
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
322
+ }
323
+
324
+ /**
325
+ * Whether this sample uses a pixel format that can hold transparency data. Note that this doesn't necessarily mean
326
+ * that the sample is transparent.
327
+ */
328
+ get hasAlpha() {
329
+ return this.format && this.format.includes('A');
330
+ }
331
+
332
+ /**
333
+ * Creates a new {@link VideoSample} from a
334
+ * [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame). This is essentially a near zero-cost
335
+ * wrapper around `VideoFrame`. The sample's metadata is optionally refined using the data specified in `init`.
336
+ */
337
+ constructor(data: VideoFrame, init?: VideoSampleInit);
338
+ /**
339
+ * Creates a new {@link VideoSample} from a
340
+ * [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource), similar to the
341
+ * [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame) constructor. When `VideoFrame` is
342
+ * available, this is simply a wrapper around its constructor. If not, it will copy the source's image data to an
343
+ * internal canvas for later use.
344
+ */
345
+ constructor(data: CanvasImageSource, init: SetRequired<VideoSampleInit, 'timestamp'>);
346
+ /**
347
+ * Creates a new {@link VideoSample} from raw pixel data specified in `data`. Additional metadata must be provided
348
+ * in `init`.
349
+ */
350
+ constructor(
351
+ data: AllowSharedBufferSource,
352
+ init: SetRequired<VideoSampleInit, 'format' | 'codedWidth' | 'codedHeight' | 'timestamp'>
353
+ );
354
+ /**
355
+ * Creates a new {@link VideoSample} backed by a custom {@link VideoSampleResource}.
356
+ */
357
+ constructor(resource: VideoSampleResource, init: SetRequired<VideoSampleInit, 'timestamp'>);
358
+ constructor(
359
+ data: VideoFrame | CanvasImageSource | AllowSharedBufferSource | VideoSampleResource,
360
+ init?: VideoSampleInit,
361
+ ) {
362
+ if (
363
+ data instanceof ArrayBuffer
364
+ || (typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer)
365
+ || ArrayBuffer.isView(data)
366
+ ) {
367
+ if (!init || typeof init !== 'object') {
368
+ throw new TypeError('init must be an object.');
369
+ }
370
+ if (init.format === undefined || !VIDEO_SAMPLE_PIXEL_FORMATS_SET.has(init.format)) {
371
+ throw new TypeError('init.format must be one of: ' + VIDEO_SAMPLE_PIXEL_FORMATS.join(', '));
372
+ }
373
+ if (!Number.isInteger(init.codedWidth) || init.codedWidth! <= 0) {
374
+ throw new TypeError('init.codedWidth must be a positive integer.');
375
+ }
376
+ if (!Number.isInteger(init.codedHeight) || init.codedHeight! <= 0) {
377
+ throw new TypeError('init.codedHeight must be a positive integer.');
378
+ }
379
+ if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
380
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
381
+ }
382
+ if (!Number.isFinite(init.timestamp)) {
383
+ throw new TypeError('init.timestamp must be a number.');
384
+ }
385
+ if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
386
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
387
+ }
388
+ if (init.layout !== undefined) {
389
+ if (!Array.isArray(init.layout)) {
390
+ throw new TypeError('init.layout, when provided, must be an array.');
391
+ }
392
+
393
+ for (const plane of init.layout) {
394
+ if (!plane || typeof plane !== 'object' || Array.isArray(plane)) {
395
+ throw new TypeError('Each entry in init.layout must be an object.');
396
+ }
397
+ if (!Number.isInteger(plane.offset) || plane.offset < 0) {
398
+ throw new TypeError('plane.offset must be a non-negative integer.');
399
+ }
400
+ if (!Number.isInteger(plane.stride) || plane.stride < 0) {
401
+ throw new TypeError('plane.stride must be a non-negative integer.');
402
+ }
403
+ }
404
+ }
405
+ if (init.visibleRect !== undefined) {
406
+ validateRectangle(init.visibleRect, 'init.visibleRect');
407
+ }
408
+ if (
409
+ init.displayWidth !== undefined
410
+ && (!Number.isInteger(init.displayWidth) || init.displayWidth <= 0)
411
+ ) {
412
+ throw new TypeError('init.displayWidth, when provided, must be a positive integer.');
413
+ }
414
+ if (
415
+ init.displayHeight !== undefined
416
+ && (!Number.isInteger(init.displayHeight) || init.displayHeight <= 0)
417
+ ) {
418
+ throw new TypeError('init.displayHeight, when provided, must be a positive integer.');
419
+ }
420
+ if ((init.displayWidth !== undefined) !== (init.displayHeight !== undefined)) {
421
+ throw new TypeError(
422
+ 'init.displayWidth and init.displayHeight must be either both provided or both omitted.',
423
+ );
424
+ }
425
+
426
+ this.format = init.format;
427
+ this.rotation = init.rotation ?? 0;
428
+ this.timestamp = init.timestamp!;
429
+ this.duration = init.duration ?? 0;
430
+
431
+ const layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth!, init.codedHeight!);
432
+
433
+ let colorSpaceInit = init.colorSpace ?? null;
434
+ if (colorSpaceInit === null) {
435
+ if (
436
+ this.format === 'RGBA' || this.format === 'RGBX'
437
+ || this.format === 'BGRA' || this.format === 'BGRX'
438
+ ) {
439
+ // sRGB Color Space
440
+ colorSpaceInit = {
441
+ primaries: 'bt709',
442
+ transfer: 'iec61966-2-1',
443
+ matrix: 'rgb',
444
+ fullRange: true,
445
+ };
446
+ } else {
447
+ // REC709 Color Space
448
+ colorSpaceInit = {
449
+ primaries: 'bt709',
450
+ transfer: 'bt709',
451
+ matrix: 'bt709',
452
+ fullRange: false,
453
+ };
454
+ }
455
+ }
456
+
457
+ this.visibleRect = {
458
+ left: init.visibleRect?.left ?? 0,
459
+ top: init.visibleRect?.top ?? 0,
460
+ width: init.visibleRect?.width ?? init.codedWidth!,
461
+ height: init.visibleRect?.height ?? init.codedHeight!,
462
+ };
463
+
464
+ if (init.displayWidth !== undefined) {
465
+ this.squarePixelWidth = this.rotation % 180 === 0 ? init.displayWidth : init.displayHeight!;
466
+ this.squarePixelHeight = this.rotation % 180 === 0 ? init.displayHeight! : init.displayWidth;
467
+ } else {
468
+ this.squarePixelWidth = this.visibleRect.width;
469
+ this.squarePixelHeight = this.visibleRect.height;
470
+ }
471
+
472
+ // As an optimization, one could check if VideoFrame is defined and if it is, create a VideoFrame here from
473
+ // the data. Since VideoFrames are typically needed anyway, doing it this way would avoid an additional
474
+ // copy of the frame data. But due to https://issues.chromium.org/issues/529413114, this is currently
475
+ // not done.
476
+
477
+ this._data = init._doNotCopy
478
+ ? toUint8Array(data)
479
+ : toUint8Array(data).slice(); // Copy it
480
+ this._layout = layout;
481
+ this.colorSpace = new VideoSampleColorSpace(colorSpaceInit);
482
+ } else if (typeof VideoFrame !== 'undefined' && data instanceof VideoFrame) {
483
+ if (init?.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
484
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
485
+ }
486
+ if (init?.timestamp !== undefined && !Number.isFinite(init?.timestamp)) {
487
+ throw new TypeError('init.timestamp, when provided, must be a number.');
488
+ }
489
+ if (init?.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
490
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
491
+ }
492
+ if (init?.visibleRect !== undefined) {
493
+ validateRectangle(init.visibleRect, 'init.visibleRect');
494
+ }
495
+
496
+ this._data = data;
497
+ this._layout = null;
498
+
499
+ this.format = data.format;
500
+ this.visibleRect = {
501
+ left: data.visibleRect?.x ?? 0,
502
+ top: data.visibleRect?.y ?? 0,
503
+ width: data.visibleRect?.width ?? data.codedWidth,
504
+ height: data.visibleRect?.height ?? data.codedHeight,
505
+ };
506
+ // The VideoFrame's rotation is ignored here. It's still a new field, and I'm not sure of any application
507
+ // where the browser makes use of it. If a case gets found, I'll add it.
508
+ this.rotation = init?.rotation ?? 0;
509
+
510
+ // Assuming no innate VideoFrame rotation here
511
+ this.squarePixelWidth = data.displayWidth;
512
+ this.squarePixelHeight = data.displayHeight;
513
+
514
+ this.timestamp = init?.timestamp ?? data.timestamp / 1e6;
515
+ this.duration = init?.duration ?? (data.duration ?? 0) / 1e6;
516
+ this.colorSpace = new VideoSampleColorSpace(data.colorSpace);
517
+ } else if (
518
+ (typeof HTMLImageElement !== 'undefined' && data instanceof HTMLImageElement)
519
+ || (typeof SVGImageElement !== 'undefined' && data instanceof SVGImageElement)
520
+ || (typeof ImageBitmap !== 'undefined' && data instanceof ImageBitmap)
521
+ || (typeof HTMLVideoElement !== 'undefined' && data instanceof HTMLVideoElement)
522
+ || (typeof HTMLCanvasElement !== 'undefined' && data instanceof HTMLCanvasElement)
523
+ || (typeof OffscreenCanvas !== 'undefined' && data instanceof OffscreenCanvas)
524
+ ) {
525
+ if (!init || typeof init !== 'object') {
526
+ throw new TypeError('init must be an object.');
527
+ }
528
+ if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
529
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
530
+ }
531
+ if (!Number.isFinite(init.timestamp)) {
532
+ throw new TypeError('init.timestamp must be a number.');
533
+ }
534
+ if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
535
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
536
+ }
537
+
538
+ if (typeof VideoFrame !== 'undefined') {
539
+ return new VideoSample(
540
+ new VideoFrame(data, {
541
+ timestamp: Math.trunc(init.timestamp! * SECOND_TO_MICROSECOND_FACTOR),
542
+ // Drag 0 to undefined
543
+ duration: Math.trunc((init.duration ?? 0) * SECOND_TO_MICROSECOND_FACTOR) || undefined,
544
+ }),
545
+ init,
546
+ );
547
+ }
548
+
549
+ let width = 0;
550
+ let height = 0;
551
+
552
+ // Determine the dimensions of the thing
553
+ if ('naturalWidth' in data) {
554
+ width = data.naturalWidth;
555
+ height = data.naturalHeight;
556
+ } else if ('videoWidth' in data) {
557
+ width = data.videoWidth;
558
+ height = data.videoHeight;
559
+ } else if ('width' in data) {
560
+ width = Number(data.width);
561
+ height = Number(data.height);
562
+ }
563
+
564
+ if (!width || !height) {
565
+ throw new TypeError('Could not determine dimensions.');
566
+ }
567
+
568
+ const canvas = new OffscreenCanvas(width, height);
569
+ const context = canvas.getContext('2d', {
570
+ alpha: isFirefox(), // Firefox has VideoFrame glitches with opaque canvases
571
+ willReadFrequently: true,
572
+ });
573
+ if (!context) {
574
+ throw new Error(
575
+ 'OffscreenCanvas must have support for the \'2d\' context in order to create a VideoSample from'
576
+ + ' this data.',
577
+ );
578
+ }
579
+
580
+ // Draw it to a canvas
581
+ context.drawImage(data, 0, 0);
582
+ this._data = canvas;
583
+ this._layout = null;
584
+
585
+ this.format = 'RGBX';
586
+ this.visibleRect = { left: 0, top: 0, width, height };
587
+ this.squarePixelWidth = width;
588
+ this.squarePixelHeight = height;
589
+ this.rotation = init.rotation ?? 0;
590
+ this.timestamp = init.timestamp!;
591
+ this.duration = init.duration ?? 0;
592
+ this.colorSpace = new VideoSampleColorSpace({
593
+ matrix: 'rgb',
594
+ primaries: 'bt709',
595
+ transfer: 'iec61966-2-1',
596
+ fullRange: true,
597
+ });
598
+ } else if (data instanceof VideoSampleResource) {
599
+ if (!init || typeof init !== 'object') {
600
+ throw new TypeError('init must be an object.');
601
+ }
602
+ if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
603
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
604
+ }
605
+ if (!Number.isFinite(init.timestamp)) {
606
+ throw new TypeError('init.timestamp must be a number.');
607
+ }
608
+ if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
609
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
610
+ }
611
+
612
+ this._data = data;
613
+ data._referenceCount++;
614
+
615
+ this.format = data.getFormat();
616
+ if (this.format !== null && !VIDEO_SAMPLE_PIXEL_FORMATS.includes(this.format)) {
617
+ throw new TypeError('getFormat() must return a VideoSamplePixelFormat or null.');
618
+ }
619
+
620
+ this.visibleRect = {
621
+ left: 0,
622
+ top: 0,
623
+ width: data.getCodedWidth(),
624
+ height: data.getCodedHeight(),
625
+ };
626
+ if (!Number.isInteger(this.visibleRect.width) || this.visibleRect.width <= 0) {
627
+ throw new TypeError('getCodedWidth() must return a positive integer.');
628
+ }
629
+ if (!Number.isInteger(this.visibleRect.height) || this.visibleRect.height <= 0) {
630
+ throw new TypeError('getCodedHeight() must return a positive integer.');
631
+ }
632
+
633
+ this.squarePixelWidth = data.getSquarePixelWidth();
634
+ if (!Number.isInteger(this.squarePixelWidth) || this.squarePixelWidth <= 0) {
635
+ throw new TypeError('getSquarePixelWidth() must return a positive integer.');
636
+ }
637
+
638
+ this.squarePixelHeight = data.getSquarePixelHeight();
639
+ if (!Number.isInteger(this.squarePixelHeight) || this.squarePixelHeight <= 0) {
640
+ throw new TypeError('getSquarePixelHeight() must return a positive integer.');
641
+ }
642
+
643
+ this.rotation = init.rotation ?? 0;
644
+ this.timestamp = init.timestamp!;
645
+ this.duration = init.duration ?? 0;
646
+ this.colorSpace = data.getColorSpace();
647
+ } else {
648
+ throw new TypeError(
649
+ 'Invalid data type: Must be a BufferSource, CanvasImageSource, or VideoSampleResource.',
650
+ );
651
+ }
652
+
653
+ this.encodeOptions = init?.encodeOptions ?? {};
654
+
655
+ this.pixelAspectRatio = simplifyRational({
656
+ num: this.squarePixelWidth * this.codedHeight,
657
+ den: this.squarePixelHeight * this.codedWidth,
658
+ });
659
+ finalizationRegistry?.register(this, { type: 'video', data: this._data }, this);
660
+ }
661
+
662
+ /** Clones this video sample. */
663
+ clone() {
664
+ if (this._closed) {
665
+ throw new Error('VideoSample is closed.');
666
+ }
667
+
668
+ assert(this._data !== null);
669
+
670
+ if (this._data instanceof VideoSampleResource) {
671
+ return new VideoSample(this._data, {
672
+ timestamp: this.timestamp,
673
+ duration: this.duration,
674
+ rotation: this.rotation,
675
+ encodeOptions: this.encodeOptions,
676
+ });
677
+ } else if (isVideoFrame(this._data)) {
678
+ return new VideoSample(this._data.clone(), {
679
+ timestamp: this.timestamp,
680
+ duration: this.duration,
681
+ rotation: this.rotation,
682
+ encodeOptions: this.encodeOptions,
683
+ });
684
+ } else if (this._data instanceof Uint8Array) {
685
+ assert(this._layout);
686
+
687
+ return new VideoSample(this._data, {
688
+ format: this.format!,
689
+ layout: this._layout,
690
+ codedWidth: this.codedWidth,
691
+ codedHeight: this.codedHeight,
692
+ timestamp: this.timestamp,
693
+ duration: this.duration,
694
+ colorSpace: this.colorSpace,
695
+ rotation: this.rotation,
696
+ visibleRect: this.visibleRect,
697
+ displayWidth: this.displayWidth,
698
+ displayHeight: this.displayHeight,
699
+ encodeOptions: this.encodeOptions,
700
+
701
+ // It's already been copied, if we copy it again we make the clone unnecessarily expensive
702
+ _doNotCopy: true,
703
+ });
704
+ } else {
705
+ return new VideoSample(this._data, {
706
+ format: this.format!,
707
+ codedWidth: this.codedWidth,
708
+ codedHeight: this.codedHeight,
709
+ timestamp: this.timestamp,
710
+ duration: this.duration,
711
+ colorSpace: this.colorSpace,
712
+ rotation: this.rotation,
713
+ visibleRect: this.visibleRect,
714
+ displayWidth: this.displayWidth,
715
+ displayHeight: this.displayHeight,
716
+ encodeOptions: this.encodeOptions,
717
+ });
718
+ }
719
+ }
720
+
721
+ /**
722
+ * Closes this video sample, releasing held resources. Video samples should be closed as soon as they are not
723
+ * needed anymore.
724
+ */
725
+ close() {
726
+ if (this._closed) {
727
+ return;
728
+ }
729
+
730
+ finalizationRegistry?.unregister(this);
731
+
732
+ if (this._data instanceof VideoSampleResource) {
733
+ this._data._referenceCount--;
734
+ if (this._data._referenceCount === 0) {
735
+ this._data.close();
736
+ }
737
+ } else if (isVideoFrame(this._data)) {
738
+ this._data.close();
739
+ } else {
740
+ this._data = null; // GC that shit
741
+ }
742
+
743
+ this._closed = true;
744
+ }
745
+
746
+ /**
747
+ * Returns the number of bytes required to hold this video sample's pixel data.
748
+ */
749
+ allocationSize(options: VideoFrameCopyToOptions = {}): number {
750
+ validateVideoFrameCopyToOptions(options);
751
+
752
+ if (this._closed) {
753
+ throw new Error('VideoSample is closed.');
754
+ }
755
+
756
+ if ((options.format ?? this.format) == null) {
757
+ // https://github.com/Vanilagy/mediabunny/issues/267
758
+ // https://github.com/w3c/webcodecs/issues/920
759
+ throw new Error('Cannot get allocation size when format is null.');
760
+ }
761
+
762
+ if (isVideoFrame(this._data)) {
763
+ // Call the native method purely for performance
764
+ return this._data.allocationSize(options);
765
+ }
766
+
767
+ const combinedLayout = ParseVideoFrameCopyToOptions(this, options);
768
+ return combinedLayout.allocationSize;
769
+ }
770
+
771
+ /**
772
+ * Copies this video sample's pixel data to an ArrayBuffer or ArrayBufferView.
773
+ * @returns The byte layout of the planes of the copied data.
774
+ */
775
+ async copyTo(destination: AllowSharedBufferSource, options: VideoFrameCopyToOptions = {}): Promise<PlaneLayout[]> {
776
+ if (!isAllowSharedBufferSource(destination)) {
777
+ throw new TypeError('destination must be an ArrayBuffer or an ArrayBuffer view.');
778
+ }
779
+ validateVideoFrameCopyToOptions(options);
780
+
781
+ if (this._closed) {
782
+ throw new Error('VideoSample is closed.');
783
+ }
784
+ if ((options.format ?? this.format) == null) {
785
+ throw new Error('Cannot copy video sample data when format is null.');
786
+ }
787
+
788
+ assert(this._data !== null);
789
+
790
+ if (isVideoFrame(this._data)) {
791
+ return this._data.copyTo(destination, options);
792
+ }
793
+
794
+ // Detect non-RGB to RGB conversion
795
+ if (
796
+ options.format
797
+ && !['RGBA', 'RGBX', 'BGRA', 'BGRX'].includes(this.format!)
798
+ && ['RGBA', 'RGBX', 'BGRA', 'BGRX'].includes(options.format)
799
+ ) {
800
+ // RGB conversion for custom VideoSampleResource
801
+ if (this._data instanceof VideoSampleResource) {
802
+ using rgbSample = await this._data.toRgbSample(
803
+ {
804
+ timestamp: this.timestamp,
805
+ duration: this.duration,
806
+ rotation: this.rotation,
807
+ },
808
+ options.colorSpace ?? 'srgb',
809
+ );
810
+ if (!(rgbSample instanceof VideoSample)) {
811
+ throw new TypeError('toRgbSample() must return a VideoSample.');
812
+ }
813
+ if (!['RGBA', 'RGBX', 'BGRA', 'BGRX'].includes(rgbSample.format!)) {
814
+ throw new Error(
815
+ `Sample returned by toRgbSample was expected to have an RGB format, got`
816
+ + ` '${rgbSample.format}' instead.`,
817
+ );
818
+ }
819
+ // Note that we DON'T force the RGB format to be exactly what was requested; any RGB format will do
820
+
821
+ return await rgbSample.copyTo(destination, options); // 'await' is intentional here cuz of using
822
+ } else {
823
+ if (typeof VideoFrame === 'undefined') {
824
+ throw new Error(
825
+ 'For this sample, converting from a non-RGB to an RGB format requires VideoFrame to'
826
+ + ' be defined.',
827
+ );
828
+ }
829
+
830
+ const tempFrame = this.toVideoFrame();
831
+ const result = await tempFrame.copyTo(destination, options);
832
+ tempFrame.close();
833
+
834
+ return result;
835
+ }
836
+ }
837
+
838
+ const combinedLayout = ParseVideoFrameCopyToOptions(this, options);
839
+ assert(this.format);
840
+
841
+ // 4. If destination.byteLength is less than combinedLayout’s allocationSize, return a promise rejected with
842
+ const destBytes = toUint8Array(destination);
843
+ if (destBytes.byteLength < combinedLayout.allocationSize) {
844
+ throw new TypeError(
845
+ `Destination buffer too small. Required: ${combinedLayout.allocationSize},`
846
+ + ` Available: ${destBytes.byteLength}`,
847
+ );
848
+ }
849
+
850
+ const planeConfigs = getPlaneConfigs(this.format);
851
+ let dataPlanes: VideoDataPlane[];
852
+
853
+ if (this._data instanceof VideoSampleResource) {
854
+ let result = this._data.getDataPlanes();
855
+ if (result instanceof Promise) result = await result;
856
+
857
+ if (
858
+ !Array.isArray(result)
859
+ || result.some(x => !(x.data instanceof Uint8Array) || !Number.isInteger(x.stride) || x.stride < 0)
860
+ ) {
861
+ throw new TypeError(
862
+ 'getDataPlanes() must return an array of objects with a Uint8Array "data" property and a'
863
+ + ' non-negative integer "stride" property.',
864
+ );
865
+ }
866
+
867
+ dataPlanes = result;
868
+ } else if (this._data instanceof Uint8Array) {
869
+ assert(this._layout);
870
+ assert(this._layout.length === planeConfigs.length);
871
+
872
+ dataPlanes = this._layout.map((planeLayout, i) => {
873
+ const height = Math.ceil(this.codedHeight / planeConfigs[i]!.heightDivisor);
874
+
875
+ return {
876
+ data: (this._data as Uint8Array).subarray(
877
+ planeLayout.offset,
878
+ planeLayout.offset + planeLayout.stride * height,
879
+ ),
880
+ stride: planeLayout.stride,
881
+ };
882
+ });
883
+ } else {
884
+ const canvas = this._data;
885
+ const context = canvas.getContext('2d');
886
+ assert(context); // We already got it earlier so it's definitely available
887
+
888
+ const imageData = context.getImageData(0, 0, this.codedWidth, this.codedHeight);
889
+
890
+ dataPlanes = [{
891
+ data: toUint8Array(imageData.data),
892
+ stride: 4 * this.codedWidth,
893
+ }];
894
+ }
895
+
896
+ // Algo taken from WebCodecs spec:
897
+
898
+ // 6. Let p be a new Promise. (Implicit)
899
+ // 7. Let copyStepsQueue be the result of starting a new parallel queue. (Implicit)
900
+ // 8. Let planeLayouts be a new list.
901
+ const planeLayouts: PlaneLayout[] = [];
902
+
903
+ // Enqueue the following steps to copyStepsQueue: (fuck the queuing part)
904
+
905
+ // Let resource be the media resource referenced by [[resource reference]].
906
+ // (this.data)
907
+
908
+ // Let numPlanes be the number of planes as defined by [[format]].
909
+ const numPlanes = planeConfigs.length;
910
+
911
+ // Let planeIndex be 0.
912
+ // While planeIndex is less than combinedLayout’s numPlanes:
913
+ for (let planeIndex = 0; planeIndex < numPlanes; planeIndex++) {
914
+ const computedLayout = combinedLayout.computedLayouts[planeIndex]!;
915
+
916
+ // Let sourceStride be the stride of the plane in resource as identified by planeIndex.
917
+ const sourceStride = dataPlanes[planeIndex]!.stride;
918
+ const sourceData = dataPlanes[planeIndex]!.data;
919
+
920
+ // Let sourceOffset be the product of multiplying computedLayout’s sourceTop by sourceStride
921
+ let sourceOffset = computedLayout.sourceTop * sourceStride;
922
+
923
+ // Add computedLayout’s sourceLeftBytes to sourceOffset.
924
+ sourceOffset += computedLayout.sourceLeftBytes;
925
+
926
+ // Let destinationOffset be computedLayout’s destinationOffset.
927
+ let destinationOffset = computedLayout.destinationOffset;
928
+
929
+ // Let rowBytes be computedLayout’s sourceWidthBytes.
930
+ const rowBytes = computedLayout.sourceWidthBytes;
931
+
932
+ // Let layout be a new PlaneLayout, with offset set to destinationOffset and stride set to rowBytes.
933
+ // This is a spec error actually (https://github.com/w3c/webcodecs/issues/918)
934
+ const layout: PlaneLayout = {
935
+ offset: destinationOffset,
936
+ stride: computedLayout.destinationStride,
937
+ };
938
+
939
+ // Let row be 0.
940
+ // While row is less than computedLayout’s sourceHeight:
941
+ for (let row = 0; row < computedLayout.sourceHeight; row++) {
942
+ // Copy rowBytes bytes from resource starting at sourceOffset to destination starting
943
+ // at destinationOffset.
944
+ if (sourceOffset + rowBytes > sourceData.byteLength) {
945
+ throw new Error(`Source buffer OOB read.`);
946
+ }
947
+ if (destinationOffset + rowBytes > destBytes.byteLength) {
948
+ throw new Error(`Destination buffer OOB write.`);
949
+ }
950
+
951
+ const srcSub = sourceData.subarray(sourceOffset, sourceOffset + rowBytes);
952
+ destBytes.set(srcSub, destinationOffset);
953
+
954
+ // Increment sourceOffset by sourceStride.
955
+ sourceOffset += sourceStride;
956
+
957
+ // Increment destinationOffset by computedLayout’s destinationStride.
958
+ destinationOffset += computedLayout.destinationStride;
959
+ }
960
+
961
+ // Append layout to planeLayouts.
962
+ planeLayouts.push(layout);
963
+ }
964
+
965
+ // Now, handle converting between different RGB formats
966
+ if (options.format !== undefined) {
967
+ const needsRgbConversion = this.format.startsWith('RGB') !== options.format.startsWith('RGB');
968
+
969
+ // Going X->A requires setting the alpha to 255, going the other way doesn't since the value of X is w/e
970
+ const needsAlphaConversion = this.format.includes('X') && options.format.includes('A');
971
+
972
+ if (needsRgbConversion || needsAlphaConversion) {
973
+ // Loop over the destination bytes
974
+ for (let i = 0; i < combinedLayout.allocationSize; i += 4) {
975
+ if (needsRgbConversion) {
976
+ // Swap R with B
977
+ const r = destBytes[i]!;
978
+ const b = destBytes[i + 2]!;
979
+ destBytes[i] = b;
980
+ destBytes[i + 2] = r;
981
+ }
982
+
983
+ if (needsAlphaConversion) {
984
+ destBytes[i + 3] = 255;
985
+ }
986
+ }
987
+ }
988
+ }
989
+
990
+ // Queue a task to resolve p with planeLayouts.
991
+ return planeLayouts;
992
+ }
993
+
994
+ /**
995
+ * Converts this video sample to a VideoFrame for use with the WebCodecs API. The VideoFrame returned by this
996
+ * method *must* be closed separately from this video sample.
997
+ */
998
+ toVideoFrame(): VideoFrame {
999
+ if (this._closed) {
1000
+ throw new Error('VideoSample is closed.');
1001
+ }
1002
+
1003
+ assert(this._data !== null);
1004
+
1005
+ if (this._data instanceof VideoSampleResource) {
1006
+ if (this.format === null) {
1007
+ throw new Error(
1008
+ 'Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if format is null.',
1009
+ );
1010
+ }
1011
+
1012
+ const planes = this._data.getDataPlanes();
1013
+ if (planes instanceof Promise) {
1014
+ throw new Error(
1015
+ 'Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if getDataPlanes() returns'
1016
+ + ' a promise.',
1017
+ );
1018
+ }
1019
+
1020
+ // We can't use allocationSize since that method assumes a tight packing
1021
+ const size = planes.reduce((a, b) => a + b.data.byteLength, 0);
1022
+ const buffer = new Uint8Array(size);
1023
+
1024
+ let offset = 0;
1025
+ const offsets: number[] = [];
1026
+
1027
+ for (const plane of planes) {
1028
+ buffer.set(plane.data, offset);
1029
+ offsets.push(offset);
1030
+
1031
+ offset += plane.data.byteLength;
1032
+ }
1033
+
1034
+ return new VideoFrame(buffer, {
1035
+ format: this.format as VideoPixelFormat,
1036
+ layout: planes.map((x, i) => ({
1037
+ offset: offsets[i]!,
1038
+ stride: x.stride,
1039
+ })),
1040
+ codedWidth: this.codedWidth,
1041
+ codedHeight: this.codedHeight,
1042
+ timestamp: this.microsecondTimestamp,
1043
+ duration: this.microsecondDuration,
1044
+ colorSpace: this.colorSpace,
1045
+ visibleRect: this.visibleRect,
1046
+ displayWidth: this.squarePixelWidth, // Not display* since we're not passing rotation
1047
+ displayHeight: this.squarePixelHeight,
1048
+ });
1049
+ } else if (isVideoFrame(this._data)) {
1050
+ return new VideoFrame(this._data, {
1051
+ timestamp: this.microsecondTimestamp,
1052
+ duration: this.microsecondDuration || undefined, // Drag 0 duration to undefined, glitches some codecs
1053
+ });
1054
+ } else if (this._data instanceof Uint8Array) {
1055
+ assert(this._layout);
1056
+
1057
+ return new VideoFrame(this._data, {
1058
+ format: this.format! as VideoPixelFormat,
1059
+ codedWidth: this.codedWidth, // This is technically wrong! codedWidth is a lie technically. But, since
1060
+ codedHeight: this.codedHeight, // we pass the layout (which contains the true coded width), we're good.
1061
+ layout: this._layout,
1062
+ timestamp: this.microsecondTimestamp,
1063
+ duration: this.microsecondDuration || undefined,
1064
+ colorSpace: this.colorSpace,
1065
+ visibleRect: this.visibleRect,
1066
+ displayWidth: this.squarePixelWidth, // Not display* since we're not passing rotation
1067
+ displayHeight: this.squarePixelHeight,
1068
+ });
1069
+ } else {
1070
+ return new VideoFrame(this._data, {
1071
+ timestamp: this.microsecondTimestamp,
1072
+ duration: this.microsecondDuration || undefined,
1073
+ });
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Draws the video sample to a 2D canvas context. Rotation metadata will be taken into account.
1079
+ *
1080
+ * @param dx - The x-coordinate in the destination canvas at which to place the top-left corner of the source image.
1081
+ * @param dy - The y-coordinate in the destination canvas at which to place the top-left corner of the source image.
1082
+ * @param dWidth - The width in pixels with which to draw the image in the destination canvas.
1083
+ * @param dHeight - The height in pixels with which to draw the image in the destination canvas.
1084
+ */
1085
+ draw(
1086
+ context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
1087
+ dx: number,
1088
+ dy: number,
1089
+ dWidth?: number,
1090
+ dHeight?: number,
1091
+ ): void;
1092
+ /**
1093
+ * Draws the video sample to a 2D canvas context. Rotation metadata will be taken into account.
1094
+ *
1095
+ * @param sx - The x-coordinate of the top left corner of the sub-rectangle of the source image to draw into the
1096
+ * destination context.
1097
+ * @param sy - The y-coordinate of the top left corner of the sub-rectangle of the source image to draw into the
1098
+ * destination context.
1099
+ * @param sWidth - The width of the sub-rectangle of the source image to draw into the destination context.
1100
+ * @param sHeight - The height of the sub-rectangle of the source image to draw into the destination context.
1101
+ * @param dx - The x-coordinate in the destination canvas at which to place the top-left corner of the source image.
1102
+ * @param dy - The y-coordinate in the destination canvas at which to place the top-left corner of the source image.
1103
+ * @param dWidth - The width in pixels with which to draw the image in the destination canvas.
1104
+ * @param dHeight - The height in pixels with which to draw the image in the destination canvas.
1105
+ */
1106
+ draw(
1107
+ context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
1108
+ sx: number,
1109
+ sy: number,
1110
+ sWidth: number,
1111
+ sHeight: number,
1112
+ dx: number,
1113
+ dy: number,
1114
+ dWidth?: number,
1115
+ dHeight?: number,
1116
+ ): void;
1117
+ draw(
1118
+ context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
1119
+ arg1: number,
1120
+ arg2: number,
1121
+ arg3?: number,
1122
+ arg4?: number,
1123
+ arg5?: number,
1124
+ arg6?: number,
1125
+ arg7?: number,
1126
+ arg8?: number,
1127
+ ) {
1128
+ let sx = 0;
1129
+ let sy = 0;
1130
+ let sWidth = this.displayWidth;
1131
+ let sHeight = this.displayHeight;
1132
+ let dx = 0;
1133
+ let dy = 0;
1134
+ let dWidth = this.displayWidth;
1135
+ let dHeight = this.displayHeight;
1136
+
1137
+ if (arg5 !== undefined) {
1138
+ sx = arg1!;
1139
+ sy = arg2!;
1140
+ sWidth = arg3!;
1141
+ sHeight = arg4!;
1142
+ dx = arg5;
1143
+ dy = arg6!;
1144
+
1145
+ if (arg7 !== undefined) {
1146
+ dWidth = arg7;
1147
+ dHeight = arg8!;
1148
+ } else {
1149
+ dWidth = sWidth;
1150
+ dHeight = sHeight;
1151
+ }
1152
+ } else {
1153
+ dx = arg1;
1154
+ dy = arg2;
1155
+
1156
+ if (arg3 !== undefined) {
1157
+ dWidth = arg3;
1158
+ dHeight = arg4!;
1159
+ }
1160
+ }
1161
+
1162
+ if (!(
1163
+ (typeof CanvasRenderingContext2D !== 'undefined' && context instanceof CanvasRenderingContext2D)
1164
+ || (
1165
+ typeof OffscreenCanvasRenderingContext2D !== 'undefined'
1166
+ && context instanceof OffscreenCanvasRenderingContext2D
1167
+ )
1168
+ )) {
1169
+ throw new TypeError('context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.');
1170
+ }
1171
+ if (!Number.isFinite(sx)) {
1172
+ throw new TypeError('sx must be a number.');
1173
+ }
1174
+ if (!Number.isFinite(sy)) {
1175
+ throw new TypeError('sy must be a number.');
1176
+ }
1177
+ if (!Number.isFinite(sWidth) || sWidth < 0) {
1178
+ throw new TypeError('sWidth must be a non-negative number.');
1179
+ }
1180
+ if (!Number.isFinite(sHeight) || sHeight < 0) {
1181
+ throw new TypeError('sHeight must be a non-negative number.');
1182
+ }
1183
+ if (!Number.isFinite(dx)) {
1184
+ throw new TypeError('dx must be a number.');
1185
+ }
1186
+ if (!Number.isFinite(dy)) {
1187
+ throw new TypeError('dy must be a number.');
1188
+ }
1189
+ if (!Number.isFinite(dWidth) || dWidth < 0) {
1190
+ throw new TypeError('dWidth must be a non-negative number.');
1191
+ }
1192
+ if (!Number.isFinite(dHeight) || dHeight < 0) {
1193
+ throw new TypeError('dHeight must be a non-negative number.');
1194
+ }
1195
+
1196
+ if (this._closed) {
1197
+ throw new Error('VideoSample is closed.');
1198
+ }
1199
+
1200
+ ({ sx, sy, sWidth, sHeight } = this._rotateSourceRegion(sx, sy, sWidth, sHeight, this.rotation));
1201
+
1202
+ const source = this.toCanvasImageSource();
1203
+
1204
+ context.save();
1205
+
1206
+ const centerX = dx + dWidth / 2;
1207
+ const centerY = dy + dHeight / 2;
1208
+
1209
+ context.translate(centerX, centerY);
1210
+ context.rotate(this.rotation * Math.PI / 180);
1211
+
1212
+ const aspectRatioChange = this.rotation % 180 === 0 ? 1 : dWidth / dHeight;
1213
+
1214
+ // Scale to compensate for aspect ratio changes when rotated
1215
+ context.scale(1 / aspectRatioChange, aspectRatioChange);
1216
+
1217
+ context.drawImage(
1218
+ source,
1219
+ sx,
1220
+ sy,
1221
+ sWidth,
1222
+ sHeight,
1223
+ -dWidth / 2,
1224
+ -dHeight / 2,
1225
+ dWidth,
1226
+ dHeight,
1227
+ );
1228
+
1229
+ context.restore();
1230
+ }
1231
+
1232
+ /**
1233
+ * Draws the sample in the middle of the canvas corresponding to the context with the specified fit behavior.
1234
+ */
1235
+ drawWithFit(context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, options: {
1236
+ /**
1237
+ * Controls the fitting algorithm.
1238
+ *
1239
+ * - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio.
1240
+ * - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to
1241
+ * letterboxing.
1242
+ * - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio.
1243
+ */
1244
+ fit: 'fill' | 'contain' | 'cover';
1245
+ /** A way to override rotation. Defaults to the rotation of the sample. */
1246
+ rotation?: Rotation;
1247
+ /**
1248
+ * Specifies the rectangular region of the video sample to crop to. The crop region will automatically be
1249
+ * clamped to the dimensions of the video sample. Cropping is performed after rotation but before resizing.
1250
+ * The crop region is in the _display pixel space_ of the underlying video data.
1251
+ */
1252
+ crop?: CropRectangle;
1253
+ }) {
1254
+ if (!(
1255
+ (typeof CanvasRenderingContext2D !== 'undefined' && context instanceof CanvasRenderingContext2D)
1256
+ || (
1257
+ typeof OffscreenCanvasRenderingContext2D !== 'undefined'
1258
+ && context instanceof OffscreenCanvasRenderingContext2D
1259
+ )
1260
+ )) {
1261
+ throw new TypeError('context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.');
1262
+ }
1263
+ if (!options || typeof options !== 'object') {
1264
+ throw new TypeError('options must be an object.');
1265
+ }
1266
+ if (!['fill', 'contain', 'cover'].includes(options.fit)) {
1267
+ throw new TypeError('options.fit must be \'fill\', \'contain\', or \'cover\'.');
1268
+ }
1269
+ if (options.rotation !== undefined && ![0, 90, 180, 270].includes(options.rotation)) {
1270
+ throw new TypeError('options.rotation, when provided, must be 0, 90, 180, or 270.');
1271
+ }
1272
+ if (options.crop !== undefined) {
1273
+ validateCropRectangle(options.crop, 'options.');
1274
+ }
1275
+
1276
+ const canvasWidth = context.canvas.width;
1277
+ const canvasHeight = context.canvas.height;
1278
+ const rotation = options.rotation ?? this.rotation;
1279
+
1280
+ const [rotatedWidth, rotatedHeight] = rotation % 180 === 0
1281
+ ? [this.squarePixelWidth, this.squarePixelHeight]
1282
+ : [this.squarePixelHeight, this.squarePixelWidth];
1283
+
1284
+ let finalCrop = options.crop;
1285
+ if (finalCrop) {
1286
+ finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight);
1287
+ }
1288
+
1289
+ // These variables specify where the final sample will be drawn on the canvas
1290
+ let dx: number;
1291
+ let dy: number;
1292
+ let newWidth: number;
1293
+ let newHeight: number;
1294
+
1295
+ const { sx, sy, sWidth, sHeight } = this._rotateSourceRegion(
1296
+ options.crop?.left ?? 0,
1297
+ options.crop?.top ?? 0,
1298
+ options.crop?.width ?? rotatedWidth,
1299
+ options.crop?.height ?? rotatedHeight,
1300
+ rotation,
1301
+ );
1302
+
1303
+ if (options.fit === 'fill') {
1304
+ dx = 0;
1305
+ dy = 0;
1306
+ newWidth = canvasWidth;
1307
+ newHeight = canvasHeight;
1308
+ } else {
1309
+ const [sampleWidth, sampleHeight] = options.crop
1310
+ ? [options.crop.width, options.crop.height]
1311
+ : [rotatedWidth, rotatedHeight];
1312
+
1313
+ const scale = options.fit === 'contain'
1314
+ ? Math.min(canvasWidth / sampleWidth, canvasHeight / sampleHeight)
1315
+ : Math.max(canvasWidth / sampleWidth, canvasHeight / sampleHeight);
1316
+ newWidth = sampleWidth * scale;
1317
+ newHeight = sampleHeight * scale;
1318
+ dx = (canvasWidth - newWidth) / 2;
1319
+ dy = (canvasHeight - newHeight) / 2;
1320
+ }
1321
+
1322
+ context.save();
1323
+
1324
+ const aspectRatioChange = rotation % 180 === 0 ? 1 : newWidth / newHeight;
1325
+ context.translate(canvasWidth / 2, canvasHeight / 2);
1326
+ context.rotate(rotation * Math.PI / 180);
1327
+ // This aspect ratio compensation is done so that we can draw the sample with the intended dimensions and
1328
+ // don't need to think about how those dimensions change after the rotation
1329
+ context.scale(1 / aspectRatioChange, aspectRatioChange);
1330
+ context.translate(-canvasWidth / 2, -canvasHeight / 2);
1331
+
1332
+ // Important that we don't use .draw() here since that would take rotation into account, but we wanna handle it
1333
+ // ourselves here
1334
+ context.drawImage(this.toCanvasImageSource(), sx, sy, sWidth, sHeight, dx, dy, newWidth, newHeight);
1335
+
1336
+ context.restore();
1337
+ }
1338
+
1339
+ /** @internal */
1340
+ _rotateSourceRegion(sx: number, sy: number, sWidth: number, sHeight: number, rotation: number) {
1341
+ // The provided sx,sy,sWidth,sHeight refer to the final rotated image, but that's not actually how the image is
1342
+ // stored. Therefore, we must map these back onto the original, pre-rotation image.
1343
+ if (rotation === 90) {
1344
+ [sx, sy, sWidth, sHeight] = [
1345
+ sy,
1346
+ this.squarePixelHeight - sx - sWidth,
1347
+ sHeight,
1348
+ sWidth,
1349
+ ];
1350
+ } else if (rotation === 180) {
1351
+ [sx, sy] = [
1352
+ this.squarePixelWidth - sx - sWidth,
1353
+ this.squarePixelHeight - sy - sHeight,
1354
+ ];
1355
+ } else if (rotation === 270) {
1356
+ [sx, sy, sWidth, sHeight] = [
1357
+ this.squarePixelWidth - sy - sHeight,
1358
+ sx,
1359
+ sHeight,
1360
+ sWidth,
1361
+ ];
1362
+ }
1363
+
1364
+ return { sx, sy, sWidth, sHeight };
1365
+ }
1366
+
1367
+ /**
1368
+ * Converts this video sample to a
1369
+ * [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource) for drawing to a canvas.
1370
+ *
1371
+ * You must use the value returned by this method immediately, as any VideoFrame created internally may
1372
+ * automatically be closed in the next microtask.
1373
+ */
1374
+ toCanvasImageSource() {
1375
+ if (this._closed) {
1376
+ throw new Error('VideoSample is closed.');
1377
+ }
1378
+
1379
+ assert(this._data !== null);
1380
+
1381
+ if (this._data instanceof VideoSampleResource || this._data instanceof Uint8Array) {
1382
+ // Requires VideoFrame to be defined
1383
+ const videoFrame = this.toVideoFrame();
1384
+ queueMicrotask(() => videoFrame.close()); // Let's automatically close the frame in the next microtask
1385
+
1386
+ return videoFrame;
1387
+ } else {
1388
+ return this._data;
1389
+ }
1390
+ }
1391
+
1392
+ /**
1393
+ * Transform this video sample to a new video sample given the options. Can be used to resize, rotate, and crop
1394
+ * the sample.
1395
+ *
1396
+ * In non-browser environments, this method will not work by default. To make it work, register a custom
1397
+ * transformer function via {@link registerVideoSampleTransformer}.
1398
+ */
1399
+ async transform(options: VideoSampleTransformOptions) {
1400
+ if (!options || typeof options !== 'object') {
1401
+ throw new TypeError('options must be an object.');
1402
+ }
1403
+ if (options.width !== undefined && (!Number.isInteger(options.width) || options.width <= 0)) {
1404
+ throw new TypeError('options.width, when provided, must be a positive integer.');
1405
+ }
1406
+ if (options.height !== undefined && (!Number.isInteger(options.height) || options.height <= 0)) {
1407
+ throw new TypeError('options.height, when provided, must be a positive integer.');
1408
+ }
1409
+ if (
1410
+ options.roundDimensionsTo !== undefined
1411
+ && (!Number.isInteger(options.roundDimensionsTo) || options.roundDimensionsTo <= 0)
1412
+ ) {
1413
+ throw new TypeError('options.roundDimensionsTo, when provided, must be a positive integer.');
1414
+ }
1415
+ if (options.fit !== undefined && !['fill', 'contain', 'cover'].includes(options.fit)) {
1416
+ throw new TypeError('options.fit, when provided, must be one of "fill", "contain", or "cover".');
1417
+ }
1418
+ if (
1419
+ options.width !== undefined
1420
+ && options.height !== undefined
1421
+ && options.fit === undefined
1422
+ ) {
1423
+ throw new TypeError(
1424
+ 'When both options.width and options.height are provided, options.fit must also be provided.',
1425
+ );
1426
+ }
1427
+ if (options.rotate !== undefined && ![0, 90, 180, 270].includes(options.rotate)) {
1428
+ throw new TypeError('options.rotate, when provided, must be 0, 90, 180 or 270.');
1429
+ }
1430
+ if (options.crop !== undefined) {
1431
+ validateCropRectangle(options.crop, 'options.');
1432
+ }
1433
+ if (options.alpha !== undefined && !['keep', 'discard'].includes(options.alpha)) {
1434
+ throw new TypeError('options.alpha, when provided, must be \'keep\' or \'discard\'.');
1435
+ }
1436
+
1437
+ const rotation = normalizeRotation(this.rotation + (options.rotate ?? 0));
1438
+ const [rotatedWidth, rotatedHeight] = rotation % 180 === 0
1439
+ ? [this.squarePixelWidth, this.squarePixelHeight]
1440
+ : [this.squarePixelHeight, this.squarePixelWidth];
1441
+
1442
+ // Clamp crop rectangle to the rotated video dimensions
1443
+ let finalCrop = options.crop;
1444
+ if (finalCrop) {
1445
+ finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight);
1446
+ }
1447
+
1448
+ const cropWidth = finalCrop ? finalCrop.width : rotatedWidth;
1449
+ const cropHeight = finalCrop ? finalCrop.height : rotatedHeight;
1450
+ const originalAspectRatio = cropWidth / cropHeight;
1451
+
1452
+ let targetWidth: number;
1453
+ let targetHeight: number;
1454
+
1455
+ if (options.width !== undefined && options.height === undefined) {
1456
+ targetWidth = options.width;
1457
+ targetHeight = targetWidth / originalAspectRatio;
1458
+ } else if (options.width === undefined && options.height !== undefined) {
1459
+ targetHeight = options.height;
1460
+ targetWidth = targetHeight * originalAspectRatio;
1461
+ } else if (options.width !== undefined && options.height !== undefined) {
1462
+ targetWidth = options.width;
1463
+ targetHeight = options.height;
1464
+ } else {
1465
+ targetWidth = cropWidth;
1466
+ targetHeight = cropHeight;
1467
+ }
1468
+
1469
+ targetWidth = roundToMultiple(targetWidth, options.roundDimensionsTo ?? 1);
1470
+ targetHeight = roundToMultiple(targetHeight, options.roundDimensionsTo ?? 1);
1471
+
1472
+ const description: VideoSampleTransformationDescription = {
1473
+ width: targetWidth,
1474
+ height: targetHeight,
1475
+ fit: options.fit ?? 'fill',
1476
+ rotation,
1477
+ crop: finalCrop ?? {
1478
+ left: 0,
1479
+ top: 0,
1480
+ width: rotatedWidth,
1481
+ height: rotatedHeight,
1482
+ },
1483
+ alpha: options.alpha ?? 'keep',
1484
+ };
1485
+
1486
+ // Description's finalized; let's see if a registered transformer wants to handle it
1487
+ for (const transformer of registeredVideoSampleTransformers) {
1488
+ let result = transformer(this, description);
1489
+ if (result instanceof Promise) result = await result;
1490
+
1491
+ if (result !== null) {
1492
+ return result;
1493
+ }
1494
+ }
1495
+
1496
+ // We need to handle it ourselves, and we use canvases to do it
1497
+
1498
+ let canvas: HTMLCanvasElement | OffscreenCanvas | null = null;
1499
+ let canvasIsNew = false;
1500
+
1501
+ for (const entry of transformationCanvasCache) {
1502
+ if (entry.canvas.width === description.width && entry.canvas.height === description.height) {
1503
+ canvas = entry.canvas;
1504
+ entry.age = transformationCanvasCacheNextAge++;
1505
+
1506
+ break;
1507
+ }
1508
+ }
1509
+
1510
+ if (canvas === null) {
1511
+ if (typeof OffscreenCanvas !== 'undefined') {
1512
+ canvas = new OffscreenCanvas(description.width, description.height);
1513
+ } else {
1514
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
1515
+ throw new Error(
1516
+ 'Cannot transform VideoSamples in this environment. Either run in an environment with'
1517
+ + ' OffscreenCanvas or HTMLCanvasElement, or supply a custom VideoSample transformer using'
1518
+ + ' registerVideoSampleTransformer().',
1519
+ );
1520
+ }
1521
+
1522
+ canvas = document.createElement('canvas');
1523
+ canvas.width = description.width;
1524
+ canvas.height = description.height;
1525
+ }
1526
+
1527
+ canvasIsNew = true;
1528
+
1529
+ if (transformationCanvasCache.length >= TRANSFORMATION_CANVAS_CACHE_MAX_SIZE) {
1530
+ transformationCanvasCache.splice(arrayArgmin(transformationCanvasCache, x => x.age), 1);
1531
+ }
1532
+
1533
+ transformationCanvasCache.push({
1534
+ canvas,
1535
+ age: transformationCanvasCacheNextAge++,
1536
+ });
1537
+ }
1538
+
1539
+ const context = canvas.getContext('2d', {
1540
+ alpha: true,
1541
+ }) as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
1542
+ if (!context) {
1543
+ throw new Error(
1544
+ 'The \'2d\' canvas context is required to transform VideoSamples. Register a custom transformer using'
1545
+ + ' registerVideoSampleTransformer to work around this limitation.',
1546
+ );
1547
+ }
1548
+
1549
+ context.imageSmoothingQuality = 'high';
1550
+
1551
+ if (description.alpha === 'discard') {
1552
+ context.fillStyle = 'black';
1553
+ context.fillRect(0, 0, description.width, description.height);
1554
+ } else if (!canvasIsNew) {
1555
+ // Cached canvases carry stale pixels from a prior draw
1556
+ context.clearRect(0, 0, description.width, description.height);
1557
+ }
1558
+
1559
+ this.drawWithFit(context, {
1560
+ fit: description.fit,
1561
+ rotation: description.rotation,
1562
+ crop: description.crop,
1563
+ });
1564
+
1565
+ return new VideoSample(canvas, {
1566
+ timestamp: this.timestamp,
1567
+ duration: this.duration,
1568
+ rotation: 0, // Any previous rotation is now baked in
1569
+ });
1570
+ }
1571
+
1572
+ /** Sets the rotation metadata of this video sample. */
1573
+ setRotation(newRotation: Rotation) {
1574
+ if (![0, 90, 180, 270].includes(newRotation)) {
1575
+ throw new TypeError('newRotation must be 0, 90, 180, or 270.');
1576
+ }
1577
+
1578
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1579
+ (this.rotation as Rotation) = newRotation;
1580
+ }
1581
+
1582
+ /** Sets the presentation timestamp of this video sample, in seconds. */
1583
+ setTimestamp(newTimestamp: number) {
1584
+ if (!Number.isFinite(newTimestamp)) {
1585
+ throw new TypeError('newTimestamp must be a number.');
1586
+ }
1587
+
1588
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1589
+ (this.timestamp as number) = newTimestamp;
1590
+ }
1591
+
1592
+ /** Sets the duration of this video sample, in seconds. */
1593
+ setDuration(newDuration: number) {
1594
+ if (!Number.isFinite(newDuration) || newDuration < 0) {
1595
+ throw new TypeError('newDuration must be a non-negative number.');
1596
+ }
1597
+
1598
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1599
+ (this.duration as number) = newDuration;
1600
+ }
1601
+
1602
+ /** Sets the encode options used when this sample is passed to an encoder. */
1603
+ setEncodeOptions(newEncodeOptions: VideoEncoderEncodeOptions) {
1604
+ if (!newEncodeOptions || typeof newEncodeOptions !== 'object') {
1605
+ throw new TypeError('newEncodeOptions must be an object.');
1606
+ }
1607
+
1608
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1609
+ (this.encodeOptions as DeepReadonly<VideoEncoderEncodeOptions>) = newEncodeOptions;
1610
+ }
1611
+
1612
+ /** Calls `.close()`. */
1613
+ [Symbol.dispose]() {
1614
+ this.close();
1615
+ }
1616
+ }
1617
+
1618
+ /**
1619
+ * Options for transforming a {@link VideoSample}. The order of operations are:
1620
+ *
1621
+ * 1. Pixel aspect ratio normalization (always applied)
1622
+ * 2. Rotation
1623
+ * 3. Crop
1624
+ * 4. Resize using fit
1625
+ * @group Samples
1626
+ * @public
1627
+ */
1628
+ export type VideoSampleTransformOptions = {
1629
+ /**
1630
+ * The width in pixels to resize the frames to. If height is not set, it will be deduced
1631
+ * automatically based on aspect ratio.
1632
+ */
1633
+ width?: number;
1634
+ /**
1635
+ * The height in pixels to resize the frames to. If width is not set, it will be deduced
1636
+ * automatically based on aspect ratio.
1637
+ */
1638
+ height?: number;
1639
+ /**
1640
+ * A positive integer. When provided, both the width and height will be rounded to the nearest multiple of
1641
+ * this number.
1642
+ */
1643
+ roundDimensionsTo?: number;
1644
+ /**
1645
+ * The fitting algorithm in case both width and height are set.
1646
+ *
1647
+ * - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio.
1648
+ * - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to
1649
+ * letterboxing.
1650
+ * - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio.
1651
+ */
1652
+ fit?: 'fill' | 'contain' | 'cover';
1653
+ /**
1654
+ * The clockwise rotation by which to rotate the frames. Rotation is applied before resizing.
1655
+ */
1656
+ rotate?: Rotation;
1657
+ /**
1658
+ * Specifies the rectangular region of the frames to crop to. The crop region will automatically be
1659
+ * clamped to the dimensions of the frame. Cropping is performed after rotation but before resizing.
1660
+ */
1661
+ crop?: CropRectangle;
1662
+ /**
1663
+ * Whether to discard or keep the transparency information of the video sample. The default is `'keep'`.
1664
+ */
1665
+ alpha?: 'keep' | 'discard';
1666
+ };
1667
+
1668
+ /**
1669
+ * A fully-resolved description of a video sample transformation, with all defaults and constraints baked in.
1670
+ *
1671
+ * The order of operations must be:
1672
+ * 1. Pixel aspect ratio normalization (always applied)
1673
+ * 2. Rotation
1674
+ * 3. Crop
1675
+ * 4. Resize using fit
1676
+ * @group Samples
1677
+ * @public
1678
+ */
1679
+ export type VideoSampleTransformationDescription = {
1680
+ /** The width in pixels to resize the frames to. */
1681
+ width: number;
1682
+ /** The height in pixels to resize the frames to. */
1683
+ height: number;
1684
+ /**
1685
+ * The fitting algorithm.
1686
+ *
1687
+ * - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio.
1688
+ * - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to
1689
+ * letterboxing.
1690
+ * - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio.
1691
+ */
1692
+ fit: 'fill' | 'contain' | 'cover';
1693
+ /** The clockwise rotation by which to rotate the frames. Rotation is applied before resizing. */
1694
+ rotation: Rotation;
1695
+ /**
1696
+ * The rectangular region of the frames to crop to, clamped to the dimensions of the frame. Cropping is
1697
+ * performed after rotation but before resizing.
1698
+ */
1699
+ crop: CropRectangle;
1700
+ /** Whether to discard or keep the transparency information of the video sample. */
1701
+ alpha: 'keep' | 'discard';
1702
+ };
1703
+
1704
+ const registeredVideoSampleTransformers: ((
1705
+ sample: VideoSample,
1706
+ description: VideoSampleTransformationDescription,
1707
+ ) => MaybePromise<VideoSample | null>)[] = [];
1708
+
1709
+ /**
1710
+ * Registers a callback to handle the transformation of {@link VideoSample} instances. The callback can either return
1711
+ * the transformed sample, or `null` to indicate that it doesn't want to handle the given transformation task.
1712
+ * @group Samples
1713
+ * @public
1714
+ */
1715
+ export const registerVideoSampleTransformer = (
1716
+ transformer: (
1717
+ sample: VideoSample,
1718
+ description: VideoSampleTransformationDescription,
1719
+ ) => MaybePromise<VideoSample | null>,
1720
+ ) => {
1721
+ if (registeredVideoSampleTransformers.includes(transformer)) {
1722
+ return; // Already in there
1723
+ }
1724
+
1725
+ registeredVideoSampleTransformers.push(transformer);
1726
+ };
1727
+
1728
+ const TRANSFORMATION_CANVAS_CACHE_MAX_SIZE = 3;
1729
+ const transformationCanvasCache: {
1730
+ canvas: HTMLCanvasElement | OffscreenCanvas;
1731
+ age: number;
1732
+ }[] = [];
1733
+ let transformationCanvasCacheNextAge = 0;
1734
+
1735
+ /**
1736
+ * Describes the color space of a {@link VideoSample}. Corresponds to the WebCodecs API's VideoColorSpace.
1737
+ * @group Samples
1738
+ * @public
1739
+ */
1740
+ export class VideoSampleColorSpace {
1741
+ /** The color primaries standard used. */
1742
+ readonly primaries: VideoColorPrimaries | null;
1743
+ /** The transfer characteristics used. */
1744
+ readonly transfer: VideoTransferCharacteristics | null;
1745
+ /** The color matrix coefficients used. */
1746
+ readonly matrix: VideoMatrixCoefficients | null;
1747
+ /** Whether the color values use the full range or limited range. */
1748
+ readonly fullRange: boolean | null;
1749
+
1750
+ /** Creates a new VideoSampleColorSpace. */
1751
+ constructor(init?: VideoColorSpaceInit) {
1752
+ if (init !== undefined) {
1753
+ if (!init || typeof init !== 'object') {
1754
+ throw new TypeError('init.colorSpace, when provided, must be an object.');
1755
+ }
1756
+
1757
+ const primariesValues = Object.keys(COLOR_PRIMARIES_MAP);
1758
+ if (init.primaries != null && !primariesValues.includes(init.primaries)) {
1759
+ throw new TypeError(
1760
+ `init.colorSpace.primaries, when provided, must be one of ${primariesValues.join(', ')}.`,
1761
+ );
1762
+ }
1763
+
1764
+ const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP);
1765
+ if (init.transfer != null && !transferValues.includes(init.transfer)) {
1766
+ throw new TypeError(
1767
+ `init.colorSpace.transfer, when provided, must be one of ${transferValues.join(', ')}.`,
1768
+ );
1769
+ }
1770
+
1771
+ const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP);
1772
+ if (init.matrix != null && !matrixValues.includes(init.matrix)) {
1773
+ throw new TypeError(
1774
+ `init.colorSpace.matrix, when provided, must be one of ${matrixValues.join(', ')}.`,
1775
+ );
1776
+ }
1777
+
1778
+ if (init.fullRange != null && typeof init.fullRange !== 'boolean') {
1779
+ throw new TypeError('init.colorSpace.fullRange, when provided, must be a boolean.');
1780
+ }
1781
+ }
1782
+
1783
+ this.primaries = init?.primaries ?? null;
1784
+ this.transfer = init?.transfer ?? null;
1785
+ this.matrix = init?.matrix ?? null;
1786
+ this.fullRange = init?.fullRange ?? null;
1787
+ }
1788
+
1789
+ /** Serializes the color space to a JSON object. */
1790
+ toJSON(): VideoColorSpaceInit {
1791
+ return {
1792
+ primaries: this.primaries,
1793
+ transfer: this.transfer,
1794
+ matrix: this.matrix,
1795
+ fullRange: this.fullRange,
1796
+ };
1797
+ }
1798
+ }
1799
+
1800
+ const isVideoFrame = (x: unknown): x is VideoFrame => {
1801
+ return typeof VideoFrame !== 'undefined' && x instanceof VideoFrame;
1802
+ };
1803
+
1804
+ /**
1805
+ * Specifies the rectangular cropping region.
1806
+ * @group Miscellaneous
1807
+ * @public
1808
+ */
1809
+ export type CropRectangle = {
1810
+ /** The distance in pixels from the left edge of the source frame to the left edge of the crop rectangle. */
1811
+ left: number;
1812
+ /** The distance in pixels from the top edge of the source frame to the top edge of the crop rectangle. */
1813
+ top: number;
1814
+ /** The width in pixels of the crop rectangle. */
1815
+ width: number;
1816
+ /** The height in pixels of the crop rectangle. */
1817
+ height: number;
1818
+ };
1819
+
1820
+ export const clampCropRectangle = (crop: CropRectangle, outerWidth: number, outerHeight: number): CropRectangle => {
1821
+ const left = Math.min(crop.left, outerWidth);
1822
+ const top = Math.min(crop.top, outerHeight);
1823
+ const width = Math.min(crop.width, outerWidth - left);
1824
+ const height = Math.min(crop.height, outerHeight - top);
1825
+
1826
+ assert(width >= 0);
1827
+ assert(height >= 0);
1828
+
1829
+ return { left, top, width, height };
1830
+ };
1831
+
1832
+ export const validateCropRectangle = (crop: CropRectangle, prefix: string) => {
1833
+ if (!crop || typeof crop !== 'object') {
1834
+ throw new TypeError(prefix + 'crop, when provided, must be an object.');
1835
+ }
1836
+ if (!Number.isInteger(crop.left) || crop.left < 0) {
1837
+ throw new TypeError(prefix + 'crop.left must be a non-negative integer.');
1838
+ }
1839
+ if (!Number.isInteger(crop.top) || crop.top < 0) {
1840
+ throw new TypeError(prefix + 'crop.top must be a non-negative integer.');
1841
+ }
1842
+ if (!Number.isInteger(crop.width) || crop.width < 0) {
1843
+ throw new TypeError(prefix + 'crop.width must be a non-negative integer.');
1844
+ }
1845
+ if (!Number.isInteger(crop.height) || crop.height < 0) {
1846
+ throw new TypeError(prefix + 'crop.height must be a non-negative integer.');
1847
+ }
1848
+ };
1849
+
1850
+ const validateVideoFrameCopyToOptions = (options: VideoFrameCopyToOptions) => {
1851
+ if (!options || typeof options !== 'object') {
1852
+ throw new TypeError('options must be an object.');
1853
+ }
1854
+ if (options.colorSpace !== undefined && !['display-p3', 'srgb'].includes(options.colorSpace)) {
1855
+ throw new TypeError('options.colorSpace, when provided, must be \'display-p3\' or \'srgb\'.');
1856
+ }
1857
+ if (options.format !== undefined && typeof options.format !== 'string') {
1858
+ throw new TypeError('options.format, when provided, must be a string.');
1859
+ }
1860
+ if (options.layout !== undefined) {
1861
+ if (!Array.isArray(options.layout)) {
1862
+ throw new TypeError('options.layout, when provided, must be an array.');
1863
+ }
1864
+
1865
+ for (const plane of options.layout) {
1866
+ if (!plane || typeof plane !== 'object') {
1867
+ throw new TypeError('Each entry in options.layout must be an object.');
1868
+ }
1869
+ if (!Number.isInteger(plane.offset) || plane.offset < 0) {
1870
+ throw new TypeError('plane.offset must be a non-negative integer.');
1871
+ }
1872
+ if (!Number.isInteger(plane.stride) || plane.stride < 0) {
1873
+ throw new TypeError('plane.stride must be a non-negative integer.');
1874
+ }
1875
+ }
1876
+ }
1877
+ if (options.rect !== undefined) {
1878
+ if (!options.rect || typeof options.rect !== 'object') {
1879
+ throw new TypeError('options.rect, when provided, must be an object.');
1880
+ }
1881
+ if (options.rect.x !== undefined && (!Number.isInteger(options.rect.x) || options.rect.x < 0)) {
1882
+ throw new TypeError('options.rect.x, when provided, must be a non-negative integer.');
1883
+ }
1884
+ if (options.rect.y !== undefined && (!Number.isInteger(options.rect.y) || options.rect.y < 0)) {
1885
+ throw new TypeError('options.rect.y, when provided, must be a non-negative integer.');
1886
+ }
1887
+ if (options.rect.width !== undefined && (!Number.isInteger(options.rect.width) || options.rect.width < 0)) {
1888
+ throw new TypeError('options.rect.width, when provided, must be a non-negative integer.');
1889
+ }
1890
+ if (options.rect.height !== undefined && (!Number.isInteger(options.rect.height) || options.rect.height < 0)) {
1891
+ throw new TypeError('options.rect.height, when provided, must be a non-negative integer.');
1892
+ }
1893
+ }
1894
+ };
1895
+
1896
+ /** Implements logic from WebCodecs § 9.4.6 "Compute Layout and Allocation Size" */
1897
+ const createDefaultPlaneLayout = (
1898
+ format: VideoSamplePixelFormat,
1899
+ codedWidth: number,
1900
+ codedHeight: number,
1901
+ ): PlaneLayout[] => {
1902
+ const planes = getPlaneConfigs(format);
1903
+ const layouts: PlaneLayout[] = [];
1904
+ let currentOffset = 0;
1905
+
1906
+ for (const plane of planes) {
1907
+ // Per § 9.8, dimensions are usually "rounded up to the nearest integer".
1908
+ const planeWidth = Math.ceil(codedWidth / plane.widthDivisor);
1909
+ const planeHeight = Math.ceil(codedHeight / plane.heightDivisor);
1910
+
1911
+ const stride = planeWidth * plane.sampleBytes;
1912
+
1913
+ // Tight packing
1914
+ const planeSize = stride * planeHeight;
1915
+
1916
+ layouts.push({
1917
+ offset: currentOffset,
1918
+ stride: stride,
1919
+ });
1920
+
1921
+ currentOffset += planeSize;
1922
+ }
1923
+
1924
+ return layouts;
1925
+ };
1926
+
1927
+ type PlaneConfig = {
1928
+ sampleBytes: number;
1929
+ widthDivisor: number; // Horizontal sub-sampling factor
1930
+ heightDivisor: number; // Vertical sub-sampling factor
1931
+ };
1932
+
1933
+ /** Helper to retrieve plane configurations based on WebCodecs § 9.8 Pixel Format definitions. */
1934
+ export const getPlaneConfigs = (format: VideoSamplePixelFormat): PlaneConfig[] => {
1935
+ // Helper for standard YUV planes
1936
+ const yuv = (
1937
+ yBytes: number,
1938
+ uvBytes: number,
1939
+ subX: number,
1940
+ subY: number,
1941
+ hasAlpha: boolean,
1942
+ ): PlaneConfig[] => {
1943
+ const configs: PlaneConfig[] = [
1944
+ { sampleBytes: yBytes, widthDivisor: 1, heightDivisor: 1 },
1945
+ { sampleBytes: uvBytes, widthDivisor: subX, heightDivisor: subY },
1946
+ { sampleBytes: uvBytes, widthDivisor: subX, heightDivisor: subY },
1947
+ ];
1948
+
1949
+ if (hasAlpha) {
1950
+ // Match luma dimensions
1951
+ configs.push({ sampleBytes: yBytes, widthDivisor: 1, heightDivisor: 1 });
1952
+ }
1953
+
1954
+ return configs;
1955
+ };
1956
+
1957
+ switch (format) {
1958
+ case 'I420':
1959
+ return yuv(1, 1, 2, 2, false);
1960
+ case 'I420P10':
1961
+ case 'I420P12':
1962
+ return yuv(2, 2, 2, 2, false);
1963
+ case 'I420A':
1964
+ return yuv(1, 1, 2, 2, true);
1965
+ case 'I420AP10':
1966
+ case 'I420AP12':
1967
+ return yuv(2, 2, 2, 2, true);
1968
+
1969
+ case 'I422':
1970
+ return yuv(1, 1, 2, 1, false);
1971
+ case 'I422P10':
1972
+ case 'I422P12':
1973
+ return yuv(2, 2, 2, 1, false);
1974
+ case 'I422A':
1975
+ return yuv(1, 1, 2, 1, true);
1976
+ case 'I422AP10':
1977
+ case 'I422AP12':
1978
+ return yuv(2, 2, 2, 1, true);
1979
+
1980
+ case 'I444':
1981
+ return yuv(1, 1, 1, 1, false);
1982
+ case 'I444P10':
1983
+ case 'I444P12':
1984
+ return yuv(2, 2, 1, 1, false);
1985
+ case 'I444A':
1986
+ return yuv(1, 1, 1, 1, true);
1987
+ case 'I444AP10':
1988
+ case 'I444AP12':
1989
+ return yuv(2, 2, 1, 1, true);
1990
+
1991
+ case 'NV12':
1992
+ return [
1993
+ { sampleBytes: 1, widthDivisor: 1, heightDivisor: 1 },
1994
+ { sampleBytes: 2, widthDivisor: 2, heightDivisor: 2 }, // Interleaved U and V
1995
+ ];
1996
+
1997
+ case 'RGBA':
1998
+ case 'RGBX':
1999
+ case 'BGRA':
2000
+ case 'BGRX':
2001
+ return [
2002
+ { sampleBytes: 4, widthDivisor: 1, heightDivisor: 1 },
2003
+ ];
2004
+
2005
+ default:
2006
+ assertNever(format);
2007
+ assert(false);
2008
+ }
2009
+ };
2010
+
2011
+ type CombinedBufferLayout = {
2012
+ allocationSize: number;
2013
+ computedLayouts: ComputedPlaneLayout[];
2014
+ };
2015
+
2016
+ type ComputedPlaneLayout = {
2017
+ destinationOffset: number;
2018
+ destinationStride: number;
2019
+ sourceTop: number;
2020
+ sourceHeight: number;
2021
+ sourceLeftBytes: number;
2022
+ sourceWidthBytes: number;
2023
+ };
2024
+
2025
+ /** Taken from the WebCodecs spec. */
2026
+ const ParseVideoFrameCopyToOptions = (
2027
+ sample: VideoSample,
2028
+ options: VideoFrameCopyToOptions,
2029
+ ): CombinedBufferLayout => {
2030
+ // 1. Let defaultRect be the result of performing the getter steps for visibleRect.
2031
+ const defaultRect: Rectangle = {
2032
+ left: 0,
2033
+ top: 0,
2034
+ width: sample.codedWidth,
2035
+ height: sample.codedHeight,
2036
+ };
2037
+
2038
+ // 2. Let overrideRect be undefined.
2039
+ // 3. If options.rect exists, assign the value of options.rect to overrideRect.
2040
+ const overrideRect = options.rect;
2041
+
2042
+ // 4. Let parsedRect be the result of running the Parse Visible Rect algorithm...
2043
+ const parsedRect = ParseVisibleRect(
2044
+ defaultRect,
2045
+ overrideRect,
2046
+ sample.codedWidth,
2047
+ sample.codedHeight,
2048
+ sample.format,
2049
+ );
2050
+
2051
+ // 5. If parsedRect is an exception, return parsedRect. (Handled by throw)
2052
+
2053
+ // 6. Let optLayout be undefined.
2054
+ // 7. If options.layout exists, assign its value to optLayout.
2055
+ const optLayout = options.layout;
2056
+
2057
+ // 8. Let format be undefined.
2058
+ let format: VideoSamplePixelFormat | undefined;
2059
+
2060
+ // 9. If options.format does not exist, assign [[format]] to format.
2061
+ if (!options.format || options.format === sample.format) {
2062
+ format = sample.format!;
2063
+ } else if (['RGBA', 'RGBX', 'BGRA', 'BGRX'].includes(options.format)) {
2064
+ // 10. Otherwise, if options.format is equal to one of RGBA, RGBX, BGRA, BGRX, then assign options.format
2065
+ // to format...
2066
+ format = options.format;
2067
+ } else {
2068
+ throw new Error('NotSupportedError: Invalid destination format.');
2069
+ }
2070
+
2071
+ // 11. Let combinedLayout be the result of running the Compute Layout and Allocation Size algorithm...
2072
+ return ComputeLayoutAndAllocationSize(parsedRect, format, optLayout);
2073
+ };
2074
+
2075
+ /** Taken from the WebCodecs spec. */
2076
+ const ParseVisibleRect = (
2077
+ defaultRect: DOMRectInit,
2078
+ overrideRect: DOMRectInit | undefined,
2079
+ codedWidth: number,
2080
+ codedHeight: number,
2081
+ format: VideoSamplePixelFormat | null,
2082
+ ): DOMRectInit => {
2083
+ // 1. Let sourceRect be defaultRect
2084
+ const sourceRect = { ...defaultRect };
2085
+
2086
+ // 2. If overrideRect is not undefined:
2087
+ if (overrideRect !== undefined) {
2088
+ // If either of overrideRect.width or height is 0, return a TypeError.
2089
+ if (overrideRect.width === 0 || overrideRect.height === 0) {
2090
+ throw new TypeError('visibleRect dimensions cannot be zero.');
2091
+ }
2092
+ // If the sum of overrideRect.x and overrideRect.width is greater than codedWidth, return a TypeError.
2093
+ if ((overrideRect.x || 0) + (overrideRect.width || 0) > codedWidth) {
2094
+ throw new TypeError('visibleRect exceeds codedWidth.');
2095
+ }
2096
+ // If the sum of overrideRect.y and overrideRect.height is greater than codedHeight, return a TypeError.
2097
+ if ((overrideRect.y || 0) + (overrideRect.height || 0) > codedHeight) {
2098
+ throw new TypeError('visibleRect exceeds codedHeight.');
2099
+ }
2100
+ // Assign overrideRect to sourceRect.
2101
+ sourceRect.x = overrideRect.x || 0;
2102
+ sourceRect.y = overrideRect.y || 0;
2103
+ sourceRect.width = overrideRect.width || 0;
2104
+ sourceRect.height = overrideRect.height || 0;
2105
+ }
2106
+
2107
+ // 3. Let validAlignment be the result of running the Verify Rect Offset Alignment algorithm.
2108
+ const validAlignment = VerifyRectOffsetAlignment(format, sourceRect);
2109
+
2110
+ // 4. If validAlignment is false, throw a TypeError.
2111
+ if (!validAlignment) {
2112
+ throw new TypeError('visibleRect alignment is invalid for the format.');
2113
+ }
2114
+
2115
+ // 5. Return sourceRect.
2116
+ return sourceRect;
2117
+ };
2118
+
2119
+ /** Taken from the WebCodecs spec. */
2120
+ const VerifyRectOffsetAlignment = (format: VideoSamplePixelFormat | null, rect: DOMRectInit): boolean => {
2121
+ // 1. If format is null, return true.
2122
+ if (format === null) return true;
2123
+
2124
+ const planes = getPlaneConfigs(format);
2125
+
2126
+ // 2. Let planeIndex be 0.
2127
+ // 3. Let numPlanes be the number of planes as defined by format.
2128
+ // 4. While planeIndex is less than numPlanes:
2129
+ for (let planeIndex = 0; planeIndex < planes.length; planeIndex++) {
2130
+ const plane = planes[planeIndex]!;
2131
+ const sampleWidth = plane.widthDivisor;
2132
+ const sampleHeight = plane.heightDivisor;
2133
+
2134
+ // If rect.x is not a multiple of sampleWidth, return false.
2135
+ if ((rect.x || 0) % sampleWidth !== 0) return false;
2136
+ // If rect.y is not a multiple of sampleHeight, return false.
2137
+ if ((rect.y || 0) % sampleHeight !== 0) return false;
2138
+ }
2139
+
2140
+ return true;
2141
+ };
2142
+
2143
+ /** Taken from the WebCodecs spec. */
2144
+ const ComputeLayoutAndAllocationSize = (
2145
+ parsedRect: DOMRectInit,
2146
+ format: VideoSamplePixelFormat,
2147
+ layout?: PlaneLayout[],
2148
+ ): CombinedBufferLayout => {
2149
+ const planes = getPlaneConfigs(format);
2150
+
2151
+ // 1. Let numPlanes be the number of planes as defined by format.
2152
+ const numPlanes = planes.length;
2153
+
2154
+ // 2. If layout is not undefined and its length does not equal numPlanes, throw a TypeError.
2155
+ if (layout !== undefined && layout.length !== numPlanes) {
2156
+ throw new TypeError(`Layout must have ${numPlanes} planes.`);
2157
+ }
2158
+
2159
+ // 3. Let minAllocationSize be 0.
2160
+ let minAllocationSize = 0;
2161
+
2162
+ // 4. Let computedLayouts be a new list.
2163
+ const computedLayouts: ComputedPlaneLayout[] = [];
2164
+
2165
+ // 5. Let endOffsets be a new list.
2166
+ const endOffsets: number[] = [];
2167
+
2168
+ // 6. Let planeIndex be 0.
2169
+ // 7. While planeIndex < numPlanes:
2170
+ for (let planeIndex = 0; planeIndex < numPlanes; planeIndex++) {
2171
+ const plane = planes[planeIndex]!;
2172
+ const sampleBytes = plane.sampleBytes;
2173
+ const sampleWidth = plane.widthDivisor;
2174
+ const sampleHeight = plane.heightDivisor;
2175
+
2176
+ // Let computedLayout be a new computed plane layout.
2177
+ const computedLayout: ComputedPlaneLayout = {
2178
+ destinationOffset: 0,
2179
+ destinationStride: 0,
2180
+ sourceTop: 0,
2181
+ sourceHeight: 0,
2182
+ sourceLeftBytes: 0,
2183
+ sourceWidthBytes: 0,
2184
+ };
2185
+
2186
+ // Set computedLayout’s sourceTop...
2187
+ computedLayout.sourceTop = Math.ceil(Math.trunc(parsedRect.y || 0) / sampleHeight);
2188
+ // Set computedLayout’s sourceHeight...
2189
+ computedLayout.sourceHeight = Math.ceil(Math.trunc(parsedRect.height || 0) / sampleHeight);
2190
+ // Set computedLayout’s sourceLeftBytes...
2191
+ computedLayout.sourceLeftBytes = Math.floor(Math.trunc(parsedRect.x || 0) / sampleWidth) * sampleBytes;
2192
+ // Set computedLayout’s sourceWidthBytes...
2193
+ computedLayout.sourceWidthBytes = Math.floor(Math.trunc(parsedRect.width || 0) / sampleWidth) * sampleBytes;
2194
+
2195
+ // If layout is not undefined:
2196
+ if (layout !== undefined) {
2197
+ const planeLayout = layout[planeIndex]!;
2198
+ // If planeLayout.stride is less than computedLayout’s sourceWidthBytes, return a TypeError.
2199
+ if (planeLayout.stride < computedLayout.sourceWidthBytes) {
2200
+ throw new TypeError(`Stride for plane ${planeIndex} is too small.`);
2201
+ }
2202
+ // Assign planeLayout.offset to computedLayout’s destinationOffset.
2203
+ computedLayout.destinationOffset = planeLayout.offset;
2204
+ // Assign planeLayout.stride to computedLayout’s destinationStride.
2205
+ computedLayout.destinationStride = planeLayout.stride;
2206
+ } else {
2207
+ // Otherwise:
2208
+ // Assign minAllocationSize to computedLayout’s destinationOffset.
2209
+ computedLayout.destinationOffset = minAllocationSize;
2210
+ // Assign computedLayout’s sourceWidthBytes to computedLayout’s destinationStride.
2211
+ computedLayout.destinationStride = computedLayout.sourceWidthBytes;
2212
+ }
2213
+
2214
+ // Let planeSize be the product of multiplying computedLayout’s destinationStride and sourceHeight.
2215
+ const planeSize = computedLayout.destinationStride * computedLayout.sourceHeight;
2216
+
2217
+ // Let planeEnd be the sum of planeSize and computedLayout’s destinationOffset.
2218
+ const planeEnd = planeSize + computedLayout.destinationOffset;
2219
+
2220
+ // If planeSize or planeEnd is greater than maximum range of unsigned long, return a TypeError.
2221
+ if (planeEnd > 4294967295) {
2222
+ throw new TypeError('Allocation size exceeds limit.');
2223
+ }
2224
+
2225
+ // Append planeEnd to endOffsets.
2226
+ endOffsets.push(planeEnd);
2227
+
2228
+ // Assign the maximum of minAllocationSize and planeEnd to minAllocationSize.
2229
+ minAllocationSize = Math.max(minAllocationSize, planeEnd);
2230
+
2231
+ // Check for overlap
2232
+ for (let earlierPlaneIndex = 0; earlierPlaneIndex < planeIndex; earlierPlaneIndex++) {
2233
+ const earlierLayout = computedLayouts[earlierPlaneIndex]!;
2234
+ // If plane A ends before plane B starts, they do not overlap.
2235
+ if (
2236
+ endOffsets[planeIndex]! <= earlierLayout.destinationOffset
2237
+ || endOffsets[earlierPlaneIndex]! <= computedLayout.destinationOffset
2238
+ ) {
2239
+ continue;
2240
+ }
2241
+
2242
+ throw new TypeError('Planes overlap.');
2243
+ }
2244
+
2245
+ computedLayouts.push(computedLayout);
2246
+ }
2247
+
2248
+ // 12. Return combinedLayout.
2249
+ return {
2250
+ allocationSize: minAllocationSize,
2251
+ computedLayouts: computedLayouts,
2252
+ };
2253
+ };
2254
+
2255
+ const AUDIO_SAMPLE_FORMATS = new Set<AudioSampleFormat>(
2256
+ ['f32', 'f32-planar', 's16', 's16-planar', 's32', 's32-planar', 'u8', 'u8-planar'],
2257
+ );
2258
+
2259
+ /**
2260
+ * Abstract base class for custom audio sample resources. Implement this class to provide custom backing
2261
+ * for AudioSample instances.
2262
+ * @group Samples
2263
+ * @public
2264
+ */
2265
+ export abstract class AudioSampleResource {
2266
+ /** @internal */
2267
+ _referenceCount: number = 0;
2268
+
2269
+ /**
2270
+ * Returns the audio sample format.
2271
+ * [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format)
2272
+ */
2273
+ abstract getFormat(): AudioSampleFormat;
2274
+
2275
+ /** Returns the audio sample rate in hertz. */
2276
+ abstract getSampleRate(): number;
2277
+
2278
+ /** Returns the number of audio frames in the sample, per channel. */
2279
+ abstract getNumberOfFrames(): number;
2280
+
2281
+ /** Returns the number of audio channels. */
2282
+ abstract getNumberOfChannels(): number;
2283
+
2284
+ /** Returns the presentation timestamp of the sample in seconds. */
2285
+ abstract getTimestamp(): number;
2286
+
2287
+ /**
2288
+ * Closes this resource, releasing held resources. Called automatically when the last {@link AudioSample} using this
2289
+ * resource is closed.
2290
+ */
2291
+ abstract close(): void;
2292
+
2293
+ /**
2294
+ * Returns the audio sample data for the plane given by `planeIndex`. The audio data must be in the format returned
2295
+ * by `getFormat()`. For interleaved formats, there is only one plane.
2296
+ */
2297
+ abstract getDataPlane(planeIndex: number): Uint8Array;
2298
+ }
2299
+
2300
+ /**
2301
+ * Metadata used for AudioSample initialization.
2302
+ * @group Samples
2303
+ * @public
2304
+ */
2305
+ export type AudioSampleInit = {
2306
+ /** The audio data for this sample. */
2307
+ data: AllowSharedBufferSource;
2308
+ /**
2309
+ * The audio sample format. [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format)
2310
+ */
2311
+ format: AudioSampleFormat;
2312
+ /** The number of audio channels. */
2313
+ numberOfChannels: number;
2314
+ /** The audio sample rate in hertz. */
2315
+ sampleRate: number;
2316
+ /** The presentation timestamp of the sample in seconds. */
2317
+ timestamp: number;
2318
+ };
2319
+
2320
+ /**
2321
+ * Options used for copying audio sample data.
2322
+ * @group Samples
2323
+ * @public
2324
+ */
2325
+ export type AudioSampleCopyToOptions = {
2326
+ /**
2327
+ * The index identifying the plane to copy from. This must be 0 if using a non-planar (interleaved) output format.
2328
+ */
2329
+ planeIndex: number;
2330
+ /**
2331
+ * The output format for the destination data. Defaults to the AudioSample's format.
2332
+ * [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format)
2333
+ */
2334
+ format?: AudioSampleFormat;
2335
+ /** An offset into the source plane data indicating which frame to begin copying from. Defaults to 0. */
2336
+ frameOffset?: number;
2337
+ /**
2338
+ * The number of frames to copy. If not provided, the copy will include all frames in the plane beginning
2339
+ * with frameOffset.
2340
+ */
2341
+ frameCount?: number;
2342
+ };
2343
+
2344
+ /**
2345
+ * Represents a raw, unencoded audio sample. Mainly used as an expressive wrapper around WebCodecs API's
2346
+ * [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData), but can also be used standalone.
2347
+ * @group Samples
2348
+ * @public
2349
+ */
2350
+ export class AudioSample implements Disposable {
2351
+ /** @internal */
2352
+ _data: AudioData | Uint8Array | AudioSampleResource;
2353
+ /** @internal */
2354
+ _closed: boolean = false;
2355
+
2356
+ /**
2357
+ * The audio sample format.
2358
+ * [See sample formats](https://developer.mozilla.org/en-US/docs/Web/API/AudioData/format)
2359
+ */
2360
+ readonly format: AudioSampleFormat;
2361
+ /** The audio sample rate in hertz. */
2362
+ readonly sampleRate: number;
2363
+ /**
2364
+ * The number of audio frames in the sample, per channel. In other words, the length of this audio sample in frames.
2365
+ */
2366
+ readonly numberOfFrames: number;
2367
+ /** The number of audio channels. */
2368
+ readonly numberOfChannels: number;
2369
+ /** The duration of the sample in seconds. */
2370
+ readonly duration: number;
2371
+ /**
2372
+ * The presentation timestamp of the sample in seconds. May be negative. Samples with negative end timestamps should
2373
+ * not be presented.
2374
+ */
2375
+ readonly timestamp: number;
2376
+
2377
+ /** The presentation timestamp of the sample in microseconds. */
2378
+ get microsecondTimestamp() {
2379
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
2380
+ }
2381
+
2382
+ /** The duration of the sample in microseconds. */
2383
+ get microsecondDuration() {
2384
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
2385
+ }
2386
+
2387
+ /**
2388
+ * Creates a new {@link AudioSample}, either from an existing
2389
+ * [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData) or from raw bytes specified in
2390
+ * {@link AudioSampleInit}.
2391
+ */
2392
+ constructor(init: AudioData | AudioSampleInit | AudioSampleResource);
2393
+ constructor(init: AudioData | AudioSampleInit | AudioSampleResource) {
2394
+ if (isAudioData(init)) {
2395
+ if (init.format === null) {
2396
+ throw new TypeError('AudioData with null format is not supported.');
2397
+ }
2398
+
2399
+ this._data = init;
2400
+
2401
+ this.format = init.format;
2402
+ this.sampleRate = init.sampleRate;
2403
+ this.numberOfFrames = init.numberOfFrames;
2404
+ this.numberOfChannels = init.numberOfChannels;
2405
+ this.timestamp = init.timestamp / 1e6;
2406
+ this.duration = init.numberOfFrames / init.sampleRate;
2407
+ } else if (init instanceof AudioSampleResource) {
2408
+ this._data = init;
2409
+ init._referenceCount++;
2410
+
2411
+ this.format = init.getFormat();
2412
+ if (!AUDIO_SAMPLE_FORMATS.has(this.format)) {
2413
+ throw new TypeError('getFormat() must return an AudioSampleFormat.');
2414
+ }
2415
+
2416
+ this.sampleRate = init.getSampleRate();
2417
+ if (!Number.isInteger(this.sampleRate) || this.sampleRate <= 0) {
2418
+ throw new TypeError('getSampleRate() must return a positive integer.');
2419
+ }
2420
+
2421
+ this.numberOfFrames = init.getNumberOfFrames();
2422
+ if (!Number.isInteger(this.numberOfFrames) || this.numberOfFrames < 0) {
2423
+ throw new TypeError('getNumberOfFrames() must return a non-negative integer.');
2424
+ }
2425
+
2426
+ this.numberOfChannels = init.getNumberOfChannels();
2427
+ if (!Number.isInteger(this.numberOfChannels) || this.numberOfChannels <= 0) {
2428
+ throw new TypeError('getNumberOfChannels() must return a positive integer.');
2429
+ }
2430
+
2431
+ this.timestamp = init.getTimestamp();
2432
+ if (!Number.isFinite(this.timestamp)) {
2433
+ throw new TypeError('getTimestamp() must return a finite number.');
2434
+ }
2435
+
2436
+ this.duration = this.numberOfFrames / this.sampleRate;
2437
+ } else {
2438
+ if (!init || typeof init !== 'object') {
2439
+ throw new TypeError('Invalid AudioDataInit: must be an object.');
2440
+ }
2441
+
2442
+ if (!AUDIO_SAMPLE_FORMATS.has(init.format)) {
2443
+ throw new TypeError('Invalid AudioDataInit: invalid format.');
2444
+ }
2445
+ if (!Number.isFinite(init.sampleRate) || init.sampleRate <= 0) {
2446
+ throw new TypeError('Invalid AudioDataInit: sampleRate must be > 0.');
2447
+ }
2448
+ if (!Number.isInteger(init.numberOfChannels) || init.numberOfChannels === 0) {
2449
+ throw new TypeError('Invalid AudioDataInit: numberOfChannels must be an integer > 0.');
2450
+ }
2451
+ if (!Number.isFinite(init?.timestamp)) {
2452
+ throw new TypeError('init.timestamp must be a number.');
2453
+ }
2454
+
2455
+ const numberOfFrames
2456
+ = init.data.byteLength / (getBytesPerSample(init.format) * init.numberOfChannels);
2457
+ if (!Number.isInteger(numberOfFrames)) {
2458
+ throw new TypeError('Invalid AudioDataInit: data size is not a multiple of frame size.');
2459
+ }
2460
+
2461
+ this.format = init.format;
2462
+ this.sampleRate = init.sampleRate;
2463
+ this.numberOfFrames = numberOfFrames;
2464
+ this.numberOfChannels = init.numberOfChannels;
2465
+ this.timestamp = init.timestamp;
2466
+ this.duration = numberOfFrames / init.sampleRate;
2467
+
2468
+ let dataBuffer: Uint8Array;
2469
+ if (init.data instanceof ArrayBuffer) {
2470
+ dataBuffer = new Uint8Array(init.data);
2471
+ } else if (ArrayBuffer.isView(init.data)) {
2472
+ dataBuffer = new Uint8Array(init.data.buffer, init.data.byteOffset, init.data.byteLength);
2473
+ } else {
2474
+ throw new TypeError('Invalid AudioDataInit: data is not a BufferSource.');
2475
+ }
2476
+
2477
+ const expectedSize
2478
+ = this.numberOfFrames * this.numberOfChannels * getBytesPerSample(this.format);
2479
+ if (dataBuffer.byteLength < expectedSize) {
2480
+ throw new TypeError('Invalid AudioDataInit: insufficient data size.');
2481
+ }
2482
+
2483
+ this._data = dataBuffer;
2484
+ }
2485
+
2486
+ finalizationRegistry?.register(this, { type: 'audio', data: this._data }, this);
2487
+ }
2488
+
2489
+ /** Returns the number of bytes required to hold the audio sample's data as specified by the given options. */
2490
+ allocationSize(options: AudioSampleCopyToOptions) {
2491
+ if (!options || typeof options !== 'object') {
2492
+ throw new TypeError('options must be an object.');
2493
+ }
2494
+ if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) {
2495
+ throw new TypeError('planeIndex must be a non-negative integer.');
2496
+ }
2497
+
2498
+ if (options.format !== undefined && !AUDIO_SAMPLE_FORMATS.has(options.format)) {
2499
+ throw new TypeError('Invalid format.');
2500
+ }
2501
+ if (options.frameOffset !== undefined && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) {
2502
+ throw new TypeError('frameOffset must be a non-negative integer.');
2503
+ }
2504
+ if (options.frameCount !== undefined && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) {
2505
+ throw new TypeError('frameCount must be a non-negative integer.');
2506
+ }
2507
+
2508
+ if (this._closed) {
2509
+ throw new Error('AudioSample is closed.');
2510
+ }
2511
+
2512
+ const destFormat = options.format ?? this.format;
2513
+
2514
+ const frameOffset = options.frameOffset ?? 0;
2515
+ if (frameOffset >= this.numberOfFrames) {
2516
+ throw new RangeError('frameOffset out of range');
2517
+ }
2518
+
2519
+ const copyFrameCount
2520
+ = options.frameCount !== undefined ? options.frameCount : (this.numberOfFrames - frameOffset);
2521
+ if (copyFrameCount > (this.numberOfFrames - frameOffset)) {
2522
+ throw new RangeError('frameCount out of range');
2523
+ }
2524
+
2525
+ const bytesPerSample = getBytesPerSample(destFormat);
2526
+ const isPlanar = formatIsPlanar(destFormat);
2527
+ if (isPlanar && options.planeIndex >= this.numberOfChannels) {
2528
+ throw new RangeError('planeIndex out of range');
2529
+ }
2530
+ if (!isPlanar && options.planeIndex !== 0) {
2531
+ throw new RangeError('planeIndex out of range');
2532
+ }
2533
+
2534
+ const elementCount = isPlanar ? copyFrameCount : copyFrameCount * this.numberOfChannels;
2535
+ return elementCount * bytesPerSample;
2536
+ }
2537
+
2538
+ /** Copies the audio sample's data to an ArrayBuffer or ArrayBufferView as specified by the given options. */
2539
+ copyTo(destination: AllowSharedBufferSource, options: AudioSampleCopyToOptions) {
2540
+ if (!isAllowSharedBufferSource(destination)) {
2541
+ throw new TypeError('destination must be an ArrayBuffer or an ArrayBuffer view.');
2542
+ }
2543
+ if (!options || typeof options !== 'object') {
2544
+ throw new TypeError('options must be an object.');
2545
+ }
2546
+ if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) {
2547
+ throw new TypeError('planeIndex must be a non-negative integer.');
2548
+ }
2549
+
2550
+ if (options.format !== undefined && !AUDIO_SAMPLE_FORMATS.has(options.format)) {
2551
+ throw new TypeError('Invalid format.');
2552
+ }
2553
+ if (options.frameOffset !== undefined && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) {
2554
+ throw new TypeError('frameOffset must be a non-negative integer.');
2555
+ }
2556
+ if (options.frameCount !== undefined && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) {
2557
+ throw new TypeError('frameCount must be a non-negative integer.');
2558
+ }
2559
+
2560
+ if (this._closed) {
2561
+ throw new Error('AudioSample is closed.');
2562
+ }
2563
+
2564
+ const { format, frameCount: optFrameCount, frameOffset: optFrameOffset } = options;
2565
+ let { planeIndex } = options;
2566
+
2567
+ const srcFormat = this.format;
2568
+ const destFormat = format ?? this.format;
2569
+ if (!destFormat) throw new Error('Destination format not determined');
2570
+
2571
+ const numFrames = this.numberOfFrames;
2572
+ const numChannels = this.numberOfChannels;
2573
+ const frameOffset = optFrameOffset ?? 0;
2574
+ if (frameOffset >= numFrames) {
2575
+ throw new RangeError('frameOffset out of range');
2576
+ }
2577
+
2578
+ const copyFrameCount = optFrameCount !== undefined ? optFrameCount : (numFrames - frameOffset);
2579
+ if (copyFrameCount > (numFrames - frameOffset)) {
2580
+ throw new RangeError('frameCount out of range');
2581
+ }
2582
+
2583
+ const destBytesPerSample = getBytesPerSample(destFormat);
2584
+ const destIsPlanar = formatIsPlanar(destFormat);
2585
+ if (destIsPlanar && planeIndex >= numChannels) {
2586
+ throw new RangeError('planeIndex out of range');
2587
+ }
2588
+ if (!destIsPlanar && planeIndex !== 0) {
2589
+ throw new RangeError('planeIndex out of range');
2590
+ }
2591
+
2592
+ const destElementCount = destIsPlanar ? copyFrameCount : copyFrameCount * numChannels;
2593
+ const requiredSize = destElementCount * destBytesPerSample;
2594
+ if (destination.byteLength < requiredSize) {
2595
+ throw new RangeError('Destination buffer is too small');
2596
+ }
2597
+
2598
+ const destView = toDataView(destination);
2599
+ const writeFn = getWriteFunction(destFormat);
2600
+
2601
+ if (isAudioData(this._data)) {
2602
+ if (isWebKit() && numChannels > 2 && destFormat !== srcFormat) {
2603
+ // WebKit bug workaround
2604
+ doAudioDataCopyToWebKitWorkaround(
2605
+ this._data,
2606
+ destView,
2607
+ srcFormat,
2608
+ destFormat,
2609
+ numChannels,
2610
+ planeIndex,
2611
+ frameOffset,
2612
+ copyFrameCount,
2613
+ );
2614
+ } else {
2615
+ // Per spec, only f32-planar conversion must be supported, but in practice, all browsers support all
2616
+ // destination formats, so let's just delegate here:
2617
+ this._data.copyTo(destination, {
2618
+ planeIndex,
2619
+ frameOffset,
2620
+ frameCount: copyFrameCount,
2621
+ format: destFormat,
2622
+ });
2623
+ }
2624
+ } else {
2625
+ const readFn = getReadFunction(srcFormat);
2626
+ const srcBytesPerSample = getBytesPerSample(srcFormat);
2627
+ const srcIsPlanar = formatIsPlanar(srcFormat);
2628
+
2629
+ let uint8Data: Uint8Array;
2630
+ if (this._data instanceof AudioSampleResource) {
2631
+ const getDataPlaneValidated = (index: number) => {
2632
+ const result = (this._data as AudioSampleResource).getDataPlane(index);
2633
+ if (!(result instanceof Uint8Array)) {
2634
+ throw new TypeError('getDataPlane() must return a Uint8Array.');
2635
+ }
2636
+
2637
+ const expectedSize = numFrames * srcBytesPerSample * (srcIsPlanar ? 1 : numChannels);
2638
+ if (result.byteLength !== expectedSize) {
2639
+ throw new TypeError(
2640
+ `Data plane ${index} has invalid size. Expected exactly ${expectedSize} bytes, got`
2641
+ + ` ${result.byteLength} bytes.`,
2642
+ );
2643
+ }
2644
+
2645
+ return result;
2646
+ };
2647
+
2648
+ if (srcIsPlanar) {
2649
+ if (destIsPlanar) {
2650
+ // Only one source plane will be extracted, so let's fetch only that one
2651
+ uint8Data = getDataPlaneValidated(planeIndex);
2652
+ planeIndex = 0; // To fix the subsequent access
2653
+ } else {
2654
+ // Pack all planes tightly together
2655
+ uint8Data = new Uint8Array(numFrames * srcBytesPerSample * numChannels);
2656
+ for (let ch = 0; ch < numChannels; ch++) {
2657
+ const planeData = getDataPlaneValidated(ch);
2658
+ uint8Data.set(planeData, ch * numFrames * srcBytesPerSample);
2659
+ }
2660
+ }
2661
+ } else {
2662
+ uint8Data = getDataPlaneValidated(0); // That's the only plane there is
2663
+ }
2664
+ } else {
2665
+ uint8Data = this._data;
2666
+ }
2667
+
2668
+ const srcView = toDataView(uint8Data);
2669
+
2670
+ for (let i = 0; i < copyFrameCount; i++) {
2671
+ if (destIsPlanar) {
2672
+ const destOffset = i * destBytesPerSample;
2673
+ let srcOffset: number;
2674
+ if (srcIsPlanar) {
2675
+ srcOffset = (planeIndex * numFrames + (i + frameOffset)) * srcBytesPerSample;
2676
+ } else {
2677
+ srcOffset = (((i + frameOffset) * numChannels) + planeIndex) * srcBytesPerSample;
2678
+ }
2679
+
2680
+ const normalized = readFn(srcView, srcOffset);
2681
+ writeFn(destView, destOffset, normalized);
2682
+ } else {
2683
+ for (let ch = 0; ch < numChannels; ch++) {
2684
+ const destIndex = i * numChannels + ch;
2685
+ const destOffset = destIndex * destBytesPerSample;
2686
+ let srcOffset: number;
2687
+ if (srcIsPlanar) {
2688
+ srcOffset = (ch * numFrames + (i + frameOffset)) * srcBytesPerSample;
2689
+ } else {
2690
+ srcOffset = (((i + frameOffset) * numChannels) + ch) * srcBytesPerSample;
2691
+ }
2692
+
2693
+ const normalized = readFn(srcView, srcOffset);
2694
+ writeFn(destView, destOffset, normalized);
2695
+ }
2696
+ }
2697
+ }
2698
+ }
2699
+ }
2700
+
2701
+ /** Clones this audio sample. */
2702
+ clone(): AudioSample {
2703
+ if (this._closed) {
2704
+ throw new Error('AudioSample is closed.');
2705
+ }
2706
+
2707
+ if (this._data instanceof AudioSampleResource) {
2708
+ const sample = new AudioSample(this._data);
2709
+ sample.setTimestamp(this.timestamp); // Make sure the timestamp is correct
2710
+
2711
+ return sample;
2712
+ } else if (isAudioData(this._data)) {
2713
+ const sample = new AudioSample(this._data.clone());
2714
+ sample.setTimestamp(this.timestamp);
2715
+
2716
+ return sample;
2717
+ } else {
2718
+ return new AudioSample({
2719
+ format: this.format,
2720
+ sampleRate: this.sampleRate,
2721
+ numberOfFrames: this.numberOfFrames,
2722
+ numberOfChannels: this.numberOfChannels,
2723
+ timestamp: this.timestamp,
2724
+ data: this._data,
2725
+ });
2726
+ }
2727
+ }
2728
+
2729
+ /**
2730
+ * Returns a new {@link AudioSample} containing only the frames in the range [startSample, endSample). Both bounds
2731
+ * must lie within this sample's range of frames. The returned sample's timestamp is shifted to match the start of
2732
+ * the trimmed section.
2733
+ */
2734
+ trim(startSample: number, endSample = this.numberOfFrames) {
2735
+ if (!Number.isInteger(startSample) || startSample < 0) {
2736
+ throw new TypeError('startSample must be a non-negative integer.');
2737
+ }
2738
+ if (!Number.isInteger(endSample) || endSample < 0) {
2739
+ throw new TypeError('endSample must be a non-negative integer.');
2740
+ }
2741
+ if (startSample > this.numberOfFrames) {
2742
+ throw new RangeError('startSample out of range.');
2743
+ }
2744
+ if (endSample > this.numberOfFrames) {
2745
+ throw new RangeError('endSample out of range.');
2746
+ }
2747
+ if (endSample < startSample) {
2748
+ throw new RangeError('endSample must not be less than startSample.');
2749
+ }
2750
+
2751
+ if (this._closed) {
2752
+ throw new Error('AudioSample is closed.');
2753
+ }
2754
+
2755
+ const frameCount = endSample - startSample;
2756
+ const bytesPerSample = getBytesPerSample(this.format);
2757
+
2758
+ let data: Uint8Array;
2759
+ if (formatIsPlanar(this.format)) {
2760
+ const planeSize = frameCount * bytesPerSample;
2761
+ data = new Uint8Array(planeSize * this.numberOfChannels);
2762
+
2763
+ if (frameCount > 0) {
2764
+ // Copy plane-by-plane
2765
+ for (let i = 0; i < this.numberOfChannels; i++) {
2766
+ this.copyTo(data.subarray(i * planeSize, (i + 1) * planeSize), {
2767
+ planeIndex: i,
2768
+ format: this.format,
2769
+ frameOffset: startSample,
2770
+ frameCount,
2771
+ });
2772
+ }
2773
+ }
2774
+ } else {
2775
+ // Trivial
2776
+ data = new Uint8Array(frameCount * this.numberOfChannels * bytesPerSample);
2777
+
2778
+ if (frameCount > 0) {
2779
+ this.copyTo(data, {
2780
+ planeIndex: 0,
2781
+ format: this.format,
2782
+ frameOffset: startSample,
2783
+ frameCount,
2784
+ });
2785
+ }
2786
+ }
2787
+
2788
+ return new AudioSample({
2789
+ data,
2790
+ format: this.format,
2791
+ sampleRate: this.sampleRate,
2792
+ numberOfChannels: this.numberOfChannels,
2793
+ timestamp: this.timestamp + startSample / this.sampleRate,
2794
+ });
2795
+ }
2796
+
2797
+ /**
2798
+ * Closes this audio sample, releasing held resources. Audio samples should be closed as soon as they are not
2799
+ * needed anymore.
2800
+ */
2801
+ close(): void {
2802
+ if (this._closed) {
2803
+ return;
2804
+ }
2805
+
2806
+ finalizationRegistry?.unregister(this);
2807
+
2808
+ if (this._data instanceof AudioSampleResource) {
2809
+ this._data._referenceCount--;
2810
+ if (this._data._referenceCount === 0) {
2811
+ this._data.close();
2812
+ }
2813
+ } else if (isAudioData(this._data)) {
2814
+ this._data.close();
2815
+ } else {
2816
+ this._data = new Uint8Array(0);
2817
+ }
2818
+
2819
+ this._closed = true;
2820
+ }
2821
+
2822
+ /**
2823
+ * Converts this audio sample to an AudioData for use with the WebCodecs API. The AudioData returned by this
2824
+ * method *must* be closed separately from this audio sample.
2825
+ */
2826
+ toAudioData() {
2827
+ if (this._closed) {
2828
+ throw new Error('AudioSample is closed.');
2829
+ }
2830
+
2831
+ if (this._data instanceof AudioSampleResource) {
2832
+ return this._createAudioDataFromData();
2833
+ } else if (isAudioData(this._data)) {
2834
+ if (this._data.timestamp === this.microsecondTimestamp) {
2835
+ // Timestamp matches, let's just return the data (but cloned)
2836
+ return this._data.clone();
2837
+ } else {
2838
+ // It's impossible to simply change an AudioData's timestamp, so we'll need to create a new one
2839
+ return this._createAudioDataFromData();
2840
+ }
2841
+ } else {
2842
+ return new AudioData({
2843
+ format: this.format,
2844
+ sampleRate: this.sampleRate,
2845
+ numberOfFrames: this.numberOfFrames,
2846
+ numberOfChannels: this.numberOfChannels,
2847
+ timestamp: this.microsecondTimestamp,
2848
+ data: this._data.buffer instanceof ArrayBuffer
2849
+ ? this._data.buffer
2850
+ : this._data.slice(), // In the case of SharedArrayBuffer, convert to ArrayBuffer
2851
+ });
2852
+ }
2853
+ }
2854
+
2855
+ /** @internal */
2856
+ _createAudioDataFromData() {
2857
+ if (formatIsPlanar(this.format)) {
2858
+ const size = this.allocationSize({ planeIndex: 0, format: this.format });
2859
+ const data = new ArrayBuffer(size * this.numberOfChannels);
2860
+
2861
+ // We gotta read out each plane individually
2862
+ for (let i = 0; i < this.numberOfChannels; i++) {
2863
+ this.copyTo(new Uint8Array(data, i * size, size), { planeIndex: i, format: this.format });
2864
+ }
2865
+
2866
+ return new AudioData({
2867
+ format: this.format,
2868
+ sampleRate: this.sampleRate,
2869
+ numberOfFrames: this.numberOfFrames,
2870
+ numberOfChannels: this.numberOfChannels,
2871
+ timestamp: this.microsecondTimestamp,
2872
+ data,
2873
+ });
2874
+ } else {
2875
+ const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
2876
+ this.copyTo(data, { planeIndex: 0, format: this.format });
2877
+
2878
+ return new AudioData({
2879
+ format: this.format,
2880
+ sampleRate: this.sampleRate,
2881
+ numberOfFrames: this.numberOfFrames,
2882
+ numberOfChannels: this.numberOfChannels,
2883
+ timestamp: this.microsecondTimestamp,
2884
+ data,
2885
+ });
2886
+ }
2887
+ }
2888
+
2889
+ /** Convert this audio sample to an AudioBuffer for use with the Web Audio API. */
2890
+ toAudioBuffer() {
2891
+ if (this._closed) {
2892
+ throw new Error('AudioSample is closed.');
2893
+ }
2894
+
2895
+ const audioBuffer = new AudioBuffer({
2896
+ numberOfChannels: this.numberOfChannels,
2897
+ length: this.numberOfFrames,
2898
+ sampleRate: this.sampleRate,
2899
+ });
2900
+
2901
+ const dataBytes = new Float32Array(this.allocationSize({ planeIndex: 0, format: 'f32-planar' }) / 4);
2902
+
2903
+ for (let i = 0; i < this.numberOfChannels; i++) {
2904
+ this.copyTo(dataBytes, { planeIndex: i, format: 'f32-planar' });
2905
+ audioBuffer.copyToChannel(dataBytes, i);
2906
+ }
2907
+
2908
+ return audioBuffer;
2909
+ }
2910
+
2911
+ /** Sets the presentation timestamp of this audio sample, in seconds. */
2912
+ setTimestamp(newTimestamp: number) {
2913
+ if (!Number.isFinite(newTimestamp)) {
2914
+ throw new TypeError('newTimestamp must be a number.');
2915
+ }
2916
+
2917
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
2918
+ (this.timestamp as number) = newTimestamp;
2919
+ }
2920
+
2921
+ /** Calls `.close()`. */
2922
+ [Symbol.dispose]() {
2923
+ this.close();
2924
+ }
2925
+
2926
+ /** @internal */
2927
+ static* _fromAudioBuffer(audioBuffer: AudioBuffer, timestamp: number) {
2928
+ if (!(audioBuffer instanceof AudioBuffer)) {
2929
+ throw new TypeError('audioBuffer must be an AudioBuffer.');
2930
+ }
2931
+
2932
+ const MAX_FLOAT_COUNT = 48000 * 5; // 5 seconds of mono 48 kHz audio per sample
2933
+
2934
+ const numberOfChannels = audioBuffer.numberOfChannels;
2935
+ const sampleRate = audioBuffer.sampleRate;
2936
+ const totalFrames = audioBuffer.length;
2937
+ const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels);
2938
+
2939
+ let currentRelativeFrame = 0;
2940
+ let remainingFrames = totalFrames;
2941
+
2942
+ // Create AudioSamples in a chunked fashion so we don't create huge Float32Arrays
2943
+ while (remainingFrames > 0) {
2944
+ const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames);
2945
+ const chunkData = new Float32Array(numberOfChannels * framesToCopy);
2946
+
2947
+ for (let channel = 0; channel < numberOfChannels; channel++) {
2948
+ audioBuffer.copyFromChannel(
2949
+ chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy),
2950
+ channel,
2951
+ currentRelativeFrame,
2952
+ );
2953
+ }
2954
+
2955
+ yield new AudioSample({
2956
+ format: 'f32-planar',
2957
+ sampleRate,
2958
+ numberOfFrames: framesToCopy,
2959
+ numberOfChannels,
2960
+ timestamp: timestamp + currentRelativeFrame / sampleRate,
2961
+ data: chunkData,
2962
+ });
2963
+
2964
+ currentRelativeFrame += framesToCopy;
2965
+ remainingFrames -= framesToCopy;
2966
+ }
2967
+ }
2968
+
2969
+ /**
2970
+ * Creates AudioSamples from an AudioBuffer, starting at the given timestamp in seconds. Typically creates exactly
2971
+ * one sample, but may create multiple if the AudioBuffer is exceedingly large.
2972
+ */
2973
+ static fromAudioBuffer(audioBuffer: AudioBuffer, timestamp: number) {
2974
+ if (!(audioBuffer instanceof AudioBuffer)) {
2975
+ throw new TypeError('audioBuffer must be an AudioBuffer.');
2976
+ }
2977
+
2978
+ const MAX_FLOAT_COUNT = 48000 * 5; // 5 seconds of mono 48 kHz audio per sample
2979
+
2980
+ const numberOfChannels = audioBuffer.numberOfChannels;
2981
+ const sampleRate = audioBuffer.sampleRate;
2982
+ const totalFrames = audioBuffer.length;
2983
+ const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels);
2984
+
2985
+ let currentRelativeFrame = 0;
2986
+ let remainingFrames = totalFrames;
2987
+
2988
+ const result: AudioSample[] = [];
2989
+
2990
+ // Create AudioSamples in a chunked fashion so we don't create huge Float32Arrays
2991
+ while (remainingFrames > 0) {
2992
+ const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames);
2993
+ const chunkData = new Float32Array(numberOfChannels * framesToCopy);
2994
+
2995
+ for (let channel = 0; channel < numberOfChannels; channel++) {
2996
+ audioBuffer.copyFromChannel(
2997
+ chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy),
2998
+ channel,
2999
+ currentRelativeFrame,
3000
+ );
3001
+ }
3002
+
3003
+ const audioSample = new AudioSample({
3004
+ format: 'f32-planar',
3005
+ sampleRate,
3006
+ numberOfFrames: framesToCopy,
3007
+ numberOfChannels,
3008
+ timestamp: timestamp + currentRelativeFrame / sampleRate,
3009
+ data: chunkData,
3010
+ });
3011
+
3012
+ result.push(audioSample);
3013
+
3014
+ currentRelativeFrame += framesToCopy;
3015
+ remainingFrames -= framesToCopy;
3016
+ }
3017
+
3018
+ return result;
3019
+ }
3020
+ }
3021
+
3022
+ export const getBytesPerSample = (format: AudioSampleFormat): number => {
3023
+ switch (format) {
3024
+ case 'u8':
3025
+ case 'u8-planar':
3026
+ return 1;
3027
+ case 's16':
3028
+ case 's16-planar':
3029
+ return 2;
3030
+ case 's32':
3031
+ case 's32-planar':
3032
+ return 4;
3033
+ case 'f32':
3034
+ case 'f32-planar':
3035
+ return 4;
3036
+ default:
3037
+ throw new Error('Unknown AudioSampleFormat');
3038
+ }
3039
+ };
3040
+
3041
+ const formatIsPlanar = (format: AudioSampleFormat): boolean => {
3042
+ switch (format) {
3043
+ case 'u8-planar':
3044
+ case 's16-planar':
3045
+ case 's32-planar':
3046
+ case 'f32-planar':
3047
+ return true;
3048
+ default:
3049
+ return false;
3050
+ }
3051
+ };
3052
+
3053
+ const getReadFunction = (format: AudioSampleFormat): (view: DataView, offset: number) => number => {
3054
+ switch (format) {
3055
+ case 'u8':
3056
+ case 'u8-planar':
3057
+ return (view, offset) => (view.getUint8(offset) - 128) / 128;
3058
+ case 's16':
3059
+ case 's16-planar':
3060
+ return (view, offset) => view.getInt16(offset, true) / 32768;
3061
+ case 's32':
3062
+ case 's32-planar':
3063
+ return (view, offset) => view.getInt32(offset, true) / 2147483648;
3064
+ case 'f32':
3065
+ case 'f32-planar':
3066
+ return (view, offset) => view.getFloat32(offset, true);
3067
+ }
3068
+ };
3069
+
3070
+ const getWriteFunction = (format: AudioSampleFormat): (view: DataView, offset: number, value: number) => void => {
3071
+ switch (format) {
3072
+ case 'u8':
3073
+ case 'u8-planar':
3074
+ return (view, offset, value) =>
3075
+ view.setUint8(offset, clamp((value + 1) * 127.5, 0, 255));
3076
+ case 's16':
3077
+ case 's16-planar':
3078
+ return (view, offset, value) =>
3079
+ view.setInt16(offset, clamp(Math.round(value * 32767), -32768, 32767), true);
3080
+ case 's32':
3081
+ case 's32-planar':
3082
+ return (view, offset, value) =>
3083
+ view.setInt32(offset, clamp(Math.round(value * 2147483647), -2147483648, 2147483647), true);
3084
+ case 'f32':
3085
+ case 'f32-planar':
3086
+ return (view, offset, value) => view.setFloat32(offset, value, true);
3087
+ }
3088
+ };
3089
+
3090
+ const isAudioData = (x: unknown): x is AudioData => {
3091
+ return typeof AudioData !== 'undefined' && x instanceof AudioData;
3092
+ };
3093
+
3094
+ export const toInterleavedAudioFormat = (format: AudioSampleFormat): 'u8' | 's16' | 's32' | 'f32' => {
3095
+ switch (format) {
3096
+ case 'u8-planar':
3097
+ return 'u8';
3098
+ case 's16-planar':
3099
+ return 's16';
3100
+ case 's32-planar':
3101
+ return 's32';
3102
+ case 'f32-planar':
3103
+ return 'f32';
3104
+ default:
3105
+ return format;
3106
+ }
3107
+ };
3108
+
3109
+ /**
3110
+ * WebKit has a bug where calling AudioData.copyTo with a format different from the source format
3111
+ * crashes the tab when there are more than 2 channels. This function works around that by always
3112
+ * copying with the source format and then manually converting to the destination format.
3113
+ *
3114
+ * See https://bugs.webkit.org/show_bug.cgi?id=302521.
3115
+ */
3116
+ const doAudioDataCopyToWebKitWorkaround = (
3117
+ audioData: AudioData,
3118
+ destView: DataView,
3119
+ srcFormat: AudioSampleFormat,
3120
+ destFormat: AudioSampleFormat,
3121
+ numChannels: number,
3122
+ planeIndex: number,
3123
+ frameOffset: number,
3124
+ copyFrameCount: number,
3125
+ ) => {
3126
+ const readFn = getReadFunction(srcFormat);
3127
+ const writeFn = getWriteFunction(destFormat);
3128
+ const srcBytesPerSample = getBytesPerSample(srcFormat);
3129
+ const destBytesPerSample = getBytesPerSample(destFormat);
3130
+ const srcIsPlanar = formatIsPlanar(srcFormat);
3131
+ const destIsPlanar = formatIsPlanar(destFormat);
3132
+
3133
+ if (destIsPlanar) {
3134
+ if (srcIsPlanar) {
3135
+ // src planar -> dest planar: copy single plane and convert
3136
+ const data = new ArrayBuffer(copyFrameCount * srcBytesPerSample);
3137
+ const dataView = toDataView(data);
3138
+
3139
+ audioData.copyTo(data, {
3140
+ planeIndex,
3141
+ frameOffset,
3142
+ frameCount: copyFrameCount,
3143
+ format: srcFormat,
3144
+ });
3145
+
3146
+ for (let i = 0; i < copyFrameCount; i++) {
3147
+ const srcOffset = i * srcBytesPerSample;
3148
+ const destOffset = i * destBytesPerSample;
3149
+ const sample = readFn(dataView, srcOffset);
3150
+ writeFn(destView, destOffset, sample);
3151
+ }
3152
+ } else {
3153
+ // src interleaved -> dest planar: copy all interleaved data, extract one channel
3154
+ const data = new ArrayBuffer(copyFrameCount * numChannels * srcBytesPerSample);
3155
+ const dataView = toDataView(data);
3156
+
3157
+ audioData.copyTo(data, {
3158
+ planeIndex: 0,
3159
+ frameOffset,
3160
+ frameCount: copyFrameCount,
3161
+ format: srcFormat,
3162
+ });
3163
+
3164
+ for (let i = 0; i < copyFrameCount; i++) {
3165
+ const srcOffset = (i * numChannels + planeIndex) * srcBytesPerSample;
3166
+ const destOffset = i * destBytesPerSample;
3167
+ const sample = readFn(dataView, srcOffset);
3168
+ writeFn(destView, destOffset, sample);
3169
+ }
3170
+ }
3171
+ } else {
3172
+ if (srcIsPlanar) {
3173
+ // src planar -> dest interleaved: copy each plane and interleave
3174
+ const planeSize = copyFrameCount * srcBytesPerSample;
3175
+ const data = new ArrayBuffer(planeSize);
3176
+ const dataView = toDataView(data);
3177
+
3178
+ for (let ch = 0; ch < numChannels; ch++) {
3179
+ audioData.copyTo(data, {
3180
+ planeIndex: ch,
3181
+ frameOffset,
3182
+ frameCount: copyFrameCount,
3183
+ format: srcFormat,
3184
+ });
3185
+
3186
+ for (let i = 0; i < copyFrameCount; i++) {
3187
+ const srcOffset = i * srcBytesPerSample;
3188
+ const destOffset = (i * numChannels + ch) * destBytesPerSample;
3189
+ const sample = readFn(dataView, srcOffset);
3190
+ writeFn(destView, destOffset, sample);
3191
+ }
3192
+ }
3193
+ } else {
3194
+ // src interleaved -> dest interleaved: copy all and convert
3195
+ const data = new ArrayBuffer(copyFrameCount * numChannels * srcBytesPerSample);
3196
+ const dataView = toDataView(data);
3197
+
3198
+ audioData.copyTo(data, {
3199
+ planeIndex: 0,
3200
+ frameOffset,
3201
+ frameCount: copyFrameCount,
3202
+ format: srcFormat,
3203
+ });
3204
+
3205
+ for (let i = 0; i < copyFrameCount; i++) {
3206
+ for (let ch = 0; ch < numChannels; ch++) {
3207
+ const idx = i * numChannels + ch;
3208
+ const srcOffset = idx * srcBytesPerSample;
3209
+ const destOffset = idx * destBytesPerSample;
3210
+ const sample = readFn(dataView, srcOffset);
3211
+ writeFn(destView, destOffset, sample);
3212
+ }
3213
+ }
3214
+ }
3215
+ }
3216
+ };
3217
+
3218
+ export const audioSampleToInterleavedFormat = (sample: AudioSample, format: 'u8' | 's16' | 's32' | 'f32') => {
3219
+ const size = sample.allocationSize({ format, planeIndex: 0 });
3220
+ const buffer = new ArrayBuffer(size);
3221
+ sample.copyTo(buffer, { format, planeIndex: 0 });
3222
+
3223
+ return new AudioSample({
3224
+ data: buffer,
3225
+ format,
3226
+ numberOfChannels: sample.numberOfChannels,
3227
+ sampleRate: sample.sampleRate,
3228
+ timestamp: sample.timestamp,
3229
+ duration: sample.duration,
3230
+ });
3231
+ };