@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,3041 @@
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 { buildAacAudioSpecificConfig, parseAacAudioSpecificConfig } from '../shared/aac-misc';
10
+ import {
11
+ AUDIO_CODECS,
12
+ AudioCodec,
13
+ MediaCodec,
14
+ parsePcmCodec,
15
+ PCM_AUDIO_CODECS,
16
+ PcmAudioCodec,
17
+ SUBTITLE_CODECS,
18
+ SubtitleCodec,
19
+ VIDEO_CODECS,
20
+ VideoCodec,
21
+ } from './codec';
22
+ import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output';
23
+ import {
24
+ assert,
25
+ assertNever,
26
+ binarySearchLessOrEqual,
27
+ CallSerializer,
28
+ clamp,
29
+ clearIntervalUnthrottled,
30
+ floorToDivisor,
31
+ last,
32
+ promiseWithResolvers,
33
+ roundToDivisor,
34
+ setInt24,
35
+ setIntervalUnthrottled,
36
+ setUint24,
37
+ toUint8Array,
38
+ UnthrottledTimerHandle,
39
+ wait,
40
+ } from './misc';
41
+ import { Muxer } from './muxer';
42
+ import { SubtitleParser } from './subtitles';
43
+ import { toAlaw, toUlaw } from './pcm';
44
+ import {
45
+ CustomVideoEncoder,
46
+ CustomAudioEncoder,
47
+ customVideoEncoders,
48
+ customAudioEncoders,
49
+ } from './custom-coder';
50
+ import { EncodedPacket, EncodedPacketSideData, PacketType } from './packet';
51
+ import {
52
+ AudioSample,
53
+ audioSampleToInterleavedFormat,
54
+ toInterleavedAudioFormat,
55
+ VideoSample,
56
+ VideoSamplePixelFormat,
57
+ } from './sample';
58
+ import {
59
+ AudioEncodingConfig,
60
+ buildAudioEncoderConfig,
61
+ buildQuantizerEncodeOptions,
62
+ buildVideoEncoderConfigs,
63
+ resolveQuality,
64
+ validateAudioEncodingConfig,
65
+ validateVideoEncodingConfig,
66
+ VideoEncoderConfigCandidate,
67
+ VideoEncodingConfig,
68
+ } from './encode';
69
+ import { AudioResampler } from './resample';
70
+ import { determineVideoPacketType } from './codec-data';
71
+ import { Logging } from './logging';
72
+
73
+ /**
74
+ * Base class for media sources. Media sources are used to add media samples to an output file.
75
+ * @group Media sources
76
+ * @public
77
+ */
78
+ export abstract class MediaSource {
79
+ /** @internal */
80
+ abstract readonly _codec: MediaCodec;
81
+ /** @internal */
82
+ _connectedTrack: OutputTrack | null = null;
83
+ /** @internal */
84
+ _closingPromise: Promise<void> | null = null;
85
+ /** @internal */
86
+ _closed = false;
87
+
88
+ /** @internal */
89
+ _ensureValidAdd() {
90
+ if (!this._connectedTrack) {
91
+ throw new Error('Source is not connected to an output track.');
92
+ }
93
+
94
+ if (this._connectedTrack.output.state === 'canceled') {
95
+ throw new Error('Output has been canceled.');
96
+ }
97
+
98
+ if (this._connectedTrack.output.state === 'finalizing' || this._connectedTrack.output.state === 'finalized') {
99
+ throw new Error('Output has been finalized.');
100
+ }
101
+
102
+ if (this._connectedTrack.output.state === 'pending') {
103
+ throw new Error('Output has not started.');
104
+ }
105
+
106
+ if (this._closed) {
107
+ throw new Error('Source is closed.');
108
+ }
109
+ }
110
+
111
+ /** @internal */
112
+ async _start() {}
113
+ /** @internal */
114
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
115
+ async _flushAndClose(forceClose: boolean) {}
116
+
117
+ /**
118
+ * Closes this source. This prevents future samples from being added and signals to the output file that no further
119
+ * samples will come in for this track. Calling `.close()` is optional but recommended after adding the
120
+ * last sample - for improved performance and reduced memory usage.
121
+ */
122
+ close() {
123
+ if (this._closingPromise) {
124
+ return;
125
+ }
126
+
127
+ const connectedTrack = this._connectedTrack;
128
+
129
+ if (!connectedTrack) {
130
+ throw new Error('Cannot call close without connecting the source to an output track.');
131
+ }
132
+
133
+ if (connectedTrack.output.state === 'pending') {
134
+ throw new Error('Cannot call close before output has been started.');
135
+ }
136
+
137
+ this._closingPromise = (async () => {
138
+ await this._flushAndClose(false);
139
+
140
+ this._closed = true;
141
+
142
+ if (connectedTrack.output.state === 'finalizing' || connectedTrack.output.state === 'finalized') {
143
+ return;
144
+ }
145
+
146
+ connectedTrack.output._muxer.onTrackClose(connectedTrack);
147
+ })();
148
+ }
149
+
150
+ /** @internal */
151
+ async _flushOrWaitForOngoingClose(forceClose: boolean) {
152
+ return this._closingPromise ??= (async () => {
153
+ await this._flushAndClose(forceClose);
154
+ this._closed = true;
155
+ })();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Base class for video sources - sources for video tracks.
161
+ * @group Media sources
162
+ * @public
163
+ */
164
+ export abstract class VideoSource extends MediaSource {
165
+ /** @internal */
166
+ override _connectedTrack: OutputVideoTrack | null = null;
167
+ /** @internal */
168
+ override readonly _codec: VideoCodec;
169
+
170
+ /** Internal constructor. */
171
+ constructor(codec: VideoCodec) {
172
+ super();
173
+
174
+ if (!VIDEO_CODECS.includes(codec)) {
175
+ throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
176
+ }
177
+
178
+ this._codec = codec;
179
+ }
180
+ }
181
+
182
+ const maybeEnsureIsKeyPacket = (track: OutputVideoTrack, packet: EncodedPacket) => {
183
+ if (track.metadata.hasOnlyKeyPackets && packet.type !== 'key') {
184
+ throw new Error('Cannot add non-key packets to a hasOnlyKeyPackets video track.');
185
+ }
186
+ };
187
+
188
+ /**
189
+ * The most basic video source; can be used to directly pipe encoded packets into the output file.
190
+ * @group Media sources
191
+ * @public
192
+ */
193
+ export class EncodedVideoPacketSource extends VideoSource {
194
+ /** Creates a new {@link EncodedVideoPacketSource} whose packets are encoded using `codec`. */
195
+ constructor(codec: VideoCodec) {
196
+ super(codec);
197
+ }
198
+
199
+ /**
200
+ * Adds an encoded packet to the output video track. Packets must be added in *decode order*, while a packet's
201
+ * timestamp must be its *presentation timestamp*. B-frames are handled automatically.
202
+ *
203
+ * @param meta - Additional metadata from the encoder. You should pass this for the first call, including a valid
204
+ * decoder config.
205
+ *
206
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
207
+ * to respect writer and encoder backpressure.
208
+ */
209
+ add(packet: EncodedPacket, meta?: EncodedVideoChunkMetadata) {
210
+ if (!(packet instanceof EncodedPacket)) {
211
+ throw new TypeError('packet must be an EncodedPacket.');
212
+ }
213
+ if (packet.isMetadataOnly) {
214
+ throw new TypeError('Metadata-only packets cannot be added.');
215
+ }
216
+ if (meta !== undefined && (!meta || typeof meta !== 'object')) {
217
+ throw new TypeError('meta, when provided, must be an object.');
218
+ }
219
+
220
+ this._ensureValidAdd();
221
+
222
+ maybeEnsureIsKeyPacket(this._connectedTrack!, packet);
223
+ return this._connectedTrack!.output._muxer.addEncodedVideoPacket(this._connectedTrack!, packet, meta);
224
+ }
225
+ }
226
+
227
+ class VideoEncoderWrapper {
228
+ private ensureEncoderPromise: Promise<void> | null = null;
229
+ private encoderInitialized = false;
230
+ private encoder: VideoEncoder | null = null;
231
+ private muxer: Muxer | null = null;
232
+ private lastMultipleOfKeyFrameInterval = -1;
233
+ private emittedEncoderPackets = 0;
234
+
235
+ // Tracks the input dimensions of the first frame
236
+ private codedWidth: number | null = null;
237
+ private codedHeight: number | null = null;
238
+ // Tracks the output dimensions of the first frame (used to lock dimensions for fill/contain/cover)
239
+ private outputWidth: number | null = null;
240
+ private outputHeight: number | null = null;
241
+
242
+ // Frame rate normalization state
243
+ private frameRateLastSample: VideoSample | null = null;
244
+ private frameRateLastTimestamp: number | null = null;
245
+ private frameRateLastEndTimestamp: number | null = null;
246
+
247
+ // VideoEncoder converts everything to microseconds, so we need to do some bookkeeping to restore the original
248
+ // timing information
249
+ private preciseTimings: {
250
+ microsecondTimestamp: number;
251
+ timestamp: number;
252
+ duration: number;
253
+ timestampIsValid: boolean;
254
+ durationIsValid: boolean;
255
+ }[] = [];
256
+
257
+ private customEncoder: CustomVideoEncoder | null = null;
258
+ private customEncoderCallSerializer = new CallSerializer();
259
+ private customEncoderQueueSize = 0;
260
+
261
+ // Set when the encoder uses quantizer-based rate control; carries the quantizer value applied to each frame
262
+ private defaultEncodeOptions: VideoEncoderEncodeOptions = {};
263
+
264
+ // Alpha stuff
265
+ private alphaEncoder: VideoEncoder | null = null;
266
+ private splitter: ColorAlphaSplitter | null = null;
267
+ private splitterCreationFailed = false;
268
+ private alphaFrameQueue: (VideoFrame | null)[] = [];
269
+
270
+ /**
271
+ * Encoders typically throw their errors "out of band", meaning asynchronously in some other execution context.
272
+ * However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught.
273
+ * So, we keep track of the encoder error and throw it as soon as we get the chance.
274
+ */
275
+ private error: unknown = null;
276
+ private errorSet = false;
277
+
278
+ private setError(error: unknown) {
279
+ if (!this.errorSet) {
280
+ this.error = error;
281
+ this.errorSet = true;
282
+ }
283
+ }
284
+
285
+ private lastMuxerPromise: Promise<void> = Promise.resolve();
286
+ private closed = false;
287
+
288
+ constructor(private source: VideoSource, private encodingConfig: VideoEncodingConfig) {}
289
+
290
+ async add(videoSample: VideoSample, shouldClose: boolean, encodeOptions?: VideoEncoderEncodeOptions) {
291
+ const originalSample = videoSample;
292
+
293
+ try {
294
+ this.checkForEncoderError();
295
+ this.source._ensureValidAdd();
296
+
297
+ const config = this.encodingConfig;
298
+ const sizeChangeBehavior = config.sizeChangeBehavior ?? 'deny';
299
+ let isSizeChange = false;
300
+
301
+ // Ensure video sample size remains constant or handle the change
302
+ if (this.codedWidth !== null && this.codedHeight !== null) {
303
+ if (videoSample.codedWidth !== this.codedWidth || videoSample.codedHeight !== this.codedHeight) {
304
+ isSizeChange = true;
305
+ if (sizeChangeBehavior === 'deny') {
306
+ throw new Error(
307
+ `Video sample size must remain constant. Expected ${this.codedWidth}x${this.codedHeight},`
308
+ + ` got ${videoSample.codedWidth}x${videoSample.codedHeight}. To allow the sample size to`
309
+ + ` change over time, set \`sizeChangeBehavior\` to a value other than 'deny' in the`
310
+ + ` encoding options.`,
311
+ );
312
+ }
313
+ }
314
+ } else {
315
+ this.codedWidth = videoSample.codedWidth;
316
+ this.codedHeight = videoSample.codedHeight;
317
+ }
318
+
319
+ // Determine if we need to apply transformations via canvas
320
+ const hasTransformConfig = config.transform?.width !== undefined
321
+ || config.transform?.height !== undefined
322
+ || config.transform?.rotate !== undefined
323
+ || config.transform?.crop !== undefined
324
+ || config.transform?.force === true;
325
+ const needsTransform = hasTransformConfig || (isSizeChange && sizeChangeBehavior !== 'passThrough');
326
+
327
+ if (needsTransform) {
328
+ let targetWidth = config.transform?.width;
329
+ let targetHeight = config.transform?.height;
330
+ let appliedFit: 'fill' | 'contain' | 'cover' = config.transform?.fit ?? 'fill';
331
+
332
+ // If the size changed and behavior is fill/contain/cover, lock to the original output dimensions
333
+ if (isSizeChange && sizeChangeBehavior !== 'passThrough') {
334
+ assert(this.outputWidth);
335
+ assert(this.outputHeight);
336
+ assert(sizeChangeBehavior !== 'deny');
337
+
338
+ targetWidth = this.outputWidth!;
339
+ targetHeight = this.outputHeight!;
340
+ appliedFit = sizeChangeBehavior;
341
+ }
342
+
343
+ const transformed = await videoSample.transform({
344
+ width: targetWidth,
345
+ height: targetHeight,
346
+ roundDimensionsTo: 2,
347
+ crop: config.transform?.crop,
348
+ rotate: config.transform?.rotate,
349
+ fit: appliedFit,
350
+ alpha: config.alpha,
351
+ });
352
+
353
+ // Save the output dimensions of the first frame
354
+ if (this.outputWidth === null || this.outputHeight === null) {
355
+ this.outputWidth = transformed.displayWidth;
356
+ this.outputHeight = transformed.displayHeight;
357
+ }
358
+
359
+ if (shouldClose) {
360
+ videoSample.close();
361
+ }
362
+
363
+ videoSample = transformed;
364
+ shouldClose = true;
365
+ } else {
366
+ // If no canvas is needed, we still need to record the output dimensions for the first frame
367
+ if (this.outputWidth === null || this.outputHeight === null) {
368
+ this.outputWidth = videoSample.codedWidth;
369
+ this.outputHeight = videoSample.codedHeight;
370
+ }
371
+ }
372
+
373
+ const frameRate = config.transform?.frameRate;
374
+ if (frameRate !== undefined) {
375
+ // Apply frame rate normalization
376
+ const originalEndTimestamp = videoSample.timestamp + videoSample.duration;
377
+ const alignedTimestamp = floorToDivisor(videoSample.timestamp, frameRate);
378
+
379
+ if (this.frameRateLastSample !== null) {
380
+ if (alignedTimestamp <= this.frameRateLastTimestamp!) {
381
+ // Same frame rate slot, replace stored sample with the newer one
382
+ this.frameRateLastSample.close();
383
+ this.frameRateLastSample = videoSample.clone();
384
+ this.frameRateLastEndTimestamp = originalEndTimestamp;
385
+ return;
386
+ } else {
387
+ // Pad the gap by repeating the previous frame
388
+ await this.padFrameRate(alignedTimestamp, encodeOptions);
389
+ }
390
+ }
391
+
392
+ // Clone if the sample is still the user's, to avoid mutating externally-owned data
393
+ if (videoSample === originalSample) {
394
+ videoSample = videoSample.clone();
395
+ shouldClose = true;
396
+ }
397
+
398
+ videoSample.setTimestamp(alignedTimestamp);
399
+ videoSample.setDuration(1 / frameRate);
400
+
401
+ this.frameRateLastSample?.close();
402
+ this.frameRateLastSample = videoSample.clone();
403
+ this.frameRateLastTimestamp = alignedTimestamp;
404
+ this.frameRateLastEndTimestamp = originalEndTimestamp;
405
+ }
406
+
407
+ await this.processAndEncode(videoSample, encodeOptions);
408
+ } finally {
409
+ if (shouldClose) {
410
+ videoSample.close();
411
+ }
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Runs the process function (if any) and encodes the resulting samples.
417
+ */
418
+ private async processAndEncode(
419
+ videoSample: VideoSample,
420
+ encodeOptions?: VideoEncoderEncodeOptions,
421
+ ) {
422
+ const config = this.encodingConfig;
423
+ let samplesToEncode: VideoSample[];
424
+
425
+ // Apply the user-defined process function, if any
426
+ if (config.transform?.process) {
427
+ let processed = config.transform.process(videoSample);
428
+ if (processed instanceof Promise) {
429
+ processed = await processed;
430
+ }
431
+
432
+ if (processed === null) {
433
+ return;
434
+ }
435
+
436
+ if (!Array.isArray(processed)) {
437
+ processed = [processed];
438
+ }
439
+
440
+ const mappedSamples: VideoSample[] = [];
441
+
442
+ try {
443
+ for (const x of processed) {
444
+ if (x instanceof VideoSample) {
445
+ mappedSamples.push(x);
446
+ } else if (typeof VideoFrame !== 'undefined' && x instanceof VideoFrame) {
447
+ mappedSamples.push(new VideoSample(x));
448
+ } else {
449
+ // Calling the VideoSample constructor here will automatically handle input validation for us
450
+ // (it throws for any non-legal argument).
451
+ mappedSamples.push(new VideoSample(x as CanvasImageSource, {
452
+ timestamp: videoSample.timestamp,
453
+ duration: videoSample.duration,
454
+ }));
455
+ }
456
+ }
457
+ } catch (error) {
458
+ // One of the returned elements was invalid; close everything closable so no resource is leaked
459
+ for (const sample of mappedSamples) {
460
+ if (sample !== videoSample) {
461
+ sample.close();
462
+ }
463
+ }
464
+ for (const x of processed) {
465
+ if (x instanceof VideoSample && x !== videoSample) {
466
+ x.close();
467
+ } else if (typeof VideoFrame !== 'undefined' && x instanceof VideoFrame) {
468
+ x.close();
469
+ }
470
+ }
471
+
472
+ throw error;
473
+ }
474
+
475
+ samplesToEncode = mappedSamples;
476
+ } else {
477
+ samplesToEncode = [videoSample];
478
+ }
479
+
480
+ try {
481
+ for (const sampleToEncode of samplesToEncode) {
482
+ if (!this.encoderInitialized) {
483
+ if (!this.ensureEncoderPromise) {
484
+ this.ensureEncoder(sampleToEncode);
485
+ }
486
+
487
+ // No, this "if" statement is not useless. Sometimes, the above call to
488
+ // `ensureEncoder` might have synchronously completed and the encoder is
489
+ // already initialized. In this case, we don't need to await the promise
490
+ // anymore. This also fixes nasty async race condition bugs when multiple
491
+ // code paths are calling this method: It's important that the call that
492
+ // initialized the encoder go through this code first.
493
+ if (!this.encoderInitialized) {
494
+ await this.ensureEncoderPromise;
495
+ }
496
+ }
497
+ assert(this.encoderInitialized);
498
+
499
+ if (this.closed) {
500
+ break;
501
+ }
502
+
503
+ const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
504
+ const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
505
+
506
+ const mergedEncodeOptions = {
507
+ ...this.defaultEncodeOptions,
508
+ ...sampleToEncode.encodeOptions,
509
+ ...encodeOptions,
510
+ };
511
+
512
+ const finalEncodeOptions = {
513
+ ...mergedEncodeOptions,
514
+ keyFrame: mergedEncodeOptions.keyFrame !== undefined
515
+ ? mergedEncodeOptions.keyFrame
516
+ // Ensure a key frame every keyFrameInterval seconds. It is important that all video tracks
517
+ // follow the same "key frame" rhythm, because aligned key frames are required to start new
518
+ // fragments in ISOBMFF or clusters in Matroska (or at least desirable).
519
+ : keyFrameInterval === 0
520
+ || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval,
521
+ };
522
+ this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
523
+
524
+ this.encodingConfig.onEncodedSample?.(sampleToEncode);
525
+
526
+ if (this.customEncoder) {
527
+ this.customEncoderQueueSize++;
528
+
529
+ // We clone the sample so it cannot be closed on us from the outside before it reaches the encoder
530
+ const clonedSample = sampleToEncode.clone();
531
+
532
+ const promise = this.customEncoderCallSerializer
533
+ .call(() => this.customEncoder!.encode(clonedSample, finalEncodeOptions))
534
+ .catch((error: unknown) => this.setError(error))
535
+ .finally(() => {
536
+ this.customEncoderQueueSize--;
537
+ clonedSample.close();
538
+ });
539
+
540
+ if (this.customEncoderQueueSize >= 4) {
541
+ await promise;
542
+ }
543
+ } else {
544
+ assert(this.encoder);
545
+
546
+ const videoFrame = sampleToEncode.toVideoFrame();
547
+
548
+ const preciseTimingIndex = binarySearchLessOrEqual(
549
+ this.preciseTimings,
550
+ videoFrame.timestamp,
551
+ x => x.microsecondTimestamp,
552
+ );
553
+ const existingEntry = preciseTimingIndex !== -1
554
+ ? this.preciseTimings[preciseTimingIndex]
555
+ : null;
556
+ if (existingEntry && existingEntry.microsecondTimestamp === videoFrame.timestamp) {
557
+ if (existingEntry.timestamp !== sampleToEncode.timestamp) {
558
+ // Mapping isn't unique, can't use the timestamp
559
+ existingEntry.timestampIsValid = false;
560
+ }
561
+ if (existingEntry.duration !== sampleToEncode.duration) {
562
+ // Mapping isn't unique, can't use the duration
563
+ existingEntry.durationIsValid = false;
564
+ }
565
+ } else {
566
+ this.preciseTimings.splice(preciseTimingIndex + 1, 0, {
567
+ microsecondTimestamp: videoFrame.timestamp,
568
+ timestamp: sampleToEncode.timestamp,
569
+ duration: sampleToEncode.duration,
570
+ timestampIsValid: true,
571
+ durationIsValid: true,
572
+ });
573
+
574
+ // Make sure it doesn't grow indefinitely
575
+ if (this.preciseTimings.length > 128) {
576
+ this.preciseTimings.shift();
577
+ }
578
+ }
579
+
580
+ if (!this.alphaEncoder) {
581
+ // No alpha encoder, simple case
582
+ try {
583
+ this.encoder.encode(videoFrame, finalEncodeOptions);
584
+ } finally {
585
+ videoFrame.close();
586
+ }
587
+ } else {
588
+ // We're expected to encode alpha as well
589
+ const frameDefinitelyHasNoAlpha = !!videoFrame.format && !videoFrame.format.includes('A');
590
+
591
+ if (frameDefinitelyHasNoAlpha || this.splitterCreationFailed) {
592
+ this.alphaFrameQueue.push(null);
593
+ try {
594
+ this.encoder.encode(videoFrame, finalEncodeOptions);
595
+ } finally {
596
+ videoFrame.close();
597
+ }
598
+ } else {
599
+ if (!this.splitter) {
600
+ this.splitter = new ColorAlphaSplitter();
601
+ }
602
+
603
+ // The splitter takes ownership, so no need to close the frames ourselves
604
+ const { colorFrame, alphaFrame } = await this.splitter.split(videoFrame);
605
+
606
+ this.alphaFrameQueue.push(alphaFrame);
607
+ try {
608
+ this.encoder.encode(colorFrame, finalEncodeOptions);
609
+ } finally {
610
+ colorFrame.close();
611
+ }
612
+ }
613
+ }
614
+
615
+ // We need to do this after sending the frame to the encoder as the frame otherwise might be closed
616
+ if (this.encoder.encodeQueueSize >= 4) {
617
+ await new Promise(resolve =>
618
+ this.encoder!.addEventListener('dequeue', resolve, { once: true }),
619
+ );
620
+ }
621
+ }
622
+
623
+ await this.lastMuxerPromise; // Allow the writer to apply backpressure
624
+ }
625
+ } finally {
626
+ for (const sample of samplesToEncode) {
627
+ if (sample !== videoSample) {
628
+ sample.close();
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ /** Repeats the last frame rate sample to fill the gap up to the given timestamp. */
635
+ private async padFrameRate(until: number, encodeOptions?: VideoEncoderEncodeOptions) {
636
+ const frameRate = this.encodingConfig.transform!.frameRate!;
637
+ assert(this.frameRateLastSample);
638
+
639
+ const frameDifference = Math.round((until - this.frameRateLastTimestamp!) * frameRate);
640
+
641
+ for (let i = 1; i < frameDifference; i++) {
642
+ using sample = this.frameRateLastSample.clone();
643
+ sample.setTimestamp(this.frameRateLastTimestamp! + i / frameRate);
644
+ sample.setDuration(1 / frameRate);
645
+ await this.processAndEncode(sample, encodeOptions);
646
+ }
647
+ }
648
+
649
+ private ensureEncoder(videoSample: VideoSample) {
650
+ this.ensureEncoderPromise = (async () => {
651
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
652
+ const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
653
+ assert(quality !== undefined);
654
+
655
+ const candidates = buildVideoEncoderConfigs({
656
+ ...this.encodingConfig,
657
+ quality,
658
+ width: videoSample.codedWidth,
659
+ height: videoSample.codedHeight,
660
+ squarePixelWidth: videoSample.squarePixelWidth,
661
+ squarePixelHeight: videoSample.squarePixelHeight,
662
+ framerate: this.source._connectedTrack?.metadata.frameRate,
663
+ });
664
+
665
+ // Try the candidate configs in order of preference until we find one that is supported
666
+ let selected: VideoEncoderConfigCandidate | null = null;
667
+ let MatchingCustomEncoder: (typeof customVideoEncoders)[number] | undefined;
668
+
669
+ for (const candidate of candidates) {
670
+ const candidateConfig = candidate.config;
671
+ this.encodingConfig.onEncoderConfig?.(candidateConfig);
672
+
673
+ MatchingCustomEncoder = customVideoEncoders.find(x => x.supports(
674
+ this.encodingConfig.codec,
675
+ candidateConfig,
676
+ ));
677
+ if (MatchingCustomEncoder) {
678
+ selected = candidate;
679
+ break;
680
+ }
681
+
682
+ if (typeof VideoEncoder === 'undefined') {
683
+ continue;
684
+ }
685
+
686
+ candidateConfig.alpha = 'discard'; // Since we handle alpha ourselves
687
+
688
+ if (this.encodingConfig.alpha === 'keep') {
689
+ // Encoding alpha requires using two parallel encoders, so we need to make sure they stay in sync
690
+ // and that neither of them drops frames. Setting latencyMode to 'quality' achieves this, because
691
+ // "User Agents MUST not drop frames to achieve the target bitrate and/or framerate."
692
+ candidateConfig.latencyMode = 'quality';
693
+ }
694
+
695
+ const hasOddDimension = candidateConfig.width % 2 === 1 || candidateConfig.height % 2 === 1;
696
+ if (
697
+ hasOddDimension
698
+ && (this.encodingConfig.codec === 'avc' || this.encodingConfig.codec === 'hevc')
699
+ ) {
700
+ // Throw a special error for this case as it gets hit often
701
+ throw new Error(
702
+ `The dimensions ${candidateConfig.width}x${candidateConfig.height} are not supported for codec`
703
+ + ` '${this.encodingConfig.codec}'; both width and height must be even numbers. Make sure to`
704
+ + ` round your dimensions to the nearest even number.`,
705
+ );
706
+ }
707
+
708
+ const support = await VideoEncoder.isConfigSupported(candidateConfig);
709
+ if (support.supported) {
710
+ selected = candidate;
711
+ break;
712
+ }
713
+ }
714
+
715
+ if (!selected) {
716
+ if (typeof VideoEncoder === 'undefined') {
717
+ throw new Error('VideoEncoder is not supported by this browser.');
718
+ }
719
+
720
+ // The candidates only differ in their rate control, so we describe them as one config with a
721
+ // slash-separated list of the attempted rate control methods
722
+ const firstConfig = candidates[0]!.config;
723
+ const rateControls = candidates.map(({ config, quantizer }) =>
724
+ quantizer !== null ? `quantizer ${quantizer}` : `${config.bitrate} bps`,
725
+ );
726
+
727
+ throw new Error(
728
+ `This specific encoder configuration (${firstConfig.codec}, ${rateControls.join(' / ')},`
729
+ + ` ${firstConfig.width}x${firstConfig.height}, hardware acceleration:`
730
+ + ` ${firstConfig.hardwareAcceleration ?? 'no-preference'}) is not supported by this browser.`
731
+ + ` Consider using another codec or changing your video parameters.`,
732
+ );
733
+ }
734
+
735
+ const encoderConfig = selected.config;
736
+ if (selected.quantizer !== null) {
737
+ // The chosen config uses quantizer-based rate control, so each frame must carry the quantizer value
738
+ this.defaultEncodeOptions = buildQuantizerEncodeOptions(
739
+ this.encodingConfig.codec,
740
+ selected.quantizer,
741
+ );
742
+ }
743
+
744
+ if (MatchingCustomEncoder) {
745
+ // @ts-expect-error "Can't create instance of abstract class 🤓"
746
+ this.customEncoder = new MatchingCustomEncoder() as CustomVideoEncoder;
747
+ // @ts-expect-error It's technically readonly
748
+ this.customEncoder.codec = this.encodingConfig.codec;
749
+ // @ts-expect-error It's technically readonly
750
+ this.customEncoder.config = encoderConfig;
751
+ // @ts-expect-error It's technically readonly
752
+ this.customEncoder.onPacket = (packet, meta) => {
753
+ if (!(packet instanceof EncodedPacket)) {
754
+ throw new TypeError('The first argument passed to onPacket must be an EncodedPacket.');
755
+ }
756
+ if (meta !== undefined && (!meta || typeof meta !== 'object')) {
757
+ throw new TypeError('The second argument passed to onPacket must be an object or undefined.');
758
+ }
759
+
760
+ maybeEnsureIsKeyPacket(this.source._connectedTrack!, packet);
761
+
762
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
763
+ this.lastMuxerPromise
764
+ = this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta)
765
+ .catch((error) => {
766
+ this.setError(error);
767
+ });
768
+ };
769
+ // @ts-expect-error It's technically readonly
770
+ this.customEncoder.onError = (error) => {
771
+ this.setError(error);
772
+ };
773
+
774
+ await this.customEncoder.init();
775
+ } else {
776
+ /** Queue of color chunks waiting for their alpha counterpart. */
777
+ const colorChunkQueue: {
778
+ chunk: EncodedVideoChunk;
779
+ meta: EncodedVideoChunkMetadata | undefined;
780
+ }[] = [];
781
+ /** Each value is the number of encoded alpha chunks at which a null alpha chunk should be added. */
782
+ const nullAlphaChunkQueue: number[] = [];
783
+ let encodedAlphaChunkCount = 0;
784
+ let alphaEncoderQueue = 0;
785
+
786
+ const addPacket = (
787
+ colorChunk: EncodedVideoChunk,
788
+ alphaChunk: EncodedVideoChunk | null,
789
+ meta: EncodedVideoChunkMetadata | undefined,
790
+ ) => {
791
+ const sideData: EncodedPacketSideData = {};
792
+
793
+ if (alphaChunk) {
794
+ const alphaData = new Uint8Array(alphaChunk.byteLength);
795
+ alphaChunk.copyTo(alphaData);
796
+
797
+ sideData.alpha = alphaData;
798
+ }
799
+
800
+ let packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData);
801
+
802
+ // See if there's a relevant timing entry to refine the packet's timing data
803
+ const preciseTimingIndex = binarySearchLessOrEqual(
804
+ this.preciseTimings,
805
+ colorChunk.timestamp,
806
+ x => x.microsecondTimestamp,
807
+ );
808
+ const entry = preciseTimingIndex !== -1
809
+ ? this.preciseTimings[preciseTimingIndex]
810
+ : null;
811
+
812
+ let actualType: PacketType | null = null;
813
+ if (this.emittedEncoderPackets === 0 && packet.type === 'delta' && meta?.decoderConfig) {
814
+ // https://github.com/Vanilagy/mediabunny/issues/365
815
+ // We expect the first packet to be a key packet. If it's not, let's actually verify that it's
816
+ // not by getting the actual type.
817
+ actualType = determineVideoPacketType(
818
+ this.encodingConfig.codec,
819
+ meta.decoderConfig,
820
+ packet.data,
821
+ );
822
+ }
823
+
824
+ // Define the packet
825
+ if ((entry && entry.microsecondTimestamp === colorChunk.timestamp) || actualType !== null) {
826
+ packet = packet.clone({
827
+ timestamp: entry?.timestampIsValid ? entry.timestamp : undefined,
828
+ duration: entry?.durationIsValid ? entry.duration : undefined,
829
+ type: actualType ?? undefined,
830
+ });
831
+ }
832
+
833
+ maybeEnsureIsKeyPacket(this.source._connectedTrack!, packet);
834
+
835
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
836
+ this.lastMuxerPromise
837
+ = this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta)
838
+ .catch((error) => {
839
+ this.setError(error);
840
+ });
841
+
842
+ this.emittedEncoderPackets++;
843
+ };
844
+
845
+ const stack = new Error('Encoding error').stack;
846
+
847
+ this.encoder = new VideoEncoder({
848
+ output: (chunk, meta) => {
849
+ if (!this.alphaEncoder) {
850
+ // We're done
851
+ addPacket(chunk, null, meta);
852
+ return;
853
+ }
854
+
855
+ const alphaFrame = this.alphaFrameQueue.shift();
856
+ assert(alphaFrame !== undefined);
857
+
858
+ if (alphaFrame) {
859
+ this.alphaEncoder.encode(alphaFrame, {
860
+ ...this.defaultEncodeOptions,
861
+ // Crucial: The alpha frame is forced to be a key frame whenever the color frame
862
+ // also is. Without this, playback can glitch and even crash in some browsers.
863
+ // This is the reason why the two encoders are wired in series and not in parallel.
864
+ keyFrame: chunk.type === 'key',
865
+ });
866
+ alphaEncoderQueue++;
867
+ alphaFrame.close();
868
+ colorChunkQueue.push({ chunk, meta });
869
+ } else {
870
+ // There was no alpha component for this frame
871
+ if (alphaEncoderQueue === 0) {
872
+ // No pending alpha encodes either, so we're done
873
+ addPacket(chunk, null, meta);
874
+ } else {
875
+ // There are still alpha encodes pending, so we can't add the packet immediately since
876
+ // we'd end up with out-of-order packets. Instead, let's queue a null alpha chunk to be
877
+ // added in the future, after the current encoder workload has completed:
878
+ nullAlphaChunkQueue.push(encodedAlphaChunkCount + alphaEncoderQueue);
879
+ colorChunkQueue.push({ chunk, meta });
880
+ }
881
+ }
882
+ },
883
+ error: (error) => {
884
+ error.stack = stack; // Provide a more useful stack trace, the default one sucks
885
+ this.setError(error);
886
+ },
887
+ });
888
+ this.encoder.configure(encoderConfig);
889
+
890
+ if (this.encodingConfig.alpha === 'keep') {
891
+ const stack = new Error('Encoding error').stack;
892
+
893
+ // We need to encode alpha as well, which we do with a separate encoder
894
+ this.alphaEncoder = new VideoEncoder({
895
+ // We ignore the alpha chunk's metadata
896
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
897
+ output: (chunk, meta) => {
898
+ alphaEncoderQueue--;
899
+
900
+ // There has to be a color chunk because the encoders are wired in series
901
+ const colorChunk = colorChunkQueue.shift();
902
+ assert(colorChunk !== undefined);
903
+
904
+ addPacket(colorChunk.chunk, chunk, colorChunk.meta);
905
+
906
+ // See if there are any null alpha chunks queued up
907
+ encodedAlphaChunkCount++;
908
+ while (
909
+ nullAlphaChunkQueue.length > 0
910
+ && nullAlphaChunkQueue[0] === encodedAlphaChunkCount
911
+ ) {
912
+ nullAlphaChunkQueue.shift();
913
+ const colorChunk = colorChunkQueue.shift();
914
+ assert(colorChunk !== undefined);
915
+
916
+ addPacket(colorChunk.chunk, null, colorChunk.meta);
917
+ }
918
+ },
919
+ error: (error) => {
920
+ error.stack = stack; // Provide a more useful stack trace
921
+ this.setError(error);
922
+ },
923
+ });
924
+ this.alphaEncoder.configure(encoderConfig);
925
+ }
926
+ }
927
+
928
+ assert(this.source._connectedTrack);
929
+ this.muxer = this.source._connectedTrack.output._muxer;
930
+
931
+ this.encoderInitialized = true;
932
+ })();
933
+ }
934
+
935
+ async flushAndClose(forceClose: boolean) {
936
+ try {
937
+ if (!forceClose) {
938
+ this.checkForEncoderError();
939
+
940
+ // Final frame rate padding: fill remaining frames up to the last sample's original end timestamp
941
+ if (this.frameRateLastSample) {
942
+ const frameRate = this.encodingConfig.transform!.frameRate!;
943
+ const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp!, frameRate);
944
+ await this.padFrameRate(alignedEnd);
945
+ }
946
+ }
947
+
948
+ this.closed = true;
949
+
950
+ if (!forceClose) {
951
+ if (this.customEncoder) {
952
+ void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
953
+ } else if (this.encoder) {
954
+ // These are wired in series, therefore they must also be flushed in series
955
+ await this.encoder.flush();
956
+ await this.alphaEncoder?.flush();
957
+
958
+ // Workaround for https://issues.chromium.org/issues/529852980 to give it time for errors to
959
+ // surface
960
+ await wait(25);
961
+ }
962
+ }
963
+ } finally {
964
+ // This cleanup must also run when padding or flushing threw (e.g. due to an encoder error), otherwise
965
+ // samples, frames and encoders would be left dangling
966
+ this.closed = true;
967
+
968
+ this.frameRateLastSample?.close();
969
+ this.frameRateLastSample = null;
970
+
971
+ if (this.customEncoder) {
972
+ await this.customEncoderCallSerializer.call(() => this.customEncoder!.close())
973
+ .catch((error: unknown) => this.setError(error));
974
+ } else if (this.encoder) {
975
+ if (this.encoder.state !== 'closed') {
976
+ this.encoder.close();
977
+ }
978
+ if (this.alphaEncoder && this.alphaEncoder.state !== 'closed') {
979
+ this.alphaEncoder.close();
980
+ }
981
+
982
+ this.alphaFrameQueue.forEach(x => x?.close());
983
+ this.alphaFrameQueue.length = 0;
984
+
985
+ this.splitter?.close();
986
+ }
987
+ }
988
+
989
+ if (!forceClose) {
990
+ this.checkForEncoderError();
991
+ }
992
+ }
993
+
994
+ getQueueSize() {
995
+ if (this.customEncoder) {
996
+ return this.customEncoderQueueSize;
997
+ } else {
998
+ // Because the color and alpha encoders are wired in series, there's no need to also include the alpha
999
+ // encoder's queue size here
1000
+ return this.encoder?.encodeQueueSize ?? 0;
1001
+ }
1002
+ }
1003
+
1004
+ checkForEncoderError() {
1005
+ if (this.errorSet) {
1006
+ throw this.error;
1007
+ }
1008
+ }
1009
+ }
1010
+
1011
+ let splitterWorkerUrl: string | null = null;
1012
+
1013
+ /** Utility class for splitting a composite frame into separate color and alpha parts on the CPU in a worker. */
1014
+ export class ColorAlphaSplitter {
1015
+ private worker: Worker | null = null;
1016
+ private pendingRequests = new Map<
1017
+ number,
1018
+ ReturnType<typeof promiseWithResolvers<{ colorFrame: VideoFrame; alphaFrame: VideoFrame }>>
1019
+ >();
1020
+
1021
+ private nextRequestId = 0;
1022
+
1023
+ split(sourceFrame: VideoFrame): Promise<{ colorFrame: VideoFrame; alphaFrame: VideoFrame }> {
1024
+ if (!this.worker) {
1025
+ if (!splitterWorkerUrl) {
1026
+ const blob = new Blob(
1027
+ [`(${colorAlphaSplitterWorkerCode.toString()})()`],
1028
+ { type: 'application/javascript' },
1029
+ );
1030
+ splitterWorkerUrl = URL.createObjectURL(blob);
1031
+ }
1032
+
1033
+ this.worker = new Worker(splitterWorkerUrl);
1034
+
1035
+ this.worker.addEventListener('message', (event: MessageEvent<ColorAlphaSplitterWorkerResponse>) => {
1036
+ const data = event.data;
1037
+ const pending = this.pendingRequests.get(data.id);
1038
+ if (!pending) {
1039
+ return;
1040
+ }
1041
+ this.pendingRequests.delete(data.id);
1042
+
1043
+ if ('error' in data) {
1044
+ pending.reject(new Error(data.error));
1045
+ } else {
1046
+ pending.resolve({ colorFrame: data.colorFrame, alphaFrame: data.alphaFrame });
1047
+ }
1048
+ });
1049
+
1050
+ this.worker.addEventListener('error', (event) => {
1051
+ const error = new Error(event.message || 'Color/alpha splitter worker error.');
1052
+ for (const pending of this.pendingRequests.values()) {
1053
+ pending.reject(error);
1054
+ }
1055
+ this.pendingRequests.clear();
1056
+ });
1057
+ }
1058
+
1059
+ const id = this.nextRequestId++;
1060
+ const pending = promiseWithResolvers<{ colorFrame: VideoFrame; alphaFrame: VideoFrame }>();
1061
+ this.pendingRequests.set(id, pending);
1062
+ this.worker.postMessage({ id, sourceFrame }, { transfer: [sourceFrame] });
1063
+
1064
+ return pending.promise;
1065
+ }
1066
+
1067
+ close() {
1068
+ this.worker?.terminate();
1069
+ this.worker = null;
1070
+
1071
+ const error = new Error('Color/alpha splitter closed.');
1072
+ for (const pending of this.pendingRequests.values()) {
1073
+ pending.reject(error);
1074
+ }
1075
+ this.pendingRequests.clear();
1076
+ }
1077
+ }
1078
+
1079
+ type ColorAlphaSplitterWorkerRequest = {
1080
+ id: number;
1081
+ sourceFrame: VideoFrame;
1082
+ };
1083
+
1084
+ type ColorAlphaSplitterWorkerResponse =
1085
+ | { id: number; colorFrame: VideoFrame; alphaFrame: VideoFrame }
1086
+ | { id: number; error: string };
1087
+
1088
+ const colorAlphaSplitterWorkerCode = () => {
1089
+ // Reused across frames as long as the size matches, since consecutive frames usually share dimensions.
1090
+ let cpuSourceBuffer: Uint8Array | null = null;
1091
+
1092
+ // Serialize execution internally so concurrent requests don't race on the shared cpuSourceBuffer.
1093
+ let chain: Promise<void> = Promise.resolve();
1094
+ self.addEventListener('message', (event: MessageEvent<ColorAlphaSplitterWorkerRequest>) => {
1095
+ const { id, sourceFrame } = event.data;
1096
+ chain = chain.then(async () => {
1097
+ try {
1098
+ const { colorFrame, alphaFrame } = await split(sourceFrame);
1099
+ self.postMessage({ id, colorFrame, alphaFrame }, { transfer: [colorFrame, alphaFrame] });
1100
+ } catch (error) {
1101
+ self.postMessage({ id, error: (error as Error).message });
1102
+ } finally {
1103
+ sourceFrame.close();
1104
+ }
1105
+ });
1106
+ });
1107
+
1108
+ const split = async (sourceFrame: VideoFrame) => {
1109
+ const format = sourceFrame.format as VideoSamplePixelFormat | null;
1110
+ if (!format) {
1111
+ throw new Error('CPU color/alpha splitting requires a known VideoFrame format.');
1112
+ }
1113
+
1114
+ const sourceSize = sourceFrame.allocationSize();
1115
+
1116
+ if (!cpuSourceBuffer || cpuSourceBuffer.byteLength !== sourceSize) {
1117
+ cpuSourceBuffer = new Uint8Array(sourceSize);
1118
+ }
1119
+ await sourceFrame.copyTo(cpuSourceBuffer);
1120
+
1121
+ if (format === 'RGBA' || format === 'BGRA') {
1122
+ return splitInterleavedRgba(cpuSourceBuffer, format, sourceFrame);
1123
+ } else if (
1124
+ format === 'I420A' || format === 'I420AP10' || format === 'I420AP12'
1125
+ || format === 'I422A' || format === 'I422AP10' || format === 'I422AP12'
1126
+ || format === 'I444A' || format === 'I444AP10' || format === 'I444AP12'
1127
+ ) {
1128
+ return splitPlanarYuvA(cpuSourceBuffer, format, sourceFrame);
1129
+ }
1130
+
1131
+ throw new Error(`CPU color/alpha splitting does not support format '${format}'.`);
1132
+ };
1133
+
1134
+ const splitInterleavedRgba = (
1135
+ source: Uint8Array,
1136
+ format: 'RGBA' | 'BGRA',
1137
+ sourceFrame: VideoFrame,
1138
+ ) => {
1139
+ const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth;
1140
+ const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight;
1141
+
1142
+ const pixelCount = width * height;
1143
+ const chromaW = Math.ceil(width / 2);
1144
+ const chromaH = Math.ceil(height / 2);
1145
+ const alphaSize = pixelCount + chromaW * chromaH * 2;
1146
+
1147
+ // Encode alpha as I420: Y = source A bytes, UV = 128
1148
+ const alphaBuffer = new Uint8Array(alphaSize);
1149
+ for (let i = 0, j = 3; i < pixelCount; i++, j += 4) {
1150
+ alphaBuffer[i] = source[j]!;
1151
+ }
1152
+ alphaBuffer.fill(128, pixelCount);
1153
+
1154
+ // Hand the source buffer straight to VideoFrame as RGBX/BGRX so the A bytes are ignored
1155
+ const colorFrame = new VideoFrame(source, {
1156
+ format: format === 'RGBA' ? 'RGBX' : 'BGRX',
1157
+ codedWidth: width,
1158
+ codedHeight: height,
1159
+ timestamp: sourceFrame.timestamp,
1160
+ duration: sourceFrame.duration ?? undefined,
1161
+ // No transfer!
1162
+ });
1163
+ const alphaInit = {
1164
+ format: 'I420' as const,
1165
+ codedWidth: width,
1166
+ codedHeight: height,
1167
+ timestamp: sourceFrame.timestamp,
1168
+ duration: sourceFrame.duration ?? undefined,
1169
+ transfer: [alphaBuffer.buffer],
1170
+ };
1171
+ const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
1172
+ return { colorFrame, alphaFrame };
1173
+ };
1174
+
1175
+ const splitPlanarYuvA = (
1176
+ source: Uint8Array,
1177
+ format:
1178
+ | 'I420A' | 'I420AP10' | 'I420AP12'
1179
+ | 'I422A' | 'I422AP10' | 'I422AP12'
1180
+ | 'I444A' | 'I444AP10' | 'I444AP12',
1181
+ sourceFrame: VideoFrame,
1182
+ ) => {
1183
+ const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth;
1184
+ const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight;
1185
+
1186
+ const is10 = format.includes('P10');
1187
+ const is12 = format.includes('P12');
1188
+ const bytesPerSample = (is10 || is12) ? 2 : 1;
1189
+
1190
+ let chromaW: number;
1191
+ let chromaH: number;
1192
+ if (format.startsWith('I420')) {
1193
+ chromaW = Math.ceil(width / 2);
1194
+ chromaH = Math.ceil(height / 2);
1195
+ } else if (format.startsWith('I422')) {
1196
+ chromaW = Math.ceil(width / 2);
1197
+ chromaH = height;
1198
+ } else {
1199
+ chromaW = width;
1200
+ chromaH = height;
1201
+ }
1202
+
1203
+ const ySamples = width * height;
1204
+ const uvSamples = chromaW * chromaH;
1205
+ const yBytes = ySamples * bytesPerSample;
1206
+ const uvBytes = uvSamples * bytesPerSample;
1207
+ const aBytes = ySamples * bytesPerSample;
1208
+
1209
+ const colorBytes = yBytes + uvBytes * 2;
1210
+ const colorFormat = format.replace('A', '') as VideoPixelFormat;
1211
+
1212
+ const alphaChromaW = Math.ceil(width / 2);
1213
+ const alphaChromaH = Math.ceil(height / 2);
1214
+ const alphaUvSamples = alphaChromaW * alphaChromaH;
1215
+ const alphaUvBytes = alphaUvSamples * bytesPerSample;
1216
+ const alphaSize = aBytes + 2 * alphaUvBytes;
1217
+ const alphaBuffer = new Uint8Array(alphaSize);
1218
+
1219
+ const aPlaneStart = colorBytes;
1220
+ alphaBuffer.set(source.subarray(aPlaneStart, aPlaneStart + aBytes), 0);
1221
+
1222
+ // Fill UV planes with the neutral chroma value
1223
+ const uvOffset = aBytes;
1224
+ const neutralChroma = is10 ? 512 : (is12 ? 2048 : 128);
1225
+
1226
+ if (bytesPerSample === 1) {
1227
+ alphaBuffer.fill(neutralChroma, uvOffset);
1228
+ } else {
1229
+ const uvView = new Uint16Array(alphaBuffer.buffer, uvOffset, 2 * alphaUvSamples);
1230
+ uvView.fill(neutralChroma);
1231
+ }
1232
+
1233
+ const alphaFormat = (is10 ? 'I420P10' : (is12 ? 'I420P12' : 'I420')) as VideoPixelFormat;
1234
+
1235
+ // Color frame is simply a prefix of the combined bytes
1236
+ const colorFrame = new VideoFrame(source.subarray(0, colorBytes), {
1237
+ format: colorFormat,
1238
+ codedWidth: width,
1239
+ codedHeight: height,
1240
+ timestamp: sourceFrame.timestamp,
1241
+ duration: sourceFrame.duration ?? undefined,
1242
+ });
1243
+ const alphaInit = {
1244
+ format: alphaFormat,
1245
+ codedWidth: width,
1246
+ codedHeight: height,
1247
+ timestamp: sourceFrame.timestamp,
1248
+ duration: sourceFrame.duration ?? undefined,
1249
+ transfer: [alphaBuffer.buffer],
1250
+ };
1251
+ const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
1252
+ return { colorFrame, alphaFrame };
1253
+ };
1254
+ };
1255
+
1256
+ /**
1257
+ * This source can be used to add raw, unencoded video samples (frames) to an output video track. These frames will
1258
+ * automatically be encoded and then piped into the output.
1259
+ * @group Media sources
1260
+ * @public
1261
+ */
1262
+ export class VideoSampleSource extends VideoSource {
1263
+ /** @internal */
1264
+ private _encoder: VideoEncoderWrapper;
1265
+
1266
+ /**
1267
+ * Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified
1268
+ * {@link VideoEncodingConfig}.
1269
+ */
1270
+ constructor(encodingConfig: VideoEncodingConfig) {
1271
+ validateVideoEncodingConfig(encodingConfig);
1272
+
1273
+ super(encodingConfig.codec);
1274
+ this._encoder = new VideoEncoderWrapper(this, encodingConfig);
1275
+ }
1276
+
1277
+ /**
1278
+ * Encodes a video sample (frame) and then adds it to the output.
1279
+ *
1280
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
1281
+ * to respect writer and encoder backpressure.
1282
+ */
1283
+ add(videoSample: VideoSample, encodeOptions?: VideoEncoderEncodeOptions) {
1284
+ if (!(videoSample instanceof VideoSample)) {
1285
+ throw new TypeError('videoSample must be a VideoSample.');
1286
+ }
1287
+
1288
+ return this._encoder.add(videoSample, false, encodeOptions);
1289
+ }
1290
+
1291
+ /** @internal */
1292
+ override _flushAndClose(forceClose: boolean) {
1293
+ return this._encoder.flushAndClose(forceClose);
1294
+ }
1295
+ }
1296
+
1297
+ /**
1298
+ * This source can be used to add video frames to the output track from a fixed canvas element. Since canvases are often
1299
+ * used for rendering, this source provides a convenient wrapper around {@link VideoSampleSource}.
1300
+ * @group Media sources
1301
+ * @public
1302
+ */
1303
+ export class CanvasSource extends VideoSource {
1304
+ /** @internal */
1305
+ private _encoder: VideoEncoderWrapper;
1306
+ /** @internal */
1307
+ private _canvas: HTMLCanvasElement | OffscreenCanvas;
1308
+
1309
+ /**
1310
+ * Creates a new {@link CanvasSource} from a canvas element or `OffscreenCanvas` whose samples are encoded
1311
+ * according to the specified {@link VideoEncodingConfig}.
1312
+ */
1313
+ constructor(canvas: HTMLCanvasElement | OffscreenCanvas, encodingConfig: VideoEncodingConfig) {
1314
+ if (
1315
+ !(typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement)
1316
+ && !(typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)
1317
+ ) {
1318
+ throw new TypeError('canvas must be an HTMLCanvasElement or OffscreenCanvas.');
1319
+ }
1320
+ validateVideoEncodingConfig(encodingConfig);
1321
+
1322
+ super(encodingConfig.codec);
1323
+ this._encoder = new VideoEncoderWrapper(this, encodingConfig);
1324
+ this._canvas = canvas;
1325
+ }
1326
+
1327
+ /**
1328
+ * Captures the current canvas state as a video sample (frame), encodes it and adds it to the output.
1329
+ *
1330
+ * @param timestamp - The timestamp of the sample, in seconds.
1331
+ * @param duration - The duration of the sample, in seconds.
1332
+ *
1333
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
1334
+ * to respect writer and encoder backpressure.
1335
+ */
1336
+ add(timestamp: number, duration = 0, encodeOptions?: VideoEncoderEncodeOptions) {
1337
+ if (!Number.isFinite(timestamp) || timestamp < 0) {
1338
+ throw new TypeError('timestamp must be a non-negative number.');
1339
+ }
1340
+ if (!Number.isFinite(duration) || duration < 0) {
1341
+ throw new TypeError('duration must be a non-negative number.');
1342
+ }
1343
+
1344
+ const sample = new VideoSample(this._canvas, { timestamp, duration });
1345
+ return this._encoder.add(sample, true, encodeOptions);
1346
+ }
1347
+
1348
+ /** @internal */
1349
+ override _flushAndClose(forceClose: boolean) {
1350
+ return this._encoder.flushAndClose(forceClose);
1351
+ }
1352
+ }
1353
+
1354
+ /**
1355
+ * Options for {@link MediaStreamVideoTrackSource}.
1356
+ * @group Media sources
1357
+ * @public
1358
+ */
1359
+ export type MediaStreamVideoTrackSourceOptions = {
1360
+ /**
1361
+ * The frame rate at which the underlying video track is sampled. Defaults to the frame rate specified in the
1362
+ * track's [`MediaTrackSettings`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings). Set to
1363
+ * `null` to only add a frame whenever the underlying track pushes one - this minimizes frame count but can
1364
+ * lead to wildly irregular FPS.
1365
+ */
1366
+ frameRate?: number | null;
1367
+ /**
1368
+ * Controls the basis (zero point) for video frame timestamps.
1369
+ *
1370
+ * When set to `'synced-zero'`, timestamps will be relative to the first chunk of media from a `MediaStreamTrack`
1371
+ * added to the {@link Output}.
1372
+ *
1373
+ * When set to `'zero'`, timestamps will be relative to the first video frame emitted by this source.
1374
+ *
1375
+ * When set to `'unix'`, timestamps will be relative to the Unix epoch, so clearly associated with a distinct point
1376
+ * in time. Here, pausing via {@link MediaStreamVideoTrackSource.pause} will also create gaps in timestamps. Be sure
1377
+ * to pair this mode with {@link BaseTrackMetadata.isRelativeToUnixEpoch}.
1378
+ *
1379
+ * Defaults to `'synced-zero'`.
1380
+ */
1381
+ timestampBase?: 'synced-zero' | 'zero' | 'unix';
1382
+ };
1383
+
1384
+ /**
1385
+ * Video source that encodes the frames of a
1386
+ * [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes them into the
1387
+ * output. This is useful for capturing live or real-time data such as webcams or screen captures. Frames will
1388
+ * automatically start being captured once the connected {@link Output} is started, and will keep being captured until
1389
+ * the {@link Output} is finalized or this source is closed.
1390
+ * @group Media sources
1391
+ * @public
1392
+ */
1393
+ export class MediaStreamVideoTrackSource extends VideoSource {
1394
+ /** @internal */
1395
+ private _options: MediaStreamVideoTrackSourceOptions;
1396
+ /** @internal */
1397
+ private _encoder: VideoEncoderWrapper;
1398
+ /** @internal */
1399
+ private _abortController: AbortController | null = null;
1400
+ /** @internal */
1401
+ private _track: MediaStreamVideoTrack;
1402
+ /** @internal */
1403
+ private _workerTrackId: number | null = null;
1404
+ /** @internal */
1405
+ private _workerListener: ((event: MessageEvent) => void) | null = null;
1406
+ /** @internal */
1407
+ private _promiseWithResolvers = promiseWithResolvers();
1408
+ /** @internal */
1409
+ private _errorPromiseAccessed = false;
1410
+ /** @internal */
1411
+ private _paused = false;
1412
+ /** @internal */
1413
+ private _lastVideoFrame: VideoFrame | null = null;
1414
+ /** @internal */
1415
+ private _timerHandle: UnthrottledTimerHandle | null = null;
1416
+ /** @internal */
1417
+ private _videoElement: HTMLVideoElement | null = null;
1418
+
1419
+ /** A promise that rejects upon any error within this source. This promise never resolves. */
1420
+ get errorPromise() {
1421
+ this._errorPromiseAccessed = true;
1422
+ return this._promiseWithResolvers.promise;
1423
+ }
1424
+
1425
+ /** Whether this source is currently paused as a result of calling `.pause()`. */
1426
+ get paused() {
1427
+ return this._paused;
1428
+ }
1429
+
1430
+ /**
1431
+ * Creates a new {@link MediaStreamVideoTrackSource} from a
1432
+ * [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack), which will pull
1433
+ * video samples from the stream in real time and encode them according to {@link VideoEncodingConfig}.
1434
+ */
1435
+ constructor(
1436
+ track: MediaStreamVideoTrack,
1437
+ encodingConfig: VideoEncodingConfig,
1438
+ options: MediaStreamVideoTrackSourceOptions = {},
1439
+ ) {
1440
+ if (!(track instanceof MediaStreamTrack) || track.kind !== 'video') {
1441
+ throw new TypeError('track must be a video MediaStreamTrack.');
1442
+ }
1443
+ validateVideoEncodingConfig(encodingConfig);
1444
+ if (typeof options !== 'object' || !options) {
1445
+ throw new TypeError('options must be an object.');
1446
+ }
1447
+ if (options.frameRate != null && (typeof options.frameRate !== 'number' || options.frameRate <= 0)) {
1448
+ throw new TypeError('options.frameRate, when provided, must be either a positive number or null.');
1449
+ }
1450
+ if (
1451
+ options.timestampBase !== undefined
1452
+ && options.timestampBase !== 'synced-zero'
1453
+ && options.timestampBase !== 'zero'
1454
+ && options.timestampBase !== 'unix'
1455
+ ) {
1456
+ throw new TypeError(
1457
+ 'options.timestampBase, when provided, must be one of \'synced-zero\', \'zero\', or \'unix\'.',
1458
+ );
1459
+ }
1460
+
1461
+ encodingConfig = {
1462
+ ...encodingConfig,
1463
+ latencyMode: 'realtime',
1464
+ };
1465
+
1466
+ super(encodingConfig.codec);
1467
+
1468
+ this._options = options;
1469
+ this._encoder = new VideoEncoderWrapper(this, encodingConfig);
1470
+ this._track = track;
1471
+ }
1472
+
1473
+ /** @internal */
1474
+ override async _start() {
1475
+ if (!this._errorPromiseAccessed) {
1476
+ Logging._warn(
1477
+ 'Make sure not to ignore the `errorPromise` field on MediaStreamVideoTrackSource, so that any internal'
1478
+ + ' errors get bubbled up properly.',
1479
+ );
1480
+ }
1481
+
1482
+ const frameRate = this._options.frameRate !== undefined
1483
+ ? this._options.frameRate
1484
+ : (this._track.getSettings().frameRate ?? null);
1485
+
1486
+ this._abortController = new AbortController();
1487
+
1488
+ let firstVideoFrameTimestamp: number | null = null;
1489
+ let lastFrameTime: number | null = null;
1490
+ let frameCount = 0;
1491
+ let errored = false;
1492
+ let lastSampleTimestamp: number | null = null;
1493
+ let timestampOffset = 0;
1494
+
1495
+ const tick = () => {
1496
+ assert(frameRate !== null);
1497
+
1498
+ if (!this._lastVideoFrame) {
1499
+ return;
1500
+ }
1501
+
1502
+ assert(lastFrameTime !== null);
1503
+ assert(firstVideoFrameTimestamp !== null);
1504
+
1505
+ const now = performance.now();
1506
+
1507
+ // Add as many frames as warranted by the elapsed time.
1508
+ // > instead of >= intentionally because tick() is called before the _lastVideoFrame is changed
1509
+ while (now - lastFrameTime > 1000 / frameRate) {
1510
+ lastFrameTime += 1000 / frameRate;
1511
+ const timestamp = firstVideoFrameTimestamp + frameCount / frameRate;
1512
+
1513
+ const frame = new VideoFrame(this._videoElement ?? this._lastVideoFrame, {
1514
+ timestamp: 1e6 * timestamp,
1515
+ duration: 1e6 / frameRate,
1516
+ });
1517
+
1518
+ addVideoFrame(frame, now);
1519
+ }
1520
+ };
1521
+
1522
+ if (frameRate !== null) {
1523
+ this._timerHandle = setIntervalUnthrottled(tick, 4); // Run it at 250 Hz
1524
+ }
1525
+
1526
+ const onVideoFrame = (videoFrame: VideoFrame) => {
1527
+ if (frameRate === null) {
1528
+ addVideoFrame(videoFrame);
1529
+ } else {
1530
+ const now = performance.now();
1531
+
1532
+ if (!this._lastVideoFrame) {
1533
+ addVideoFrame(videoFrame.clone(), now);
1534
+ lastFrameTime = now;
1535
+ this._lastVideoFrame = videoFrame;
1536
+ } else {
1537
+ tick();
1538
+ this._lastVideoFrame?.close();
1539
+ this._lastVideoFrame = videoFrame;
1540
+ }
1541
+ }
1542
+ };
1543
+
1544
+ const addVideoFrame = (videoFrame: VideoFrame, now = performance.now()) => {
1545
+ if (errored) {
1546
+ videoFrame.close();
1547
+ return;
1548
+ }
1549
+
1550
+ frameCount++;
1551
+ const currentTimestamp = videoFrame.timestamp / 1e6;
1552
+
1553
+ if (this._paused) {
1554
+ const frameSeen = firstVideoFrameTimestamp !== null;
1555
+ if (frameSeen) {
1556
+ if (lastSampleTimestamp !== null && this._options.timestampBase !== 'unix') {
1557
+ // In addition to dropping this frame, let's also keep track of the time we have lost due to the
1558
+ // pause. Doing it like this instead of simply keeping track of the paused time is better since
1559
+ // it retains the frame rate of the underlying source.
1560
+ const timeDelta = currentTimestamp - lastSampleTimestamp;
1561
+ timestampOffset -= timeDelta;
1562
+ }
1563
+ lastSampleTimestamp = currentTimestamp;
1564
+ }
1565
+
1566
+ videoFrame.close();
1567
+ return;
1568
+ }
1569
+
1570
+ if (firstVideoFrameTimestamp === null) {
1571
+ firstVideoFrameTimestamp = currentTimestamp;
1572
+
1573
+ let target: number;
1574
+ const timestampBase = this._options.timestampBase ?? 'synced-zero';
1575
+ if (timestampBase === 'unix') {
1576
+ target = Date.now() / 1000;
1577
+ } else if (timestampBase === 'zero') {
1578
+ target = 0;
1579
+ } else {
1580
+ const output = this._connectedTrack!.output;
1581
+ if (output._firstMediaStreamTimestamp === null) {
1582
+ output._firstMediaStreamTimestamp = now / 1000;
1583
+ target = 0;
1584
+ } else {
1585
+ target = now / 1000 - output._firstMediaStreamTimestamp;
1586
+ }
1587
+ }
1588
+
1589
+ timestampOffset = target - firstVideoFrameTimestamp;
1590
+ }
1591
+
1592
+ lastSampleTimestamp = currentTimestamp;
1593
+
1594
+ if (this._encoder.getQueueSize() >= 8) {
1595
+ // Drop frames if the encoder is overloaded
1596
+ videoFrame.close();
1597
+ return;
1598
+ }
1599
+
1600
+ const sample = new VideoSample(videoFrame, {
1601
+ timestamp: currentTimestamp + timestampOffset,
1602
+ });
1603
+
1604
+ void this._encoder.add(sample, true)
1605
+ .catch((error) => {
1606
+ errored = true;
1607
+
1608
+ this._abortController?.abort();
1609
+ this._promiseWithResolvers.reject(error);
1610
+
1611
+ if (this._workerTrackId !== null) {
1612
+ // Tell the worker to stop the track
1613
+ sendMessageToMediaStreamTrackProcessorWorker({
1614
+ type: 'stopTrack',
1615
+ trackId: this._workerTrackId,
1616
+ });
1617
+ }
1618
+ });
1619
+ };
1620
+
1621
+ if (typeof MediaStreamTrackProcessor !== 'undefined') {
1622
+ // We can do it here directly, perfect
1623
+ const processor = new MediaStreamTrackProcessor({ track: this._track });
1624
+ const consumer = new WritableStream<VideoFrame>({ write: onVideoFrame });
1625
+
1626
+ processor.readable.pipeTo(consumer, {
1627
+ signal: this._abortController.signal,
1628
+ }).catch((error) => {
1629
+ // Handle AbortError silently
1630
+ if (error instanceof DOMException && error.name === 'AbortError') return;
1631
+
1632
+ this._promiseWithResolvers.reject(error);
1633
+ });
1634
+ } else {
1635
+ // It might still be supported in a worker, so let's check that
1636
+ const supportedInWorker = await mediaStreamTrackProcessorIsSupportedInWorker();
1637
+
1638
+ if (supportedInWorker) {
1639
+ this._workerTrackId = nextMediaStreamTrackProcessorWorkerId++;
1640
+
1641
+ sendMessageToMediaStreamTrackProcessorWorker({
1642
+ type: 'videoTrack',
1643
+ trackId: this._workerTrackId,
1644
+ track: this._track,
1645
+ });
1646
+
1647
+ this._workerListener = (event: MessageEvent) => {
1648
+ const message = event.data as MediaStreamTrackProcessorWorkerMessage;
1649
+
1650
+ if (message.type === 'videoFrame' && message.trackId === this._workerTrackId) {
1651
+ onVideoFrame(message.videoFrame);
1652
+ } else if (message.type === 'error' && message.trackId === this._workerTrackId) {
1653
+ this._promiseWithResolvers.reject(message.error);
1654
+ }
1655
+ };
1656
+
1657
+ mediaStreamTrackProcessorWorker!.addEventListener('message', this._workerListener);
1658
+ } else if (frameRate !== null) {
1659
+ // No MediaStreamTrackProcessor support at all (e.g. Firefox), but we have a frame rate, so we can
1660
+ // manually sample from a hidden <video> element instead.
1661
+ const video = document.createElement('video');
1662
+ video.style.position = 'fixed';
1663
+ video.style.left = '-10000px';
1664
+ video.style.top = '-10000px';
1665
+ video.style.width = '1px';
1666
+ video.style.height = '1px';
1667
+ video.style.opacity = '0';
1668
+ video.style.pointerEvents = 'none';
1669
+ video.muted = true;
1670
+ video.srcObject = new MediaStream([this._track]);
1671
+ document.body.appendChild(video);
1672
+ this._videoElement = video;
1673
+
1674
+ video.addEventListener('loadeddata', () => {
1675
+ if (errored || !this._videoElement) {
1676
+ return;
1677
+ }
1678
+
1679
+ // This will be the first frame
1680
+ const frame = new VideoFrame(video, {
1681
+ timestamp: 1000 * performance.now(),
1682
+ });
1683
+ onVideoFrame(frame);
1684
+ frame.close();
1685
+ }, { once: true });
1686
+
1687
+ void video.play().catch((error) => {
1688
+ errored = true;
1689
+ this._promiseWithResolvers.reject(error);
1690
+ });
1691
+ } else {
1692
+ throw new Error(
1693
+ 'When no explicit frame rate is set, MediaStreamTrackProcessor is required; but it\'s not supported'
1694
+ + ' by this browser.',
1695
+ );
1696
+ }
1697
+ }
1698
+ }
1699
+
1700
+ /**
1701
+ * Pauses the capture of video frames - any video frames emitted by the underlying media stream will be ignored
1702
+ * while paused. This does *not* close the underlying `MediaStreamVideoTrack`, it just ignores its output.
1703
+ */
1704
+ pause() {
1705
+ this._paused = true;
1706
+ }
1707
+
1708
+ /** Resumes the capture of video frames after being paused. */
1709
+ resume() {
1710
+ this._paused = false;
1711
+ }
1712
+
1713
+ /** @internal */
1714
+ override async _flushAndClose(forceClose: boolean) {
1715
+ if (this._abortController) {
1716
+ this._abortController.abort();
1717
+ this._abortController = null;
1718
+ }
1719
+
1720
+ if (this._timerHandle) {
1721
+ clearIntervalUnthrottled(this._timerHandle);
1722
+ }
1723
+ this._lastVideoFrame?.close();
1724
+
1725
+ if (this._videoElement) {
1726
+ this._videoElement.srcObject = null;
1727
+ this._videoElement.remove();
1728
+ this._videoElement = null;
1729
+ }
1730
+
1731
+ if (this._workerTrackId !== null) {
1732
+ assert(this._workerListener);
1733
+
1734
+ sendMessageToMediaStreamTrackProcessorWorker({
1735
+ type: 'stopTrack',
1736
+ trackId: this._workerTrackId,
1737
+ });
1738
+
1739
+ // Wait for the worker to stop the track
1740
+ await new Promise<void>((resolve) => {
1741
+ const listener = (event: MessageEvent) => {
1742
+ const message = event.data as MediaStreamTrackProcessorWorkerMessage;
1743
+
1744
+ if (message.type === 'trackStopped' && message.trackId === this._workerTrackId) {
1745
+ assert(this._workerListener);
1746
+ mediaStreamTrackProcessorWorker!.removeEventListener('message', this._workerListener);
1747
+ mediaStreamTrackProcessorWorker!.removeEventListener('message', listener);
1748
+
1749
+ resolve();
1750
+ }
1751
+ };
1752
+
1753
+ mediaStreamTrackProcessorWorker!.addEventListener('message', listener);
1754
+ });
1755
+ }
1756
+
1757
+ await this._encoder.flushAndClose(forceClose);
1758
+ }
1759
+ }
1760
+
1761
+ /**
1762
+ * Base class for audio sources - sources for audio tracks.
1763
+ * @group Media sources
1764
+ * @public
1765
+ */
1766
+ export abstract class AudioSource extends MediaSource {
1767
+ /** @internal */
1768
+ override _connectedTrack: OutputAudioTrack | null = null;
1769
+ /** @internal */
1770
+ override readonly _codec: AudioCodec;
1771
+
1772
+ /** Internal constructor. */
1773
+ constructor(codec: AudioCodec) {
1774
+ super();
1775
+
1776
+ if (!AUDIO_CODECS.includes(codec)) {
1777
+ throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
1778
+ }
1779
+
1780
+ this._codec = codec;
1781
+ }
1782
+ }
1783
+
1784
+ /**
1785
+ * The most basic audio source; can be used to directly pipe encoded packets into the output file.
1786
+ * @group Media sources
1787
+ * @public
1788
+ */
1789
+ export class EncodedAudioPacketSource extends AudioSource {
1790
+ /** Creates a new {@link EncodedAudioPacketSource} whose packets are encoded using `codec`. */
1791
+ constructor(codec: AudioCodec) {
1792
+ super(codec);
1793
+ }
1794
+
1795
+ /**
1796
+ * Adds an encoded packet to the output audio track. Packets must be added in *decode order*.
1797
+ *
1798
+ * @param meta - Additional metadata from the encoder. You should pass this for the first call, including a valid
1799
+ * decoder config.
1800
+ *
1801
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
1802
+ * to respect writer and encoder backpressure.
1803
+ */
1804
+ add(packet: EncodedPacket, meta?: EncodedAudioChunkMetadata) {
1805
+ if (!(packet instanceof EncodedPacket)) {
1806
+ throw new TypeError('packet must be an EncodedPacket.');
1807
+ }
1808
+ if (packet.isMetadataOnly) {
1809
+ throw new TypeError('Metadata-only packets cannot be added.');
1810
+ }
1811
+ if (meta !== undefined && (!meta || typeof meta !== 'object')) {
1812
+ throw new TypeError('meta, when provided, must be an object.');
1813
+ }
1814
+
1815
+ this._ensureValidAdd();
1816
+ return this._connectedTrack!.output._muxer.addEncodedAudioPacket(this._connectedTrack!, packet, meta);
1817
+ }
1818
+ }
1819
+
1820
+ class AudioEncoderWrapper {
1821
+ private ensureEncoderPromise: Promise<void> | null = null;
1822
+ private encoderInitialized = false;
1823
+ private encoder: AudioEncoder | null = null;
1824
+ private muxer: Muxer | null = null;
1825
+ private lastNumberOfChannels: number | null = null;
1826
+ private lastSampleRate: number | null = null;
1827
+
1828
+ private isPcmEncoder = false;
1829
+ private outputSampleSize: number | null = null;
1830
+ private writeOutputValue: ((view: DataView, byteOffset: number, value: number) => void) | null = null;
1831
+
1832
+ private customEncoder: CustomAudioEncoder | null = null;
1833
+ private customEncoderCallSerializer = new CallSerializer();
1834
+ private customEncoderQueueSize = 0;
1835
+
1836
+ private lastEndSampleIndex: number | null = null;
1837
+
1838
+ private resampler: AudioResampler | null = null;
1839
+
1840
+ /**
1841
+ * Encoders typically throw their errors "out of band", meaning asynchronously in some other execution context.
1842
+ * However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught.
1843
+ * So, we keep track of the encoder error and throw it as soon as we get the chance.
1844
+ */
1845
+ private error: unknown = null;
1846
+ private errorSet = false;
1847
+
1848
+ private setError(error: unknown) {
1849
+ if (!this.errorSet) {
1850
+ this.error = error;
1851
+ this.errorSet = true;
1852
+ }
1853
+ }
1854
+
1855
+ private lastMuxerPromise: Promise<void> = Promise.resolve();
1856
+ private closed = false;
1857
+
1858
+ constructor(private source: AudioSource, private encodingConfig: AudioEncodingConfig) {}
1859
+
1860
+ async add(audioSample: AudioSample, shouldClose: boolean) {
1861
+ try {
1862
+ this.checkForEncoderError();
1863
+ this.source._ensureValidAdd();
1864
+
1865
+ // Ensure audio parameters remain constant
1866
+ if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
1867
+ if (
1868
+ audioSample.numberOfChannels !== this.lastNumberOfChannels
1869
+ || audioSample.sampleRate !== this.lastSampleRate
1870
+ ) {
1871
+ throw new Error(
1872
+ `Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at`
1873
+ + ` ${this.lastSampleRate} Hz, got ${audioSample.numberOfChannels} channels at`
1874
+ + ` ${audioSample.sampleRate} Hz.`,
1875
+ );
1876
+ }
1877
+ } else {
1878
+ this.lastNumberOfChannels = audioSample.numberOfChannels;
1879
+ this.lastSampleRate = audioSample.sampleRate;
1880
+ }
1881
+
1882
+ const config = this.encodingConfig;
1883
+ const needsResample = config.transform?.numberOfChannels !== undefined
1884
+ || config.transform?.sampleRate !== undefined;
1885
+
1886
+ if (needsResample) {
1887
+ if (!this.resampler) {
1888
+ // Initialize the resampler on first sample
1889
+ this.resampler = new AudioResampler({
1890
+ targetNumberOfChannels: config.transform!.numberOfChannels
1891
+ ?? audioSample.numberOfChannels,
1892
+ targetSampleRate: config.transform!.sampleRate
1893
+ ?? audioSample.sampleRate,
1894
+ onSample: async (sample) => {
1895
+ await this.processAndEncode(sample, true);
1896
+ },
1897
+ });
1898
+ }
1899
+
1900
+ await this.resampler.add(audioSample);
1901
+ } else {
1902
+ await this.processAndEncode(audioSample, shouldClose);
1903
+ }
1904
+ } finally {
1905
+ if (shouldClose) {
1906
+ audioSample.close();
1907
+ }
1908
+ }
1909
+ }
1910
+
1911
+ /**
1912
+ * Runs the process function (if any) and encodes the resulting samples.
1913
+ */
1914
+ private async processAndEncode(audioSample: AudioSample, shouldClose: boolean) {
1915
+ const config = this.encodingConfig;
1916
+
1917
+ if (
1918
+ config.transform?.sampleFormat !== undefined
1919
+ && toInterleavedAudioFormat(audioSample.format) !== config.transform.sampleFormat
1920
+ ) {
1921
+ // Do a sample format conversion
1922
+ const newSample = audioSampleToInterleavedFormat(audioSample, config.transform.sampleFormat);
1923
+
1924
+ if (shouldClose) {
1925
+ audioSample.close();
1926
+ }
1927
+
1928
+ audioSample = newSample;
1929
+ shouldClose = true;
1930
+ }
1931
+
1932
+ if (config.transform?.process) {
1933
+ try {
1934
+ let processed = config.transform.process(audioSample);
1935
+ if (processed instanceof Promise) {
1936
+ processed = await processed;
1937
+ }
1938
+
1939
+ if (processed === null) {
1940
+ return;
1941
+ }
1942
+
1943
+ if (!Array.isArray(processed)) {
1944
+ processed = [processed];
1945
+ }
1946
+
1947
+ try {
1948
+ for (const sample of processed) {
1949
+ if (!(sample instanceof AudioSample)) {
1950
+ throw new TypeError(
1951
+ 'The audio process function must return an AudioSample, null, or an array of'
1952
+ + ' AudioSamples.',
1953
+ );
1954
+ }
1955
+ }
1956
+
1957
+ for (const sample of processed) {
1958
+ await this.encodeSample(sample, true);
1959
+ }
1960
+ } finally {
1961
+ // encodeSample closes the samples it was passed; this additionally covers the samples never
1962
+ // reached because an earlier one threw (closing is idempotent)
1963
+ for (const sample of processed) {
1964
+ if (sample instanceof AudioSample) {
1965
+ sample.close();
1966
+ }
1967
+ }
1968
+ }
1969
+ } finally {
1970
+ if (shouldClose) {
1971
+ audioSample.close();
1972
+ }
1973
+ }
1974
+ } else {
1975
+ await this.encodeSample(audioSample, shouldClose);
1976
+ }
1977
+ }
1978
+
1979
+ /**
1980
+ * Encodes a single audio sample, handling encoder init, gap padding, and backpressure.
1981
+ */
1982
+ private async encodeSample(audioSample: AudioSample, shouldClose: boolean) {
1983
+ try {
1984
+ if (!this.encoderInitialized) {
1985
+ if (!this.ensureEncoderPromise) {
1986
+ this.ensureEncoder(audioSample);
1987
+ }
1988
+
1989
+ // No, this "if" statement is not useless. Sometimes, the above call to `ensureEncoder` might have
1990
+ // synchronously completed and the encoder is already initialized. In this case, we don't need to await
1991
+ // the promise anymore. This also fixes nasty async race condition bugs when multiple code paths are
1992
+ // calling this method: It's important that the call that initialized the encoder go through this
1993
+ // code first.
1994
+ if (!this.encoderInitialized) {
1995
+ await this.ensureEncoderPromise;
1996
+ }
1997
+ }
1998
+ assert(this.encoderInitialized);
1999
+
2000
+ if (this.closed) {
2001
+ return;
2002
+ }
2003
+
2004
+ // Handle padding of gaps with silence to avoid audio drift over time, like in
2005
+ // https://github.com/Vanilagy/mediabunny/issues/176
2006
+ // TODO An open question is how encoders deal with the first AudioData having a non-zero timestamp, and with
2007
+ // AudioDatas that have an overlapping timestamp range.
2008
+ {
2009
+ const startSampleIndex = Math.round(
2010
+ audioSample.timestamp * audioSample.sampleRate,
2011
+ );
2012
+ const endSampleIndex = Math.round(
2013
+ (audioSample.timestamp + audioSample.duration) * audioSample.sampleRate,
2014
+ );
2015
+
2016
+ if (this.lastEndSampleIndex === null) {
2017
+ this.lastEndSampleIndex = endSampleIndex;
2018
+ } else {
2019
+ const sampleDiff = startSampleIndex - this.lastEndSampleIndex;
2020
+
2021
+ if (sampleDiff >= 64) {
2022
+ // The gap is big enough, let's add a correction sample
2023
+ const fillSample = new AudioSample({
2024
+ data: new Float32Array(sampleDiff * audioSample.numberOfChannels),
2025
+ format: 'f32-planar',
2026
+ sampleRate: audioSample.sampleRate,
2027
+ numberOfChannels: audioSample.numberOfChannels,
2028
+ numberOfFrames: sampleDiff,
2029
+ timestamp: this.lastEndSampleIndex / audioSample.sampleRate,
2030
+ });
2031
+
2032
+ await this.encodeSample(fillSample, true); // Recursive call
2033
+ }
2034
+
2035
+ this.lastEndSampleIndex += audioSample.numberOfFrames;
2036
+ }
2037
+ }
2038
+
2039
+ this.encodingConfig.onEncodedSample?.(audioSample);
2040
+
2041
+ if (this.customEncoder) {
2042
+ this.customEncoderQueueSize++;
2043
+
2044
+ // We clone the sample so it cannot be closed on us from the outside before it reaches the encoder
2045
+ const clonedSample = audioSample.clone();
2046
+
2047
+ const promise = this.customEncoderCallSerializer
2048
+ .call(() => this.customEncoder!.encode(clonedSample))
2049
+ .catch((error: unknown) => this.setError(error))
2050
+ .finally(() => {
2051
+ this.customEncoderQueueSize--;
2052
+ clonedSample.close();
2053
+ });
2054
+
2055
+ if (this.customEncoderQueueSize >= 4) {
2056
+ await promise;
2057
+ }
2058
+
2059
+ await this.lastMuxerPromise; // Allow the writer to apply backpressure
2060
+ } else if (this.isPcmEncoder) {
2061
+ await this.doPcmEncoding(audioSample, shouldClose);
2062
+ } else {
2063
+ assert(this.encoder);
2064
+ const audioData = audioSample.toAudioData();
2065
+ this.encoder.encode(audioData);
2066
+ audioData.close();
2067
+
2068
+ if (shouldClose) {
2069
+ audioSample.close();
2070
+ }
2071
+
2072
+ if (this.encoder.encodeQueueSize >= 4) {
2073
+ await new Promise(resolve => this.encoder!.addEventListener('dequeue', resolve, { once: true }));
2074
+ }
2075
+
2076
+ await this.lastMuxerPromise; // Allow the writer to apply backpressure
2077
+ }
2078
+ } finally {
2079
+ if (shouldClose) {
2080
+ audioSample.close();
2081
+ }
2082
+ }
2083
+ }
2084
+
2085
+ private async doPcmEncoding(audioSample: AudioSample, shouldClose: boolean) {
2086
+ assert(this.outputSampleSize);
2087
+ assert(this.writeOutputValue);
2088
+
2089
+ // Need to extract data from the audio data before we close it
2090
+ const { numberOfChannels, numberOfFrames, sampleRate, timestamp } = audioSample;
2091
+
2092
+ const CHUNK_SIZE = 2048;
2093
+ const outputs: {
2094
+ frameCount: number;
2095
+ view: DataView;
2096
+ }[] = [];
2097
+
2098
+ // Prepare all of the output buffers, each being bounded by CHUNK_SIZE so we don't generate huge packets
2099
+ for (let frame = 0; frame < numberOfFrames; frame += CHUNK_SIZE) {
2100
+ const frameCount = Math.min(CHUNK_SIZE, audioSample.numberOfFrames - frame);
2101
+ const outputSize = frameCount * numberOfChannels * this.outputSampleSize;
2102
+ const outputBuffer = new ArrayBuffer(outputSize);
2103
+ const outputView = new DataView(outputBuffer);
2104
+
2105
+ outputs.push({ frameCount, view: outputView });
2106
+ }
2107
+
2108
+ const allocationSize = audioSample.allocationSize(({ planeIndex: 0, format: 'f32-planar' }));
2109
+ const floats = new Float32Array(allocationSize / Float32Array.BYTES_PER_ELEMENT);
2110
+
2111
+ for (let i = 0; i < numberOfChannels; i++) {
2112
+ audioSample.copyTo(floats, { planeIndex: i, format: 'f32-planar' });
2113
+
2114
+ for (let j = 0; j < outputs.length; j++) {
2115
+ const { frameCount, view } = outputs[j]!;
2116
+
2117
+ for (let k = 0; k < frameCount; k++) {
2118
+ this.writeOutputValue(
2119
+ view,
2120
+ (k * numberOfChannels + i) * this.outputSampleSize,
2121
+ floats[j * CHUNK_SIZE + k]!,
2122
+ );
2123
+ }
2124
+ }
2125
+ }
2126
+
2127
+ if (shouldClose) {
2128
+ audioSample.close();
2129
+ }
2130
+
2131
+ const meta: EncodedAudioChunkMetadata = {
2132
+ decoderConfig: {
2133
+ codec: this.encodingConfig.codec,
2134
+ numberOfChannels,
2135
+ sampleRate,
2136
+ },
2137
+ };
2138
+
2139
+ for (let i = 0; i < outputs.length; i++) {
2140
+ const { frameCount, view } = outputs[i]!;
2141
+ const outputBuffer = view.buffer;
2142
+ const startFrame = i * CHUNK_SIZE;
2143
+
2144
+ const packet = new EncodedPacket(
2145
+ new Uint8Array(outputBuffer),
2146
+ 'key',
2147
+ timestamp + startFrame / sampleRate,
2148
+ frameCount / sampleRate,
2149
+ );
2150
+
2151
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
2152
+ await this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta); // With backpressure
2153
+ }
2154
+ }
2155
+
2156
+ private ensureEncoder(audioSample: AudioSample) {
2157
+ this.ensureEncoderPromise = (async () => {
2158
+ const { numberOfChannels, sampleRate } = audioSample;
2159
+
2160
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2161
+ const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
2162
+
2163
+ const encoderConfig = buildAudioEncoderConfig({
2164
+ numberOfChannels,
2165
+ sampleRate,
2166
+ ...this.encodingConfig,
2167
+ quality,
2168
+ });
2169
+ this.encodingConfig.onEncoderConfig?.(encoderConfig);
2170
+
2171
+ const MatchingCustomEncoder = customAudioEncoders.find(x => x.supports(
2172
+ this.encodingConfig.codec,
2173
+ encoderConfig,
2174
+ ));
2175
+
2176
+ if (MatchingCustomEncoder) {
2177
+ // @ts-expect-error "Can't create instance of abstract class 🤓"
2178
+ this.customEncoder = new MatchingCustomEncoder() as CustomAudioEncoder;
2179
+ // @ts-expect-error It's technically readonly
2180
+ this.customEncoder.codec = this.encodingConfig.codec;
2181
+ // @ts-expect-error It's technically readonly
2182
+ this.customEncoder.config = encoderConfig;
2183
+ // @ts-expect-error It's technically readonly
2184
+ this.customEncoder.onPacket = (packet, meta) => {
2185
+ if (!(packet instanceof EncodedPacket)) {
2186
+ throw new TypeError('The first argument passed to onPacket must be an EncodedPacket.');
2187
+ }
2188
+ if (meta !== undefined && (!meta || typeof meta !== 'object')) {
2189
+ throw new TypeError('The second argument passed to onPacket must be an object or undefined.');
2190
+ }
2191
+
2192
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
2193
+ this.lastMuxerPromise
2194
+ = this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta)
2195
+ .catch((error) => {
2196
+ this.setError(error);
2197
+ });
2198
+ };
2199
+ // @ts-expect-error It's technically readonly
2200
+ this.customEncoder.onError = (error) => {
2201
+ this.setError(error);
2202
+ };
2203
+
2204
+ await this.customEncoder.init();
2205
+ } else if ((PCM_AUDIO_CODECS as readonly string[]).includes(this.encodingConfig.codec)) {
2206
+ this.initPcmEncoder();
2207
+ } else {
2208
+ if (typeof AudioEncoder === 'undefined') {
2209
+ throw new Error('AudioEncoder is not supported by this browser.');
2210
+ }
2211
+
2212
+ const support = await AudioEncoder.isConfigSupported(encoderConfig);
2213
+ if (!support.supported) {
2214
+ throw new Error(
2215
+ `This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,`
2216
+ + ` ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not`
2217
+ + ` supported by this browser. Consider using another codec or changing your audio parameters.`,
2218
+ );
2219
+ }
2220
+
2221
+ const stack = new Error('Encoding error').stack;
2222
+
2223
+ this.encoder = new AudioEncoder({
2224
+ output: (chunk, meta) => {
2225
+ // WebKit emits an invalid description for AAC (https://bugs.webkit.org/show_bug.cgi?id=302253),
2226
+ // which we try to detect here. If detected, we'll provide our own description instead, derived
2227
+ // from the codec string and audio parameters.
2228
+ if (this.encodingConfig.codec === 'aac' && meta?.decoderConfig) {
2229
+ let needsDescriptionOverwrite = false;
2230
+ if (!meta.decoderConfig.description || meta.decoderConfig.description.byteLength < 2) {
2231
+ needsDescriptionOverwrite = true;
2232
+ } else {
2233
+ const audioSpecificConfig = parseAacAudioSpecificConfig(
2234
+ toUint8Array(meta.decoderConfig.description),
2235
+ );
2236
+
2237
+ needsDescriptionOverwrite = audioSpecificConfig.objectType === 0;
2238
+ }
2239
+
2240
+ if (needsDescriptionOverwrite) {
2241
+ const objectType = Number(last(encoderConfig.codec.split('.')));
2242
+
2243
+ meta.decoderConfig.description = buildAacAudioSpecificConfig({
2244
+ objectType,
2245
+ numberOfChannels: meta.decoderConfig.numberOfChannels,
2246
+ sampleRate: meta.decoderConfig.sampleRate,
2247
+ });
2248
+ }
2249
+ }
2250
+
2251
+ let packet = EncodedPacket.fromEncodedChunk(chunk);
2252
+
2253
+ // Snap the timing information to the sample rate to recover information lost in microsecond
2254
+ // conversion
2255
+ packet = packet.clone({
2256
+ timestamp: roundToDivisor(packet.timestamp, encoderConfig.sampleRate),
2257
+ duration: chunk.duration != null
2258
+ ? roundToDivisor(packet.duration, encoderConfig.sampleRate)
2259
+ : undefined,
2260
+ });
2261
+
2262
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
2263
+ this.lastMuxerPromise
2264
+ = this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta)
2265
+ .catch((error) => {
2266
+ this.setError(error);
2267
+ });
2268
+ },
2269
+ error: (error) => {
2270
+ error.stack = stack; // Provide a more useful stack trace
2271
+ this.setError(error);
2272
+ },
2273
+ });
2274
+ this.encoder.configure(encoderConfig);
2275
+ }
2276
+
2277
+ assert(this.source._connectedTrack);
2278
+ this.muxer = this.source._connectedTrack.output._muxer;
2279
+
2280
+ this.encoderInitialized = true;
2281
+ })();
2282
+ }
2283
+
2284
+ private initPcmEncoder() {
2285
+ this.isPcmEncoder = true;
2286
+
2287
+ const codec = this.encodingConfig.codec as PcmAudioCodec;
2288
+ const { dataType, sampleSize, littleEndian } = parsePcmCodec(codec);
2289
+
2290
+ this.outputSampleSize = sampleSize;
2291
+
2292
+ // All these functions receive a float sample as input and map it into the desired format
2293
+
2294
+ switch (sampleSize) {
2295
+ case 1: {
2296
+ if (dataType === 'unsigned') {
2297
+ this.writeOutputValue = (view, byteOffset, value) =>
2298
+ view.setUint8(byteOffset, clamp((value + 1) * 127.5, 0, 255));
2299
+ } else if (dataType === 'signed') {
2300
+ this.writeOutputValue = (view, byteOffset, value) => {
2301
+ view.setInt8(byteOffset, clamp(Math.round(value * 128), -128, 127));
2302
+ };
2303
+ } else if (dataType === 'ulaw') {
2304
+ this.writeOutputValue = (view, byteOffset, value) => {
2305
+ const int16 = clamp(Math.floor(value * 32767), -32768, 32767);
2306
+ view.setUint8(byteOffset, toUlaw(int16));
2307
+ };
2308
+ } else if (dataType === 'alaw') {
2309
+ this.writeOutputValue = (view, byteOffset, value) => {
2310
+ const int16 = clamp(Math.floor(value * 32767), -32768, 32767);
2311
+ view.setUint8(byteOffset, toAlaw(int16));
2312
+ };
2313
+ } else {
2314
+ assert(false);
2315
+ }
2316
+ }; break;
2317
+ case 2: {
2318
+ if (dataType === 'unsigned') {
2319
+ this.writeOutputValue = (view, byteOffset, value) =>
2320
+ view.setUint16(byteOffset, clamp((value + 1) * 32767.5, 0, 65535), littleEndian);
2321
+ } else if (dataType === 'signed') {
2322
+ this.writeOutputValue = (view, byteOffset, value) =>
2323
+ view.setInt16(byteOffset, clamp(Math.round(value * 32767), -32768, 32767), littleEndian);
2324
+ } else {
2325
+ assert(false);
2326
+ }
2327
+ }; break;
2328
+ case 3: {
2329
+ if (dataType === 'unsigned') {
2330
+ this.writeOutputValue = (view, byteOffset, value) =>
2331
+ setUint24(view, byteOffset, clamp((value + 1) * 8388607.5, 0, 16777215), littleEndian);
2332
+ } else if (dataType === 'signed') {
2333
+ this.writeOutputValue = (view, byteOffset, value) =>
2334
+ setInt24(
2335
+ view,
2336
+ byteOffset,
2337
+ clamp(Math.round(value * 8388607), -8388608, 8388607),
2338
+ littleEndian,
2339
+ );
2340
+ } else {
2341
+ assert(false);
2342
+ }
2343
+ }; break;
2344
+ case 4: {
2345
+ if (dataType === 'unsigned') {
2346
+ this.writeOutputValue = (view, byteOffset, value) =>
2347
+ view.setUint32(byteOffset, clamp((value + 1) * 2147483647.5, 0, 4294967295), littleEndian);
2348
+ } else if (dataType === 'signed') {
2349
+ this.writeOutputValue = (view, byteOffset, value) =>
2350
+ view.setInt32(
2351
+ byteOffset,
2352
+ clamp(Math.round(value * 2147483647), -2147483648, 2147483647),
2353
+ littleEndian,
2354
+ );
2355
+ } else if (dataType === 'float') {
2356
+ this.writeOutputValue = (view, byteOffset, value) =>
2357
+ view.setFloat32(byteOffset, value, littleEndian);
2358
+ } else {
2359
+ assert(false);
2360
+ }
2361
+ }; break;
2362
+ case 8: {
2363
+ if (dataType === 'float') {
2364
+ this.writeOutputValue = (view, byteOffset, value) =>
2365
+ view.setFloat64(byteOffset, value, littleEndian);
2366
+ } else {
2367
+ assert(false);
2368
+ }
2369
+ }; break;
2370
+ default: {
2371
+ assertNever(sampleSize);
2372
+ assert(false);
2373
+ };
2374
+ }
2375
+ }
2376
+
2377
+ async flushAndClose(forceClose: boolean) {
2378
+ try {
2379
+ if (!forceClose) {
2380
+ this.checkForEncoderError();
2381
+
2382
+ // Finalize the resampler to flush any buffered audio
2383
+ if (this.resampler) {
2384
+ await this.resampler.finalize();
2385
+ }
2386
+ }
2387
+
2388
+ this.closed = true;
2389
+
2390
+ if (!forceClose) {
2391
+ if (this.customEncoder) {
2392
+ void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
2393
+ } else if (this.encoder) {
2394
+ await this.encoder.flush();
2395
+ }
2396
+ }
2397
+ } finally {
2398
+ // This cleanup must also run when flushing threw (e.g. due to an encoder error), otherwise the encoder
2399
+ // would be left dangling
2400
+ this.closed = true;
2401
+ this.resampler = null;
2402
+
2403
+ if (this.customEncoder) {
2404
+ await this.customEncoderCallSerializer.call(() => this.customEncoder!.close())
2405
+ .catch((error: unknown) => this.setError(error));
2406
+ } else if (this.encoder && this.encoder.state !== 'closed') {
2407
+ this.encoder.close();
2408
+ }
2409
+ }
2410
+
2411
+ if (!forceClose) {
2412
+ this.checkForEncoderError();
2413
+ }
2414
+ }
2415
+
2416
+ getQueueSize() {
2417
+ if (this.customEncoder) {
2418
+ return this.customEncoderQueueSize;
2419
+ } else if (this.isPcmEncoder) {
2420
+ return 0;
2421
+ } else {
2422
+ return this.encoder?.encodeQueueSize ?? 0;
2423
+ }
2424
+ }
2425
+
2426
+ checkForEncoderError() {
2427
+ if (this.errorSet) {
2428
+ throw this.error;
2429
+ }
2430
+ }
2431
+ }
2432
+
2433
+ /**
2434
+ * This source can be used to add raw, unencoded audio samples to an output audio track. These samples will
2435
+ * automatically be encoded and then piped into the output.
2436
+ * @group Media sources
2437
+ * @public
2438
+ */
2439
+ export class AudioSampleSource extends AudioSource {
2440
+ /** @internal */
2441
+ private _encoder: AudioEncoderWrapper;
2442
+
2443
+ /**
2444
+ * Creates a new {@link AudioSampleSource} whose samples are encoded according to the specified
2445
+ * {@link AudioEncodingConfig}.
2446
+ */
2447
+ constructor(encodingConfig: AudioEncodingConfig) {
2448
+ validateAudioEncodingConfig(encodingConfig);
2449
+
2450
+ super(encodingConfig.codec);
2451
+ this._encoder = new AudioEncoderWrapper(this, encodingConfig);
2452
+ }
2453
+
2454
+ /**
2455
+ * Encodes an audio sample and then adds it to the output.
2456
+ *
2457
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
2458
+ * to respect writer and encoder backpressure.
2459
+ */
2460
+ add(audioSample: AudioSample) {
2461
+ if (!(audioSample instanceof AudioSample)) {
2462
+ throw new TypeError('audioSample must be an AudioSample.');
2463
+ }
2464
+
2465
+ return this._encoder.add(audioSample, false);
2466
+ }
2467
+
2468
+ /** @internal */
2469
+ override _flushAndClose(forceClose: boolean) {
2470
+ return this._encoder.flushAndClose(forceClose);
2471
+ }
2472
+ }
2473
+
2474
+ /**
2475
+ * This source can be used to add audio data from an AudioBuffer to the output track. This is useful when working with
2476
+ * the Web Audio API.
2477
+ * @group Media sources
2478
+ * @public
2479
+ */
2480
+ export class AudioBufferSource extends AudioSource {
2481
+ /** @internal */
2482
+ private _encoder: AudioEncoderWrapper;
2483
+ /** @internal */
2484
+ private _accumulatedTime = 0;
2485
+
2486
+ /**
2487
+ * Creates a new {@link AudioBufferSource} whose `AudioBuffer` instances are encoded according to the specified
2488
+ * {@link AudioEncodingConfig}.
2489
+ */
2490
+ constructor(encodingConfig: AudioEncodingConfig) {
2491
+ validateAudioEncodingConfig(encodingConfig);
2492
+
2493
+ super(encodingConfig.codec);
2494
+ this._encoder = new AudioEncoderWrapper(this, encodingConfig);
2495
+ }
2496
+
2497
+ /**
2498
+ * Converts an AudioBuffer to audio samples, encodes them and adds them to the output. The first AudioBuffer will
2499
+ * be played at timestamp 0, and any subsequent AudioBuffer will have a timestamp equal to the total duration of
2500
+ * all previous AudioBuffers.
2501
+ *
2502
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
2503
+ * to respect writer and encoder backpressure.
2504
+ */
2505
+ async add(audioBuffer: AudioBuffer) {
2506
+ if (!(audioBuffer instanceof AudioBuffer)) {
2507
+ throw new TypeError('audioBuffer must be an AudioBuffer.');
2508
+ }
2509
+
2510
+ const iterator = AudioSample._fromAudioBuffer(audioBuffer, this._accumulatedTime);
2511
+ this._accumulatedTime += audioBuffer.duration;
2512
+
2513
+ for (const audioSample of iterator) {
2514
+ await this._encoder.add(audioSample, true);
2515
+ }
2516
+ }
2517
+
2518
+ /** @internal */
2519
+ override _flushAndClose(forceClose: boolean) {
2520
+ return this._encoder.flushAndClose(forceClose);
2521
+ }
2522
+ }
2523
+
2524
+ /**
2525
+ * Options for {@link MediaStreamAudioTrackSource}.
2526
+ * @group Media sources
2527
+ * @public
2528
+ */
2529
+ export type MediaStreamAudioTrackSourceOptions = {
2530
+ /**
2531
+ * Controls the basis (zero point) for audio sample timestamps.
2532
+ *
2533
+ * When set to `'synced-zero'`, timestamps will be relative to the first chunk of media from a `MediaStreamTrack`
2534
+ * added to the {@link Output}.
2535
+ *
2536
+ * When set to `'zero'`, timestamps will be relative to the first audio sample emitted by this source.
2537
+ *
2538
+ * When set to `'unix'`, timestamps will be relative to the Unix epoch, so clearly associated with a distinct point
2539
+ * in time. Here, pausing via {@link MediaStreamAudioTrackSource.pause} will also create gaps in timestamps. Be sure
2540
+ * to pair this mode with {@link BaseTrackMetadata.isRelativeToUnixEpoch}.
2541
+ *
2542
+ * Defaults to `'synced-zero'`.
2543
+ */
2544
+ timestampBase?: 'synced-zero' | 'zero' | 'unix';
2545
+ };
2546
+
2547
+ /**
2548
+ * Audio source that encodes the data of a
2549
+ * [`MediaStreamAudioTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes it into the
2550
+ * output. This is useful for capturing live or real-time audio such as microphones or audio from other media elements.
2551
+ * Audio will automatically start being captured once the connected {@link Output} is started, and will keep being
2552
+ * captured until the {@link Output} is finalized or this source is closed.
2553
+ * @group Media sources
2554
+ * @public
2555
+ */
2556
+ export class MediaStreamAudioTrackSource extends AudioSource {
2557
+ /** @internal */
2558
+ private _options: MediaStreamAudioTrackSourceOptions;
2559
+ /** @internal */
2560
+ private _encoder: AudioEncoderWrapper;
2561
+ /** @internal */
2562
+ private _abortController: AbortController | null = null;
2563
+ /** @internal */
2564
+ private _track: MediaStreamAudioTrack;
2565
+ /** @internal */
2566
+ private _audioContext: AudioContext | null = null;
2567
+ /** @internal */
2568
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2569
+ private _scriptProcessorNode: ScriptProcessorNode | null = null; // Deprecated but goated
2570
+ /** @internal */
2571
+ private _promiseWithResolvers = promiseWithResolvers();
2572
+ /** @internal */
2573
+ private _errorPromiseAccessed = false;
2574
+ /** @internal */
2575
+ private _paused = false;
2576
+
2577
+ /** A promise that rejects upon any error within this source. This promise never resolves. */
2578
+ get errorPromise() {
2579
+ this._errorPromiseAccessed = true;
2580
+ return this._promiseWithResolvers.promise;
2581
+ }
2582
+
2583
+ /** Whether this source is currently paused as a result of calling `.pause()`. */
2584
+ get paused() {
2585
+ return this._paused;
2586
+ }
2587
+
2588
+ /**
2589
+ * Creates a new {@link MediaStreamAudioTrackSource} from a `MediaStreamAudioTrack`, which will pull audio samples
2590
+ * from the stream in real time and encode them according to {@link AudioEncodingConfig}.
2591
+ */
2592
+ constructor(
2593
+ track: MediaStreamAudioTrack,
2594
+ encodingConfig: AudioEncodingConfig,
2595
+ options: MediaStreamAudioTrackSourceOptions = {},
2596
+ ) {
2597
+ if (!(track instanceof MediaStreamTrack) || track.kind !== 'audio') {
2598
+ throw new TypeError('track must be an audio MediaStreamTrack.');
2599
+ }
2600
+ validateAudioEncodingConfig(encodingConfig);
2601
+ if (typeof options !== 'object' || !options) {
2602
+ throw new TypeError('options must be an object.');
2603
+ }
2604
+ if (
2605
+ options.timestampBase !== undefined
2606
+ && options.timestampBase !== 'synced-zero'
2607
+ && options.timestampBase !== 'zero'
2608
+ && options.timestampBase !== 'unix'
2609
+ ) {
2610
+ throw new TypeError(
2611
+ 'options.timestampBase, when provided, must be one of \'synced-zero\', \'zero\', or \'unix\'.',
2612
+ );
2613
+ }
2614
+
2615
+ super(encodingConfig.codec);
2616
+ this._options = options;
2617
+ this._encoder = new AudioEncoderWrapper(this, encodingConfig);
2618
+ this._track = track;
2619
+ }
2620
+
2621
+ /** @internal */
2622
+ override async _start() {
2623
+ if (!this._errorPromiseAccessed) {
2624
+ Logging._warn(
2625
+ 'Make sure not to ignore the `errorPromise` field on MediaStreamAudioTrackSource, so that any internal'
2626
+ + ' errors get bubbled up properly.',
2627
+ );
2628
+ }
2629
+
2630
+ this._abortController = new AbortController();
2631
+
2632
+ let firstAudioDataTimestamp: number | null = null;
2633
+ let errored = false;
2634
+ let lastSampleTimestamp: number | null = null;
2635
+ let timestampOffset = 0;
2636
+
2637
+ const onAudioSample = (audioSample: AudioSample) => {
2638
+ if (errored) {
2639
+ audioSample.close();
2640
+ return;
2641
+ }
2642
+
2643
+ const currentTimestamp = audioSample.timestamp;
2644
+
2645
+ if (this._paused) {
2646
+ const dataSeen = firstAudioDataTimestamp !== null;
2647
+ if (dataSeen) {
2648
+ if (lastSampleTimestamp !== null && this._options.timestampBase !== 'unix') {
2649
+ // In addition to dropping this sample, let's also keep track of the time we have lost due to
2650
+ // the pause. Doing it like this instead of simply keeping track of the paused time is better
2651
+ // since it retains the sample rate of the underlying source.
2652
+ const timeDelta = currentTimestamp - lastSampleTimestamp;
2653
+ timestampOffset -= timeDelta;
2654
+ }
2655
+ lastSampleTimestamp = currentTimestamp;
2656
+ }
2657
+
2658
+ audioSample.close();
2659
+ return;
2660
+ }
2661
+
2662
+ if (firstAudioDataTimestamp === null) {
2663
+ firstAudioDataTimestamp = audioSample.timestamp;
2664
+
2665
+ let target: number;
2666
+ const timestampBase = this._options.timestampBase ?? 'synced-zero';
2667
+ if (timestampBase === 'unix') {
2668
+ target = Date.now() / 1000;
2669
+ } else if (timestampBase === 'zero') {
2670
+ target = 0;
2671
+ } else {
2672
+ const output = this._connectedTrack!.output;
2673
+ if (output._firstMediaStreamTimestamp === null) {
2674
+ output._firstMediaStreamTimestamp = performance.now() / 1000;
2675
+ target = 0;
2676
+ } else {
2677
+ target = performance.now() / 1000 - output._firstMediaStreamTimestamp;
2678
+ }
2679
+ }
2680
+
2681
+ timestampOffset = target - firstAudioDataTimestamp;
2682
+ }
2683
+
2684
+ lastSampleTimestamp = currentTimestamp;
2685
+
2686
+ if (this._encoder.getQueueSize() >= 8) {
2687
+ // Drop data if the encoder is overloaded
2688
+ audioSample.close();
2689
+ return;
2690
+ }
2691
+
2692
+ audioSample.setTimestamp(currentTimestamp + timestampOffset);
2693
+
2694
+ void this._encoder.add(audioSample, true)
2695
+ .catch((error) => {
2696
+ errored = true;
2697
+
2698
+ this._abortController?.abort();
2699
+ this._promiseWithResolvers.reject(error);
2700
+ void this._audioContext?.suspend();
2701
+ });
2702
+ };
2703
+
2704
+ if (typeof MediaStreamTrackProcessor !== 'undefined') {
2705
+ // Great, MediaStreamTrackProcessor is supported, this is the preferred way of doing things
2706
+ const processor = new MediaStreamTrackProcessor({ track: this._track });
2707
+ const consumer = new WritableStream<AudioData>({
2708
+ write: audioData => onAudioSample(new AudioSample(audioData)),
2709
+ });
2710
+
2711
+ processor.readable.pipeTo(consumer, {
2712
+ signal: this._abortController.signal,
2713
+ }).catch((error) => {
2714
+ // Handle AbortError silently
2715
+ if (error instanceof DOMException && error.name === 'AbortError') return;
2716
+
2717
+ this._promiseWithResolvers.reject(error);
2718
+ });
2719
+ } else {
2720
+ // Let's fall back to an AudioContext approach
2721
+
2722
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
2723
+ const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
2724
+
2725
+ this._audioContext = new AudioContext({ sampleRate: this._track.getSettings().sampleRate });
2726
+ const sourceNode = this._audioContext.createMediaStreamSource(new MediaStream([this._track]));
2727
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2728
+ this._scriptProcessorNode = this._audioContext.createScriptProcessor(4096);
2729
+
2730
+ if (this._audioContext.state === 'suspended') {
2731
+ await this._audioContext.resume();
2732
+ }
2733
+
2734
+ sourceNode.connect(this._scriptProcessorNode);
2735
+ this._scriptProcessorNode.connect(this._audioContext.destination);
2736
+
2737
+ let totalDuration = 0;
2738
+
2739
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2740
+ this._scriptProcessorNode.onaudioprocess = (event) => {
2741
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2742
+ const iterator = AudioSample._fromAudioBuffer(event.inputBuffer, totalDuration);
2743
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
2744
+ totalDuration += event.inputBuffer.duration;
2745
+
2746
+ for (const audioSample of iterator) {
2747
+ onAudioSample(audioSample);
2748
+ }
2749
+ };
2750
+ }
2751
+ }
2752
+
2753
+ /**
2754
+ * Pauses the capture of audio data - any audio data emitted by the underlying media stream will be ignored
2755
+ * while paused. This does *not* close the underlying `MediaStreamAudioTrack`, it just ignores its output.
2756
+ */
2757
+ pause() {
2758
+ this._paused = true;
2759
+ }
2760
+
2761
+ /** Resumes the capture of audio data after being paused. */
2762
+ resume() {
2763
+ this._paused = false;
2764
+ }
2765
+
2766
+ /** @internal */
2767
+ override async _flushAndClose(forceClose: boolean) {
2768
+ if (this._abortController) {
2769
+ this._abortController.abort();
2770
+ this._abortController = null;
2771
+ }
2772
+
2773
+ if (this._audioContext) {
2774
+ assert(this._scriptProcessorNode);
2775
+
2776
+ this._scriptProcessorNode.disconnect();
2777
+ await this._audioContext.suspend();
2778
+ }
2779
+
2780
+ await this._encoder.flushAndClose(forceClose);
2781
+ }
2782
+ }
2783
+
2784
+ // === MEDIA STREAM TRACK PROCESSOR WORKER ===
2785
+
2786
+ type MediaStreamTrackProcessorWorkerMessage = {
2787
+ type: 'support';
2788
+ supported: boolean;
2789
+ } | {
2790
+ type: 'videoFrame';
2791
+ trackId: number;
2792
+ videoFrame: VideoFrame;
2793
+ } | {
2794
+ type: 'trackStopped';
2795
+ trackId: number;
2796
+ } | {
2797
+ type: 'error';
2798
+ trackId: number;
2799
+ error: unknown;
2800
+ };
2801
+
2802
+ type MediaStreamTrackProcessorControllerMessage = {
2803
+ type: 'videoTrack';
2804
+ trackId: number;
2805
+ track: MediaStreamVideoTrack;
2806
+ } | {
2807
+ type: 'stopTrack';
2808
+ trackId: number;
2809
+ };
2810
+
2811
+ const mediaStreamTrackProcessorWorkerCode = () => {
2812
+ const sendMessage = (message: MediaStreamTrackProcessorWorkerMessage, transfer?: Transferable[]) => {
2813
+ if (transfer) {
2814
+ self.postMessage(message, { transfer });
2815
+ } else {
2816
+ self.postMessage(message);
2817
+ }
2818
+ };
2819
+
2820
+ // Immediately send a message to the main thread, letting them know of the support
2821
+ sendMessage({
2822
+ type: 'support',
2823
+ supported: typeof MediaStreamTrackProcessor !== 'undefined',
2824
+ });
2825
+
2826
+ const abortControllers = new Map<number, AbortController>();
2827
+ const activeTracks = new Map<number, MediaStreamVideoTrack>();
2828
+
2829
+ self.addEventListener('message', (event) => {
2830
+ const message = event.data as MediaStreamTrackProcessorControllerMessage;
2831
+
2832
+ switch (message.type) {
2833
+ case 'videoTrack': {
2834
+ activeTracks.set(message.trackId, message.track);
2835
+
2836
+ const processor = new MediaStreamTrackProcessor({ track: message.track });
2837
+ const consumer = new WritableStream<VideoFrame>({
2838
+ write: (videoFrame) => {
2839
+ if (!activeTracks.has(message.trackId)) {
2840
+ videoFrame.close();
2841
+ return;
2842
+ }
2843
+
2844
+ // Send it to the main thread
2845
+ sendMessage({
2846
+ type: 'videoFrame',
2847
+ trackId: message.trackId,
2848
+ videoFrame,
2849
+ }, [videoFrame]);
2850
+ },
2851
+ });
2852
+
2853
+ const abortController = new AbortController();
2854
+ abortControllers.set(message.trackId, abortController);
2855
+
2856
+ processor.readable.pipeTo(consumer, {
2857
+ signal: abortController.signal,
2858
+ }).catch((error: unknown) => {
2859
+ // Handle AbortError silently
2860
+ if (error instanceof DOMException && error.name === 'AbortError') return;
2861
+
2862
+ sendMessage({
2863
+ type: 'error',
2864
+ trackId: message.trackId,
2865
+ error,
2866
+ });
2867
+ });
2868
+ }; break;
2869
+
2870
+ case 'stopTrack': {
2871
+ const abortController = abortControllers.get(message.trackId);
2872
+ if (abortController) {
2873
+ abortController.abort();
2874
+ abortControllers.delete(message.trackId);
2875
+ }
2876
+
2877
+ const track = activeTracks.get(message.trackId);
2878
+ track?.stop();
2879
+ activeTracks.delete(message.trackId);
2880
+
2881
+ sendMessage({
2882
+ type: 'trackStopped',
2883
+ trackId: message.trackId,
2884
+ });
2885
+ }; break;
2886
+
2887
+ default: assertNever(message);
2888
+ }
2889
+ });
2890
+ };
2891
+
2892
+ let nextMediaStreamTrackProcessorWorkerId = 0;
2893
+ let mediaStreamTrackProcessorWorker: Worker | null = null;
2894
+
2895
+ const initMediaStreamTrackProcessorWorker = () => {
2896
+ const blob = new Blob(
2897
+ [`(${mediaStreamTrackProcessorWorkerCode.toString()})()`],
2898
+ { type: 'application/javascript' },
2899
+ );
2900
+ const url = URL.createObjectURL(blob);
2901
+
2902
+ mediaStreamTrackProcessorWorker = new Worker(url);
2903
+ };
2904
+
2905
+ let mediaStreamTrackProcessorIsSupportedInWorkerCache: boolean | null = null;
2906
+ const mediaStreamTrackProcessorIsSupportedInWorker = async () => {
2907
+ if (mediaStreamTrackProcessorIsSupportedInWorkerCache !== null) {
2908
+ return mediaStreamTrackProcessorIsSupportedInWorkerCache;
2909
+ }
2910
+
2911
+ if (!mediaStreamTrackProcessorWorker) {
2912
+ initMediaStreamTrackProcessorWorker();
2913
+ }
2914
+
2915
+ return new Promise<boolean>((resolve) => {
2916
+ assert(mediaStreamTrackProcessorWorker);
2917
+
2918
+ const listener = (event: MessageEvent) => {
2919
+ const message = event.data as MediaStreamTrackProcessorWorkerMessage;
2920
+
2921
+ if (message.type === 'support') {
2922
+ mediaStreamTrackProcessorIsSupportedInWorkerCache = message.supported;
2923
+ mediaStreamTrackProcessorWorker!.removeEventListener('message', listener);
2924
+
2925
+ resolve(message.supported);
2926
+ }
2927
+ };
2928
+
2929
+ mediaStreamTrackProcessorWorker.addEventListener('message', listener);
2930
+ });
2931
+ };
2932
+
2933
+ const sendMessageToMediaStreamTrackProcessorWorker = (
2934
+ message: MediaStreamTrackProcessorControllerMessage,
2935
+ transfer?: Transferable[],
2936
+ ) => {
2937
+ assert(mediaStreamTrackProcessorWorker);
2938
+
2939
+ if (transfer) {
2940
+ mediaStreamTrackProcessorWorker.postMessage(message, transfer);
2941
+ } else {
2942
+ mediaStreamTrackProcessorWorker.postMessage(message);
2943
+ }
2944
+ };
2945
+
2946
+ /**
2947
+ * Base class for subtitle sources - sources for subtitle tracks.
2948
+ * @group Media sources
2949
+ * @public
2950
+ */
2951
+ export abstract class SubtitleSource extends MediaSource {
2952
+ /** @internal */
2953
+ override _connectedTrack: OutputSubtitleTrack | null = null;
2954
+ /** @internal */
2955
+ override readonly _codec: SubtitleCodec;
2956
+
2957
+ /** Internal constructor. */
2958
+ constructor(codec: SubtitleCodec) {
2959
+ super();
2960
+
2961
+ if (!SUBTITLE_CODECS.includes(codec)) {
2962
+ throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(', ')}.`);
2963
+ }
2964
+
2965
+ this._codec = codec;
2966
+ }
2967
+ }
2968
+
2969
+ /**
2970
+ * This source can be used to add subtitles from a subtitle text file.
2971
+ * @group Media sources
2972
+ * @public
2973
+ */
2974
+ export class TextSubtitleSource extends SubtitleSource {
2975
+ /** @internal */
2976
+ private _parser: SubtitleParser;
2977
+ /** @internal */
2978
+ private _error: unknown = null;
2979
+ /** @internal */
2980
+ private _errorSet = false;
2981
+ /** @internal */
2982
+ private _lastMuxerPromise: Promise<void> = Promise.resolve();
2983
+
2984
+ /** Creates a new {@link TextSubtitleSource} where added text chunks are in the specified `codec`. */
2985
+ constructor(codec: SubtitleCodec) {
2986
+ super(codec);
2987
+
2988
+ this._parser = new SubtitleParser({
2989
+ codec,
2990
+ output: (cue, metadata) => {
2991
+ this._lastMuxerPromise
2992
+ = this._connectedTrack!.output._muxer.addSubtitleCue(this._connectedTrack!, cue, metadata)
2993
+ .catch((error) => {
2994
+ this._setError(error);
2995
+ });
2996
+ },
2997
+ });
2998
+ }
2999
+
3000
+ /**
3001
+ * Parses the subtitle text according to the specified codec and adds it to the output track. You don't have to
3002
+ * add the entire subtitle file at once here; you can provide it in chunks.
3003
+ *
3004
+ * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
3005
+ * to respect writer and encoder backpressure.
3006
+ */
3007
+ add(text: string) {
3008
+ if (typeof text !== 'string') {
3009
+ throw new TypeError('text must be a string.');
3010
+ }
3011
+
3012
+ this._checkForError();
3013
+
3014
+ this._ensureValidAdd();
3015
+ this._parser.parse(text);
3016
+
3017
+ return this._lastMuxerPromise; // Allow the writer to apply backpressure
3018
+ }
3019
+
3020
+ /** @internal */
3021
+ private _setError(error: unknown) {
3022
+ if (!this._errorSet) {
3023
+ this._error = error;
3024
+ this._errorSet = true;
3025
+ }
3026
+ }
3027
+
3028
+ /** @internal */
3029
+ _checkForError() {
3030
+ if (this._errorSet) {
3031
+ throw this._error;
3032
+ }
3033
+ }
3034
+
3035
+ /** @internal */
3036
+ override async _flushAndClose(forceClose: boolean) {
3037
+ if (!forceClose) {
3038
+ this._checkForError();
3039
+ }
3040
+ }
3041
+ }