@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,3953 @@
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 { TrackType } from '../output';
10
+ import { parseAacAudioSpecificConfig } from '../../shared/aac-misc';
11
+ import {
12
+ AacCodecInfo,
13
+ AudioCodec,
14
+ extractAudioCodecString,
15
+ extractVideoCodecString,
16
+ MediaCodec,
17
+ OPUS_SAMPLE_RATE,
18
+ parsePcmCodec,
19
+ PCM_AUDIO_CODECS,
20
+ PcmAudioCodec,
21
+ PRORES_FOURCCS,
22
+ ProresFourCc,
23
+ VideoCodec,
24
+ } from '../codec';
25
+ import {
26
+ Av1CodecInfo,
27
+ AvcDecoderConfigurationRecord,
28
+ extractAv1CodecInfoFromPacket,
29
+ extractVp9CodecInfoFromPacket,
30
+ FlacBlockType,
31
+ HevcDecoderConfigurationRecord,
32
+ Vp9CodecInfo,
33
+ parseEac3Config,
34
+ getEac3SampleRate,
35
+ getEac3ChannelCount,
36
+ AC3_ACMOD_CHANNEL_COUNTS,
37
+ } from '../codec-data';
38
+ import { Demuxer } from '../demuxer';
39
+ import { Input } from '../input';
40
+ import {
41
+ InputAudioTrackBacking,
42
+ InputTrackBacking,
43
+ InputVideoTrackBacking,
44
+ } from '../input-track';
45
+ import { PacketRetrievalOptions } from '../media-sink';
46
+ import {
47
+ assert,
48
+ binarySearchExact,
49
+ binarySearchLessOrEqual,
50
+ bytesToHexString,
51
+ COLOR_PRIMARIES_MAP_INVERSE,
52
+ findLastIndex,
53
+ isIso639Dash2LanguageCode,
54
+ last,
55
+ MATRIX_COEFFICIENTS_MAP_INVERSE,
56
+ normalizeRotation,
57
+ roundToMultiple,
58
+ Rotation,
59
+ textDecoder,
60
+ TransformationMatrix,
61
+ TRANSFER_CHARACTERISTICS_MAP_INVERSE,
62
+ UNDETERMINED_LANGUAGE,
63
+ toDataView,
64
+ roundIfAlmostInteger,
65
+ hexStringToBytes,
66
+ HEX_STRING_REGEX,
67
+ } from '../misc';
68
+ import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
69
+ import { buildIsobmffMimeType, parsePsshBoxContents, psshBoxesAreEqual, PsshBox } from './isobmff-misc';
70
+ import {
71
+ MAX_BOX_HEADER_SIZE,
72
+ MIN_BOX_HEADER_SIZE,
73
+ readBoxHeader,
74
+ readDataBox,
75
+ readFixed_16_16,
76
+ readFixed_2_30,
77
+ readIsomVariableInteger,
78
+ readMetadataStringShort,
79
+ } from './isobmff-reader';
80
+ import {
81
+ FileSlice,
82
+ readBytes,
83
+ readF64Be,
84
+ readI16Be,
85
+ readI32Be,
86
+ readI64Be,
87
+ Reader,
88
+ readU16Be,
89
+ readU24Be,
90
+ readU32Be,
91
+ readU64Be,
92
+ readU8,
93
+ readAscii,
94
+ } from '../reader';
95
+ import { DEFAULT_TRACK_DISPOSITION, MetadataTags, RichImageData, TrackDisposition } from '../metadata';
96
+ import { AC3_SAMPLE_RATES } from '../../shared/ac3-misc';
97
+ import { Bitstream } from '../../shared/bitstream';
98
+ import { Aes128CbcContext } from '../aes';
99
+ import { Logging } from '../logging';
100
+
101
+ type InternalTrack = {
102
+ id: number;
103
+ demuxer: IsobmffDemuxer;
104
+ trackBacking: InputTrackBacking | null;
105
+ disposition: TrackDisposition;
106
+ timescale: number;
107
+ durationInMovieTimescale: number;
108
+ durationInMediaTimescale: number;
109
+ rotation: Rotation;
110
+ internalCodecId: string | null;
111
+ name: string | null;
112
+ languageCode: string;
113
+ sampleTableByteOffset: number | null; // null when the track's sample table is another file (ominous ik 👀)
114
+ sampleTable: SampleTable | null;
115
+ fragmentLookupTable: FragmentLookupTableEntry[];
116
+ currentFragmentState: FragmentTrackState | null;
117
+ /**
118
+ * List of all encountered fragment offsets alongside their timestamps. This list never gets truncated, but memory
119
+ * consumption should be negligible.
120
+ */
121
+ fragmentPositionCache: {
122
+ moofOffset: number;
123
+ startTimestamp: number;
124
+ endTimestamp: number;
125
+ }[];
126
+ /** The segment durations of all edit list entries leading up to the main one (from which the offset is taken.) */
127
+ editListPreviousSegmentDurations: number;
128
+ /** The media time offset of the main edit list entry (with media time !== -1) */
129
+ editListOffset: number;
130
+ /** Set when the track's samples are encrypted using a supported scheme (cenc/cens/cbcs), parsed from sinf/tenc. */
131
+ encryptionInfo: TrackEncryptionInfo | null;
132
+ /** For non-fragmented encrypted tracks: parsed saiz+saio from stbl; aux info is fetched lazily on first use. */
133
+ encryptionAuxInfo: SampleEncryptionAuxInfo | null;
134
+ frmaCodecString: string | null;
135
+ } & ({
136
+ info: null;
137
+ } | {
138
+ info: {
139
+ type: 'video';
140
+ width: number;
141
+ height: number;
142
+ squarePixelWidth: number;
143
+ squarePixelHeight: number;
144
+ codec: VideoCodec | null;
145
+ codecDescription: Uint8Array | null;
146
+ colorSpace: VideoColorSpaceInit | null;
147
+ avcType: 1 | 3 | null;
148
+ avcCodecInfo: AvcDecoderConfigurationRecord | null;
149
+ hevcCodecInfo: HevcDecoderConfigurationRecord | null;
150
+ vp9CodecInfo: Vp9CodecInfo | null;
151
+ av1CodecInfo: Av1CodecInfo | null;
152
+ proresFormat: ProresFourCc | null;
153
+ };
154
+ } | {
155
+ info: {
156
+ type: 'audio';
157
+ numberOfChannels: number;
158
+ sampleRate: number;
159
+ codec: AudioCodec | null;
160
+ codecDescription: Uint8Array | null;
161
+ aacCodecInfo: AacCodecInfo | null;
162
+ pcmLittleEndian: boolean;
163
+ pcmSampleSize: number | null;
164
+ };
165
+ });
166
+
167
+ type InternalVideoTrack = InternalTrack & { info: { type: 'video' } };
168
+ type InternalAudioTrack = InternalTrack & { info: { type: 'audio' } };
169
+
170
+ type SampleTable = {
171
+ sampleTimingEntries: SampleTimingEntry[];
172
+ sampleCompositionTimeOffsets: SampleCompositionTimeOffsetEntry[];
173
+ sampleSizes: number[];
174
+ keySampleIndices: number[] | null; // Samples that are keyframes
175
+ chunkOffsets: number[];
176
+ sampleToChunk: SampleToChunkEntry[];
177
+ presentationTimestamps: {
178
+ presentationTimestamp: number;
179
+ sampleIndex: number;
180
+ }[] | null;
181
+ /**
182
+ * Provides a fast map from sample index to index in the sorted presentation timestamps array - so, a fast map from
183
+ * decode order to presentation order.
184
+ */
185
+ presentationTimestampIndexMap: number[] | null;
186
+ };
187
+ type SampleTimingEntry = {
188
+ startIndex: number;
189
+ startDecodeTimestamp: number;
190
+ count: number;
191
+ delta: number;
192
+ };
193
+ type SampleCompositionTimeOffsetEntry = {
194
+ startIndex: number;
195
+ count: number;
196
+ offset: number;
197
+ };
198
+ type SampleToChunkEntry = {
199
+ startSampleIndex: number;
200
+ startChunkIndex: number;
201
+ samplesPerChunk: number;
202
+ sampleDescriptionIndex: number;
203
+ };
204
+
205
+ type FragmentTrackDefaults = {
206
+ trackId: number;
207
+ defaultSampleDescriptionIndex: number;
208
+ defaultSampleDuration: number;
209
+ defaultSampleSize: number;
210
+ defaultSampleFlags: number;
211
+ };
212
+
213
+ type FragmentLookupTableEntry = {
214
+ timestamp: number;
215
+ moofOffset: number;
216
+ };
217
+
218
+ type FragmentTrackState = {
219
+ baseDataOffset: number;
220
+ sampleDescriptionIndex: number | null;
221
+ defaultSampleDuration: number | null;
222
+ defaultSampleSize: number | null;
223
+ defaultSampleFlags: number | null;
224
+ startTimestamp: number | null;
225
+ encryptionAuxInfo: SampleEncryptionAuxInfo | null;
226
+ };
227
+
228
+ type FragmentTrackData = {
229
+ track: InternalTrack;
230
+
231
+ // Kept as state for the presence of multiple trun boxes
232
+ currentTimestamp: number;
233
+ currentOffset: number;
234
+
235
+ startTimestamp: number;
236
+ endTimestamp: number;
237
+ firstKeyFrameTimestamp: number | null;
238
+ samples: FragmentTrackSample[];
239
+ presentationTimestamps: {
240
+ presentationTimestamp: number;
241
+ sampleIndex: number;
242
+ }[];
243
+ startTimestampIsFinal: boolean;
244
+ encryptionAuxInfo: SampleEncryptionAuxInfo | null;
245
+ };
246
+
247
+ type FragmentTrackSample = {
248
+ presentationTimestamp: number;
249
+ duration: number;
250
+ byteOffset: number;
251
+ byteSize: number;
252
+ isKeyFrame: boolean;
253
+ encryption: SampleEncryptionInfo | null;
254
+ };
255
+
256
+ type Fragment = {
257
+ moofOffset: number;
258
+ moofSize: number;
259
+ implicitBaseDataOffset: number;
260
+ trackData: Map<InternalTrack['id'], FragmentTrackData>;
261
+ psshBoxes: PsshBox[];
262
+ };
263
+
264
+ type TrackEncryptionInfo = {
265
+ scheme: 'cenc' | 'cens' | 'cbcs';
266
+ defaultKid: string | null;
267
+ defaultIsProtected: boolean | null;
268
+ defaultPerSampleIvSize: number | null;
269
+ defaultConstantIv: Uint8Array | null;
270
+ defaultCryptByteBlock: number | null;
271
+ defaultSkipByteBlock: number | null;
272
+ };
273
+
274
+ type SampleEncryptionInfo = {
275
+ iv: Uint8Array;
276
+ subsamples: {
277
+ clearLen: number;
278
+ protectedLen: number;
279
+ }[] | null;
280
+ };
281
+
282
+ /**
283
+ * Holds parsed saiz+saio state. The encryption info itself lives at a file offset and is fetched lazily.
284
+ * For fragmented files this state is per-traf; for non-fragmented files it's per-track (on stbl).
285
+ */
286
+ type SampleEncryptionAuxInfo = {
287
+ defaultSampleInfoSize: number;
288
+ sampleSizes: Uint8Array | null;
289
+ sampleCount: number;
290
+ offset: number | null; // Absolute file offset of the first sample's aux info
291
+ resolved: SampleEncryptionInfo[] | null;
292
+ };
293
+
294
+ export class IsobmffDemuxer extends Demuxer {
295
+ reader: Reader;
296
+ moovSlice: FileSlice | null = null;
297
+
298
+ currentTrack: InternalTrack | null = null;
299
+ tracks: InternalTrack[] = [];
300
+ metadataPromise: Promise<void> | null = null;
301
+ movieTimescale = -1;
302
+ movieDurationInTimescale = -1;
303
+ isQuickTime = false;
304
+ metadataTags: MetadataTags = {};
305
+ currentMetadataKeys: Map<number, string> | null = null;
306
+
307
+ isFragmented = false;
308
+ fragmentTrackDefaults: FragmentTrackDefaults[] = [];
309
+ psshBoxes: PsshBox[] = [];
310
+ currentFragment: Fragment | null = null;
311
+ /**
312
+ * Caches the last fragment that was read. Based on the assumption that there will be multiple reads to the
313
+ * same fragment in quick succession.
314
+ */
315
+ lastReadFragment: Fragment | null = null;
316
+
317
+ decryptionKeyCache = new Map<string, Promise<Uint8Array>>();
318
+
319
+ constructor(input: Input) {
320
+ super(input);
321
+
322
+ this.reader = input._reader;
323
+ }
324
+
325
+ override async getTrackBackings() {
326
+ await this.readMetadata();
327
+ return this.tracks.map(track => track.trackBacking!);
328
+ }
329
+
330
+ override async getMimeType() {
331
+ await this.readMetadata();
332
+
333
+ const backings = await this.getTrackBackings();
334
+ const codecStrings = await Promise.all(backings.map(
335
+ x => x.getDecoderConfig().then(c => c?.codec ?? null),
336
+ ));
337
+
338
+ return buildIsobmffMimeType({
339
+ isQuickTime: this.isQuickTime,
340
+ hasVideo: this.tracks.some(x => x.info?.type === 'video'),
341
+ hasAudio: this.tracks.some(x => x.info?.type === 'audio'),
342
+ codecStrings: codecStrings.filter(Boolean) as string[],
343
+ });
344
+ }
345
+
346
+ async getMetadataTags() {
347
+ await this.readMetadata();
348
+ return this.metadataTags;
349
+ }
350
+
351
+ readMetadata() {
352
+ return this.metadataPromise ??= (async () => {
353
+ let currentPos = 0;
354
+ let lookForMfraBox = false;
355
+
356
+ while (true) {
357
+ let slice = this.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
358
+ if (slice instanceof Promise) slice = await slice;
359
+ if (!slice) break;
360
+
361
+ const startPos = currentPos;
362
+ const boxInfo = readBoxHeader(slice);
363
+ if (!boxInfo) {
364
+ break;
365
+ }
366
+
367
+ if (boxInfo.name === 'ftyp' || boxInfo.name === 'styp') {
368
+ const majorBrand = readAscii(slice, 4);
369
+ this.isQuickTime = majorBrand === 'qt ';
370
+ } else if (boxInfo.name === 'moov') {
371
+ // Found moov, load it
372
+
373
+ let moovSlice = this.reader.requestSlice(slice.filePos, boxInfo.contentSize);
374
+ if (moovSlice instanceof Promise) moovSlice = await moovSlice;
375
+ if (!moovSlice) break;
376
+
377
+ this.moovSlice = moovSlice;
378
+ this.readContiguousBoxes(this.moovSlice);
379
+
380
+ for (const track of this.tracks) {
381
+ // Modify the edit list offset based on the previous segment durations. They are in different
382
+ // timescales, so we first convert to seconds and then into the track timescale.
383
+ const previousSegmentDurationsInSeconds
384
+ = track.editListPreviousSegmentDurations / this.movieTimescale;
385
+ track.editListOffset -= Math.round(previousSegmentDurationsInSeconds * track.timescale);
386
+ }
387
+
388
+ lookForMfraBox = this.isFragmented
389
+ && this.reader.fileSize !== null
390
+ && this.reader.fileSize > startPos + boxInfo.totalSize; // There's more after the moov box
391
+
392
+ break;
393
+ } else if (boxInfo.name === 'moof') {
394
+ if (!this.input._initInput) {
395
+ throw new Error(
396
+ '"moof" box encountered with no "moov" box present; this file is likely a Segment as'
397
+ + ' described in ISO/IEC 14496-12 Section 8.16. A separate init file that contains a "moov"'
398
+ + ' box is required to read this file, please provide it using InputOptions.initInput.',
399
+ );
400
+ }
401
+
402
+ const initDemuxer = (await this.input._initInput._getDemuxer()) as IsobmffDemuxer;
403
+ if (initDemuxer.constructor !== IsobmffDemuxer) {
404
+ throw new Error('Init input must match the input\'s format.');
405
+ }
406
+
407
+ await initDemuxer.readMetadata();
408
+
409
+ this.movieTimescale = initDemuxer.movieTimescale;
410
+ this.movieDurationInTimescale = initDemuxer.movieDurationInTimescale;
411
+ this.metadataTags = initDemuxer.metadataTags;
412
+ this.isFragmented = true;
413
+ this.fragmentTrackDefaults = initDemuxer.fragmentTrackDefaults;
414
+ this.psshBoxes = initDemuxer.psshBoxes;
415
+
416
+ // Create tracks from the init input's tracks
417
+ for (const foreignTrack of initDemuxer.tracks) {
418
+ const track: InternalTrack = {
419
+ id: foreignTrack.id,
420
+ demuxer: this,
421
+ trackBacking: null,
422
+ disposition: foreignTrack.disposition,
423
+ timescale: foreignTrack.timescale,
424
+ durationInMediaTimescale: foreignTrack.durationInMediaTimescale,
425
+ durationInMovieTimescale: foreignTrack.durationInMovieTimescale,
426
+ rotation: foreignTrack.rotation,
427
+ internalCodecId: foreignTrack.internalCodecId,
428
+ name: foreignTrack.name,
429
+ languageCode: foreignTrack.languageCode,
430
+ sampleTableByteOffset: null,
431
+ sampleTable: null,
432
+ fragmentLookupTable: [],
433
+ currentFragmentState: null,
434
+ fragmentPositionCache: [],
435
+ editListPreviousSegmentDurations: foreignTrack.editListPreviousSegmentDurations,
436
+ editListOffset: foreignTrack.editListOffset,
437
+ encryptionInfo: foreignTrack.encryptionInfo,
438
+ encryptionAuxInfo: null,
439
+ frmaCodecString: null,
440
+ info: foreignTrack.info,
441
+ };
442
+
443
+ if (foreignTrack.trackBacking) {
444
+ assert(track.info);
445
+
446
+ if (track.info.type === 'video' && track.info.width !== -1) {
447
+ const videoTrack = track as InternalVideoTrack;
448
+ track.trackBacking = new IsobmffVideoTrackBacking(videoTrack);
449
+ this.tracks.push(track);
450
+ } else if (track.info.type === 'audio' && track.info.numberOfChannels !== -1) {
451
+ const audioTrack = track as InternalAudioTrack;
452
+ track.trackBacking = new IsobmffAudioTrackBacking(audioTrack);
453
+ this.tracks.push(track);
454
+ }
455
+ } else {
456
+ // The track didn't have enough info to warrant a backing
457
+ }
458
+ }
459
+
460
+ lookForMfraBox = false; // No point in doing it for segment files
461
+
462
+ break;
463
+ }
464
+
465
+ currentPos = startPos + boxInfo.totalSize;
466
+ }
467
+
468
+ if (lookForMfraBox) {
469
+ assert(this.reader.fileSize !== null);
470
+
471
+ // The last 4 bytes may contain the size of the mfra box at the end of the file
472
+ let lastWordSlice = this.reader.requestSlice(this.reader.fileSize - 4, 4);
473
+ if (lastWordSlice instanceof Promise) lastWordSlice = await lastWordSlice;
474
+ assert(lastWordSlice);
475
+
476
+ const lastWord = readU32Be(lastWordSlice);
477
+ const potentialMfraPos = this.reader.fileSize - lastWord;
478
+
479
+ if (potentialMfraPos >= 0 && potentialMfraPos <= this.reader.fileSize - MAX_BOX_HEADER_SIZE) {
480
+ let mfraHeaderSlice = this.reader.requestSliceRange(
481
+ potentialMfraPos,
482
+ MIN_BOX_HEADER_SIZE,
483
+ MAX_BOX_HEADER_SIZE,
484
+ );
485
+ if (mfraHeaderSlice instanceof Promise) mfraHeaderSlice = await mfraHeaderSlice;
486
+
487
+ if (mfraHeaderSlice) {
488
+ const boxInfo = readBoxHeader(mfraHeaderSlice);
489
+
490
+ if (boxInfo && boxInfo.name === 'mfra') {
491
+ // We found the mfra box, allowing for much better random access. Let's parse it.
492
+ let mfraSlice = this.reader.requestSlice(mfraHeaderSlice.filePos, boxInfo.contentSize);
493
+ if (mfraSlice instanceof Promise) mfraSlice = await mfraSlice;
494
+
495
+ if (mfraSlice) {
496
+ this.readContiguousBoxes(mfraSlice);
497
+ }
498
+ }
499
+ }
500
+ }
501
+ }
502
+ })();
503
+ }
504
+
505
+ getSampleTableForTrack(internalTrack: InternalTrack) {
506
+ if (internalTrack.sampleTable) {
507
+ return internalTrack.sampleTable;
508
+ }
509
+
510
+ const sampleTable: SampleTable = {
511
+ sampleTimingEntries: [],
512
+ sampleCompositionTimeOffsets: [],
513
+ sampleSizes: [],
514
+ keySampleIndices: null,
515
+ chunkOffsets: [],
516
+ sampleToChunk: [],
517
+ presentationTimestamps: null,
518
+ presentationTimestampIndexMap: null,
519
+ };
520
+ internalTrack.sampleTable = sampleTable;
521
+
522
+ if (internalTrack.sampleTableByteOffset === null) {
523
+ // There's no sample table to read, it's in another file (happens with segments)
524
+ return sampleTable;
525
+ }
526
+
527
+ assert(this.moovSlice);
528
+
529
+ const stblContainerSlice = this.moovSlice.slice(internalTrack.sampleTableByteOffset);
530
+
531
+ this.currentTrack = internalTrack;
532
+ this.traverseBox(stblContainerSlice);
533
+ this.currentTrack = null;
534
+
535
+ const isPcmCodec = internalTrack.info?.type === 'audio'
536
+ && internalTrack.info.codec
537
+ && (PCM_AUDIO_CODECS as readonly string[]).includes(internalTrack.info.codec);
538
+
539
+ if (isPcmCodec && sampleTable.sampleCompositionTimeOffsets.length === 0) {
540
+ // If the audio has PCM samples, the way the samples are defined in the sample table is somewhat
541
+ // suboptimal: Each individual audio sample is its own sample, meaning we can have 48000 samples per second.
542
+ // Because we treat each sample as its own atomic unit that can be decoded, this would lead to a huge
543
+ // amount of very short samples for PCM audio. So instead, we make a transformation: If the audio is in PCM,
544
+ // we say that each chunk (that normally holds many samples) now is one big sample. We can this because
545
+ // the samples in the chunk are contiguous and the format is PCM, so the entire chunk as one thing still
546
+ // encodes valid audio information.
547
+
548
+ assert(internalTrack.info?.type === 'audio');
549
+ const pcmInfo = parsePcmCodec(internalTrack.info.codec as PcmAudioCodec);
550
+
551
+ const newSampleTimingEntries: SampleTimingEntry[] = [];
552
+ const newSampleSizes: number[] = [];
553
+
554
+ for (let i = 0; i < sampleTable.sampleToChunk.length; i++) {
555
+ const chunkEntry = sampleTable.sampleToChunk[i]!;
556
+ const nextEntry = sampleTable.sampleToChunk[i + 1];
557
+ const chunkCount = (nextEntry ? nextEntry.startChunkIndex : sampleTable.chunkOffsets.length)
558
+ - chunkEntry.startChunkIndex;
559
+
560
+ for (let j = 0; j < chunkCount; j++) {
561
+ const startSampleIndex = chunkEntry.startSampleIndex + j * chunkEntry.samplesPerChunk;
562
+ const endSampleIndex = startSampleIndex + chunkEntry.samplesPerChunk; // Exclusive, outside of chunk
563
+
564
+ const startTimingEntryIndex = binarySearchLessOrEqual(
565
+ sampleTable.sampleTimingEntries,
566
+ startSampleIndex,
567
+ x => x.startIndex,
568
+ );
569
+ const startTimingEntry = sampleTable.sampleTimingEntries[startTimingEntryIndex]!;
570
+ const endTimingEntryIndex = binarySearchLessOrEqual(
571
+ sampleTable.sampleTimingEntries,
572
+ endSampleIndex,
573
+ x => x.startIndex,
574
+ );
575
+ const endTimingEntry = sampleTable.sampleTimingEntries[endTimingEntryIndex]!;
576
+
577
+ const firstSampleTimestamp = startTimingEntry.startDecodeTimestamp
578
+ + (startSampleIndex - startTimingEntry.startIndex) * startTimingEntry.delta;
579
+ const lastSampleTimestamp = endTimingEntry.startDecodeTimestamp
580
+ + (endSampleIndex - endTimingEntry.startIndex) * endTimingEntry.delta;
581
+ const delta = lastSampleTimestamp - firstSampleTimestamp;
582
+
583
+ const lastSampleTimingEntry = last(newSampleTimingEntries);
584
+ if (lastSampleTimingEntry && lastSampleTimingEntry.delta === delta) {
585
+ lastSampleTimingEntry.count++;
586
+ } else {
587
+ // One sample for the entire chunk
588
+ newSampleTimingEntries.push({
589
+ startIndex: chunkEntry.startChunkIndex + j,
590
+ startDecodeTimestamp: firstSampleTimestamp,
591
+ count: 1,
592
+ delta,
593
+ });
594
+ }
595
+
596
+ // Instead of determining the chunk's size by looping over the samples sizes in the sample table, we
597
+ // can directly compute it as we know how many PCM frames are in this chunk, and the size of each
598
+ // PCM frame. This also improves compatibility with some files which fail to write proper sample
599
+ // size values into their sample tables in the PCM case.
600
+ const chunkSize = chunkEntry.samplesPerChunk
601
+ * pcmInfo.sampleSize
602
+ * internalTrack.info.numberOfChannels;
603
+
604
+ newSampleSizes.push(chunkSize);
605
+ }
606
+
607
+ chunkEntry.startSampleIndex = chunkEntry.startChunkIndex;
608
+ chunkEntry.samplesPerChunk = 1;
609
+ }
610
+
611
+ sampleTable.sampleTimingEntries = newSampleTimingEntries;
612
+ sampleTable.sampleSizes = newSampleSizes;
613
+ }
614
+
615
+ if (sampleTable.sampleCompositionTimeOffsets.length > 0) {
616
+ // If composition time offsets are defined, we must build a list of all presentation timestamps and then
617
+ // sort them
618
+ sampleTable.presentationTimestamps = [];
619
+
620
+ for (const entry of sampleTable.sampleTimingEntries) {
621
+ for (let i = 0; i < entry.count; i++) {
622
+ sampleTable.presentationTimestamps.push({
623
+ presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta,
624
+ sampleIndex: entry.startIndex + i,
625
+ });
626
+ }
627
+ }
628
+
629
+ for (const entry of sampleTable.sampleCompositionTimeOffsets) {
630
+ for (let i = 0; i < entry.count; i++) {
631
+ const sampleIndex = entry.startIndex + i;
632
+ const sample = sampleTable.presentationTimestamps[sampleIndex];
633
+ if (!sample) {
634
+ continue;
635
+ }
636
+
637
+ sample.presentationTimestamp += entry.offset;
638
+ }
639
+ }
640
+
641
+ sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
642
+
643
+ sampleTable.presentationTimestampIndexMap = Array(sampleTable.presentationTimestamps.length).fill(-1);
644
+ for (let i = 0; i < sampleTable.presentationTimestamps.length; i++) {
645
+ sampleTable.presentationTimestampIndexMap[sampleTable.presentationTimestamps[i]!.sampleIndex] = i;
646
+ }
647
+ } else {
648
+ // If they're not defined, we can simply use the decode timestamps as presentation timestamps
649
+ }
650
+
651
+ return sampleTable;
652
+ }
653
+
654
+ async readFragment(startPos: number): Promise<Fragment> {
655
+ if (this.lastReadFragment?.moofOffset === startPos) {
656
+ return this.lastReadFragment;
657
+ }
658
+
659
+ let headerSlice = this.reader.requestSliceRange(startPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
660
+ if (headerSlice instanceof Promise) headerSlice = await headerSlice;
661
+ assert(headerSlice);
662
+
663
+ const moofBoxInfo = readBoxHeader(headerSlice);
664
+ assert(moofBoxInfo?.name === 'moof');
665
+
666
+ let entireSlice = this.reader.requestSlice(startPos, moofBoxInfo.totalSize);
667
+ if (entireSlice instanceof Promise) entireSlice = await entireSlice;
668
+ assert(entireSlice);
669
+
670
+ this.traverseBox(entireSlice);
671
+
672
+ const fragment = this.lastReadFragment;
673
+ assert(fragment && fragment.moofOffset === startPos);
674
+
675
+ for (const [, trackData] of fragment.trackData) {
676
+ const track = trackData.track;
677
+ const { fragmentPositionCache } = track;
678
+
679
+ if (!trackData.startTimestampIsFinal) {
680
+ // It may be that some tracks don't define the base decode time, i.e. when the fragment begins. This
681
+ // we'll need to figure out the start timestamp another way. We'll compute the timestamp by accessing
682
+ // the lookup entries and fragment cache, which works out nicely with the lookup algorithm: If these
683
+ // exist, then the lookup will automatically start at the furthest possible point. If they don't, the
684
+ // lookup starts sequentially from the start, incrementally summing up all fragment durations. It's sort
685
+ // of implicit, but it ends up working nicely.
686
+
687
+ const lookupEntry = track.fragmentLookupTable.find(x => x.moofOffset === fragment.moofOffset);
688
+ if (lookupEntry) {
689
+ // There's a lookup entry, let's use its timestamp
690
+ offsetFragmentTrackDataByTimestamp(trackData, lookupEntry.timestamp);
691
+ } else {
692
+ const lastCacheIndex = binarySearchLessOrEqual(
693
+ fragmentPositionCache,
694
+ fragment.moofOffset - 1,
695
+ x => x.moofOffset,
696
+ );
697
+ if (lastCacheIndex !== -1) {
698
+ // Let's use the timestamp of the previous fragment in the cache
699
+ const lastCache = fragmentPositionCache[lastCacheIndex]!;
700
+ offsetFragmentTrackDataByTimestamp(trackData, lastCache.endTimestamp);
701
+ } else {
702
+ // We're the first fragment I guess, "offset by 0"
703
+ }
704
+ }
705
+
706
+ trackData.startTimestampIsFinal = true;
707
+ }
708
+
709
+ // Let's remember that a fragment with a given timestamp is here, speeding up future lookups if no
710
+ // lookup table exists
711
+ const insertionIndex = binarySearchLessOrEqual(
712
+ fragmentPositionCache,
713
+ trackData.startTimestamp,
714
+ x => x.startTimestamp,
715
+ );
716
+ if (
717
+ insertionIndex === -1
718
+ || fragmentPositionCache[insertionIndex]!.moofOffset !== fragment.moofOffset
719
+ ) {
720
+ fragmentPositionCache.splice(insertionIndex + 1, 0, {
721
+ moofOffset: fragment.moofOffset,
722
+ startTimestamp: trackData.startTimestamp,
723
+ endTimestamp: trackData.endTimestamp,
724
+ });
725
+ }
726
+
727
+ // If senc wasn't parsed but saiz+saio were, fetch the aux info now and stamp each sample with it
728
+ if (trackData.encryptionAuxInfo && track.encryptionInfo) {
729
+ const entries = await resolveEncryptionAuxInfo(
730
+ this.reader,
731
+ track.encryptionInfo,
732
+ trackData.encryptionAuxInfo,
733
+ );
734
+
735
+ for (let i = 0; i < Math.min(trackData.samples.length, entries.length); i++) {
736
+ const entry = entries[i]!;
737
+ trackData.samples[i]!.encryption = entry;
738
+ }
739
+ }
740
+ }
741
+
742
+ return fragment;
743
+ }
744
+
745
+ readContiguousBoxes(slice: FileSlice) {
746
+ const startIndex = slice.filePos;
747
+
748
+ while (slice.filePos - startIndex <= slice.length - MIN_BOX_HEADER_SIZE) {
749
+ const foundBox = this.traverseBox(slice);
750
+
751
+ if (!foundBox) {
752
+ break;
753
+ }
754
+ }
755
+ }
756
+
757
+ // eslint-disable-next-line @stylistic/generator-star-spacing
758
+ *iterateContiguousBoxes(slice: FileSlice) {
759
+ const startIndex = slice.filePos;
760
+
761
+ while (slice.filePos - startIndex <= slice.length - MIN_BOX_HEADER_SIZE) {
762
+ const startPos = slice.filePos;
763
+ const boxInfo = readBoxHeader(slice);
764
+ if (!boxInfo) {
765
+ break;
766
+ }
767
+
768
+ yield { boxInfo, slice };
769
+ slice.filePos = startPos + boxInfo.totalSize;
770
+ }
771
+ }
772
+
773
+ traverseBox(slice: FileSlice): boolean {
774
+ const startPos = slice.filePos;
775
+ const boxInfo = readBoxHeader(slice);
776
+ if (!boxInfo) {
777
+ return false;
778
+ }
779
+
780
+ const contentStartPos = slice.filePos;
781
+ const boxEndPos = startPos + boxInfo.totalSize;
782
+
783
+ switch (boxInfo.name) {
784
+ case 'mdia':
785
+ case 'minf':
786
+ case 'dinf':
787
+ case 'mfra':
788
+ case 'edts':
789
+ case 'sinf':
790
+ case 'schi': {
791
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
792
+ }; break;
793
+
794
+ case 'mvhd': {
795
+ const version = readU8(slice);
796
+ slice.skip(3); // Flags
797
+
798
+ if (version === 1) {
799
+ slice.skip(8 + 8);
800
+ this.movieTimescale = readU32Be(slice);
801
+ this.movieDurationInTimescale = readU64Be(slice);
802
+ } else {
803
+ slice.skip(4 + 4);
804
+ this.movieTimescale = readU32Be(slice);
805
+ this.movieDurationInTimescale = readU32Be(slice);
806
+ }
807
+ }; break;
808
+
809
+ case 'trak': {
810
+ const track = {
811
+ id: -1,
812
+ demuxer: this,
813
+ trackBacking: null,
814
+ disposition: {
815
+ ...DEFAULT_TRACK_DISPOSITION,
816
+ primary: false,
817
+ },
818
+ info: null,
819
+ timescale: -1,
820
+ durationInMovieTimescale: -1,
821
+ durationInMediaTimescale: -1,
822
+ rotation: 0,
823
+ internalCodecId: null,
824
+ name: null,
825
+ languageCode: UNDETERMINED_LANGUAGE,
826
+ sampleTableByteOffset: -1,
827
+ sampleTable: null,
828
+ fragmentLookupTable: [],
829
+ currentFragmentState: null,
830
+ fragmentPositionCache: [],
831
+ editListPreviousSegmentDurations: 0,
832
+ editListOffset: 0,
833
+ encryptionInfo: null,
834
+ encryptionAuxInfo: null,
835
+ frmaCodecString: null,
836
+ } satisfies InternalTrack as InternalTrack;
837
+ this.currentTrack = track;
838
+
839
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
840
+
841
+ if (track.id !== -1 && track.timescale !== -1 && track.info !== null) {
842
+ if (track.info.type === 'video' && track.info.width !== -1) {
843
+ const videoTrack = track as InternalVideoTrack;
844
+ track.trackBacking = new IsobmffVideoTrackBacking(videoTrack);
845
+ this.tracks.push(track);
846
+ } else if (track.info.type === 'audio' && track.info.numberOfChannels !== -1) {
847
+ const audioTrack = track as InternalAudioTrack;
848
+ track.trackBacking = new IsobmffAudioTrackBacking(audioTrack);
849
+ this.tracks.push(track);
850
+ }
851
+ }
852
+
853
+ this.currentTrack = null;
854
+ }; break;
855
+
856
+ case 'tkhd': {
857
+ const track = this.currentTrack;
858
+ if (!track) {
859
+ break;
860
+ }
861
+
862
+ const version = readU8(slice);
863
+ const flags = readU24Be(slice);
864
+
865
+ // Spec says disabled tracks are to be treated like they don't exist, but in practice, they are treated
866
+ // more like non-default tracks.
867
+ const trackEnabled = !!(flags & 0x1);
868
+ track.disposition.default = trackEnabled;
869
+
870
+ // Skip over creation & modification time to reach the track ID
871
+ if (version === 0) {
872
+ slice.skip(8);
873
+ track.id = readU32Be(slice);
874
+ slice.skip(4);
875
+ track.durationInMovieTimescale = readU32Be(slice);
876
+ } else if (version === 1) {
877
+ slice.skip(16);
878
+ track.id = readU32Be(slice);
879
+ slice.skip(4);
880
+ track.durationInMovieTimescale = readU64Be(slice);
881
+ } else {
882
+ throw new Error(`Incorrect track header version ${version}.`);
883
+ }
884
+
885
+ slice.skip(2 * 4 + 2 + 2 + 2 + 2);
886
+ const matrix: TransformationMatrix = [
887
+ readFixed_16_16(slice),
888
+ readFixed_16_16(slice),
889
+ readFixed_2_30(slice),
890
+ readFixed_16_16(slice),
891
+ readFixed_16_16(slice),
892
+ readFixed_2_30(slice),
893
+ readFixed_16_16(slice),
894
+ readFixed_16_16(slice),
895
+ readFixed_2_30(slice),
896
+ ];
897
+
898
+ const rotation = normalizeRotation(roundToMultiple(extractRotationFromMatrix(matrix), 90));
899
+ assert(rotation === 0 || rotation === 90 || rotation === 180 || rotation === 270);
900
+
901
+ track.rotation = rotation;
902
+ }; break;
903
+
904
+ case 'elst': {
905
+ const track = this.currentTrack;
906
+ if (!track) {
907
+ break;
908
+ }
909
+
910
+ const version = readU8(slice);
911
+ slice.skip(3); // Flags
912
+
913
+ let relevantEntryFound = false;
914
+ let previousSegmentDurations = 0;
915
+
916
+ const entryCount = readU32Be(slice);
917
+ for (let i = 0; i < entryCount; i++) {
918
+ const segmentDuration = version === 1
919
+ ? readU64Be(slice)
920
+ : readU32Be(slice);
921
+ const mediaTime = version === 1
922
+ ? readI64Be(slice)
923
+ : readI32Be(slice);
924
+ const mediaRate = readFixed_16_16(slice);
925
+
926
+ if (segmentDuration === 0) {
927
+ // Don't care
928
+ continue;
929
+ }
930
+
931
+ if (relevantEntryFound) {
932
+ Logging._warn(
933
+ 'Unsupported edit list: multiple edits are not currently supported. Only using first edit.',
934
+ );
935
+ break;
936
+ }
937
+
938
+ if (mediaTime === -1) {
939
+ previousSegmentDurations += segmentDuration;
940
+ continue;
941
+ }
942
+
943
+ if (mediaRate !== 1) {
944
+ Logging._warn('Unsupported edit list entry: media rate must be 1.');
945
+ break;
946
+ }
947
+
948
+ track.editListPreviousSegmentDurations = previousSegmentDurations;
949
+ track.editListOffset = mediaTime;
950
+ relevantEntryFound = true;
951
+ }
952
+ }; break;
953
+
954
+ case 'mdhd': {
955
+ const track = this.currentTrack;
956
+ if (!track) {
957
+ break;
958
+ }
959
+
960
+ const version = readU8(slice);
961
+ slice.skip(3); // Flags
962
+
963
+ if (version === 0) {
964
+ slice.skip(8);
965
+ track.timescale = readU32Be(slice);
966
+ track.durationInMediaTimescale = readU32Be(slice);
967
+ } else if (version === 1) {
968
+ slice.skip(16);
969
+ track.timescale = readU32Be(slice);
970
+ track.durationInMediaTimescale = readU64Be(slice);
971
+ }
972
+
973
+ let language = readU16Be(slice);
974
+
975
+ if (language > 0) {
976
+ track.languageCode = '';
977
+
978
+ for (let i = 0; i < 3; i++) {
979
+ track.languageCode = String.fromCharCode(0x60 + (language & 0b11111)) + track.languageCode;
980
+ language >>= 5;
981
+ }
982
+
983
+ if (!isIso639Dash2LanguageCode(track.languageCode)) {
984
+ // Sometimes the bytes are garbage
985
+ track.languageCode = UNDETERMINED_LANGUAGE;
986
+ }
987
+ }
988
+ }; break;
989
+
990
+ case 'hdlr': {
991
+ const track = this.currentTrack;
992
+ if (!track) {
993
+ break;
994
+ }
995
+
996
+ slice.skip(8); // Version + flags + pre-defined
997
+ const handlerType = readAscii(slice, 4);
998
+
999
+ if (handlerType === 'vide') {
1000
+ track.info = {
1001
+ type: 'video',
1002
+ width: -1,
1003
+ height: -1,
1004
+ squarePixelWidth: -1,
1005
+ squarePixelHeight: -1,
1006
+ codec: null,
1007
+ codecDescription: null,
1008
+ colorSpace: null,
1009
+ avcType: null,
1010
+ avcCodecInfo: null,
1011
+ hevcCodecInfo: null,
1012
+ vp9CodecInfo: null,
1013
+ av1CodecInfo: null,
1014
+ proresFormat: null,
1015
+ };
1016
+ } else if (handlerType === 'soun') {
1017
+ track.info = {
1018
+ type: 'audio',
1019
+ numberOfChannels: -1,
1020
+ sampleRate: -1,
1021
+ codec: null,
1022
+ codecDescription: null,
1023
+ aacCodecInfo: null,
1024
+ pcmLittleEndian: false,
1025
+ pcmSampleSize: null,
1026
+ };
1027
+ }
1028
+ }; break;
1029
+
1030
+ case 'stbl': {
1031
+ const track = this.currentTrack;
1032
+ if (!track) {
1033
+ break;
1034
+ }
1035
+
1036
+ track.sampleTableByteOffset = startPos;
1037
+
1038
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
1039
+ }; break;
1040
+
1041
+ case 'stsd': {
1042
+ const track = this.currentTrack;
1043
+ if (!track) {
1044
+ break;
1045
+ }
1046
+
1047
+ if (track.info === null || track.sampleTable) {
1048
+ break;
1049
+ }
1050
+
1051
+ const stsdVersion = readU8(slice);
1052
+ slice.skip(3); // Flags
1053
+
1054
+ const entries = readU32Be(slice);
1055
+
1056
+ for (let i = 0; i < entries; i++) {
1057
+ const sampleBoxStartPos = slice.filePos;
1058
+ const sampleBoxInfo = readBoxHeader(slice);
1059
+ if (!sampleBoxInfo) {
1060
+ break;
1061
+ }
1062
+
1063
+ track.internalCodecId = sampleBoxInfo.name;
1064
+ const lowercaseBoxName = sampleBoxInfo.name.toLowerCase();
1065
+
1066
+ if (track.info.type === 'video') {
1067
+ slice.skip(6 * 1 + 2 + 2 + 2 + 3 * 4);
1068
+
1069
+ track.info.width = readU16Be(slice);
1070
+ track.info.height = readU16Be(slice);
1071
+ track.info.squarePixelWidth = track.info.width;
1072
+ track.info.squarePixelHeight = track.info.height;
1073
+
1074
+ slice.skip(4 + 4 + 4 + 2 + 32 + 2 + 2);
1075
+
1076
+ track.frmaCodecString = null;
1077
+ this.readContiguousBoxes(
1078
+ slice.slice(
1079
+ slice.filePos,
1080
+ (sampleBoxStartPos + sampleBoxInfo.totalSize) - slice.filePos,
1081
+ ),
1082
+ );
1083
+
1084
+ const codecName = lowercaseBoxName === 'encv'
1085
+ ? track.frmaCodecString
1086
+ : lowercaseBoxName;
1087
+ track.frmaCodecString = null;
1088
+
1089
+ if (codecName === 'avc1' || codecName === 'avc3') {
1090
+ track.info.codec = 'avc';
1091
+ track.info.avcType = codecName === 'avc1' ? 1 : 3;
1092
+ } else if (codecName === 'hvc1' || codecName === 'hev1') {
1093
+ track.info.codec = 'hevc';
1094
+ } else if (codecName === 'vp08') {
1095
+ track.info.codec = 'vp8';
1096
+ } else if (codecName === 'vp09') {
1097
+ track.info.codec = 'vp9';
1098
+ } else if (codecName === 'av01') {
1099
+ track.info.codec = 'av1';
1100
+ } else if ((PRORES_FOURCCS as readonly string[]).includes(lowercaseBoxName)) {
1101
+ track.info.codec = 'prores';
1102
+ track.info.proresFormat = lowercaseBoxName as ProresFourCc;
1103
+ } else if (codecName === null) {
1104
+ Logging._warn(`Unknown encrypted video codec due to missing frma box.`);
1105
+ } else {
1106
+ Logging._warn(`Unsupported video codec (sample entry type '${sampleBoxInfo.name}').`);
1107
+ }
1108
+ } else {
1109
+ slice.skip(6 * 1 + 2);
1110
+
1111
+ const version = readU16Be(slice);
1112
+ slice.skip(3 * 2);
1113
+
1114
+ let channelCount = readU16Be(slice);
1115
+ let sampleSize = readU16Be(slice);
1116
+
1117
+ slice.skip(2 * 2);
1118
+
1119
+ // Can't use fixed16_16 as that's signed
1120
+ let sampleRate = readU32Be(slice) / 0x10000;
1121
+ let lpcmFlags: number | null = null;
1122
+
1123
+ if (stsdVersion === 0 && version > 0) {
1124
+ // Additional QuickTime fields
1125
+ if (version === 1) {
1126
+ slice.skip(4);
1127
+ sampleSize = 8 * readU32Be(slice);
1128
+ slice.skip(2 * 4);
1129
+ } else if (version === 2) {
1130
+ slice.skip(4);
1131
+ sampleRate = readF64Be(slice);
1132
+ channelCount = readU32Be(slice);
1133
+ slice.skip(4); // Always 0x7f000000
1134
+
1135
+ sampleSize = readU32Be(slice);
1136
+
1137
+ lpcmFlags = readU32Be(slice);
1138
+
1139
+ slice.skip(2 * 4);
1140
+ }
1141
+ }
1142
+
1143
+ track.info.numberOfChannels = channelCount;
1144
+ track.info.sampleRate = sampleRate;
1145
+
1146
+ track.frmaCodecString = null;
1147
+ this.readContiguousBoxes(
1148
+ slice.slice(
1149
+ slice.filePos,
1150
+ (sampleBoxStartPos + sampleBoxInfo.totalSize) - slice.filePos,
1151
+ ),
1152
+ );
1153
+
1154
+ const codecName = lowercaseBoxName === 'enca'
1155
+ ? track.frmaCodecString
1156
+ : lowercaseBoxName;
1157
+ track.frmaCodecString = null;
1158
+
1159
+ // developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions/
1160
+ if (codecName === 'mp4a') {
1161
+ // The codec is set by the esds box
1162
+ } else if (codecName === 'opus') {
1163
+ track.info.codec = 'opus';
1164
+ track.info.sampleRate = OPUS_SAMPLE_RATE; // Always the same
1165
+ } else if (codecName === 'flac') {
1166
+ track.info.codec = 'flac';
1167
+ } else if (codecName === 'ulaw') {
1168
+ track.info.codec = 'ulaw';
1169
+ } else if (codecName === 'alaw') {
1170
+ track.info.codec = 'alaw';
1171
+ } else if (codecName === 'ac-3') {
1172
+ track.info.codec = 'ac3';
1173
+ } else if (codecName === 'ec-3') {
1174
+ track.info.codec = 'eac3';
1175
+ } else if (codecName === 'twos') {
1176
+ if (sampleSize === 8) {
1177
+ track.info.codec = 'pcm-s8';
1178
+ } else if (sampleSize === 16) {
1179
+ track.info.codec = track.info.pcmLittleEndian ? 'pcm-s16' : 'pcm-s16be';
1180
+ } else {
1181
+ Logging._warn(`Unsupported sample size ${sampleSize} for codec 'twos'.`);
1182
+ track.info.codec = null;
1183
+ }
1184
+ } else if (codecName === 'sowt') {
1185
+ if (sampleSize === 8) {
1186
+ track.info.codec = 'pcm-s8';
1187
+ } else if (sampleSize === 16) {
1188
+ track.info.codec = 'pcm-s16';
1189
+ } else {
1190
+ Logging._warn(`Unsupported sample size ${sampleSize} for codec 'sowt'.`);
1191
+ track.info.codec = null;
1192
+ }
1193
+ } else if (codecName === 'raw ') {
1194
+ track.info.codec = 'pcm-u8';
1195
+ } else if (codecName === 'in24') {
1196
+ track.info.codec = track.info.pcmLittleEndian ? 'pcm-s24' : 'pcm-s24be';
1197
+ } else if (codecName === 'in32') {
1198
+ track.info.codec = track.info.pcmLittleEndian ? 'pcm-s32' : 'pcm-s32be';
1199
+ } else if (codecName === 'fl32') {
1200
+ track.info.codec = track.info.pcmLittleEndian ? 'pcm-f32' : 'pcm-f32be';
1201
+ } else if (codecName === 'fl64') {
1202
+ track.info.codec = track.info.pcmLittleEndian ? 'pcm-f64' : 'pcm-f64be';
1203
+ } else if (codecName === 'ipcm') {
1204
+ const pcmSampleSize = track.info.pcmSampleSize;
1205
+
1206
+ if (track.info.pcmLittleEndian) {
1207
+ if (pcmSampleSize === 16) {
1208
+ track.info.codec = 'pcm-s16';
1209
+ } else if (pcmSampleSize === 24) {
1210
+ track.info.codec = 'pcm-s24';
1211
+ } else if (pcmSampleSize === 32) {
1212
+ track.info.codec = 'pcm-s32';
1213
+ } else {
1214
+ Logging._warn(`Invalid ipcm sample size ${pcmSampleSize}.`);
1215
+ track.info.codec = null;
1216
+ }
1217
+ } else {
1218
+ if (pcmSampleSize === 16) {
1219
+ track.info.codec = 'pcm-s16be';
1220
+ } else if (pcmSampleSize === 24) {
1221
+ track.info.codec = 'pcm-s24be';
1222
+ } else if (pcmSampleSize === 32) {
1223
+ track.info.codec = 'pcm-s32be';
1224
+ } else {
1225
+ Logging._warn(`Invalid ipcm sample size ${pcmSampleSize}.`);
1226
+ track.info.codec = null;
1227
+ }
1228
+ }
1229
+ } else if (codecName === 'fpcm') {
1230
+ const pcmSampleSize = track.info.pcmSampleSize;
1231
+
1232
+ if (track.info.pcmLittleEndian) {
1233
+ if (pcmSampleSize === 32) {
1234
+ track.info.codec = 'pcm-f32';
1235
+ } else if (pcmSampleSize === 64) {
1236
+ track.info.codec = 'pcm-f64';
1237
+ } else {
1238
+ Logging._warn(`Invalid fpcm sample size ${pcmSampleSize}.`);
1239
+ track.info.codec = null;
1240
+ }
1241
+ } else {
1242
+ if (pcmSampleSize === 32) {
1243
+ track.info.codec = 'pcm-f32be';
1244
+ } else if (pcmSampleSize === 64) {
1245
+ track.info.codec = 'pcm-f64be';
1246
+ } else {
1247
+ Logging._warn(`Invalid fpcm sample size ${pcmSampleSize}.`);
1248
+ track.info.codec = null;
1249
+ }
1250
+ }
1251
+ } else if (codecName === 'lpcm' && lpcmFlags !== null) {
1252
+ const bytesPerSample = (sampleSize + 7) >> 3;
1253
+ const isFloat = Boolean(lpcmFlags & 1);
1254
+ const isBigEndian = Boolean(lpcmFlags & 2);
1255
+ const sFlags = lpcmFlags & 4 ? -1 : 0; // I guess it means "signed flags" or something?
1256
+
1257
+ if (sampleSize > 0 && sampleSize <= 64) {
1258
+ if (isFloat) {
1259
+ if (sampleSize === 32) {
1260
+ track.info.codec = isBigEndian ? 'pcm-f32be' : 'pcm-f32';
1261
+ }
1262
+ } else {
1263
+ if (sFlags & (1 << (bytesPerSample - 1))) {
1264
+ if (bytesPerSample === 1) {
1265
+ track.info.codec = 'pcm-s8';
1266
+ } else if (bytesPerSample === 2) {
1267
+ track.info.codec = isBigEndian ? 'pcm-s16be' : 'pcm-s16';
1268
+ } else if (bytesPerSample === 3) {
1269
+ track.info.codec = isBigEndian ? 'pcm-s24be' : 'pcm-s24';
1270
+ } else if (bytesPerSample === 4) {
1271
+ track.info.codec = isBigEndian ? 'pcm-s32be' : 'pcm-s32';
1272
+ }
1273
+ } else {
1274
+ if (bytesPerSample === 1) {
1275
+ track.info.codec = 'pcm-u8';
1276
+ }
1277
+ }
1278
+ }
1279
+ }
1280
+
1281
+ if (track.info.codec === null) {
1282
+ Logging._warn('Unsupported PCM format.');
1283
+ }
1284
+ } else if (codecName === null) {
1285
+ Logging._warn(`Unknown encrypted audio codec due to missing frma box.`);
1286
+ } else {
1287
+ Logging._warn(`Unsupported audio codec (sample entry type '${sampleBoxInfo.name}').`);
1288
+ }
1289
+ }
1290
+
1291
+ slice.filePos = sampleBoxStartPos + sampleBoxInfo.totalSize;
1292
+ }
1293
+ }; break;
1294
+
1295
+ case 'frma': {
1296
+ const track = this.currentTrack;
1297
+ if (!track) {
1298
+ break;
1299
+ }
1300
+
1301
+ const format = readAscii(slice, 4);
1302
+ const lowercase = format.toLowerCase();
1303
+
1304
+ // Tells us what codec the encrypted track actually uses
1305
+ track.frmaCodecString = lowercase;
1306
+ }; break;
1307
+
1308
+ case 'schm': {
1309
+ const track = this.currentTrack;
1310
+ if (!track) {
1311
+ break;
1312
+ }
1313
+
1314
+ slice.skip(4); // Version + flags
1315
+
1316
+ const schemeType = readAscii(slice, 4);
1317
+ if (schemeType === 'cenc' || schemeType === 'cens' || schemeType === 'cbcs') {
1318
+ track.encryptionInfo = {
1319
+ scheme: schemeType,
1320
+ defaultKid: null,
1321
+ defaultIsProtected: null,
1322
+ defaultPerSampleIvSize: null,
1323
+ defaultConstantIv: null,
1324
+ defaultCryptByteBlock: null,
1325
+ defaultSkipByteBlock: null,
1326
+ };
1327
+ } else {
1328
+ Logging._warn(`Unsupported encryption scheme '${schemeType}'.`);
1329
+ }
1330
+ }; break;
1331
+
1332
+ case 'tenc': {
1333
+ const track = this.currentTrack;
1334
+ if (!track || !track.encryptionInfo) {
1335
+ break;
1336
+ }
1337
+
1338
+ const version = readU8(slice);
1339
+ slice.skip(3); // Flags
1340
+ slice.skip(1); // Reserved
1341
+
1342
+ const patternByte = readU8(slice);
1343
+ if (version > 0) {
1344
+ track.encryptionInfo.defaultCryptByteBlock = patternByte >> 4;
1345
+ track.encryptionInfo.defaultSkipByteBlock = patternByte & 0xf;
1346
+ } else {
1347
+ track.encryptionInfo.defaultCryptByteBlock = 0;
1348
+ track.encryptionInfo.defaultSkipByteBlock = 0;
1349
+ }
1350
+
1351
+ track.encryptionInfo.defaultIsProtected = readU8(slice) !== 0;
1352
+ track.encryptionInfo.defaultPerSampleIvSize = readU8(slice);
1353
+ track.encryptionInfo.defaultKid = bytesToHexString(readBytes(slice, 16));
1354
+
1355
+ if (track.encryptionInfo.defaultIsProtected && track.encryptionInfo.defaultPerSampleIvSize === 0) {
1356
+ const constantIvSize = readU8(slice);
1357
+ const constantIv = new Uint8Array(16);
1358
+ constantIv.set(readBytes(slice, constantIvSize), 0);
1359
+ track.encryptionInfo.defaultConstantIv = constantIv;
1360
+ }
1361
+ }; break;
1362
+
1363
+ case 'avcC': {
1364
+ const track = this.currentTrack;
1365
+ if (!track) {
1366
+ break;
1367
+ }
1368
+ assert(track.info);
1369
+
1370
+ track.info.codecDescription = readBytes(slice, boxInfo.contentSize);
1371
+ }; break;
1372
+
1373
+ case 'hvcC': {
1374
+ const track = this.currentTrack;
1375
+ if (!track) {
1376
+ break;
1377
+ }
1378
+ assert(track.info);
1379
+
1380
+ track.info.codecDescription = readBytes(slice, boxInfo.contentSize);
1381
+ }; break;
1382
+
1383
+ case 'vpcC': {
1384
+ const track = this.currentTrack;
1385
+ if (!track) {
1386
+ break;
1387
+ }
1388
+ assert(track.info?.type === 'video');
1389
+
1390
+ slice.skip(4); // Version + flags
1391
+
1392
+ const profile = readU8(slice);
1393
+ const level = readU8(slice);
1394
+ const thirdByte = readU8(slice);
1395
+ const bitDepth = thirdByte >> 4;
1396
+ const chromaSubsampling = (thirdByte >> 1) & 0b111;
1397
+ const videoFullRangeFlag = thirdByte & 1;
1398
+ const colourPrimaries = readU8(slice);
1399
+ const transferCharacteristics = readU8(slice);
1400
+ const matrixCoefficients = readU8(slice);
1401
+
1402
+ track.info.vp9CodecInfo = {
1403
+ profile,
1404
+ level,
1405
+ bitDepth,
1406
+ chromaSubsampling,
1407
+ videoFullRangeFlag,
1408
+ colourPrimaries,
1409
+ transferCharacteristics,
1410
+ matrixCoefficients,
1411
+ };
1412
+ }; break;
1413
+
1414
+ case 'av1C': {
1415
+ const track = this.currentTrack;
1416
+ if (!track) {
1417
+ break;
1418
+ }
1419
+ assert(track.info?.type === 'video');
1420
+
1421
+ slice.skip(1); // Marker + version
1422
+
1423
+ const secondByte = readU8(slice);
1424
+ const profile = secondByte >> 5;
1425
+ const level = secondByte & 0b11111;
1426
+
1427
+ const thirdByte = readU8(slice);
1428
+ const tier = thirdByte >> 7;
1429
+ const highBitDepth = (thirdByte >> 6) & 1;
1430
+ const twelveBit = (thirdByte >> 5) & 1;
1431
+ const monochrome = (thirdByte >> 4) & 1;
1432
+ const chromaSubsamplingX = (thirdByte >> 3) & 1;
1433
+ const chromaSubsamplingY = (thirdByte >> 2) & 1;
1434
+ const chromaSamplePosition = thirdByte & 0b11;
1435
+
1436
+ // Logic from https://aomediacodec.github.io/av1-spec/av1-spec.pdf
1437
+ const bitDepth = profile === 2 && highBitDepth ? (twelveBit ? 12 : 10) : (highBitDepth ? 10 : 8);
1438
+
1439
+ track.info.av1CodecInfo = {
1440
+ profile,
1441
+ level,
1442
+ tier,
1443
+ bitDepth,
1444
+ monochrome,
1445
+ chromaSubsamplingX,
1446
+ chromaSubsamplingY,
1447
+ chromaSamplePosition,
1448
+ };
1449
+ }; break;
1450
+
1451
+ case 'colr': {
1452
+ const track = this.currentTrack;
1453
+ if (!track) {
1454
+ break;
1455
+ }
1456
+ assert(track.info?.type === 'video');
1457
+
1458
+ const colourType = readAscii(slice, 4);
1459
+ if (colourType !== 'nclx' && colourType !== 'nclc') {
1460
+ break;
1461
+ }
1462
+
1463
+ const colourPrimaries = readU16Be(slice);
1464
+ const transferCharacteristics = readU16Be(slice);
1465
+ const matrixCoefficients = readU16Be(slice);
1466
+
1467
+ let fullRange: boolean | undefined = undefined;
1468
+ if (colourType === 'nclx') {
1469
+ fullRange = Boolean(readU8(slice) & 0x80);
1470
+ }
1471
+
1472
+ track.info.colorSpace = {
1473
+ primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
1474
+ transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
1475
+ matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
1476
+ fullRange,
1477
+ } as VideoColorSpaceInit;
1478
+ }; break;
1479
+
1480
+ case 'pasp': {
1481
+ const track = this.currentTrack;
1482
+ if (!track) {
1483
+ break;
1484
+ }
1485
+ assert(track.info?.type === 'video');
1486
+
1487
+ const num = readU32Be(slice);
1488
+ const den = readU32Be(slice);
1489
+
1490
+ // https://github.com/Vanilagy/mediabunny/issues/362
1491
+ if (num > 0 && den > 0) {
1492
+ if (num > den) {
1493
+ track.info.squarePixelWidth = Math.round(track.info.width * num / den);
1494
+ } else {
1495
+ track.info.squarePixelHeight = Math.round(track.info.height * den / num);
1496
+ }
1497
+ }
1498
+ }; break;
1499
+
1500
+ case 'wave': {
1501
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
1502
+ }; break;
1503
+
1504
+ case 'esds': {
1505
+ const track = this.currentTrack;
1506
+ if (!track) {
1507
+ break;
1508
+ }
1509
+ assert(track.info?.type === 'audio');
1510
+
1511
+ slice.skip(4); // Version + flags
1512
+
1513
+ const tag = readU8(slice);
1514
+ assert(tag === 0x03); // ES Descriptor
1515
+
1516
+ readIsomVariableInteger(slice); // Length
1517
+
1518
+ slice.skip(2); // ES ID
1519
+ const mixed = readU8(slice);
1520
+
1521
+ const streamDependenceFlag = (mixed & 0x80) !== 0;
1522
+ const urlFlag = (mixed & 0x40) !== 0;
1523
+ const ocrStreamFlag = (mixed & 0x20) !== 0;
1524
+
1525
+ if (streamDependenceFlag) {
1526
+ slice.skip(2);
1527
+ }
1528
+ if (urlFlag) {
1529
+ const urlLength = readU8(slice);
1530
+ slice.skip(urlLength);
1531
+ }
1532
+ if (ocrStreamFlag) {
1533
+ slice.skip(2);
1534
+ }
1535
+
1536
+ const decoderConfigTag = readU8(slice);
1537
+ assert(decoderConfigTag === 0x04); // DecoderConfigDescriptor
1538
+
1539
+ const decoderConfigDescriptorLength = readIsomVariableInteger(slice); // Length
1540
+
1541
+ const payloadStart = slice.filePos;
1542
+
1543
+ const objectTypeIndication = readU8(slice);
1544
+ if (objectTypeIndication === 0x40 || objectTypeIndication === 0x67) {
1545
+ track.info.codec = 'aac';
1546
+ track.info.aacCodecInfo = {
1547
+ isMpeg2: objectTypeIndication === 0x67,
1548
+ objectType: null,
1549
+ };
1550
+ } else if (objectTypeIndication === 0x69 || objectTypeIndication === 0x6b) {
1551
+ track.info.codec = 'mp3';
1552
+ } else if (objectTypeIndication === 0xdd) {
1553
+ track.info.codec = 'vorbis'; // "nonstandard, gpac uses it" - FFmpeg
1554
+ } else {
1555
+ Logging._warn(
1556
+ `Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`,
1557
+ );
1558
+ }
1559
+
1560
+ slice.skip(1 + 3 + 4 + 4);
1561
+
1562
+ if (decoderConfigDescriptorLength > slice.filePos - payloadStart) {
1563
+ // There's a DecoderSpecificInfo at the end, let's read it
1564
+
1565
+ const decoderSpecificInfoTag = readU8(slice);
1566
+ assert(decoderSpecificInfoTag === 0x05); // DecoderSpecificInfo
1567
+
1568
+ const decoderSpecificInfoLength = readIsomVariableInteger(slice);
1569
+ track.info.codecDescription = readBytes(slice, decoderSpecificInfoLength);
1570
+
1571
+ if (track.info.codec === 'aac') {
1572
+ // Let's try to deduce more accurate values directly from the AudioSpecificConfig:
1573
+ const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription);
1574
+ if (audioSpecificConfig.numberOfChannels !== null) {
1575
+ track.info.numberOfChannels = audioSpecificConfig.numberOfChannels;
1576
+ }
1577
+ if (audioSpecificConfig.sampleRate !== null) {
1578
+ track.info.sampleRate = audioSpecificConfig.sampleRate;
1579
+ }
1580
+ }
1581
+ }
1582
+ }; break;
1583
+
1584
+ case 'enda': {
1585
+ const track = this.currentTrack;
1586
+ if (!track) {
1587
+ break;
1588
+ }
1589
+ assert(track.info?.type === 'audio');
1590
+
1591
+ track.info.pcmLittleEndian = !!(readU16Be(slice) & 0xff); // 0xff is from FFmpeg
1592
+ }; break;
1593
+
1594
+ case 'pcmC': {
1595
+ const track = this.currentTrack;
1596
+ if (!track) {
1597
+ break;
1598
+ }
1599
+ assert(track.info?.type === 'audio');
1600
+
1601
+ slice.skip(1 + 3); // Version + flags
1602
+
1603
+ // ISO/IEC 23003-5
1604
+
1605
+ const formatFlags = readU8(slice);
1606
+ track.info.pcmLittleEndian = Boolean(formatFlags & 0x01);
1607
+ track.info.pcmSampleSize = readU8(slice);
1608
+ }; break;
1609
+
1610
+ case 'dOps': { // Used for Opus audio
1611
+ const track = this.currentTrack;
1612
+ if (!track) {
1613
+ break;
1614
+ }
1615
+ assert(track.info?.type === 'audio');
1616
+
1617
+ slice.skip(1); // Version
1618
+
1619
+ // https://www.opus-codec.org/docs/opus_in_isobmff.html
1620
+ const outputChannelCount = readU8(slice);
1621
+ const preSkip = readU16Be(slice);
1622
+ const inputSampleRate = readU32Be(slice);
1623
+ const outputGain = readI16Be(slice);
1624
+ const channelMappingFamily = readU8(slice);
1625
+
1626
+ let channelMappingTable: Uint8Array;
1627
+ if (channelMappingFamily !== 0) {
1628
+ channelMappingTable = readBytes(slice, 2 + outputChannelCount);
1629
+ } else {
1630
+ channelMappingTable = new Uint8Array(0);
1631
+ }
1632
+
1633
+ // https://datatracker.ietf.org/doc/html/draft-ietf-codec-oggopus-06
1634
+ const description = new Uint8Array(8 + 1 + 1 + 2 + 4 + 2 + 1 + channelMappingTable.byteLength);
1635
+ const view = new DataView(description.buffer);
1636
+ view.setUint32(0, 0x4f707573, false); // 'Opus'
1637
+ view.setUint32(4, 0x48656164, false); // 'Head'
1638
+ view.setUint8(8, 1); // Version
1639
+ view.setUint8(9, outputChannelCount);
1640
+ view.setUint16(10, preSkip, true);
1641
+ view.setUint32(12, inputSampleRate, true);
1642
+ view.setInt16(16, outputGain, true);
1643
+ view.setUint8(18, channelMappingFamily);
1644
+ description.set(channelMappingTable, 19);
1645
+
1646
+ track.info.codecDescription = description;
1647
+ track.info.numberOfChannels = outputChannelCount;
1648
+ // Don't copy the input sample rate, irrelevant, and output sample rate is fixed
1649
+ }; break;
1650
+
1651
+ case 'dfLa': { // Used for FLAC audio
1652
+ const track = this.currentTrack;
1653
+ if (!track) {
1654
+ break;
1655
+ }
1656
+ assert(track.info?.type === 'audio');
1657
+
1658
+ slice.skip(4); // Version + flags
1659
+
1660
+ // https://datatracker.ietf.org/doc/rfc9639/
1661
+
1662
+ const BLOCK_TYPE_MASK = 0x7f;
1663
+ const LAST_METADATA_BLOCK_FLAG_MASK = 0x80;
1664
+
1665
+ const startPos = slice.filePos;
1666
+
1667
+ while (slice.filePos < boxEndPos) {
1668
+ const flagAndType = readU8(slice);
1669
+ const metadataBlockLength = readU24Be(slice);
1670
+ const type = flagAndType & BLOCK_TYPE_MASK;
1671
+
1672
+ // It's a STREAMINFO block; let's extract the actual sample rate and channel count
1673
+ if (type === FlacBlockType.STREAMINFO) {
1674
+ slice.skip(10);
1675
+
1676
+ // Extract sample rate and channel count
1677
+ const word = readU32Be(slice);
1678
+ const sampleRate = word >>> 12;
1679
+ const numberOfChannels = ((word >> 9) & 0b111) + 1;
1680
+
1681
+ track.info.sampleRate = sampleRate;
1682
+ track.info.numberOfChannels = numberOfChannels;
1683
+
1684
+ slice.skip(20);
1685
+ } else {
1686
+ // Simply skip ahead to the next block
1687
+ slice.skip(metadataBlockLength);
1688
+ }
1689
+
1690
+ if (flagAndType & LAST_METADATA_BLOCK_FLAG_MASK) {
1691
+ break;
1692
+ }
1693
+ }
1694
+
1695
+ const endPos = slice.filePos;
1696
+ slice.filePos = startPos;
1697
+ const bytes = readBytes(slice, endPos - startPos);
1698
+
1699
+ const description = new Uint8Array(4 + bytes.byteLength);
1700
+ const view = new DataView(description.buffer);
1701
+ view.setUint32(0, 0x664c6143, false); // 'fLaC'
1702
+ description.set(bytes, 4);
1703
+
1704
+ // Set the codec description to be 'fLaC' + all metadata blocks
1705
+ track.info.codecDescription = description;
1706
+ }; break;
1707
+
1708
+ case 'dac3': { // AC3SpecificBox
1709
+ const track = this.currentTrack;
1710
+ if (!track) {
1711
+ break;
1712
+ }
1713
+ assert(track.info?.type === 'audio');
1714
+
1715
+ const bytes = readBytes(slice, 3);
1716
+ const bitstream = new Bitstream(bytes);
1717
+
1718
+ const fscod = bitstream.readBits(2);
1719
+ bitstream.skipBits(5 + 3); // Skip bsid and bsmod
1720
+ const acmod = bitstream.readBits(3);
1721
+ const lfeon = bitstream.readBits(1);
1722
+
1723
+ if (fscod < 3) {
1724
+ track.info.sampleRate = AC3_SAMPLE_RATES[fscod]!;
1725
+ }
1726
+
1727
+ track.info.numberOfChannels = AC3_ACMOD_CHANNEL_COUNTS[acmod]! + lfeon;
1728
+ }; break;
1729
+
1730
+ case 'dec3': { // EC3SpecificBox
1731
+ const track = this.currentTrack;
1732
+ if (!track) {
1733
+ break;
1734
+ }
1735
+ assert(track.info?.type === 'audio');
1736
+
1737
+ const bytes = readBytes(slice, boxInfo.contentSize);
1738
+ const config = parseEac3Config(bytes);
1739
+
1740
+ if (!config) {
1741
+ Logging._warn('Invalid dec3 box contents, ignoring.');
1742
+ break;
1743
+ }
1744
+
1745
+ const sampleRate = getEac3SampleRate(config);
1746
+ if (sampleRate !== null) {
1747
+ track.info.sampleRate = sampleRate;
1748
+ }
1749
+
1750
+ track.info.numberOfChannels = getEac3ChannelCount(config);
1751
+ }; break;
1752
+
1753
+ case 'stts': {
1754
+ const track = this.currentTrack;
1755
+ if (!track) {
1756
+ break;
1757
+ }
1758
+
1759
+ if (!track.sampleTable) {
1760
+ break;
1761
+ }
1762
+
1763
+ slice.skip(4); // Version + flags
1764
+
1765
+ const entryCount = readU32Be(slice);
1766
+
1767
+ let currentIndex = 0;
1768
+ let currentTimestamp = 0;
1769
+
1770
+ for (let i = 0; i < entryCount; i++) {
1771
+ const sampleCount = readU32Be(slice);
1772
+ const sampleDelta = readU32Be(slice);
1773
+
1774
+ track.sampleTable.sampleTimingEntries.push({
1775
+ startIndex: currentIndex,
1776
+ startDecodeTimestamp: currentTimestamp,
1777
+ count: sampleCount,
1778
+ delta: sampleDelta,
1779
+ });
1780
+
1781
+ currentIndex += sampleCount;
1782
+ currentTimestamp += sampleCount * sampleDelta;
1783
+ }
1784
+ }; break;
1785
+
1786
+ case 'ctts': {
1787
+ const track = this.currentTrack;
1788
+ if (!track) {
1789
+ break;
1790
+ }
1791
+
1792
+ if (!track.sampleTable) {
1793
+ break;
1794
+ }
1795
+
1796
+ slice.skip(1 + 3); // Version + flags
1797
+
1798
+ const entryCount = readU32Be(slice);
1799
+
1800
+ let sampleIndex = 0;
1801
+ for (let i = 0; i < entryCount; i++) {
1802
+ const sampleCount = readU32Be(slice);
1803
+ const sampleOffset = readI32Be(slice);
1804
+
1805
+ track.sampleTable.sampleCompositionTimeOffsets.push({
1806
+ startIndex: sampleIndex,
1807
+ count: sampleCount,
1808
+ offset: sampleOffset,
1809
+ });
1810
+
1811
+ sampleIndex += sampleCount;
1812
+ }
1813
+ }; break;
1814
+
1815
+ case 'stsz': {
1816
+ const track = this.currentTrack;
1817
+ if (!track) {
1818
+ break;
1819
+ }
1820
+
1821
+ if (!track.sampleTable) {
1822
+ break;
1823
+ }
1824
+
1825
+ slice.skip(4); // Version + flags
1826
+
1827
+ const sampleSize = readU32Be(slice);
1828
+ const sampleCount = readU32Be(slice);
1829
+
1830
+ if (sampleSize === 0) {
1831
+ for (let i = 0; i < sampleCount; i++) {
1832
+ const sampleSize = readU32Be(slice);
1833
+ track.sampleTable.sampleSizes.push(sampleSize);
1834
+ }
1835
+ } else {
1836
+ track.sampleTable.sampleSizes.push(sampleSize);
1837
+ }
1838
+ }; break;
1839
+
1840
+ case 'stz2': {
1841
+ const track = this.currentTrack;
1842
+ if (!track) {
1843
+ break;
1844
+ }
1845
+
1846
+ if (!track.sampleTable) {
1847
+ break;
1848
+ }
1849
+
1850
+ slice.skip(4); // Version + flags
1851
+ slice.skip(3); // Reserved
1852
+
1853
+ const fieldSize = readU8(slice); // in bits
1854
+ const sampleCount = readU32Be(slice);
1855
+
1856
+ const bytes = readBytes(slice, Math.ceil(sampleCount * fieldSize / 8));
1857
+ const bitstream = new Bitstream(bytes);
1858
+
1859
+ for (let i = 0; i < sampleCount; i++) {
1860
+ const sampleSize = bitstream.readBits(fieldSize);
1861
+ track.sampleTable.sampleSizes.push(sampleSize);
1862
+ }
1863
+ }; break;
1864
+
1865
+ case 'stss': {
1866
+ const track = this.currentTrack;
1867
+ if (!track) {
1868
+ break;
1869
+ }
1870
+
1871
+ if (!track.sampleTable) {
1872
+ break;
1873
+ }
1874
+
1875
+ slice.skip(4); // Version + flags
1876
+
1877
+ track.sampleTable.keySampleIndices = [];
1878
+
1879
+ const entryCount = readU32Be(slice);
1880
+ for (let i = 0; i < entryCount; i++) {
1881
+ const sampleIndex = readU32Be(slice) - 1; // Convert to 0-indexed
1882
+ track.sampleTable.keySampleIndices.push(sampleIndex);
1883
+ }
1884
+
1885
+ if (track.sampleTable.keySampleIndices[0] !== 0) {
1886
+ // Some files don't mark the first sample a key sample, which is basically almost always incorrect.
1887
+ // Here, we correct for that mistake:
1888
+ track.sampleTable.keySampleIndices.unshift(0);
1889
+ }
1890
+ }; break;
1891
+
1892
+ case 'stsc': {
1893
+ const track = this.currentTrack;
1894
+ if (!track) {
1895
+ break;
1896
+ }
1897
+
1898
+ if (!track.sampleTable) {
1899
+ break;
1900
+ }
1901
+
1902
+ slice.skip(4);
1903
+
1904
+ const entryCount = readU32Be(slice);
1905
+
1906
+ for (let i = 0; i < entryCount; i++) {
1907
+ const startChunkIndex = readU32Be(slice) - 1; // Convert to 0-indexed
1908
+ const samplesPerChunk = readU32Be(slice);
1909
+ const sampleDescriptionIndex = readU32Be(slice);
1910
+
1911
+ track.sampleTable.sampleToChunk.push({
1912
+ startSampleIndex: -1,
1913
+ startChunkIndex,
1914
+ samplesPerChunk,
1915
+ sampleDescriptionIndex,
1916
+ });
1917
+ }
1918
+
1919
+ let startSampleIndex = 0;
1920
+ for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) {
1921
+ track.sampleTable.sampleToChunk[i]!.startSampleIndex = startSampleIndex;
1922
+
1923
+ if (i < track.sampleTable.sampleToChunk.length - 1) {
1924
+ const nextChunk = track.sampleTable.sampleToChunk[i + 1]!;
1925
+ const chunkCount = nextChunk.startChunkIndex
1926
+ - track.sampleTable.sampleToChunk[i]!.startChunkIndex;
1927
+ startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i]!.samplesPerChunk;
1928
+ }
1929
+ }
1930
+ }; break;
1931
+
1932
+ case 'stco': {
1933
+ const track = this.currentTrack;
1934
+ if (!track) {
1935
+ break;
1936
+ }
1937
+
1938
+ if (!track.sampleTable) {
1939
+ break;
1940
+ }
1941
+
1942
+ slice.skip(4); // Version + flags
1943
+
1944
+ const entryCount = readU32Be(slice);
1945
+
1946
+ for (let i = 0; i < entryCount; i++) {
1947
+ const chunkOffset = readU32Be(slice);
1948
+ track.sampleTable.chunkOffsets.push(chunkOffset);
1949
+ }
1950
+ }; break;
1951
+
1952
+ case 'co64': {
1953
+ const track = this.currentTrack;
1954
+ if (!track) {
1955
+ break;
1956
+ }
1957
+
1958
+ if (!track.sampleTable) {
1959
+ break;
1960
+ }
1961
+
1962
+ slice.skip(4); // Version + flags
1963
+
1964
+ const entryCount = readU32Be(slice);
1965
+
1966
+ for (let i = 0; i < entryCount; i++) {
1967
+ const chunkOffset = readU64Be(slice);
1968
+ track.sampleTable.chunkOffsets.push(chunkOffset);
1969
+ }
1970
+ }; break;
1971
+
1972
+ case 'mvex': {
1973
+ this.isFragmented = true;
1974
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
1975
+ }; break;
1976
+
1977
+ case 'mehd': {
1978
+ const version = readU8(slice);
1979
+ slice.skip(3); // Flags
1980
+
1981
+ const fragmentDuration = version === 1 ? readU64Be(slice) : readU32Be(slice);
1982
+ this.movieDurationInTimescale = fragmentDuration;
1983
+ }; break;
1984
+
1985
+ case 'trex': {
1986
+ slice.skip(4); // Version + flags
1987
+
1988
+ const trackId = readU32Be(slice);
1989
+ const defaultSampleDescriptionIndex = readU32Be(slice);
1990
+ const defaultSampleDuration = readU32Be(slice);
1991
+ const defaultSampleSize = readU32Be(slice);
1992
+ const defaultSampleFlags = readU32Be(slice);
1993
+
1994
+ // We store these separately rather than in the tracks since the tracks may not exist yet
1995
+ this.fragmentTrackDefaults.push({
1996
+ trackId,
1997
+ defaultSampleDescriptionIndex,
1998
+ defaultSampleDuration,
1999
+ defaultSampleSize,
2000
+ defaultSampleFlags,
2001
+ });
2002
+ }; break;
2003
+
2004
+ case 'tfra': {
2005
+ const version = readU8(slice);
2006
+ slice.skip(3); // Flags
2007
+
2008
+ const trackId = readU32Be(slice);
2009
+ const track = this.tracks.find(x => x.id === trackId);
2010
+ if (!track) {
2011
+ break;
2012
+ }
2013
+
2014
+ const word = readU32Be(slice);
2015
+
2016
+ const lengthSizeOfTrafNum = (word & 0b110000) >> 4;
2017
+ const lengthSizeOfTrunNum = (word & 0b001100) >> 2;
2018
+ const lengthSizeOfSampleNum = word & 0b000011;
2019
+
2020
+ const functions = [readU8, readU16Be, readU24Be, readU32Be];
2021
+
2022
+ const readTrafNum = functions[lengthSizeOfTrafNum]!;
2023
+ const readTrunNum = functions[lengthSizeOfTrunNum]!;
2024
+ const readSampleNum = functions[lengthSizeOfSampleNum]!;
2025
+
2026
+ const numberOfEntries = readU32Be(slice);
2027
+ for (let i = 0; i < numberOfEntries; i++) {
2028
+ const time = version === 1 ? readU64Be(slice) : readU32Be(slice);
2029
+ const moofOffset = version === 1 ? readU64Be(slice) : readU32Be(slice);
2030
+
2031
+ readTrafNum(slice);
2032
+ readTrunNum(slice);
2033
+ readSampleNum(slice);
2034
+
2035
+ track.fragmentLookupTable.push({
2036
+ timestamp: time,
2037
+ moofOffset,
2038
+ });
2039
+ }
2040
+
2041
+ // Sort by timestamp in case it's not naturally sorted
2042
+ track.fragmentLookupTable.sort((a, b) => a.timestamp - b.timestamp);
2043
+
2044
+ // Remove multiple entries for the same time
2045
+ for (let i = 0; i < track.fragmentLookupTable.length - 1; i++) {
2046
+ const entry1 = track.fragmentLookupTable[i]!;
2047
+ const entry2 = track.fragmentLookupTable[i + 1]!;
2048
+
2049
+ if (entry1.timestamp === entry2.timestamp) {
2050
+ track.fragmentLookupTable.splice(i + 1, 1);
2051
+ i--;
2052
+ }
2053
+ }
2054
+ }; break;
2055
+
2056
+ case 'moof': {
2057
+ this.currentFragment = {
2058
+ moofOffset: startPos,
2059
+ moofSize: boxInfo.totalSize,
2060
+ implicitBaseDataOffset: startPos,
2061
+ trackData: new Map(),
2062
+ psshBoxes: [],
2063
+ };
2064
+
2065
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
2066
+
2067
+ this.lastReadFragment = this.currentFragment;
2068
+ this.currentFragment = null;
2069
+ }; break;
2070
+
2071
+ case 'traf': {
2072
+ assert(this.currentFragment);
2073
+
2074
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
2075
+
2076
+ // It is possible that there is no current track, for example when we don't care about the track
2077
+ // referenced in the track fragment header.
2078
+ if (this.currentTrack) {
2079
+ const trackData = this.currentFragment.trackData.get(this.currentTrack.id);
2080
+ cond:
2081
+ if (trackData) {
2082
+ if (trackData.samples.length === 0) {
2083
+ // Don't associate the fragment with the track if it has no samples, this simplifies
2084
+ // other code
2085
+ this.currentFragment.trackData.delete(this.currentTrack.id);
2086
+ break cond;
2087
+ }
2088
+
2089
+ trackData.presentationTimestamps = trackData.samples
2090
+ .map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i }))
2091
+ .sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
2092
+
2093
+ for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
2094
+ const currentEntry = trackData.presentationTimestamps[i]!;
2095
+ const currentSample = trackData.samples[currentEntry.sampleIndex]!;
2096
+
2097
+ if (trackData.firstKeyFrameTimestamp === null && currentSample.isKeyFrame) {
2098
+ trackData.firstKeyFrameTimestamp = currentSample.presentationTimestamp;
2099
+ }
2100
+
2101
+ if (i < trackData.presentationTimestamps.length - 1) {
2102
+ // Update sample durations based on presentation order
2103
+ const nextEntry = trackData.presentationTimestamps[i + 1]!;
2104
+ const duration = nextEntry.presentationTimestamp - currentEntry.presentationTimestamp;
2105
+
2106
+ currentSample.duration = duration;
2107
+ }
2108
+ }
2109
+
2110
+ const firstSample = trackData.samples[trackData.presentationTimestamps[0]!.sampleIndex]!;
2111
+ const lastSample = trackData.samples[last(trackData.presentationTimestamps)!.sampleIndex]!;
2112
+
2113
+ trackData.startTimestamp = firstSample.presentationTimestamp;
2114
+ trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration;
2115
+
2116
+ const { currentFragmentState } = this.currentTrack;
2117
+ assert(currentFragmentState);
2118
+
2119
+ if (currentFragmentState.startTimestamp !== null) {
2120
+ offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp);
2121
+ trackData.startTimestampIsFinal = true;
2122
+ }
2123
+
2124
+ // Transfer the buffered saiz+saio state onto the track data, so readFragment can resolve it
2125
+ // once all boxes are parsed. Only relevant if senc wasn't already used to populate samples.
2126
+ if (currentFragmentState.encryptionAuxInfo && !trackData.samples[0]!.encryption) {
2127
+ trackData.encryptionAuxInfo = currentFragmentState.encryptionAuxInfo;
2128
+ }
2129
+ }
2130
+
2131
+ this.currentTrack.currentFragmentState = null;
2132
+ this.currentTrack = null;
2133
+ }
2134
+ }; break;
2135
+
2136
+ case 'pssh': {
2137
+ if (this.input._formatOptions.isobmff?._suppressPsshParsing) {
2138
+ break;
2139
+ }
2140
+
2141
+ const psshBox = parsePsshBoxContents(readBytes(slice, boxInfo.contentSize));
2142
+
2143
+ if (this.currentFragment) {
2144
+ this.currentFragment.psshBoxes.push(psshBox);
2145
+ } else if (!this.currentTrack) {
2146
+ this.psshBoxes.push(psshBox);
2147
+ }
2148
+ }; break;
2149
+
2150
+ case 'tfhd': {
2151
+ assert(this.currentFragment);
2152
+
2153
+ slice.skip(1); // Version
2154
+
2155
+ const flags = readU24Be(slice);
2156
+ const baseDataOffsetPresent = Boolean(flags & 0x000001);
2157
+ const sampleDescriptionIndexPresent = Boolean(flags & 0x000002);
2158
+ const defaultSampleDurationPresent = Boolean(flags & 0x000008);
2159
+ const defaultSampleSizePresent = Boolean(flags & 0x000010);
2160
+ const defaultSampleFlagsPresent = Boolean(flags & 0x000020);
2161
+ const durationIsEmpty = Boolean(flags & 0x010000);
2162
+ const defaultBaseIsMoof = Boolean(flags & 0x020000);
2163
+
2164
+ const trackId = readU32Be(slice);
2165
+ const track = this.tracks.find(x => x.id === trackId);
2166
+ if (!track) {
2167
+ // We don't care about this track
2168
+ break;
2169
+ }
2170
+
2171
+ const defaults = this.fragmentTrackDefaults.find(x => x.trackId === trackId);
2172
+
2173
+ this.currentTrack = track;
2174
+ track.currentFragmentState = {
2175
+ baseDataOffset: this.currentFragment.implicitBaseDataOffset,
2176
+ sampleDescriptionIndex: defaults?.defaultSampleDescriptionIndex ?? null,
2177
+ defaultSampleDuration: defaults?.defaultSampleDuration ?? null,
2178
+ defaultSampleSize: defaults?.defaultSampleSize ?? null,
2179
+ defaultSampleFlags: defaults?.defaultSampleFlags ?? null,
2180
+ startTimestamp: null,
2181
+ encryptionAuxInfo: null,
2182
+ };
2183
+
2184
+ if (baseDataOffsetPresent) {
2185
+ track.currentFragmentState.baseDataOffset = readU64Be(slice);
2186
+ } else if (defaultBaseIsMoof) {
2187
+ track.currentFragmentState.baseDataOffset = this.currentFragment.moofOffset;
2188
+ }
2189
+ if (sampleDescriptionIndexPresent) {
2190
+ track.currentFragmentState.sampleDescriptionIndex = readU32Be(slice);
2191
+ }
2192
+ if (defaultSampleDurationPresent) {
2193
+ track.currentFragmentState.defaultSampleDuration = readU32Be(slice);
2194
+ }
2195
+ if (defaultSampleSizePresent) {
2196
+ track.currentFragmentState.defaultSampleSize = readU32Be(slice);
2197
+ }
2198
+ if (defaultSampleFlagsPresent) {
2199
+ track.currentFragmentState.defaultSampleFlags = readU32Be(slice);
2200
+ }
2201
+ if (durationIsEmpty) {
2202
+ track.currentFragmentState.defaultSampleDuration = 0;
2203
+ }
2204
+ }; break;
2205
+
2206
+ case 'tfdt': {
2207
+ const track = this.currentTrack;
2208
+ if (!track) {
2209
+ break;
2210
+ }
2211
+
2212
+ assert(track.currentFragmentState);
2213
+
2214
+ const version = readU8(slice);
2215
+ slice.skip(3); // Flags
2216
+
2217
+ const baseMediaDecodeTime = version === 0 ? readU32Be(slice) : readU64Be(slice);
2218
+ track.currentFragmentState.startTimestamp = baseMediaDecodeTime;
2219
+ }; break;
2220
+
2221
+ case 'trun': {
2222
+ const track = this.currentTrack;
2223
+ if (!track) {
2224
+ break;
2225
+ }
2226
+
2227
+ assert(this.currentFragment);
2228
+ assert(track.currentFragmentState);
2229
+
2230
+ const version = readU8(slice);
2231
+ const flags = readU24Be(slice);
2232
+ const dataOffsetPresent = Boolean(flags & 0x000001);
2233
+ const firstSampleFlagsPresent = Boolean(flags & 0x000004);
2234
+ const sampleDurationPresent = Boolean(flags & 0x000100);
2235
+ const sampleSizePresent = Boolean(flags & 0x000200);
2236
+ const sampleFlagsPresent = Boolean(flags & 0x000400);
2237
+ const sampleCompositionTimeOffsetsPresent = Boolean(flags & 0x000800);
2238
+
2239
+ const sampleCount = readU32Be(slice);
2240
+
2241
+ let dataOffset: number | null = null;
2242
+ if (dataOffsetPresent) {
2243
+ dataOffset = readI32Be(slice);
2244
+ }
2245
+ let firstSampleFlags: number | null = null;
2246
+ if (firstSampleFlagsPresent) {
2247
+ firstSampleFlags = readU32Be(slice);
2248
+ }
2249
+
2250
+ let trackData: FragmentTrackData;
2251
+
2252
+ if (this.currentFragment.trackData.has(track.id)) {
2253
+ trackData = this.currentFragment.trackData.get(track.id)!;
2254
+
2255
+ if (dataOffset !== null) {
2256
+ trackData.currentOffset = track.currentFragmentState.baseDataOffset + dataOffset;
2257
+ } else {
2258
+ // "If the data-offset is not present, then the data for this run starts immediately after the
2259
+ // data of the previous run"
2260
+ }
2261
+ } else {
2262
+ trackData = {
2263
+ track,
2264
+ currentTimestamp: 0,
2265
+ currentOffset: track.currentFragmentState.baseDataOffset + (dataOffset ?? 0),
2266
+ startTimestamp: 0,
2267
+ endTimestamp: 0,
2268
+ firstKeyFrameTimestamp: null,
2269
+ samples: [],
2270
+ presentationTimestamps: [],
2271
+ startTimestampIsFinal: false,
2272
+ encryptionAuxInfo: null,
2273
+ };
2274
+ this.currentFragment.trackData.set(track.id, trackData);
2275
+ }
2276
+
2277
+ for (let i = 0; i < sampleCount; i++) {
2278
+ let sampleDuration: number;
2279
+ if (sampleDurationPresent) {
2280
+ sampleDuration = readU32Be(slice);
2281
+ } else {
2282
+ assert(track.currentFragmentState.defaultSampleDuration !== null);
2283
+ sampleDuration = track.currentFragmentState.defaultSampleDuration;
2284
+ }
2285
+
2286
+ let sampleSize: number;
2287
+ if (sampleSizePresent) {
2288
+ sampleSize = readU32Be(slice);
2289
+ } else {
2290
+ assert(track.currentFragmentState.defaultSampleSize !== null);
2291
+ sampleSize = track.currentFragmentState.defaultSampleSize;
2292
+ }
2293
+
2294
+ let sampleFlags: number;
2295
+ if (sampleFlagsPresent) {
2296
+ sampleFlags = readU32Be(slice);
2297
+ } else {
2298
+ assert(track.currentFragmentState.defaultSampleFlags !== null);
2299
+ sampleFlags = track.currentFragmentState.defaultSampleFlags;
2300
+ }
2301
+ if (i === 0 && firstSampleFlags !== null) {
2302
+ sampleFlags = firstSampleFlags;
2303
+ }
2304
+
2305
+ let sampleCompositionTimeOffset = 0;
2306
+ if (sampleCompositionTimeOffsetsPresent) {
2307
+ if (version === 0) {
2308
+ sampleCompositionTimeOffset = readU32Be(slice);
2309
+ } else {
2310
+ sampleCompositionTimeOffset = readI32Be(slice);
2311
+ }
2312
+ }
2313
+
2314
+ const isKeyFrame = !(sampleFlags & 0x00010000);
2315
+
2316
+ trackData.samples.push({
2317
+ presentationTimestamp: trackData.currentTimestamp + sampleCompositionTimeOffset,
2318
+ duration: sampleDuration,
2319
+ byteOffset: trackData.currentOffset,
2320
+ byteSize: sampleSize,
2321
+ isKeyFrame,
2322
+ encryption: null,
2323
+ });
2324
+
2325
+ trackData.currentOffset += sampleSize;
2326
+ trackData.currentTimestamp += sampleDuration;
2327
+ }
2328
+
2329
+ this.currentFragment.implicitBaseDataOffset = trackData.currentOffset;
2330
+ }; break;
2331
+
2332
+ case 'saiz': {
2333
+ // Sample Auxiliary Information Sizes - per-sample sizes of (typically) the encryption aux info.
2334
+ const track = this.currentTrack;
2335
+ if (!track || !track.encryptionInfo) {
2336
+ break;
2337
+ }
2338
+
2339
+ slice.skip(1); // Version
2340
+ const flags = readU24Be(slice);
2341
+
2342
+ if (flags & 0x01) {
2343
+ const auxInfoType = readAscii(slice, 4);
2344
+ const auxInfoTypeParam = readU32Be(slice);
2345
+ if (auxInfoType !== track.encryptionInfo.scheme || auxInfoTypeParam !== 0) {
2346
+ // Not the encryption aux info
2347
+ break;
2348
+ }
2349
+ }
2350
+
2351
+ const defaultSampleInfoSize = readU8(slice);
2352
+ const sampleCount = readU32Be(slice);
2353
+
2354
+ let sampleSizes: Uint8Array | null = null;
2355
+ if (defaultSampleInfoSize === 0 && sampleCount > 0) {
2356
+ sampleSizes = readBytes(slice, sampleCount);
2357
+ }
2358
+
2359
+ const aux = getOrCreateEncryptionAuxInfo(track);
2360
+ aux.defaultSampleInfoSize = defaultSampleInfoSize;
2361
+ aux.sampleSizes = sampleSizes;
2362
+ aux.sampleCount = sampleCount;
2363
+ }; break;
2364
+
2365
+ case 'saio': {
2366
+ // Sample Auxiliary Information Offsets - file offset(s) where the aux info lives.
2367
+ const track = this.currentTrack;
2368
+ if (!track || !track.encryptionInfo) {
2369
+ break;
2370
+ }
2371
+
2372
+ const version = readU8(slice);
2373
+ const flags = readU24Be(slice);
2374
+
2375
+ if (flags & 0x01) {
2376
+ const auxInfoType = readAscii(slice, 4);
2377
+ const auxInfoTypeParam = readU32Be(slice);
2378
+ if (auxInfoType !== track.encryptionInfo.scheme || auxInfoTypeParam !== 0) {
2379
+ break;
2380
+ }
2381
+ }
2382
+
2383
+ const entryCount = readU32Be(slice);
2384
+ if (entryCount === 0) {
2385
+ break;
2386
+ }
2387
+ if (entryCount > 1) {
2388
+ Logging._warn('Multiple saio entries are not supported; using the first offset only.');
2389
+ }
2390
+
2391
+ let offset = version === 0 ? readU32Be(slice) : Number(readU64Be(slice));
2392
+
2393
+ // Per ISO/IEC 23001-7: when saio is inside a moof, offsets are relative to the start of the moof box.
2394
+ if (this.currentFragment) {
2395
+ offset += this.currentFragment.moofOffset;
2396
+ }
2397
+
2398
+ const aux = getOrCreateEncryptionAuxInfo(track);
2399
+ aux.offset = offset;
2400
+ }; break;
2401
+
2402
+ case 'senc': {
2403
+ // Per-sample encryption info inside a 'traf'. Holds per-sample IV and optional subsample breakdown
2404
+ // for CENC-protected samples
2405
+ const track = this.currentTrack;
2406
+ if (!track || !track.encryptionInfo) {
2407
+ break;
2408
+ }
2409
+
2410
+ assert(this.currentFragment);
2411
+ const trackData = this.currentFragment.trackData.get(track.id);
2412
+ if (!trackData) {
2413
+ break;
2414
+ }
2415
+
2416
+ slice.skip(1); // Version
2417
+ const flags = readU24Be(slice);
2418
+ const useSubsamples = Boolean(flags & 0x000002);
2419
+
2420
+ const sampleCount = readU32Be(slice);
2421
+ const ivSize = track.encryptionInfo.defaultPerSampleIvSize;
2422
+ assert(ivSize !== null);
2423
+
2424
+ for (let i = 0; i < Math.min(sampleCount, trackData.samples.length); i++) {
2425
+ // Normalize the IV to 16 bytes so downstream code can assume a full-length buffer. For CTR with
2426
+ // an 8-byte per-sample IV the lower 8 bytes are zero (that's the CENC spec's block counter start);
2427
+ // for CBC/cbcs the IV is always 16 bytes by spec.
2428
+ const iv = new Uint8Array(16);
2429
+ if (ivSize > 0) {
2430
+ iv.set(readBytes(slice, ivSize), 0);
2431
+ } else {
2432
+ iv.set(track.encryptionInfo.defaultConstantIv!, 0);
2433
+ }
2434
+
2435
+ let subsamples: SampleEncryptionInfo['subsamples'] = null;
2436
+ if (useSubsamples) {
2437
+ const subsampleCount = readU16Be(slice);
2438
+ subsamples = [];
2439
+ for (let j = 0; j < subsampleCount; j++) {
2440
+ const clearLen = readU16Be(slice);
2441
+ const protectedLen = readU32Be(slice);
2442
+ subsamples.push({ clearLen, protectedLen });
2443
+ }
2444
+ }
2445
+
2446
+ const sample = trackData.samples[i]!;
2447
+ sample.encryption = { iv, subsamples };
2448
+ }
2449
+ }; break;
2450
+
2451
+ // Metadata section
2452
+ // https://exiftool.org/TagNames/QuickTime.html
2453
+ // https://mp4workshop.com/about
2454
+
2455
+ case 'udta': { // Contains either movie metadata or track metadata
2456
+ const iterator = this.iterateContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
2457
+
2458
+ for (const { boxInfo, slice } of iterator) {
2459
+ if (boxInfo.name !== 'meta' && !this.currentTrack) {
2460
+ const startPos = slice.filePos;
2461
+ this.metadataTags.raw ??= {};
2462
+
2463
+ if (boxInfo.name[0] === '©') {
2464
+ // https://mp4workshop.com/about
2465
+ // Box name starting with © indicates "international text"
2466
+ this.metadataTags.raw[boxInfo.name] ??= readMetadataStringShort(slice);
2467
+ } else {
2468
+ this.metadataTags.raw[boxInfo.name] ??= readBytes(slice, boxInfo.contentSize);
2469
+ }
2470
+
2471
+ slice.filePos = startPos;
2472
+ }
2473
+
2474
+ switch (boxInfo.name) {
2475
+ case 'meta': {
2476
+ slice.skip(-boxInfo.headerSize);
2477
+ this.traverseBox(slice);
2478
+ }; break;
2479
+
2480
+ case '©nam':
2481
+ case 'name': {
2482
+ if (this.currentTrack) {
2483
+ this.currentTrack.name = textDecoder.decode(readBytes(slice, boxInfo.contentSize));
2484
+ } else {
2485
+ this.metadataTags.title ??= readMetadataStringShort(slice);
2486
+ }
2487
+ }; break;
2488
+
2489
+ case '©des': {
2490
+ if (!this.currentTrack) {
2491
+ this.metadataTags.description ??= readMetadataStringShort(slice);
2492
+ }
2493
+ }; break;
2494
+
2495
+ case '©ART': {
2496
+ if (!this.currentTrack) {
2497
+ this.metadataTags.artist ??= readMetadataStringShort(slice);
2498
+ }
2499
+ }; break;
2500
+
2501
+ case '©alb': {
2502
+ if (!this.currentTrack) {
2503
+ this.metadataTags.album ??= readMetadataStringShort(slice);
2504
+ }
2505
+ }; break;
2506
+
2507
+ case 'albr': {
2508
+ if (!this.currentTrack) {
2509
+ this.metadataTags.albumArtist ??= readMetadataStringShort(slice);
2510
+ }
2511
+ }; break;
2512
+
2513
+ case '©gen': {
2514
+ if (!this.currentTrack) {
2515
+ this.metadataTags.genre ??= readMetadataStringShort(slice);
2516
+ }
2517
+ }; break;
2518
+
2519
+ case '©day': {
2520
+ if (!this.currentTrack) {
2521
+ const date = new Date(readMetadataStringShort(slice));
2522
+ if (!Number.isNaN(date.getTime())) {
2523
+ this.metadataTags.date ??= date;
2524
+ }
2525
+ }
2526
+ }; break;
2527
+
2528
+ case '©cmt': {
2529
+ if (!this.currentTrack) {
2530
+ this.metadataTags.comment ??= readMetadataStringShort(slice);
2531
+ }
2532
+ }; break;
2533
+
2534
+ case '©lyr': {
2535
+ if (!this.currentTrack) {
2536
+ this.metadataTags.lyrics ??= readMetadataStringShort(slice);
2537
+ }
2538
+ }; break;
2539
+ }
2540
+ }
2541
+ }; break;
2542
+
2543
+ case 'meta': {
2544
+ if (this.currentTrack) {
2545
+ break; // Only care about movie-level metadata for now
2546
+ }
2547
+
2548
+ // The 'meta' box comes in two flavors, one with flags/version and one without. To know which is which,
2549
+ // let's read the next 4 bytes, which are either the version or the size of the first subbox.
2550
+ const word = readU32Be(slice);
2551
+ const isQuickTime = word !== 0;
2552
+
2553
+ this.currentMetadataKeys = new Map();
2554
+
2555
+ if (isQuickTime) {
2556
+ this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
2557
+ } else {
2558
+ this.readContiguousBoxes(slice.slice(contentStartPos + 4, boxInfo.contentSize - 4));
2559
+ }
2560
+
2561
+ this.currentMetadataKeys = null;
2562
+ }; break;
2563
+
2564
+ case 'keys': {
2565
+ if (!this.currentMetadataKeys) {
2566
+ break;
2567
+ }
2568
+
2569
+ slice.skip(4); // Version + flags
2570
+
2571
+ const entryCount = readU32Be(slice);
2572
+
2573
+ for (let i = 0; i < entryCount; i++) {
2574
+ const keySize = readU32Be(slice);
2575
+ slice.skip(4); // Key namespace
2576
+ const keyName = textDecoder.decode(readBytes(slice, keySize - 8));
2577
+
2578
+ this.currentMetadataKeys.set(i + 1, keyName);
2579
+ }
2580
+ }; break;
2581
+
2582
+ case 'ilst': {
2583
+ if (!this.currentMetadataKeys) {
2584
+ break;
2585
+ }
2586
+
2587
+ const iterator = this.iterateContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
2588
+
2589
+ for (const { boxInfo, slice } of iterator) {
2590
+ let metadataKey = boxInfo.name;
2591
+
2592
+ // Interpret the box name as a u32be
2593
+ const nameAsNumber = (metadataKey.charCodeAt(0) << 24)
2594
+ + (metadataKey.charCodeAt(1) << 16)
2595
+ + (metadataKey.charCodeAt(2) << 8)
2596
+ + metadataKey.charCodeAt(3);
2597
+
2598
+ if (this.currentMetadataKeys.has(nameAsNumber)) {
2599
+ // An entry exists for this number
2600
+ metadataKey = this.currentMetadataKeys.get(nameAsNumber)!;
2601
+ }
2602
+
2603
+ const data = readDataBox(slice);
2604
+
2605
+ this.metadataTags.raw ??= {};
2606
+ this.metadataTags.raw[metadataKey] ??= data;
2607
+
2608
+ switch (metadataKey) {
2609
+ case '©nam':
2610
+ case 'titl':
2611
+ case 'com.apple.quicktime.title':
2612
+ case 'title': {
2613
+ if (typeof data === 'string') {
2614
+ this.metadataTags.title ??= data;
2615
+ }
2616
+ }; break;
2617
+
2618
+ case '©des':
2619
+ case 'desc':
2620
+ case 'dscp':
2621
+ case 'com.apple.quicktime.description':
2622
+ case 'description': {
2623
+ if (typeof data === 'string') {
2624
+ this.metadataTags.description ??= data;
2625
+ }
2626
+ }; break;
2627
+
2628
+ case '©ART':
2629
+ case 'com.apple.quicktime.artist':
2630
+ case 'artist': {
2631
+ if (typeof data === 'string') {
2632
+ this.metadataTags.artist ??= data;
2633
+ }
2634
+ }; break;
2635
+
2636
+ case '©alb':
2637
+ case 'albm':
2638
+ case 'com.apple.quicktime.album':
2639
+ case 'album': {
2640
+ if (typeof data === 'string') {
2641
+ this.metadataTags.album ??= data;
2642
+ }
2643
+ }; break;
2644
+
2645
+ case 'aART':
2646
+ case 'album_artist': {
2647
+ if (typeof data === 'string') {
2648
+ this.metadataTags.albumArtist ??= data;
2649
+ }
2650
+ }; break;
2651
+
2652
+ case '©cmt':
2653
+ case 'com.apple.quicktime.comment':
2654
+ case 'comment': {
2655
+ if (typeof data === 'string') {
2656
+ this.metadataTags.comment ??= data;
2657
+ }
2658
+ }; break;
2659
+
2660
+ case '©gen':
2661
+ case 'gnre':
2662
+ case 'com.apple.quicktime.genre':
2663
+ case 'genre': {
2664
+ if (typeof data === 'string') {
2665
+ this.metadataTags.genre ??= data;
2666
+ }
2667
+ }; break;
2668
+
2669
+ case '©lyr':
2670
+ case 'lyrics': {
2671
+ if (typeof data === 'string') {
2672
+ this.metadataTags.lyrics ??= data;
2673
+ }
2674
+ }; break;
2675
+
2676
+ case '©day':
2677
+ case 'rldt':
2678
+ case 'com.apple.quicktime.creationdate':
2679
+ case 'date': {
2680
+ if (typeof data === 'string') {
2681
+ const date = new Date(data);
2682
+ if (!Number.isNaN(date.getTime())) {
2683
+ this.metadataTags.date ??= date;
2684
+ }
2685
+ }
2686
+ }; break;
2687
+
2688
+ case 'covr':
2689
+ case 'com.apple.quicktime.artwork': {
2690
+ if (data instanceof RichImageData) {
2691
+ this.metadataTags.images ??= [];
2692
+ this.metadataTags.images.push({
2693
+ data: data.data,
2694
+ kind: 'coverFront',
2695
+ mimeType: data.mimeType,
2696
+ });
2697
+ } else if (data instanceof Uint8Array) {
2698
+ this.metadataTags.images ??= [];
2699
+ this.metadataTags.images.push({
2700
+ data,
2701
+ kind: 'coverFront',
2702
+ mimeType: 'image/*',
2703
+ });
2704
+ }
2705
+ }; break;
2706
+
2707
+ case 'track': {
2708
+ if (typeof data === 'string') {
2709
+ const parts = data.split('/');
2710
+ const trackNum = Number.parseInt(parts[0]!, 10);
2711
+ const tracksTotal = parts[1] && Number.parseInt(parts[1], 10);
2712
+
2713
+ if (Number.isInteger(trackNum) && trackNum > 0) {
2714
+ this.metadataTags.trackNumber ??= trackNum;
2715
+ }
2716
+ if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) {
2717
+ this.metadataTags.tracksTotal ??= tracksTotal;
2718
+ }
2719
+ }
2720
+ }; break;
2721
+
2722
+ case 'trkn': {
2723
+ if (data instanceof Uint8Array && data.length >= 6) {
2724
+ const view = toDataView(data);
2725
+
2726
+ const trackNumber = view.getUint16(2, false);
2727
+ const tracksTotal = view.getUint16(4, false);
2728
+
2729
+ if (trackNumber > 0) {
2730
+ this.metadataTags.trackNumber ??= trackNumber;
2731
+ }
2732
+ if (tracksTotal > 0) {
2733
+ this.metadataTags.tracksTotal ??= tracksTotal;
2734
+ }
2735
+ }
2736
+ }; break;
2737
+
2738
+ case 'disc':
2739
+ case 'disk': {
2740
+ if (data instanceof Uint8Array && data.length >= 6) {
2741
+ const view = toDataView(data);
2742
+
2743
+ const discNumber = view.getUint16(2, false);
2744
+ const discNumberMax = view.getUint16(4, false);
2745
+
2746
+ if (discNumber > 0) {
2747
+ this.metadataTags.discNumber ??= discNumber;
2748
+ }
2749
+ if (discNumberMax > 0) {
2750
+ this.metadataTags.discsTotal ??= discNumberMax;
2751
+ }
2752
+ }
2753
+ }; break;
2754
+ }
2755
+ }
2756
+ }; break;
2757
+ }
2758
+
2759
+ slice.filePos = boxEndPos;
2760
+ return true;
2761
+ }
2762
+ }
2763
+
2764
+ abstract class IsobmffTrackBacking implements InputTrackBacking {
2765
+ packetToSampleIndex = new WeakMap<EncodedPacket, number>();
2766
+ packetToFragmentLocation = new WeakMap<EncodedPacket, {
2767
+ fragment: Fragment;
2768
+ sampleIndex: number;
2769
+ }>();
2770
+
2771
+ constructor(public internalTrack: InternalTrack) {}
2772
+
2773
+ abstract getType(): TrackType;
2774
+ abstract getDecoderConfig(): Promise<VideoDecoderConfig | AudioDecoderConfig | null>;
2775
+
2776
+ getId() {
2777
+ return this.internalTrack.id;
2778
+ }
2779
+
2780
+ getNumber() {
2781
+ const demuxer = this.internalTrack.demuxer;
2782
+ const trackType = this.internalTrack.trackBacking!.getType();
2783
+
2784
+ let number = 0;
2785
+ for (const track of demuxer.tracks) {
2786
+ if (track.trackBacking!.getType() === trackType) {
2787
+ number++;
2788
+ }
2789
+
2790
+ if (track === this.internalTrack) {
2791
+ break;
2792
+ }
2793
+ }
2794
+
2795
+ return number;
2796
+ }
2797
+
2798
+ getCodec(): MediaCodec | null {
2799
+ throw new Error('Not implemented on base class.');
2800
+ }
2801
+
2802
+ getInternalCodecId() {
2803
+ return this.internalTrack.internalCodecId;
2804
+ }
2805
+
2806
+ getName() {
2807
+ return this.internalTrack.name;
2808
+ }
2809
+
2810
+ getLanguageCode() {
2811
+ return this.internalTrack.languageCode;
2812
+ }
2813
+
2814
+ getTimeResolution() {
2815
+ return this.internalTrack.timescale;
2816
+ }
2817
+
2818
+ isRelativeToUnixEpoch() {
2819
+ return false;
2820
+ }
2821
+
2822
+ getUnixTimeForTimestamp() {
2823
+ return null;
2824
+ }
2825
+
2826
+ getDisposition() {
2827
+ return this.internalTrack.disposition;
2828
+ }
2829
+
2830
+ getPairingMask() {
2831
+ return 1n;
2832
+ }
2833
+
2834
+ getBitrate() {
2835
+ return null;
2836
+ }
2837
+
2838
+ getAverageBitrate() {
2839
+ return null;
2840
+ }
2841
+
2842
+ async getDurationFromMetadata() {
2843
+ const track = this.internalTrack;
2844
+ if (track.durationInMediaTimescale <= 0) {
2845
+ // The duration is often zero for fragmented files for example; return `null` to signal that the duration
2846
+ // must be computed instead.
2847
+ return null;
2848
+ }
2849
+
2850
+ assert(track.trackBacking);
2851
+
2852
+ const firstPacket = await track.trackBacking.getFirstPacket({ metadataOnly: true });
2853
+ return (firstPacket?.timestamp ?? 0) + track.durationInMediaTimescale / track.timescale;
2854
+ }
2855
+
2856
+ async getLiveRefreshInterval() {
2857
+ return null;
2858
+ }
2859
+
2860
+ async getFirstPacket(options: PacketRetrievalOptions) {
2861
+ const regularPacket = await this.fetchPacketForSampleIndex(0, options);
2862
+ if (regularPacket || !this.internalTrack.demuxer.isFragmented) {
2863
+ // If there's a non-fragmented packet, always prefer that
2864
+ return regularPacket;
2865
+ }
2866
+
2867
+ return this.performFragmentedLookup(
2868
+ null,
2869
+ (fragment) => {
2870
+ const trackData = fragment.trackData.get(this.internalTrack.id);
2871
+ if (trackData) {
2872
+ return {
2873
+ sampleIndex: 0,
2874
+ correctSampleFound: true,
2875
+ };
2876
+ }
2877
+
2878
+ return {
2879
+ sampleIndex: -1,
2880
+ correctSampleFound: false,
2881
+ };
2882
+ },
2883
+ -Infinity, // Use -Infinity as a search timestamp to avoid using the lookup entries
2884
+ Infinity,
2885
+ options,
2886
+ );
2887
+ }
2888
+
2889
+ private mapTimestampIntoTimescale(timestamp: number) {
2890
+ // Do a little rounding to catch cases where the result is very close to an integer. If it is, it's likely
2891
+ // that the number was originally an integer divided by the timescale. For stability, it's best
2892
+ // to return the integer in this case.
2893
+ return roundIfAlmostInteger(timestamp * this.internalTrack.timescale) + this.internalTrack.editListOffset;
2894
+ }
2895
+
2896
+ async getPacket(timestamp: number, options: PacketRetrievalOptions) {
2897
+ const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
2898
+
2899
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
2900
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale);
2901
+ const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options);
2902
+
2903
+ if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
2904
+ // Prefer the non-fragmented packet
2905
+ return regularPacket;
2906
+ }
2907
+
2908
+ return this.performFragmentedLookup(
2909
+ null,
2910
+ (fragment) => {
2911
+ const trackData = fragment.trackData.get(this.internalTrack.id);
2912
+ if (!trackData) {
2913
+ return { sampleIndex: -1, correctSampleFound: false };
2914
+ }
2915
+
2916
+ const index = binarySearchLessOrEqual(
2917
+ trackData.presentationTimestamps,
2918
+ timestampInTimescale,
2919
+ x => x.presentationTimestamp,
2920
+ );
2921
+
2922
+ const sampleIndex = index !== -1 ? trackData.presentationTimestamps[index]!.sampleIndex : -1;
2923
+ const correctSampleFound = index !== -1 && timestampInTimescale < trackData.endTimestamp;
2924
+
2925
+ return { sampleIndex, correctSampleFound };
2926
+ },
2927
+ timestampInTimescale,
2928
+ timestampInTimescale,
2929
+ options,
2930
+ );
2931
+ }
2932
+
2933
+ async getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
2934
+ const regularSampleIndex = this.packetToSampleIndex.get(packet);
2935
+
2936
+ if (regularSampleIndex !== undefined) {
2937
+ // Prefer the non-fragmented packet
2938
+ return this.fetchPacketForSampleIndex(regularSampleIndex + 1, options);
2939
+ }
2940
+
2941
+ const locationInFragment = this.packetToFragmentLocation.get(packet);
2942
+ if (locationInFragment === undefined) {
2943
+ throw new Error('Packet was not created from this track.');
2944
+ }
2945
+
2946
+ return this.performFragmentedLookup(
2947
+ locationInFragment.fragment,
2948
+ (fragment) => {
2949
+ if (fragment === locationInFragment.fragment) {
2950
+ const trackData = fragment.trackData.get(this.internalTrack.id)!;
2951
+ if (locationInFragment.sampleIndex + 1 < trackData.samples.length) {
2952
+ // We can simply take the next sample in the fragment
2953
+ return {
2954
+ sampleIndex: locationInFragment.sampleIndex + 1,
2955
+ correctSampleFound: true,
2956
+ };
2957
+ }
2958
+ } else {
2959
+ const trackData = fragment.trackData.get(this.internalTrack.id);
2960
+ if (trackData) {
2961
+ return {
2962
+ sampleIndex: 0,
2963
+ correctSampleFound: true,
2964
+ };
2965
+ }
2966
+ }
2967
+
2968
+ return {
2969
+ sampleIndex: -1,
2970
+ correctSampleFound: false,
2971
+ };
2972
+ },
2973
+ -Infinity, // Use -Infinity as a search timestamp to avoid using the lookup entries
2974
+ Infinity,
2975
+ options,
2976
+ );
2977
+ }
2978
+
2979
+ async getKeyPacket(timestamp: number, options: PacketRetrievalOptions) {
2980
+ const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
2981
+
2982
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
2983
+ const sampleIndex = getKeyframeSampleIndexForTimestamp(sampleTable, timestampInTimescale);
2984
+ const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options);
2985
+
2986
+ if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
2987
+ // Prefer the non-fragmented packet
2988
+ return regularPacket;
2989
+ }
2990
+
2991
+ return this.performFragmentedLookup(
2992
+ null,
2993
+ (fragment) => {
2994
+ const trackData = fragment.trackData.get(this.internalTrack.id);
2995
+ if (!trackData) {
2996
+ return { sampleIndex: -1, correctSampleFound: false };
2997
+ }
2998
+
2999
+ const index = findLastIndex(trackData.presentationTimestamps, (x) => {
3000
+ const sample = trackData.samples[x.sampleIndex]!;
3001
+ return sample.isKeyFrame && x.presentationTimestamp <= timestampInTimescale;
3002
+ });
3003
+
3004
+ const sampleIndex = index !== -1 ? trackData.presentationTimestamps[index]!.sampleIndex : -1;
3005
+ const correctSampleFound = index !== -1 && timestampInTimescale < trackData.endTimestamp;
3006
+
3007
+ return { sampleIndex, correctSampleFound };
3008
+ },
3009
+ timestampInTimescale,
3010
+ timestampInTimescale,
3011
+ options,
3012
+ );
3013
+ }
3014
+
3015
+ async getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
3016
+ const regularSampleIndex = this.packetToSampleIndex.get(packet);
3017
+ if (regularSampleIndex !== undefined) {
3018
+ // Prefer the non-fragmented packet
3019
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
3020
+ const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, regularSampleIndex);
3021
+ return this.fetchPacketForSampleIndex(nextKeyFrameSampleIndex, options);
3022
+ }
3023
+
3024
+ const locationInFragment = this.packetToFragmentLocation.get(packet);
3025
+ if (locationInFragment === undefined) {
3026
+ throw new Error('Packet was not created from this track.');
3027
+ }
3028
+
3029
+ return this.performFragmentedLookup(
3030
+ locationInFragment.fragment,
3031
+ (fragment) => {
3032
+ if (fragment === locationInFragment.fragment) {
3033
+ const trackData = fragment.trackData.get(this.internalTrack.id)!;
3034
+ const nextKeyFrameIndex = trackData.samples.findIndex(
3035
+ (x, i) => x.isKeyFrame && i > locationInFragment.sampleIndex,
3036
+ );
3037
+
3038
+ if (nextKeyFrameIndex !== -1) {
3039
+ // We can simply take the next key frame in the fragment
3040
+ return {
3041
+ sampleIndex: nextKeyFrameIndex,
3042
+ correctSampleFound: true,
3043
+ };
3044
+ }
3045
+ } else {
3046
+ const trackData = fragment.trackData.get(this.internalTrack.id);
3047
+ if (trackData && trackData.firstKeyFrameTimestamp !== null) {
3048
+ const keyFrameIndex = trackData.samples.findIndex(x => x.isKeyFrame);
3049
+ assert(keyFrameIndex !== -1); // There must be one
3050
+
3051
+ return {
3052
+ sampleIndex: keyFrameIndex,
3053
+ correctSampleFound: true,
3054
+ };
3055
+ }
3056
+ }
3057
+
3058
+ return {
3059
+ sampleIndex: -1,
3060
+ correctSampleFound: false,
3061
+ };
3062
+ },
3063
+ -Infinity, // Use -Infinity as a search timestamp to avoid using the lookup entries
3064
+ Infinity,
3065
+ options,
3066
+ );
3067
+ }
3068
+
3069
+ private async fetchPacketForSampleIndex(sampleIndex: number, options: PacketRetrievalOptions) {
3070
+ if (sampleIndex === -1) {
3071
+ return null;
3072
+ }
3073
+
3074
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
3075
+ const sampleInfo = getSampleInfo(sampleTable, sampleIndex);
3076
+ if (!sampleInfo) {
3077
+ return null;
3078
+ }
3079
+
3080
+ let data: Uint8Array;
3081
+ if (options.metadataOnly) {
3082
+ data = PLACEHOLDER_DATA;
3083
+ } else {
3084
+ let slice = this.internalTrack.demuxer.reader.requestSlice(
3085
+ sampleInfo.sampleOffset,
3086
+ sampleInfo.sampleSize,
3087
+ );
3088
+ if (slice instanceof Promise) slice = await slice;
3089
+ if (!slice) {
3090
+ return null; // Data is outside
3091
+ }
3092
+
3093
+ data = readBytes(slice, sampleInfo.sampleSize);
3094
+
3095
+ if (this.internalTrack.encryptionAuxInfo) {
3096
+ assert(this.internalTrack.encryptionInfo);
3097
+
3098
+ const entries = await resolveEncryptionAuxInfo(
3099
+ this.internalTrack.demuxer.reader,
3100
+ this.internalTrack.encryptionInfo,
3101
+ this.internalTrack.encryptionAuxInfo,
3102
+ );
3103
+
3104
+ if (sampleIndex < entries.length) {
3105
+ data = await decryptSample(this.internalTrack, entries[sampleIndex]!, data, null);
3106
+ }
3107
+ }
3108
+ }
3109
+
3110
+ const timestamp = (sampleInfo.presentationTimestamp - this.internalTrack.editListOffset)
3111
+ / this.internalTrack.timescale;
3112
+ const duration = sampleInfo.duration / this.internalTrack.timescale;
3113
+ const packet = new EncodedPacket(
3114
+ data,
3115
+ sampleInfo.isKeyFrame ? 'key' : 'delta',
3116
+ timestamp,
3117
+ duration,
3118
+ sampleIndex,
3119
+ sampleInfo.sampleSize,
3120
+ );
3121
+
3122
+ this.packetToSampleIndex.set(packet, sampleIndex);
3123
+
3124
+ return packet;
3125
+ }
3126
+
3127
+ private async fetchPacketInFragment(fragment: Fragment, sampleIndex: number, options: PacketRetrievalOptions) {
3128
+ if (sampleIndex === -1) {
3129
+ return null;
3130
+ }
3131
+
3132
+ const trackData = fragment.trackData.get(this.internalTrack.id)!;
3133
+ const fragmentSample = trackData.samples[sampleIndex];
3134
+ assert(fragmentSample);
3135
+
3136
+ let data: Uint8Array;
3137
+ if (options.metadataOnly) {
3138
+ data = PLACEHOLDER_DATA;
3139
+ } else {
3140
+ let slice = this.internalTrack.demuxer.reader.requestSlice(
3141
+ fragmentSample.byteOffset,
3142
+ fragmentSample.byteSize,
3143
+ );
3144
+ if (slice instanceof Promise) slice = await slice;
3145
+ if (!slice) {
3146
+ return null; // Data is outside
3147
+ }
3148
+
3149
+ data = readBytes(slice, fragmentSample.byteSize);
3150
+
3151
+ if (fragmentSample.encryption) {
3152
+ data = await decryptSample(this.internalTrack, fragmentSample.encryption, data, fragment);
3153
+ }
3154
+ }
3155
+
3156
+ const timestamp = (fragmentSample.presentationTimestamp - this.internalTrack.editListOffset)
3157
+ / this.internalTrack.timescale;
3158
+ const duration = fragmentSample.duration / this.internalTrack.timescale;
3159
+ const packet = new EncodedPacket(
3160
+ data,
3161
+ fragmentSample.isKeyFrame ? 'key' : 'delta',
3162
+ timestamp,
3163
+ duration,
3164
+ fragment.moofOffset + sampleIndex,
3165
+ fragmentSample.byteSize,
3166
+ );
3167
+
3168
+ this.packetToFragmentLocation.set(packet, { fragment, sampleIndex });
3169
+
3170
+ return packet;
3171
+ }
3172
+
3173
+ /** Looks for a packet in the fragments while trying to load as few fragments as possible to retrieve it. */
3174
+ private async performFragmentedLookup(
3175
+ // The fragment where we start looking
3176
+ startFragment: Fragment | null,
3177
+ // This function returns the best-matching sample in a given fragment
3178
+ getMatchInFragment: (fragment: Fragment) => { sampleIndex: number; correctSampleFound: boolean },
3179
+ // The timestamp with which we can search the lookup table
3180
+ searchTimestamp: number,
3181
+ // The timestamp for which we know the correct sample will not come after it
3182
+ latestTimestamp: number,
3183
+ options: PacketRetrievalOptions,
3184
+ ): Promise<EncodedPacket | null> {
3185
+ const demuxer = this.internalTrack.demuxer;
3186
+
3187
+ let currentFragment: Fragment | null = null;
3188
+ let bestFragment: Fragment | null = null;
3189
+ let bestSampleIndex = -1;
3190
+
3191
+ if (startFragment) {
3192
+ const { sampleIndex, correctSampleFound } = getMatchInFragment(startFragment);
3193
+
3194
+ if (correctSampleFound) {
3195
+ return this.fetchPacketInFragment(startFragment, sampleIndex, options);
3196
+ }
3197
+
3198
+ if (sampleIndex !== -1) {
3199
+ bestFragment = startFragment;
3200
+ bestSampleIndex = sampleIndex;
3201
+ }
3202
+ }
3203
+
3204
+ // Search for a lookup entry; this way, we won't need to start searching from the start of the file
3205
+ // but can jump right into the correct fragment (or at least nearby).
3206
+ const lookupEntryIndex = binarySearchLessOrEqual(
3207
+ this.internalTrack.fragmentLookupTable,
3208
+ searchTimestamp,
3209
+ x => x.timestamp,
3210
+ );
3211
+ const lookupEntry = lookupEntryIndex !== -1
3212
+ ? this.internalTrack.fragmentLookupTable[lookupEntryIndex]!
3213
+ : null;
3214
+
3215
+ const positionCacheIndex = binarySearchLessOrEqual(
3216
+ this.internalTrack.fragmentPositionCache,
3217
+ searchTimestamp,
3218
+ x => x.startTimestamp,
3219
+ );
3220
+ const positionCacheEntry = positionCacheIndex !== -1
3221
+ ? this.internalTrack.fragmentPositionCache[positionCacheIndex]!
3222
+ : null;
3223
+
3224
+ const lookupEntryPosition = Math.max(
3225
+ lookupEntry?.moofOffset ?? 0,
3226
+ positionCacheEntry?.moofOffset ?? 0,
3227
+ ) || null;
3228
+
3229
+ let currentPos: number;
3230
+
3231
+ if (!startFragment) {
3232
+ currentPos = lookupEntryPosition ?? 0;
3233
+ } else {
3234
+ if (lookupEntryPosition === null || startFragment.moofOffset >= lookupEntryPosition) {
3235
+ currentPos = startFragment.moofOffset + startFragment.moofSize;
3236
+ currentFragment = startFragment;
3237
+ } else {
3238
+ // Use the lookup entry
3239
+ currentPos = lookupEntryPosition;
3240
+ }
3241
+ }
3242
+
3243
+ while (true) {
3244
+ if (currentFragment) {
3245
+ const trackData = currentFragment.trackData.get(this.internalTrack.id);
3246
+ if (trackData && trackData.startTimestamp > latestTimestamp) {
3247
+ // We're already past the upper bound, no need to keep searching
3248
+ break;
3249
+ }
3250
+ }
3251
+
3252
+ // Load the header
3253
+ let slice = demuxer.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
3254
+ if (slice instanceof Promise) slice = await slice;
3255
+ if (!slice) break;
3256
+
3257
+ const boxStartPos = currentPos;
3258
+ const boxInfo = readBoxHeader(slice);
3259
+ if (!boxInfo) {
3260
+ break;
3261
+ }
3262
+
3263
+ if (boxInfo.name === 'moof') {
3264
+ currentFragment = await demuxer.readFragment(boxStartPos);
3265
+ const { sampleIndex, correctSampleFound } = getMatchInFragment(currentFragment);
3266
+ if (correctSampleFound) {
3267
+ return this.fetchPacketInFragment(currentFragment, sampleIndex, options);
3268
+ }
3269
+ if (sampleIndex !== -1) {
3270
+ bestFragment = currentFragment;
3271
+ bestSampleIndex = sampleIndex;
3272
+ }
3273
+ }
3274
+
3275
+ currentPos = boxStartPos + boxInfo.totalSize;
3276
+ }
3277
+
3278
+ // Catch faulty lookup table entries
3279
+ if (lookupEntry && (!bestFragment || bestFragment.moofOffset < lookupEntry.moofOffset)) {
3280
+ // The lookup table entry lied to us! We found a lookup entry but no fragment there that satisfied
3281
+ // the match. In this case, let's search again but using the lookup entry before that.
3282
+ const previousLookupEntry = this.internalTrack.fragmentLookupTable[lookupEntryIndex - 1];
3283
+ assert(!previousLookupEntry || previousLookupEntry.timestamp < lookupEntry.timestamp);
3284
+
3285
+ const newSearchTimestamp = previousLookupEntry?.timestamp ?? -Infinity;
3286
+ return this.performFragmentedLookup(
3287
+ null,
3288
+ getMatchInFragment,
3289
+ newSearchTimestamp,
3290
+ latestTimestamp,
3291
+ options,
3292
+ );
3293
+ }
3294
+
3295
+ if (bestFragment) {
3296
+ // If we finished looping but didn't find a perfect match, still return the best match we found
3297
+ return this.fetchPacketInFragment(bestFragment, bestSampleIndex, options);
3298
+ }
3299
+
3300
+ return null;
3301
+ }
3302
+ }
3303
+
3304
+ class IsobmffVideoTrackBacking extends IsobmffTrackBacking implements InputVideoTrackBacking {
3305
+ override internalTrack: InternalVideoTrack;
3306
+ decoderConfigPromise: Promise<VideoDecoderConfig> | null = null;
3307
+
3308
+ constructor(internalTrack: InternalVideoTrack) {
3309
+ super(internalTrack);
3310
+ this.internalTrack = internalTrack;
3311
+ }
3312
+
3313
+ getType() {
3314
+ return 'video' as const;
3315
+ }
3316
+
3317
+ override getCodec(): VideoCodec | null {
3318
+ return this.internalTrack.info.codec;
3319
+ }
3320
+
3321
+ getCodedWidth() {
3322
+ return this.internalTrack.info.width;
3323
+ }
3324
+
3325
+ getCodedHeight() {
3326
+ return this.internalTrack.info.height;
3327
+ }
3328
+
3329
+ getSquarePixelWidth() {
3330
+ return this.internalTrack.info.squarePixelWidth;
3331
+ }
3332
+
3333
+ getSquarePixelHeight() {
3334
+ return this.internalTrack.info.squarePixelHeight;
3335
+ }
3336
+
3337
+ getRotation() {
3338
+ return this.internalTrack.rotation;
3339
+ }
3340
+
3341
+ async getColorSpace(): Promise<VideoColorSpaceInit> {
3342
+ return {
3343
+ primaries: this.internalTrack.info.colorSpace?.primaries,
3344
+ transfer: this.internalTrack.info.colorSpace?.transfer,
3345
+ matrix: this.internalTrack.info.colorSpace?.matrix,
3346
+ fullRange: this.internalTrack.info.colorSpace?.fullRange,
3347
+ };
3348
+ }
3349
+
3350
+ async canBeTransparent() {
3351
+ return this.internalTrack.info.codec === 'prores' && (
3352
+ this.internalTrack.info.proresFormat === 'ap4h'
3353
+ || this.internalTrack.info.proresFormat === 'ap4x'
3354
+ );
3355
+ }
3356
+
3357
+ async getDecoderConfig(): Promise<VideoDecoderConfig | null> {
3358
+ if (!this.internalTrack.info.codec) {
3359
+ return null;
3360
+ }
3361
+
3362
+ return this.decoderConfigPromise ??= (async (): Promise<VideoDecoderConfig> => {
3363
+ if (this.internalTrack.info.codec === 'vp9' && !this.internalTrack.info.vp9CodecInfo) {
3364
+ const firstPacket = await this.getFirstPacket({});
3365
+ this.internalTrack.info.vp9CodecInfo = firstPacket && extractVp9CodecInfoFromPacket(firstPacket.data);
3366
+ } else if (this.internalTrack.info.codec === 'av1' && !this.internalTrack.info.av1CodecInfo) {
3367
+ const firstPacket = await this.getFirstPacket({});
3368
+ this.internalTrack.info.av1CodecInfo = firstPacket && extractAv1CodecInfoFromPacket(firstPacket.data);
3369
+ }
3370
+
3371
+ const config: VideoDecoderConfig = {
3372
+ codec: extractVideoCodecString(this.internalTrack.info),
3373
+ codedWidth: this.internalTrack.info.width,
3374
+ codedHeight: this.internalTrack.info.height,
3375
+ description: this.internalTrack.info.codecDescription ?? undefined,
3376
+ colorSpace: this.internalTrack.info.colorSpace ?? undefined,
3377
+ };
3378
+
3379
+ if (
3380
+ this.internalTrack.info.width !== this.internalTrack.info.squarePixelWidth
3381
+ || this.internalTrack.info.height !== this.internalTrack.info.squarePixelHeight
3382
+ ) {
3383
+ config.displayAspectWidth = this.internalTrack.info.squarePixelWidth;
3384
+ config.displayAspectHeight = this.internalTrack.info.squarePixelHeight;
3385
+ }
3386
+
3387
+ return config;
3388
+ })();
3389
+ }
3390
+ }
3391
+
3392
+ class IsobmffAudioTrackBacking extends IsobmffTrackBacking implements InputAudioTrackBacking {
3393
+ override internalTrack: InternalAudioTrack;
3394
+ decoderConfig: AudioDecoderConfig | null = null;
3395
+
3396
+ constructor(internalTrack: InternalAudioTrack) {
3397
+ super(internalTrack);
3398
+ this.internalTrack = internalTrack;
3399
+ }
3400
+
3401
+ getType() {
3402
+ return 'audio' as const;
3403
+ }
3404
+
3405
+ override getCodec(): AudioCodec | null {
3406
+ return this.internalTrack.info.codec;
3407
+ }
3408
+
3409
+ getNumberOfChannels() {
3410
+ return this.internalTrack.info.numberOfChannels;
3411
+ }
3412
+
3413
+ getSampleRate() {
3414
+ return this.internalTrack.info.sampleRate;
3415
+ }
3416
+
3417
+ async getDecoderConfig(): Promise<AudioDecoderConfig | null> {
3418
+ if (!this.internalTrack.info.codec) {
3419
+ return null;
3420
+ }
3421
+
3422
+ return this.decoderConfig ??= {
3423
+ codec: extractAudioCodecString(this.internalTrack.info),
3424
+ numberOfChannels: this.internalTrack.info.numberOfChannels,
3425
+ sampleRate: this.internalTrack.info.sampleRate,
3426
+ description: this.internalTrack.info.codecDescription ?? undefined,
3427
+ };
3428
+ }
3429
+ }
3430
+
3431
+ const getSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: number) => {
3432
+ if (sampleTable.presentationTimestamps) {
3433
+ const index = binarySearchLessOrEqual(
3434
+ sampleTable.presentationTimestamps,
3435
+ timescaleUnits,
3436
+ x => x.presentationTimestamp,
3437
+ );
3438
+ if (index === -1) {
3439
+ return -1;
3440
+ }
3441
+
3442
+ return sampleTable.presentationTimestamps[index]!.sampleIndex;
3443
+ } else {
3444
+ const index = binarySearchLessOrEqual(
3445
+ sampleTable.sampleTimingEntries,
3446
+ timescaleUnits,
3447
+ x => x.startDecodeTimestamp,
3448
+ );
3449
+ if (index === -1) {
3450
+ return -1;
3451
+ }
3452
+
3453
+ const entry = sampleTable.sampleTimingEntries[index]!;
3454
+ return entry.startIndex
3455
+ + Math.min(
3456
+ Math.floor((timescaleUnits - entry.startDecodeTimestamp) / entry.delta),
3457
+ entry.count - 1,
3458
+ );
3459
+ }
3460
+ };
3461
+
3462
+ const getKeyframeSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: number) => {
3463
+ if (!sampleTable.keySampleIndices) {
3464
+ // Every sample is a keyframe
3465
+ return getSampleIndexForTimestamp(sampleTable, timescaleUnits);
3466
+ }
3467
+
3468
+ if (sampleTable.presentationTimestamps) {
3469
+ const index = binarySearchLessOrEqual(
3470
+ sampleTable.presentationTimestamps,
3471
+ timescaleUnits,
3472
+ x => x.presentationTimestamp,
3473
+ );
3474
+ if (index === -1) {
3475
+ return -1;
3476
+ }
3477
+
3478
+ // Walk the samples in presentation order until we find one that's a keyframe
3479
+ for (let i = index; i >= 0; i--) {
3480
+ const sampleIndex = sampleTable.presentationTimestamps[i]!.sampleIndex;
3481
+ const isKeyFrame = binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1;
3482
+
3483
+ if (isKeyFrame) {
3484
+ return sampleIndex;
3485
+ }
3486
+ }
3487
+
3488
+ return -1;
3489
+ } else {
3490
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timescaleUnits);
3491
+
3492
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
3493
+ return sampleTable.keySampleIndices[index] ?? -1;
3494
+ }
3495
+ };
3496
+
3497
+ type SampleInfo = {
3498
+ presentationTimestamp: number;
3499
+ duration: number;
3500
+ sampleOffset: number;
3501
+ sampleSize: number;
3502
+ chunkOffset: number;
3503
+ chunkSize: number;
3504
+ isKeyFrame: boolean;
3505
+ };
3506
+
3507
+ const getSampleInfo = (sampleTable: SampleTable, sampleIndex: number): SampleInfo | null => {
3508
+ const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, x => x.startIndex);
3509
+ const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex];
3510
+ if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) {
3511
+ return null;
3512
+ }
3513
+
3514
+ const decodeTimestamp = timingEntry.startDecodeTimestamp
3515
+ + (sampleIndex - timingEntry.startIndex) * timingEntry.delta;
3516
+ let presentationTimestamp = decodeTimestamp;
3517
+ const offsetEntryIndex = binarySearchLessOrEqual(
3518
+ sampleTable.sampleCompositionTimeOffsets,
3519
+ sampleIndex,
3520
+ x => x.startIndex,
3521
+ );
3522
+ const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex];
3523
+ if (offsetEntry && sampleIndex - offsetEntry.startIndex < offsetEntry.count) {
3524
+ presentationTimestamp += offsetEntry.offset;
3525
+ }
3526
+
3527
+ const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)]!;
3528
+ const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, x => x.startSampleIndex);
3529
+ const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex];
3530
+ assert(chunkEntry);
3531
+
3532
+ const chunkIndex = chunkEntry.startChunkIndex
3533
+ + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk);
3534
+ const chunkOffset = sampleTable.chunkOffsets[chunkIndex]!;
3535
+
3536
+ const startSampleIndexOfChunk = chunkEntry.startSampleIndex
3537
+ + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk;
3538
+ let chunkSize = 0;
3539
+ let sampleOffset = chunkOffset;
3540
+
3541
+ if (sampleTable.sampleSizes.length === 1) {
3542
+ sampleOffset += sampleSize * (sampleIndex - startSampleIndexOfChunk);
3543
+ chunkSize += sampleSize * chunkEntry.samplesPerChunk;
3544
+ } else {
3545
+ for (let i = startSampleIndexOfChunk; i < startSampleIndexOfChunk + chunkEntry.samplesPerChunk; i++) {
3546
+ const sampleSize = sampleTable.sampleSizes[i]!;
3547
+
3548
+ if (i < sampleIndex) {
3549
+ sampleOffset += sampleSize;
3550
+ }
3551
+ chunkSize += sampleSize;
3552
+ }
3553
+ }
3554
+
3555
+ let duration = timingEntry.delta;
3556
+ if (sampleTable.presentationTimestamps) {
3557
+ // In order to accurately compute the duration, we need to take the duration to the next sample in presentation
3558
+ // order, not in decode order
3559
+ const presentationIndex = sampleTable.presentationTimestampIndexMap![sampleIndex];
3560
+ assert(presentationIndex !== undefined);
3561
+
3562
+ if (presentationIndex < sampleTable.presentationTimestamps.length - 1) {
3563
+ const nextEntry = sampleTable.presentationTimestamps[presentationIndex + 1]!;
3564
+ const nextPresentationTimestamp = nextEntry.presentationTimestamp;
3565
+ duration = nextPresentationTimestamp - presentationTimestamp;
3566
+ }
3567
+ }
3568
+
3569
+ return {
3570
+ presentationTimestamp,
3571
+ duration,
3572
+ sampleOffset,
3573
+ sampleSize,
3574
+ chunkOffset,
3575
+ chunkSize,
3576
+ isKeyFrame: sampleTable.keySampleIndices
3577
+ ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1
3578
+ : true,
3579
+ };
3580
+ };
3581
+
3582
+ const getNextKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
3583
+ if (!sampleTable.keySampleIndices) {
3584
+ return sampleIndex + 1;
3585
+ }
3586
+
3587
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
3588
+ return sampleTable.keySampleIndices[index + 1] ?? -1;
3589
+ };
3590
+
3591
+ const offsetFragmentTrackDataByTimestamp = (trackData: FragmentTrackData, timestamp: number) => {
3592
+ trackData.startTimestamp += timestamp;
3593
+ trackData.endTimestamp += timestamp;
3594
+
3595
+ for (const sample of trackData.samples) {
3596
+ sample.presentationTimestamp += timestamp;
3597
+ }
3598
+ for (const entry of trackData.presentationTimestamps) {
3599
+ entry.presentationTimestamp += timestamp;
3600
+ }
3601
+ };
3602
+
3603
+ /** Extracts the rotation component from a transformation matrix, in degrees. */
3604
+ const extractRotationFromMatrix = (matrix: TransformationMatrix) => {
3605
+ const [a, b] = matrix; // (1, 0) projects onto (a, b), so that's all we need
3606
+
3607
+ const radians = Math.atan2(b, a);
3608
+
3609
+ if (!Number.isFinite(radians)) {
3610
+ // Can happen if the entire matrix is 0, for example
3611
+ return 0;
3612
+ }
3613
+
3614
+ return radians * (180 / Math.PI);
3615
+ };
3616
+
3617
+ const sampleTableIsEmpty = (sampleTable: SampleTable) => {
3618
+ return sampleTable.sampleSizes.length === 0;
3619
+ };
3620
+
3621
+ const getOrCreateEncryptionAuxInfo = (track: InternalTrack) => {
3622
+ if (track.currentFragmentState) {
3623
+ return track.currentFragmentState.encryptionAuxInfo ??= {
3624
+ defaultSampleInfoSize: 0,
3625
+ sampleSizes: null,
3626
+ sampleCount: 0,
3627
+ offset: null,
3628
+ resolved: null,
3629
+ };
3630
+ } else {
3631
+ return track.encryptionAuxInfo ??= {
3632
+ defaultSampleInfoSize: 0,
3633
+ sampleSizes: null,
3634
+ sampleCount: 0,
3635
+ offset: null,
3636
+ resolved: null,
3637
+ };
3638
+ }
3639
+ };
3640
+
3641
+ const resolveEncryptionAuxInfo = async (
3642
+ reader: Reader,
3643
+ encryptionInfo: TrackEncryptionInfo,
3644
+ aux: SampleEncryptionAuxInfo,
3645
+ ) => {
3646
+ if (aux.resolved) {
3647
+ return aux.resolved;
3648
+ }
3649
+
3650
+ if (aux.offset === null || aux.sampleCount === 0) {
3651
+ throw new Error('Incomplete saiz/saio info; cannot resolve encryption data.');
3652
+ }
3653
+
3654
+ let totalSize = 0;
3655
+ if (aux.defaultSampleInfoSize > 0) {
3656
+ totalSize = aux.defaultSampleInfoSize * aux.sampleCount;
3657
+ } else {
3658
+ assert(aux.sampleSizes);
3659
+ for (let i = 0; i < aux.sampleCount; i++) {
3660
+ totalSize += aux.sampleSizes[i]!;
3661
+ }
3662
+ }
3663
+
3664
+ let slice = reader.requestSlice(aux.offset, totalSize);
3665
+ if (slice instanceof Promise) slice = await slice;
3666
+ if (!slice) {
3667
+ throw new Error('Failed to read auxiliary encryption info.');
3668
+ }
3669
+
3670
+ const ivSize = encryptionInfo.defaultPerSampleIvSize;
3671
+ assert(ivSize !== null);
3672
+
3673
+ // Each aux entry has the same byte layout as a senc entry: IV (of size ivSize, or the constant IV from tenc
3674
+ // when ivSize is 0), then optionally subsample count + [clearLen, protectedLen] pairs. Subsamples are present
3675
+ // iff the entry is larger than the IV.
3676
+ const entries: SampleEncryptionInfo[] = [];
3677
+ for (let i = 0; i < aux.sampleCount; i++) {
3678
+ const entrySize = aux.defaultSampleInfoSize > 0
3679
+ ? aux.defaultSampleInfoSize
3680
+ : aux.sampleSizes![i]!;
3681
+
3682
+ const iv = new Uint8Array(16);
3683
+ if (ivSize > 0) {
3684
+ iv.set(readBytes(slice, ivSize), 0);
3685
+ } else {
3686
+ iv.set(encryptionInfo.defaultConstantIv!, 0);
3687
+ }
3688
+
3689
+ let subsamples: { clearLen: number; protectedLen: number }[] | null = null;
3690
+ if (entrySize > ivSize) {
3691
+ const subsampleCount = readU16Be(slice);
3692
+ subsamples = [];
3693
+ for (let j = 0; j < subsampleCount; j++) {
3694
+ const clearLen = readU16Be(slice);
3695
+ const protectedLen = readU32Be(slice);
3696
+ subsamples.push({ clearLen, protectedLen });
3697
+ }
3698
+ }
3699
+
3700
+ entries.push({ iv, subsamples });
3701
+ }
3702
+
3703
+ aux.resolved = entries;
3704
+ return entries;
3705
+ };
3706
+
3707
+ const decryptSample = async (
3708
+ track: InternalTrack,
3709
+ sampleEncryption: SampleEncryptionInfo,
3710
+ data: Uint8Array,
3711
+ fragment: Fragment | null,
3712
+ ): Promise<Uint8Array> => {
3713
+ assert(track.encryptionInfo);
3714
+ const encryptionInfo = track.encryptionInfo;
3715
+ assert(encryptionInfo.defaultKid !== null);
3716
+
3717
+ const keyId = encryptionInfo.defaultKid;
3718
+ let keyBytes: Uint8Array;
3719
+
3720
+ const cacheEntry = track.demuxer.decryptionKeyCache.get(keyId);
3721
+ if (cacheEntry) {
3722
+ keyBytes = await cacheEntry;
3723
+ } else {
3724
+ if (!track.demuxer.input._formatOptions.isobmff?.resolveKeyId) {
3725
+ throw new Error(
3726
+ 'Encrypted media samples encountered. To decrypt them, please provide a callback for'
3727
+ + ' InputOptions.formatOptions.isobmff.resolveKeyId.',
3728
+ );
3729
+ }
3730
+
3731
+ const promise = (async () => {
3732
+ let psshBoxes = track.demuxer.psshBoxes;
3733
+ if (fragment) {
3734
+ psshBoxes = [
3735
+ ...psshBoxes,
3736
+ ...fragment.psshBoxes,
3737
+ ].filter(x => x.keyIds === null || x.keyIds.includes(keyId));
3738
+
3739
+ // Filter out duplicates
3740
+ for (let i = 0; i < psshBoxes.length - 1; i++) {
3741
+ for (let j = i + 1; j < psshBoxes.length; j++) {
3742
+ if (psshBoxesAreEqual(psshBoxes[i]!, psshBoxes[j]!)) {
3743
+ psshBoxes.splice(j, 1);
3744
+ j--;
3745
+ }
3746
+ }
3747
+ }
3748
+ }
3749
+
3750
+ const keyResult = await track.demuxer.input._formatOptions.isobmff!.resolveKeyId!({ keyId, psshBoxes });
3751
+
3752
+ if (!(
3753
+ (typeof keyResult === 'string' && keyResult.length === 32 && HEX_STRING_REGEX.test(keyResult))
3754
+ || (keyResult instanceof Uint8Array && keyResult.byteLength === 16)
3755
+ )) {
3756
+ throw new TypeError(
3757
+ 'resolveKeyId must return a 32-character hex string or a 16-byte Uint8Array containing the'
3758
+ + ' decryption key.',
3759
+ );
3760
+ }
3761
+
3762
+ return keyResult instanceof Uint8Array
3763
+ ? keyResult
3764
+ : hexStringToBytes(keyResult);
3765
+ })();
3766
+
3767
+ track.demuxer.decryptionKeyCache.set(keyId, promise);
3768
+ keyBytes = await promise;
3769
+ }
3770
+
3771
+ if (encryptionInfo.scheme === 'cenc' || encryptionInfo.scheme === 'cens') {
3772
+ return decryptCtr(keyBytes, encryptionInfo, sampleEncryption, data);
3773
+ } else {
3774
+ return decryptCbcs(keyBytes, encryptionInfo, sampleEncryption, data);
3775
+ }
3776
+ };
3777
+
3778
+ const decryptCtr = async (
3779
+ key: Uint8Array,
3780
+ encryptionInfo: TrackEncryptionInfo,
3781
+ sampleEncryption: SampleEncryptionInfo,
3782
+ data: Uint8Array,
3783
+ ) => {
3784
+ const counter = new Uint8Array(16);
3785
+ counter.set(sampleEncryption.iv, 0);
3786
+
3787
+ const cryptoKey = await crypto.subtle.importKey(
3788
+ 'raw',
3789
+ key as BufferSource,
3790
+ { name: 'AES-CTR' },
3791
+ false,
3792
+ ['decrypt'],
3793
+ );
3794
+
3795
+ const cryptApply = async (input: Uint8Array) => {
3796
+ const plaintext = await crypto.subtle.decrypt(
3797
+ { name: 'AES-CTR', counter, length: 64 },
3798
+ cryptoKey,
3799
+ input as BufferSource,
3800
+ );
3801
+
3802
+ return new Uint8Array(plaintext);
3803
+ };
3804
+
3805
+ if (!sampleEncryption.subsamples) {
3806
+ // Whole sample is protected, no pattern
3807
+ return cryptApply(data);
3808
+ }
3809
+
3810
+ assert(encryptionInfo.defaultCryptByteBlock !== null && encryptionInfo.defaultSkipByteBlock !== null);
3811
+ const cryptRanges = collectCryptRanges(
3812
+ sampleEncryption.subsamples,
3813
+ encryptionInfo.defaultCryptByteBlock,
3814
+ encryptionInfo.defaultSkipByteBlock,
3815
+ );
3816
+
3817
+ // Concatenate all crypt ranges into a single buffer so the continuous CTR counter behavior is preserved
3818
+ let totalCryptLen = 0;
3819
+ for (const range of cryptRanges) {
3820
+ for (const seg of range.perSubsample) {
3821
+ totalCryptLen += seg.length;
3822
+ }
3823
+ }
3824
+ const cryptBuffer = new Uint8Array(totalCryptLen);
3825
+ let writePos = 0;
3826
+ for (const range of cryptRanges) {
3827
+ for (const seg of range.perSubsample) {
3828
+ cryptBuffer.set(data.subarray(seg.offset, seg.offset + seg.length), writePos);
3829
+ writePos += seg.length;
3830
+ }
3831
+ }
3832
+
3833
+ const plain = await cryptApply(cryptBuffer);
3834
+
3835
+ // Now let's build the output
3836
+ const output = new Uint8Array(data);
3837
+ let readPos = 0;
3838
+ for (const range of cryptRanges) {
3839
+ for (const seg of range.perSubsample) {
3840
+ output.set(plain.subarray(readPos, readPos + seg.length), seg.offset);
3841
+ readPos += seg.length;
3842
+ }
3843
+ }
3844
+
3845
+ return output;
3846
+ };
3847
+
3848
+ const decryptCbcs = (
3849
+ key: Uint8Array,
3850
+ encryptionInfo: TrackEncryptionInfo,
3851
+ sampleEncryption: SampleEncryptionInfo,
3852
+ data: Uint8Array,
3853
+ ) => {
3854
+ const ctx = new Aes128CbcContext();
3855
+ ctx.init({ key, iv: sampleEncryption.iv });
3856
+
3857
+ const cryptByteBlock = encryptionInfo.defaultCryptByteBlock;
3858
+ const skipByteBlock = encryptionInfo.defaultSkipByteBlock;
3859
+ assert(cryptByteBlock !== null && skipByteBlock !== null);
3860
+
3861
+ if (!sampleEncryption.subsamples) {
3862
+ // Whole-sample encryption: straightforward CBC over floor(size / 16) blocks, any trailing bytes stay clear
3863
+ const output = new Uint8Array(data);
3864
+ const numBlocks = Math.floor(data.length / 16);
3865
+
3866
+ for (let b = 0; b < numBlocks; b++) {
3867
+ const off = b * 16;
3868
+ ctx.in.set(data.subarray(off, off + 16));
3869
+ ctx.decrypt();
3870
+ output.set(ctx.out, off);
3871
+ }
3872
+
3873
+ return output;
3874
+ }
3875
+
3876
+ if (cryptByteBlock === 0 && skipByteBlock === 0) {
3877
+ throw new Error('cbcs with subsamples requires pattern encryption.');
3878
+ }
3879
+
3880
+ const output = new Uint8Array(data);
3881
+
3882
+ // Pattern decryption: IV is reset at the start of each subsample. Within a subsample, the CBC chain continues
3883
+ // across skipped blocks (the IV after a crypt group carries over to the next crypt group's first block).
3884
+ const cryptRanges = collectCryptRanges(sampleEncryption.subsamples, cryptByteBlock, skipByteBlock);
3885
+ const ivView = new DataView(sampleEncryption.iv.buffer, sampleEncryption.iv.byteOffset, 16);
3886
+
3887
+ for (const range of cryptRanges) {
3888
+ // Reset IV per subsample
3889
+ ctx.iv[0] = ivView.getUint32(0, false);
3890
+ ctx.iv[1] = ivView.getUint32(4, false);
3891
+ ctx.iv[2] = ivView.getUint32(8, false);
3892
+ ctx.iv[3] = ivView.getUint32(12, false);
3893
+
3894
+ for (const seg of range.perSubsample) {
3895
+ // Decrypt length / 16 blocks at this offset
3896
+ const numBlocks = seg.length / 16;
3897
+
3898
+ for (let b = 0; b < numBlocks; b++) {
3899
+ const offset = seg.offset + b * 16;
3900
+ ctx.in.set(data.subarray(offset, offset + 16));
3901
+ ctx.decrypt();
3902
+ output.set(ctx.out, offset);
3903
+ }
3904
+ }
3905
+ }
3906
+
3907
+ return output;
3908
+ };
3909
+
3910
+ const collectCryptRanges = (
3911
+ subsamples: { clearLen: number; protectedLen: number }[],
3912
+ cryptByteBlock: number,
3913
+ skipByteBlock: number,
3914
+ ) => {
3915
+ const ranges: { perSubsample: { offset: number; length: number }[] }[] = [];
3916
+ const hasPattern = cryptByteBlock !== 0 || skipByteBlock !== 0;
3917
+
3918
+ let cursor = 0;
3919
+ for (const subsample of subsamples) {
3920
+ cursor += subsample.clearLen;
3921
+
3922
+ const perSubsample: { offset: number; length: number }[] = [];
3923
+
3924
+ if (!hasPattern) {
3925
+ if (subsample.protectedLen > 0) {
3926
+ perSubsample.push({ offset: cursor, length: subsample.protectedLen });
3927
+ }
3928
+ cursor += subsample.protectedLen;
3929
+ } else {
3930
+ let remaining = subsample.protectedLen;
3931
+ let pos = cursor;
3932
+ while (remaining > 0) {
3933
+ if (remaining < 16 * cryptByteBlock) {
3934
+ break; // Partial final crypt group stays clear
3935
+ }
3936
+
3937
+ const cryptBytes = 16 * cryptByteBlock;
3938
+ perSubsample.push({ offset: pos, length: cryptBytes });
3939
+ pos += cryptBytes;
3940
+ remaining -= cryptBytes;
3941
+
3942
+ const skipBytes = Math.min(16 * skipByteBlock, remaining);
3943
+ pos += skipBytes;
3944
+ remaining -= skipBytes;
3945
+ }
3946
+ cursor += subsample.protectedLen;
3947
+ }
3948
+
3949
+ ranges.push({ perSubsample });
3950
+ }
3951
+
3952
+ return ranges;
3953
+ };