xbintsc 0.5.22 → 0.5.23

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 (81) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +15 -5
  3. package/README.zh-CN.md +11 -4
  4. package/dist/src/cli/hints.js +11 -0
  5. package/dist/src/cli/hints.js.map +1 -1
  6. package/dist/src/cli/main.js +1 -1
  7. package/dist/src/driver/compiler.js +1 -0
  8. package/dist/src/driver/compiler.js.map +1 -1
  9. package/dist/src/driver/version.d.ts +1 -1
  10. package/dist/src/driver/version.js +1 -1
  11. package/dist/src/extensions/catalog.js +2 -1
  12. package/dist/src/extensions/catalog.js.map +1 -1
  13. package/dist/src/extensions/media/index.d.ts +27 -0
  14. package/dist/src/extensions/media/index.js +60 -0
  15. package/dist/src/extensions/media/index.js.map +1 -0
  16. package/dist/src/extensions/registry.d.ts +12 -0
  17. package/dist/src/extensions/registry.js +16 -0
  18. package/dist/src/extensions/registry.js.map +1 -1
  19. package/dist/src/index.d.ts +1 -0
  20. package/dist/src/index.js +1 -0
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/tests/cli/hints.test.js +15 -0
  23. package/dist/tests/cli/hints.test.js.map +1 -1
  24. package/dist/tests/cli/process.test.js +12 -0
  25. package/dist/tests/cli/process.test.js.map +1 -1
  26. package/dist/tests/e2e/gui-example-canvas.test.js +3 -1
  27. package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -1
  28. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  29. package/dist/tests/e2e/gui-helpers.js +2 -0
  30. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  31. package/dist/tests/e2e/gui-media-disabled.test.d.ts +9 -0
  32. package/dist/tests/e2e/gui-media-disabled.test.js +42 -0
  33. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -0
  34. package/dist/tests/e2e/gui-media.test.d.ts +6 -4
  35. package/dist/tests/e2e/gui-media.test.js +15 -12
  36. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  37. package/dist/tests/e2e/unicode-output.test.d.ts +16 -0
  38. package/dist/tests/e2e/unicode-output.test.js +65 -0
  39. package/dist/tests/e2e/unicode-output.test.js.map +1 -0
  40. package/dist/tests/extensions/media.test.d.ts +1 -0
  41. package/dist/tests/extensions/media.test.js +58 -0
  42. package/dist/tests/extensions/media.test.js.map +1 -0
  43. package/dist/tests/extensions/registry.test.js +8 -0
  44. package/dist/tests/extensions/registry.test.js.map +1 -1
  45. package/dist/tests/lexer/identifiers.test.js +12 -0
  46. package/dist/tests/lexer/identifiers.test.js.map +1 -1
  47. package/dist/tests/lexer/scanner.test.js +8 -0
  48. package/dist/tests/lexer/scanner.test.js.map +1 -1
  49. package/dist/tests/lexer/strings.test.js +30 -0
  50. package/dist/tests/lexer/strings.test.js.map +1 -1
  51. package/doc/ai/cli.md +1 -1
  52. package/doc/ai/extensions.md +35 -23
  53. package/doc/ai/troubleshooting.md +9 -0
  54. package/doc/ai/zh-CN/cli.md +1 -1
  55. package/doc/ai/zh-CN/extensions.md +20 -8
  56. package/doc/ai/zh-CN/troubleshooting.md +8 -0
  57. package/doc/gui/build.md +37 -21
  58. package/doc/gui/known-issues.md +7 -6
  59. package/doc/gui/media.md +26 -11
  60. package/doc/gui.md +43 -33
  61. package/doc/zh-CN/gui/build.md +28 -16
  62. package/doc/zh-CN/gui/known-issues.md +6 -5
  63. package/doc/zh-CN/gui/media.md +21 -10
  64. package/doc/zh-CN/gui.md +37 -32
  65. package/package.json +2 -1
  66. package/runtime/ext_gui/gui_media.cpp +69 -2
  67. package/runtime/ext_gui/media.cpp +34 -488
  68. package/runtime/ext_gui/media.h +54 -19
  69. package/runtime/ext_gui/media_api.h +65 -0
  70. package/runtime/ext_media/media.cpp +507 -0
  71. package/runtime/ext_media/media_bridge.c +35 -0
  72. package/scripts/build-gui.ts +6 -72
  73. package/scripts/build-media.ts +245 -0
  74. package/src/cli/hints.ts +11 -0
  75. package/src/cli/main.ts +1 -1
  76. package/src/driver/compiler.ts +1 -0
  77. package/src/driver/version.ts +1 -1
  78. package/src/extensions/catalog.ts +2 -1
  79. package/src/extensions/media/index.ts +67 -0
  80. package/src/extensions/registry.ts +24 -0
  81. package/src/index.ts +1 -0
package/doc/zh-CN/gui.md CHANGED
@@ -3,9 +3,9 @@
3
3
  > 语言 / Language:[English](../gui.md) | **简体中文**
4
4
 
5
5
  状态:**M16** —— 功能(M1–M15)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内、Flexbox 与 CSS 定位)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制——位图(PNG/JPEG/…)与 SVG 文件皆可,后者按绘制尺寸光栅化,因此矢量图保持清晰——CSS transition 会为绘制属性做动画。**M16** 让内联 `<svg>` 子树获得同样的处理:子树被序列化回 SVG 文档,并按绘制设备尺寸光栅化,因此图标不再需要单独的文件。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。**M11** 增加 `position: relative`/`absolute`/`fixed` 及其偏移,`examples/gui/pelican-bike`(一个可玩的 2D 游戏)就构建在它之上。**M12** 明确了 DOM 节点生命周期(哪些变更释放、哪些保留),并让引擎内存有界:参见[节点生命周期](./gui/dom.md)与[内存与 GC](./gui/runtime.md)。**M13** 真正播放
6
- `<video>`/`<audio>`——一个 vendored 的最小 FFmpeg 在主线程解码,视频帧走图像
7
- 纹理路径、音频进入 SDL 流,DOM 媒体 API 暴露 `play`/`pause`/`currentTime`/…
8
- 与媒体事件。**M14** 增加了 `<canvas>`,配一个自研的 2D 软件光栅器(抗锯齿
6
+ `<video>`/`<audio>`——可选的 `media` 扩展用 vendored 的最小 FFmpeg 在主线程解码,
7
+ 视频帧走图像纹理路径、音频进入 SDL 流,DOM 媒体 API 暴露
8
+ `play`/`pause`/`currentTime`/… 与媒体事件。只启用 `gui` 不会链接 FFmpeg。**M14** 增加了 `<canvas>`,配一个自研的 2D 软件光栅器(抗锯齿
9
9
  填充、路径、文本与 `drawImage`),并经图像纹理路径上传——参见
10
10
  [Canvas](./gui/canvas.md)。**M15** 增加了 CSS `transform` /
11
11
  `transform-origin`:每个布局盒一个合成好的 `Matrix2D`,由显示列表一路带到
@@ -52,7 +52,7 @@
52
52
  | 4 | 引擎**非自托管**(它是 C/C++,不是 TS),但不得影响编译器的平台无关设计。以按平台预构建的归档形式交付,通过 `nativeObjects` 链接。 |
53
53
  | 5 | **事件循环是通用的**:运行时暴露一个通用主循环钩子与一个轮询原语;任何 GUI 特定的东西都不会进入 `runtime/`。 |
54
54
  | 6 | 核心对象模型支持**多窗口**。 |
55
- | 7 | 第三方底层库**静态链接进 `gui.a`**,使发布版保持自包含;链接时只额外加入操作系统框架。 |
55
+ | 7 | 第三方底层库**静态链接进扩展归档**,使发布版保持自包含;链接时只额外加入操作系统框架。`gui.a` 携带 SDL3/FreeType/HarfBuzz,可选的 `media.a` 携带 FFmpeg,仅在 `--ext media` 时链接。 |
56
56
 
57
57
  ## 架构
58
58
 
@@ -74,6 +74,7 @@
74
74
  窗口 + 输入(SDL3)── 多窗口
75
75
  文本(HarfBuzz + FreeType)── 字形图集 / SDF
76
76
  图像(stb_image;SVG 经 NanoSVG)
77
+ 媒体 DOM + 音频 sink ── 可选的 media.a(FFmpeg)安装解码器
77
78
  ```
78
79
 
79
80
  引擎从不与编译器通信。编译器只看到一个带有 `nativeObjects()`、`linkerFlags()` 和 `modules()` 的 `Extension`。
@@ -87,16 +88,16 @@
87
88
  | 文本整形 | **HarfBuzz** | 正确处理复杂文种的整形 |
88
89
  | 字形光栅化 | **FreeType** | 字形轮廓 → GPU 图集 / SDF |
89
90
  | 图像 | 位图用 **stb_image**(vendored 头文件),SVG 用 **NanoSVG**(vendored 头文件) | 起步阶段用单头文件;SVG 文件按需光栅化 |
90
- | 视频 + 音频 | **FFmpeg**(静态 vendored,最小化构建) | 解码常用容器/编码;不依赖 WebView 或平台媒体框架 |
91
+ | 视频 + 音频 | **FFmpeg**(静态 vendored,最小化构建,可选 `media` 扩展) | 解码常用容器/编码;不依赖 WebView 或平台媒体框架 |
91
92
 
92
93
  固定版本:SDL3 `release-3.2.10`、FreeType `2.13.3`、HarfBuzz `10.1.0`、
93
- FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
94
- `FFMPEG_VERSION` 覆盖)。
94
+ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
95
+ `FREETYPE_VERSION` / `HARFBUZZ_VERSION` / `FFMPEG_VERSION` 覆盖)。
95
96
 
96
97
  如果否决 SDL3,备选是 **GLFW + OpenGL 3.3**(更简单,但 OpenGL 在 macOS 上
97
98
  已弃用,且不提供现代 GPU 抽象)。如果引擎用 Rust 编写,则用 **wgpu-native**。
98
99
 
99
- > 这些 vendored 库会**静态链接进 `gui.a`**,因此发布的 xbintsc 保持
100
+ > 这些 vendored 库会**静态链接进 `gui.a` / `media.a`**,因此发布的 xbintsc 保持
100
101
  > “下载即用”;`linkerFlags()` 只添加操作系统框架。
101
102
 
102
103
  ## 里程碑
@@ -135,14 +136,15 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
135
136
  `background-color`/`color`/`border-color`/`border-radius` 的动画、重定向
136
137
  与 `win.advance(ms)`。`@keyframes` 仍待完成。
137
138
  7. **M7 — CI 与发布** ✅(Linux/macOS 构建) / 🚧(运行 + Windows)
138
- - `compile-examples` 在 Linux 与 macOS 上构建 `gui.a`(必需),然后在
139
- Linux 上的 Xvfb + lavapipe 下运行示例与 `tests/e2e/gui-*.test.ts`(必需)。
140
- macOS 运行在确认 WindowServer 之前为试验性;它被保护起来,失败只记录
141
- 日志而不标注运行。
142
- - `package` job 在组装发布之前构建 `gui.a`,因此它会随现有 runtime 归档
143
- 一起发布(`package-release` 复制整个 `runtime/`)。
144
- - `vendor/`(SDL3/FreeType/HarfBuzz,缓慢的部分)按 OS/arch 缓存,以
145
- `scripts/build-gui.ts` 为键。
139
+ - `compile-examples` 在 Linux 与 macOS 上构建 `gui.a` 与 `media.a`(必需),
140
+ 然后在 Linux 上的 Xvfb + lavapipe 下运行示例与 `tests/e2e/gui-*.test.ts`
141
+ (必需)。macOS 运行在确认 WindowServer 之前为试验性;它被保护起来,失败
142
+ 只记录日志而不标注运行。
143
+ - `package` job 在组装发布之前构建 `gui.a` 与 `media.a`,因此两者都会随现有
144
+ runtime 归档一起发布(`package-release` 复制整个 `runtime/`)。
145
+ - `vendor/`(`gui.a` 的 SDL3/FreeType/HarfBuzz,以及 `media.a` 的 FFmpeg,
146
+ 缓慢的部分)按 OS/arch 缓存,以 `scripts/build-gui.ts` /
147
+ `scripts/build-media.ts` 为键。
146
148
  - Windows(构建 + 运行)在 CI 中保持试验性:MSVC 兼容的 `gui.lib` 可在本机
147
149
  通过 SDL_GPU 的 Vulkan 后端构建并运行,但托管 runner 没有 GPU/Vulkan
148
150
  驱动。失败只记录日志而不标注运行(见[待决问题](./gui/known-issues.md))。
@@ -189,13 +191,14 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
189
191
  - `win.stats()` 暴露引擎计数器;e2e 覆盖见
190
192
  `tests/e2e/gui-retention.test.ts`(见[内存与 GC](./gui/runtime.md))。
191
193
  13. **M13 — 视频与音频** ✅
192
- - `<video>` / `<audio>` 通过静态 vendored 的最小 FFmpeg 解码
193
- (`media.*`、`gui_media.cpp`),帧沿用现有图像管线上传到 GPU 纹理,
194
- 音频在主线程推送进 SDL 流,并在元素句柄上暴露 DOM 媒体 API 与
195
- `loadedmetadata`/`play`/`pause`/`ended`/`timeupdate`/`error` 事件
196
- (`dom_api_media.cpp`、`dom_api_proto.cpp`)。由 TS + CSS 控制;没有
197
- 内置控件。示例见 `examples/gui/media.*`,e2e 见
198
- `tests/e2e/gui-media.test.ts`。
194
+ - `<video>` / `<audio>` 通过可选的 `media` 扩展(`runtime/ext_media/media.cpp`)
195
+ 使用静态 vendored 的最小 FFmpeg 解码,帧沿用现有图像管线上传到 GPU 纹理,
196
+ 音频由 GUI 的音频 sink 在主线程推送进 SDL 流,并在元素句柄上暴露 DOM
197
+ 媒体 API 与 `loadedmetadata`/`play`/`pause`/`ended`/`timeupdate`/`error`
198
+ 事件(`dom_api_media.cpp`、`dom_api_proto.cpp`)。由 TS + CSS 控制;没有
199
+ 内置控件。示例见 `examples/gui/media.*`;e2e 见
200
+ `tests/e2e/gui-media.test.ts`(启用 `media`)与
201
+ `tests/e2e/gui-media-disabled.test.ts`(不启用)。
199
202
  14. **M14 — `<canvas>` 2D** ✅
200
203
  - `<canvas>` 是一个替换元素(`xtgui::Canvas2D`、`layout.*`),
201
204
  `getContext("2d")` 句柄与绘制 API 位于 `dom_api_canvas.cpp`,自研的抗锯齿
@@ -249,10 +252,10 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
249
252
  按绘制设备像素尺寸光栅化并按该尺寸缓存,因此缩放与 HiDPI 保持清晰。
250
253
  - **M6b** ✅ CSS transition(在 `style.*` 中解析、在 `document.*` 中维护动画
251
254
  时钟与 transition 状态、`advance`/`advance(ms)` 钩子、e2e 覆盖)。
252
- - **M7** ✅ 在 CI 中于 Linux 与 macOS 上构建 `gui.a`(必需),并在 Linux 的
253
- Xvfb 下运行示例与 GUI e2e 套件;`package` 发布 `gui.a`,`vendor/` 被缓存,
254
- `ar -M` 合并在 GNU/Linux 与 macOS 上可用。macOS 运行与所有 Windows 在 CI
255
- 中仍为试验性。
255
+ - **M7** ✅ 在 CI 中于 Linux 与 macOS 上构建 `gui.a`(以及可选的 `media.a`,
256
+ 必需),并在 Linux 的 Xvfb 下运行示例与 GUI e2e 套件;`package` 发布两个归档,
257
+ `vendor/` 被缓存,`ar -M` 合并在 GNU/Linux 与 macOS 上可用。macOS 运行与所有
258
+ Windows 在 CI 中仍为试验性。
256
259
  - **M8** ✅ 元素/文档句柄(`dom_api.*`)、带延迟重算样式/重新布局的 DOM 变更
257
260
  (`document.*`、`xt_gui_flush_dom`)、带捕获/冒泡与 `stopPropagation` 的
258
261
  元素事件分发(`dom_api.*`、`gui.cpp`),以及
@@ -283,11 +286,13 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
283
286
  失效;收集器的触发阈值改为跟随存活集,而不再只升不降。`win.stats()` 暴露
284
287
  引擎计数器,`tests/e2e/gui-retention.test.ts` 对其做断言;每帧重写精灵的游戏
285
288
  循环现在内存恒定(见[内存与 GC](./gui/runtime.md))。
286
- - **M13** ✅ `<video>` / `<audio>` 支持。一个最小化的静态 FFmpeg 被 vendored
287
- 并合并进 `gui.a`(`scripts/build-gui.ts`),`media.*` 在主线程把视频解码为
288
- RGBA 帧、把音频解码为 48 kHz 立体声 PCM,`gui_media.cpp` 持有播放器并在每帧
289
- 推进媒体时钟与事件,`renderer_image.cpp` 把每个新视频帧沿图像纹理路径上传,
290
- `dom_api_media.cpp` 在元素句柄上暴露 `play`/`pause`/`load` 与媒体属性。
289
+ - **M13** ✅ `<video>` / `<audio>` 支持。可选的 `media` 扩展 vendored 并构建一个
290
+ 最小化的静态 FFmpeg(`scripts/build-media.ts`),合并进 `media.a`;
291
+ `runtime/ext_media/media.cpp` 在主线程把视频解码为 RGBA 帧、把音频解码为
292
+ 48 kHz 立体声 PCM,`runtime/ext_gui/gui_media.cpp` 持有播放器、实现 SDL 音频
293
+ sink 并在每帧推进媒体时钟与事件,`renderer_image.cpp` 把每个新视频帧沿图像纹理
294
+ 路径上传,`dom_api_media.cpp` 在元素句柄上暴露 `play`/`pause`/`load` 与媒体
295
+ 属性。`gui.a` 不含 FFmpeg;只启用 `media` 而不启用 `gui` 会被注册表拒绝。
291
296
  `examples/gui/media.*` 演示 TS + CSS 控件的模式,
292
297
  `tests/e2e/gui-media.test.ts` 对其做断言。
293
298
  - **M14** ✅ `<canvas>` 支持。新的替换元素(`layout.*`)从 `width`/`height`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.5.22",
3
+ "version": "0.5.23",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,6 +37,7 @@
37
37
  "prepublishOnly": "npm run build",
38
38
  "runtime": "tsx scripts/build-runtime.ts",
39
39
  "gui": "tsx scripts/build-gui.ts",
40
+ "media": "tsx scripts/build-media.ts",
40
41
  "package-release": "tsx scripts/package-release.ts",
41
42
  "runtime:clean": "node -e \"require('fs').rmSync('build/runtime-obj',{recursive:true,force:true});require('fs').rmSync('runtime/lib',{recursive:true,force:true})\"",
42
43
  "xbintsc": "tsx src/cli/main.ts",
@@ -3,9 +3,13 @@
3
3
  *
4
4
  * This unit bridges three pieces:
5
5
  * - the DOM (`Node`) and the per-window media table on `XtGuiWindow`;
6
- * - the decoders (`media.cpp`, backed by FFmpeg + SDL audio);
6
+ * - the optional decoders the `media` extension installs (the engine's
7
+ * `media.cpp` dispatch; FFmpeg itself lives in `runtime/ext_media/`);
7
8
  * - the renderer (`renderer_image.cpp`), which owns the GPU textures.
8
9
  *
10
+ * It also provides the SDL audio sink the decoder writes PCM to, which keeps
11
+ * SDL out of `media.a`.
12
+ *
9
13
  * The frame loop calls `xt_gui_media_sync` (open/close decoders so the next
10
14
  * layout sees the current sources and intrinsic sizes) and `xt_gui_media_tick`
11
15
  * (decode for the elapsed wall clock and fire the resulting DOM events) once
@@ -15,12 +19,75 @@
15
19
 
16
20
  #include "gui_internal.h"
17
21
 
22
+ #include <cstring>
18
23
  #include <string>
19
24
  #include <unordered_set>
20
25
  #include <vector>
21
26
 
22
27
  namespace {
23
28
 
29
+ /** Audio format the decoder resamples everything to. */
30
+ constexpr int kAudioRate = 48000;
31
+ constexpr int kAudioChannels = 2;
32
+
33
+ /** Initialise SDL's audio subsystem once, best effort: a headless CI box has
34
+ * no device, and video still plays without sound. */
35
+ bool audioSubsystemReady() {
36
+ static const bool ready = SDL_InitSubSystem(SDL_INIT_AUDIO);
37
+ return ready;
38
+ }
39
+
40
+ /** SDL push-mode audio output for one decoder.
41
+ *
42
+ * There is no audio callback: the decoder writes PCM on the main thread and SDL
43
+ * pulls from the queued buffer on its own thread. `queued_seconds()` lets the
44
+ * decoder keep that buffer shallow. */
45
+ class SdlAudioSink final : public xtgui::AudioSink {
46
+ public:
47
+ ~SdlAudioSink() override {
48
+ if (stream_ != nullptr) SDL_DestroyAudioStream(stream_);
49
+ }
50
+
51
+ bool open() override {
52
+ if (tried_) return stream_ != nullptr;
53
+ tried_ = true;
54
+ if (!audioSubsystemReady()) return false;
55
+ SDL_AudioSpec spec;
56
+ spec.format = SDL_AUDIO_F32;
57
+ spec.channels = kAudioChannels;
58
+ spec.freq = kAudioRate;
59
+ stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
60
+ if (stream_ == nullptr) return false;
61
+ SDL_PauseAudioStreamDevice(stream_);
62
+ return true;
63
+ }
64
+
65
+ void write(const void *data, size_t bytes) override {
66
+ if (stream_ != nullptr) SDL_PutAudioStreamData(stream_, data, static_cast<int>(bytes));
67
+ }
68
+ void clear() override {
69
+ if (stream_ != nullptr) SDL_ClearAudioStream(stream_);
70
+ }
71
+ void pause() override {
72
+ if (stream_ != nullptr) SDL_PauseAudioStreamDevice(stream_);
73
+ }
74
+ void resume() override {
75
+ if (stream_ != nullptr) SDL_ResumeAudioStreamDevice(stream_);
76
+ }
77
+ void set_gain(float gain) override {
78
+ if (stream_ != nullptr) SDL_SetAudioStreamGain(stream_, gain);
79
+ }
80
+ double queued_seconds() const override {
81
+ if (stream_ == nullptr) return 0.0;
82
+ const int bytes = SDL_GetAudioStreamQueued(stream_);
83
+ return bytes / static_cast<double>(kAudioRate * kAudioChannels * sizeof(float));
84
+ }
85
+
86
+ private:
87
+ SDL_AudioStream *stream_ = nullptr;
88
+ bool tried_ = false;
89
+ };
90
+
24
91
  bool isMediaElement(const xtgui::Node *node) {
25
92
  return node != nullptr && node->isElement() && (node->isTag("video") || node->isTag("audio"));
26
93
  }
@@ -65,7 +132,7 @@ xtgui::MediaPlayer *openEntry(XtGuiWindow *win, const xtgui::Node *node, const s
65
132
  XtGuiMediaEntry entry;
66
133
  entry.src = src;
67
134
  std::string error;
68
- entry.player = xtgui::MediaPlayer::open(src, &error);
135
+ entry.player = xtgui::MediaPlayer::open(src, &error, std::make_unique<SdlAudioSink>());
69
136
  if (!entry.player) {
70
137
  entry.failed = true;
71
138
  win->media_players[node] = std::move(entry);