xbintsc 0.5.22 → 0.5.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +12 -3
- package/README.md +46 -11
- package/README.zh-CN.md +32 -6
- package/dist/scripts/support-matrix.d.ts +18 -0
- package/dist/scripts/support-matrix.js +141 -0
- package/dist/scripts/support-matrix.js.map +1 -0
- package/dist/src/ast/common.d.ts +2 -2
- package/dist/src/ast/declarations.d.ts +7 -1
- package/dist/src/ast/expressions.d.ts +5 -0
- package/dist/src/ast/kinds.d.ts +2 -0
- package/dist/src/ast/kinds.js +2 -0
- package/dist/src/ast/kinds.js.map +1 -1
- package/dist/src/binder/binder.d.ts +2 -1
- package/dist/src/binder/binder.js +2 -0
- package/dist/src/binder/binder.js.map +1 -1
- package/dist/src/binder/declarations.d.ts +9 -0
- package/dist/src/binder/declarations.js +64 -0
- package/dist/src/binder/declarations.js.map +1 -1
- package/dist/src/binder/references.js +85 -3
- package/dist/src/binder/references.js.map +1 -1
- package/dist/src/binder/types.d.ts +32 -0
- package/dist/src/binder/types.js.map +1 -1
- package/dist/src/canonical.d.ts +29 -0
- package/dist/src/canonical.js +35 -0
- package/dist/src/canonical.js.map +1 -0
- package/dist/src/checker/assignability.d.ts +13 -0
- package/dist/src/checker/assignability.js +146 -0
- package/dist/src/checker/assignability.js.map +1 -0
- package/dist/src/checker/calls.d.ts +13 -0
- package/dist/src/checker/calls.js +127 -0
- package/dist/src/checker/calls.js.map +1 -0
- package/dist/src/checker/checker.d.ts +26 -0
- package/dist/src/checker/checker.js +31 -0
- package/dist/src/checker/checker.js.map +1 -0
- package/dist/src/checker/declarations.d.ts +9 -0
- package/dist/src/checker/declarations.js +15 -0
- package/dist/src/checker/declarations.js.map +1 -0
- package/dist/src/checker/flow.d.ts +21 -0
- package/dist/src/checker/flow.js +241 -0
- package/dist/src/checker/flow.js.map +1 -0
- package/dist/src/checker/globals.d.ts +9 -0
- package/dist/src/checker/globals.js +29 -0
- package/dist/src/checker/globals.js.map +1 -0
- package/dist/src/checker/source.d.ts +38 -0
- package/dist/src/checker/source.js +48 -0
- package/dist/src/checker/source.js.map +1 -0
- package/dist/src/checker/type-infer.d.ts +18 -0
- package/dist/src/checker/type-infer.js +173 -0
- package/dist/src/checker/type-infer.js.map +1 -0
- package/dist/src/checker/type-model.d.ts +73 -0
- package/dist/src/checker/type-model.js +151 -0
- package/dist/src/checker/type-model.js.map +1 -0
- package/dist/src/checker/type-system.d.ts +58 -0
- package/dist/src/checker/type-system.js +569 -0
- package/dist/src/checker/type-system.js.map +1 -0
- package/dist/src/checker/typecheck-types.d.ts +22 -0
- package/dist/src/checker/typecheck-types.js +109 -0
- package/dist/src/checker/typecheck-types.js.map +1 -0
- package/dist/src/checker/typecheck.d.ts +62 -0
- package/dist/src/checker/typecheck.js +574 -0
- package/dist/src/checker/typecheck.js.map +1 -0
- package/dist/src/checker/undefined.d.ts +14 -0
- package/dist/src/checker/undefined.js +32 -0
- package/dist/src/checker/undefined.js.map +1 -0
- package/dist/src/checker/walk.d.ts +10 -0
- package/dist/src/checker/walk.js +15 -0
- package/dist/src/checker/walk.js.map +1 -0
- package/dist/src/cli/help.d.ts +7 -0
- package/dist/src/cli/help.js +64 -0
- package/dist/src/cli/help.js.map +1 -0
- package/dist/src/cli/hints.js +13 -1
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +12 -48
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +8 -8
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +9 -4
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/calls/super.d.ts +9 -1
- package/dist/src/codegen/generator/calls/super.js +56 -5
- package/dist/src/codegen/generator/calls/super.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +13 -2
- package/dist/src/codegen/generator/context.js +25 -19
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.js +2 -1
- package/dist/src/codegen/generator/debug.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.d.ts +4 -1
- package/dist/src/codegen/generator/expressions/primary.js +68 -8
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.js +11 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +2 -0
- package/dist/src/codegen/generator/module.js +62 -26
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.d.ts +11 -1
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +20 -3
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +2 -0
- package/dist/src/codegen/generator/tables.js +4 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/build-key.d.ts +2 -0
- package/dist/src/driver/build-key.js +1 -0
- package/dist/src/driver/build-key.js.map +1 -1
- package/dist/src/driver/bundler/graph.js +5 -1
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +16 -1
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/bundler/types.d.ts +12 -0
- package/dist/src/driver/compiler.d.ts +1 -1
- package/dist/src/driver/compiler.js +4 -0
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.js +2 -1
- package/dist/src/driver/fingerprint.js.map +1 -1
- package/dist/src/driver/frontend.d.ts +8 -6
- package/dist/src/driver/frontend.js +4 -0
- package/dist/src/driver/frontend.js.map +1 -1
- package/dist/src/driver/options.d.ts +6 -0
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/catalog.js +2 -1
- package/dist/src/extensions/catalog.js.map +1 -1
- package/dist/src/extensions/gui/html.js +20 -6
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/gui/index.js +63 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/media/index.d.ts +27 -0
- package/dist/src/extensions/media/index.js +60 -0
- package/dist/src/extensions/media/index.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +50 -0
- package/dist/src/extensions/registry.js +26 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/declarations/classes.js +7 -0
- package/dist/src/parser/declarations/classes.js.map +1 -1
- package/dist/src/parser/expressions.js +14 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/src/parser/literals.js +6 -2
- package/dist/src/parser/literals.js.map +1 -1
- package/dist/tests/binder/binder.test.js +29 -0
- package/dist/tests/binder/binder.test.js.map +1 -1
- package/dist/tests/canonical.test.d.ts +1 -0
- package/dist/tests/canonical.test.js +19 -0
- package/dist/tests/canonical.test.js.map +1 -0
- package/dist/tests/checker/checker.test.d.ts +1 -0
- package/dist/tests/checker/checker.test.js +245 -0
- package/dist/tests/checker/checker.test.js.map +1 -0
- package/dist/tests/cli/check.test.js +20 -0
- package/dist/tests/cli/check.test.js.map +1 -1
- package/dist/tests/cli/hints.test.js +15 -0
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/cli/process.test.js +12 -0
- package/dist/tests/cli/process.test.js.map +1 -1
- package/dist/tests/conformance/matrix.test.d.ts +6 -0
- package/dist/tests/conformance/matrix.test.js +23 -0
- package/dist/tests/conformance/matrix.test.js.map +1 -0
- package/dist/tests/conformance/test262/manifest.d.ts +115 -0
- package/dist/tests/conformance/test262/manifest.js +257 -0
- package/dist/tests/conformance/test262/manifest.js.map +1 -0
- package/dist/tests/conformance/test262.test.d.ts +11 -0
- package/dist/tests/conformance/test262.test.js +74 -0
- package/dist/tests/conformance/test262.test.js.map +1 -0
- package/dist/tests/docs/structure.test.js +9 -11
- package/dist/tests/docs/structure.test.js.map +1 -1
- package/dist/tests/e2e/child-process.test.js +3 -2
- package/dist/tests/e2e/child-process.test.js.map +1 -1
- package/dist/tests/e2e/compile-builtins.test.js +21 -0
- package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
- package/dist/tests/e2e/compile-objects-classes.test.js +54 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/differential-syntax.test.d.ts +5 -0
- package/dist/tests/e2e/differential-syntax.test.js +95 -0
- package/dist/tests/e2e/differential-syntax.test.js.map +1 -0
- package/dist/tests/e2e/differential-values.test.js +15 -0
- package/dist/tests/e2e/differential-values.test.js.map +1 -1
- package/dist/tests/e2e/gui-browser.test.d.ts +13 -0
- package/dist/tests/e2e/gui-browser.test.js +163 -0
- package/dist/tests/e2e/gui-browser.test.js.map +1 -0
- package/dist/tests/e2e/gui-example-browser.test.js +2 -2
- package/dist/tests/e2e/gui-example-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-canvas.test.js +8 -5
- package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-svg.test.js +4 -3
- package/dist/tests/e2e/gui-example-svg.test.js.map +1 -1
- package/dist/tests/e2e/gui-example.test.js +4 -3
- package/dist/tests/e2e/gui-example.test.js.map +1 -1
- package/dist/tests/e2e/gui-helpers.d.ts +3 -0
- package/dist/tests/e2e/gui-helpers.js +6 -3
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-media-disabled.test.d.ts +9 -0
- package/dist/tests/e2e/gui-media-disabled.test.js +42 -0
- package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -0
- package/dist/tests/e2e/gui-media.test.d.ts +6 -4
- package/dist/tests/e2e/gui-media.test.js +20 -16
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/harness.d.ts +9 -0
- package/dist/tests/e2e/harness.js +14 -2
- package/dist/tests/e2e/harness.js.map +1 -1
- package/dist/tests/e2e/native-extensions.test.js +2 -1
- package/dist/tests/e2e/native-extensions.test.js.map +1 -1
- package/dist/tests/e2e/property-descriptors.test.js +20 -0
- package/dist/tests/e2e/property-descriptors.test.js.map +1 -1
- package/dist/tests/e2e/unicode-output.test.d.ts +16 -0
- package/dist/tests/e2e/unicode-output.test.js +67 -0
- package/dist/tests/e2e/unicode-output.test.js.map +1 -0
- package/dist/tests/e2e/weak-collections.test.js +16 -2
- package/dist/tests/e2e/weak-collections.test.js.map +1 -1
- package/dist/tests/extensions/media.test.d.ts +1 -0
- package/dist/tests/extensions/media.test.js +58 -0
- package/dist/tests/extensions/media.test.js.map +1 -0
- package/dist/tests/extensions/registry.test.js +8 -0
- package/dist/tests/extensions/registry.test.js.map +1 -1
- package/dist/tests/helpers.d.ts +1 -0
- package/dist/tests/helpers.js +1 -0
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/lexer/identifiers.test.js +12 -0
- package/dist/tests/lexer/identifiers.test.js.map +1 -1
- package/dist/tests/lexer/scanner.test.js +8 -0
- package/dist/tests/lexer/scanner.test.js.map +1 -1
- package/dist/tests/lexer/strings.test.js +30 -0
- package/dist/tests/lexer/strings.test.js.map +1 -1
- package/dist/tests/parser/declarations.test.js +20 -0
- package/dist/tests/parser/declarations.test.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +36 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/platform.d.ts +32 -0
- package/dist/tests/platform.js +40 -0
- package/dist/tests/platform.js.map +1 -0
- package/dist/tests/platform.test.d.ts +12 -0
- package/dist/tests/platform.test.js +51 -0
- package/dist/tests/platform.test.js.map +1 -0
- package/dist/tests/version.test.js +4 -4
- package/dist/tests/version.test.js.map +1 -1
- package/doc/ai/README.md +10 -4
- package/doc/ai/build-recipe.md +1 -0
- package/doc/ai/cli.md +18 -6
- package/doc/ai/contributing.md +59 -2
- package/doc/ai/extensions.md +46 -23
- package/doc/ai/language-support.md +25 -16
- package/doc/ai/troubleshooting.md +9 -0
- package/doc/ai/zh-CN/README.md +9 -4
- package/doc/ai/zh-CN/build-recipe.md +1 -0
- package/doc/ai/zh-CN/cli.md +14 -4
- package/doc/ai/zh-CN/contributing.md +49 -3
- package/doc/ai/zh-CN/extensions.md +29 -8
- package/doc/ai/zh-CN/language-support.md +23 -14
- package/doc/ai/zh-CN/troubleshooting.md +8 -0
- package/doc/gui/api.md +45 -0
- package/doc/gui/build.md +37 -21
- package/doc/gui/known-issues.md +17 -6
- package/doc/gui/media.md +26 -11
- package/doc/gui/runtime.md +18 -0
- package/doc/gui-scripts.md +7 -0
- package/doc/gui.md +43 -33
- package/doc/implemented/binder.md +2 -0
- package/doc/implemented/checker.md +117 -0
- package/doc/implemented/cli.md +13 -1
- package/doc/implemented/codegen.md +3 -2
- package/doc/implemented/driver.md +2 -2
- package/doc/implemented/operators.md +2 -1
- package/doc/implemented/parser.md +3 -2
- package/doc/implemented/pipeline.md +8 -1
- package/doc/implemented/stdlib.md +2 -1
- package/doc/implemented/tests.md +3 -1
- package/doc/implemented.md +2 -0
- package/doc/npm-package.md +417 -0
- package/doc/requirements.md +4 -1
- package/doc/support-matrix.md +83 -0
- package/doc/unimplemented/classes.md +3 -0
- package/doc/unimplemented/deviations.md +3 -2
- package/doc/unimplemented/toolchain.md +1 -1
- package/doc/unimplemented/types.md +24 -8
- package/doc/unimplemented.md +1 -1
- package/doc/zh-CN/gui/api.md +41 -0
- package/doc/zh-CN/gui/build.md +28 -16
- package/doc/zh-CN/gui/known-issues.md +13 -5
- package/doc/zh-CN/gui/media.md +21 -10
- package/doc/zh-CN/gui/runtime.md +16 -0
- package/doc/zh-CN/gui-scripts.md +5 -0
- package/doc/zh-CN/gui.md +37 -32
- package/doc/zh-CN/implemented/binder.md +2 -0
- package/doc/zh-CN/implemented/checker.md +99 -0
- package/doc/zh-CN/implemented/cli.md +12 -1
- package/doc/zh-CN/implemented/codegen.md +3 -2
- package/doc/zh-CN/implemented/driver.md +2 -2
- package/doc/zh-CN/implemented/operators.md +1 -1
- package/doc/zh-CN/implemented/parser.md +3 -2
- package/doc/zh-CN/implemented/pipeline.md +3 -1
- package/doc/zh-CN/implemented/stdlib.md +2 -1
- package/doc/zh-CN/implemented/tests.md +3 -1
- package/doc/zh-CN/implemented.md +2 -0
- package/doc/zh-CN/npm-package.md +391 -0
- package/doc/zh-CN/post/announce.md +47 -47
- package/doc/zh-CN/requirements.md +4 -1
- package/doc/zh-CN/support-matrix.md +82 -0
- package/doc/zh-CN/unimplemented/classes.md +3 -0
- package/doc/zh-CN/unimplemented/deviations.md +3 -2
- package/doc/zh-CN/unimplemented/toolchain.md +1 -1
- package/doc/zh-CN/unimplemented/types.md +16 -6
- package/doc/zh-CN/unimplemented.md +1 -1
- package/llms.txt +4 -1
- package/package.json +3 -1
- package/runtime/ext_gui/audio.cpp +316 -0
- package/runtime/ext_gui/browser.cpp +547 -0
- package/runtime/ext_gui/browser_escape.cpp +85 -0
- package/runtime/ext_gui/browser_loop.cpp +83 -0
- package/runtime/ext_gui/canvas.cpp +9 -3
- package/runtime/ext_gui/canvas_raster.cpp +19 -1
- package/runtime/ext_gui/dom_api.cpp +36 -0
- package/runtime/ext_gui/dom_api_document.cpp +96 -5
- package/runtime/ext_gui/dom_api_events.cpp +20 -0
- package/runtime/ext_gui/dom_api_internal.h +19 -0
- package/runtime/ext_gui/dom_api_proto.cpp +6 -0
- package/runtime/ext_gui/gui.cpp +90 -23
- package/runtime/ext_gui/gui_engine.h +44 -0
- package/runtime/ext_gui/gui_media.cpp +69 -2
- package/runtime/ext_gui/media.cpp +34 -488
- package/runtime/ext_gui/media.h +54 -19
- package/runtime/ext_gui/media_api.h +65 -0
- package/runtime/ext_gui/window.cpp +9 -0
- package/runtime/ext_media/media.cpp +507 -0
- package/runtime/ext_media/media_bridge.c +35 -0
- package/runtime/ext_node/path/path.c +7 -7
- package/runtime/ext_node/url/url.c +4 -2
- package/runtime/rt.h +15 -0
- package/runtime/xt_constructors.c +2 -1
- package/runtime/xt_containers/objects.inc +22 -2
- package/runtime/xt_io/exception.inc +4 -2
- package/runtime/xt_stdlib/math.inc +62 -0
- package/runtime/xt_stdlib2/date.inc +7 -1
- package/runtime/xt_stdlib2/objects.inc +14 -0
- package/scripts/build-gui.ts +6 -72
- package/scripts/build-media.ts +245 -0
- package/scripts/support-matrix.ts +167 -0
- package/src/ast/common.ts +2 -0
- package/src/ast/declarations.ts +12 -1
- package/src/ast/expressions.ts +6 -0
- package/src/ast/kinds.ts +2 -0
- package/src/binder/binder.ts +3 -0
- package/src/binder/declarations.ts +70 -0
- package/src/binder/references.ts +84 -3
- package/src/binder/types.ts +30 -0
- package/src/canonical.ts +37 -0
- package/src/checker/assignability.ts +134 -0
- package/src/checker/calls.ts +135 -0
- package/src/checker/checker.ts +47 -0
- package/src/checker/declarations.ts +26 -0
- package/src/checker/flow.ts +236 -0
- package/src/checker/globals.ts +40 -0
- package/src/checker/source.ts +53 -0
- package/src/checker/type-infer.ts +185 -0
- package/src/checker/type-model.ts +212 -0
- package/src/checker/type-system.ts +578 -0
- package/src/checker/typecheck-types.ts +112 -0
- package/src/checker/typecheck.ts +586 -0
- package/src/checker/undefined.ts +43 -0
- package/src/checker/walk.ts +16 -0
- package/src/cli/help.ts +65 -0
- package/src/cli/hints.ts +13 -1
- package/src/cli/main.ts +12 -49
- package/src/codegen/generator/calls/access.ts +8 -8
- package/src/codegen/generator/calls/invocation.ts +8 -5
- package/src/codegen/generator/calls/super.ts +67 -5
- package/src/codegen/generator/context.ts +31 -19
- package/src/codegen/generator/debug.ts +2 -1
- package/src/codegen/generator/expressions/primary.ts +84 -13
- package/src/codegen/generator/generator.ts +11 -1
- package/src/codegen/generator/module.ts +65 -24
- package/src/codegen/generator/state.ts +11 -1
- package/src/codegen/generator/statements/variables.ts +20 -3
- package/src/codegen/generator/tables.ts +5 -0
- package/src/driver/build-key.ts +3 -0
- package/src/driver/bundler/graph.ts +6 -2
- package/src/driver/bundler/merge.ts +14 -1
- package/src/driver/bundler/types.ts +13 -0
- package/src/driver/compiler.ts +5 -1
- package/src/driver/fingerprint.ts +2 -1
- package/src/driver/frontend.ts +13 -2
- package/src/driver/options.ts +6 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/catalog.ts +2 -1
- package/src/extensions/gui/html.ts +23 -10
- package/src/extensions/gui/index.ts +65 -0
- package/src/extensions/media/index.ts +67 -0
- package/src/extensions/registry.ts +72 -0
- package/src/index.ts +2 -0
- package/src/parser/declarations/classes.ts +7 -0
- package/src/parser/expressions.ts +13 -1
- package/src/parser/literals.ts +6 -2
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc media extension — `<video>`/`<audio>` decoding.
|
|
3
|
+
*
|
|
4
|
+
* This translation unit is the only place that knows about FFmpeg. It is
|
|
5
|
+
* compiled into the prebuilt `media.a` by `scripts/build-media.ts` and linked
|
|
6
|
+
* only when the `media` extension is enabled; the GUI engine itself is
|
|
7
|
+
* decoder-free (see `../ext_gui/media_api.h`).
|
|
8
|
+
*
|
|
9
|
+
* Threading: the engine is single-threaded and its values live on the main
|
|
10
|
+
* thread, so nothing here may run on an audio callback. Instead the decoder is
|
|
11
|
+
* pushed from `tick()` on the main thread: it writes PCM into the
|
|
12
|
+
* `xtgui::AudioSink` supplied by the GUI (an SDL audio stream opened in push
|
|
13
|
+
* mode), and SDL pulls from its queued buffer on its own thread.
|
|
14
|
+
* `queued_seconds()` keeps the buffer shallow so there is no unbounded latency.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#include "../ext_gui/media.h"
|
|
18
|
+
|
|
19
|
+
#include <algorithm>
|
|
20
|
+
#include <chrono>
|
|
21
|
+
#include <cmath>
|
|
22
|
+
#include <cstdio>
|
|
23
|
+
#include <cstring>
|
|
24
|
+
#include <memory>
|
|
25
|
+
#include <unordered_map>
|
|
26
|
+
#include <vector>
|
|
27
|
+
|
|
28
|
+
extern "C" {
|
|
29
|
+
#include <libavcodec/avcodec.h>
|
|
30
|
+
#include <libavformat/avformat.h>
|
|
31
|
+
#include <libavutil/channel_layout.h>
|
|
32
|
+
#include <libavutil/opt.h>
|
|
33
|
+
#include <libswresample/swresample.h>
|
|
34
|
+
#include <libswscale/swscale.h>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
namespace xtgui {
|
|
38
|
+
|
|
39
|
+
namespace {
|
|
40
|
+
|
|
41
|
+
std::unordered_map<uint64_t, MediaPlayer*>& PlayerTable() {
|
|
42
|
+
static std::unordered_map<uint64_t, MediaPlayer*> table;
|
|
43
|
+
return table;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Audio format everything is resampled to before it reaches the sink. */
|
|
47
|
+
constexpr int kAudioRate = 48000;
|
|
48
|
+
constexpr int kAudioChannels = 2;
|
|
49
|
+
/** How much audio to keep buffered ahead of the play head. */
|
|
50
|
+
constexpr double kAudioAhead = 0.25;
|
|
51
|
+
/** Minimum gap between `timeupdate` events. */
|
|
52
|
+
constexpr double kTimeUpdateIntervalMs = 250.0;
|
|
53
|
+
|
|
54
|
+
/** Monotonic wall clock, the reference for playback timing. */
|
|
55
|
+
double NowMs() {
|
|
56
|
+
static const auto start = std::chrono::steady_clock::now();
|
|
57
|
+
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
uint64_t NextId() {
|
|
61
|
+
static uint64_t next = 1;
|
|
62
|
+
return next++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
struct QueuedFrame {
|
|
66
|
+
Image image;
|
|
67
|
+
double pts = 0;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
class FfmpegPlayer final : public MediaPlayer {
|
|
71
|
+
public:
|
|
72
|
+
explicit FfmpegPlayer(std::unique_ptr<AudioSink> audio) : audio_(std::move(audio)) {}
|
|
73
|
+
|
|
74
|
+
~FfmpegPlayer() override {
|
|
75
|
+
PlayerTable().erase(id_);
|
|
76
|
+
if (sws_) sws_freeContext(sws_);
|
|
77
|
+
if (swr_) swr_free(&swr_);
|
|
78
|
+
if (vframe_) av_frame_free(&vframe_);
|
|
79
|
+
if (aframe_) av_frame_free(&aframe_);
|
|
80
|
+
if (pkt_) av_packet_free(&pkt_);
|
|
81
|
+
if (vctx_) avcodec_free_context(&vctx_);
|
|
82
|
+
if (actx_) avcodec_free_context(&actx_);
|
|
83
|
+
if (fmt_) avformat_close_input(&fmt_);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
bool Open(const std::string& path, std::string* error) {
|
|
87
|
+
source_ = path;
|
|
88
|
+
if (avformat_open_input(&fmt_, path.c_str(), nullptr, nullptr) < 0) {
|
|
89
|
+
if (error) *error = "cannot open media: " + path;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (avformat_find_stream_info(fmt_, nullptr) < 0) {
|
|
93
|
+
if (error) *error = "cannot read media metadata: " + path;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
duration_ = fmt_->duration > 0 ? static_cast<double>(fmt_->duration) / AV_TIME_BASE : 0.0;
|
|
97
|
+
|
|
98
|
+
vstream_ = av_find_best_stream(fmt_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
|
|
99
|
+
astream_ = av_find_best_stream(fmt_, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
|
|
100
|
+
if (vstream_ < 0 && astream_ < 0) {
|
|
101
|
+
if (error) *error = "no audio or video stream: " + path;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (vstream_ >= 0 && !OpenVideo(fmt_->streams[vstream_])) {
|
|
105
|
+
vstream_ = -1;
|
|
106
|
+
}
|
|
107
|
+
if (astream_ >= 0 && !OpenAudio(fmt_->streams[astream_])) {
|
|
108
|
+
astream_ = -1;
|
|
109
|
+
}
|
|
110
|
+
if (vstream_ < 0 && astream_ < 0) {
|
|
111
|
+
if (error) *error = "no supported stream: " + path;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
pkt_ = av_packet_alloc();
|
|
115
|
+
vframe_ = av_frame_alloc();
|
|
116
|
+
aframe_ = av_frame_alloc();
|
|
117
|
+
|
|
118
|
+
/* A file whose default duration is unknown falls back to the last
|
|
119
|
+
* timestamp of the video stream, which is enough for the `duration`
|
|
120
|
+
* property and for end-of-stream detection. */
|
|
121
|
+
if (duration_ <= 0 && vstream_ >= 0) {
|
|
122
|
+
AVStream* vs = fmt_->streams[vstream_];
|
|
123
|
+
if (vs->duration > 0) duration_ = vs->duration * av_q2d(vs->time_base);
|
|
124
|
+
}
|
|
125
|
+
id_ = NextId();
|
|
126
|
+
PlayerTable()[id_] = this;
|
|
127
|
+
pending_events_ |= kMediaEventLoadedMetadata;
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
uint64_t id() const override { return id_; }
|
|
132
|
+
const std::string& source() const override { return source_; }
|
|
133
|
+
bool has_video() const override { return vstream_ >= 0; }
|
|
134
|
+
bool has_audio() const override { return astream_ >= 0; }
|
|
135
|
+
double duration() const override { return duration_; }
|
|
136
|
+
|
|
137
|
+
bool paused() const override { return paused_; }
|
|
138
|
+
bool ended() const override { return ended_; }
|
|
139
|
+
double current_time() const override {
|
|
140
|
+
if (paused_) return clock_base_pts_;
|
|
141
|
+
return clock_base_pts_ + (NowMs() - clock_base_ms_) / 1000.0;
|
|
142
|
+
}
|
|
143
|
+
double volume() const override { return volume_; }
|
|
144
|
+
bool muted() const override { return muted_; }
|
|
145
|
+
bool loop() const override { return loop_; }
|
|
146
|
+
bool autoplay() const override { return autoplay_; }
|
|
147
|
+
|
|
148
|
+
void play() override {
|
|
149
|
+
if (ended_) Seek(0);
|
|
150
|
+
if (!paused_) return;
|
|
151
|
+
paused_ = false;
|
|
152
|
+
clock_base_ms_ = NowMs();
|
|
153
|
+
if (audio_ready_) audio_->resume();
|
|
154
|
+
pending_events_ |= kMediaEventPlay;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
void pause() override {
|
|
158
|
+
if (paused_) return;
|
|
159
|
+
clock_base_pts_ = current_time();
|
|
160
|
+
paused_ = true;
|
|
161
|
+
if (audio_ready_) audio_->pause();
|
|
162
|
+
pending_events_ |= kMediaEventPause;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
void seek(double seconds) override { Seek(seconds); }
|
|
166
|
+
|
|
167
|
+
void set_volume(double value) override {
|
|
168
|
+
volume_ = std::min(1.0, std::max(0.0, value));
|
|
169
|
+
ApplyGain();
|
|
170
|
+
}
|
|
171
|
+
void set_muted(bool value) override {
|
|
172
|
+
muted_ = value;
|
|
173
|
+
ApplyGain();
|
|
174
|
+
}
|
|
175
|
+
void set_loop(bool value) override { loop_ = value; }
|
|
176
|
+
void set_autoplay(bool value) override { autoplay_ = value; }
|
|
177
|
+
|
|
178
|
+
int frame_width() const override { return vctx_ ? vctx_->width : 0; }
|
|
179
|
+
int frame_height() const override { return vctx_ ? vctx_->height : 0; }
|
|
180
|
+
const Image* frame() const override { return current_valid_ ? ¤t_ : nullptr; }
|
|
181
|
+
uint64_t frame_generation() const override { return generation_; }
|
|
182
|
+
|
|
183
|
+
unsigned tick(double now_ms) override {
|
|
184
|
+
unsigned events = pending_events_;
|
|
185
|
+
pending_events_ = 0;
|
|
186
|
+
if (vstream_ < 0 && astream_ < 0) return events;
|
|
187
|
+
if (!started_ && autoplay_) {
|
|
188
|
+
started_ = true;
|
|
189
|
+
play();
|
|
190
|
+
events |= pending_events_;
|
|
191
|
+
pending_events_ = 0;
|
|
192
|
+
}
|
|
193
|
+
if (paused_ || ended_) return events;
|
|
194
|
+
if (now_ms - last_timeupdate_ms_ >= kTimeUpdateIntervalMs) {
|
|
195
|
+
events |= kMediaEventTimeUpdate;
|
|
196
|
+
last_timeupdate_ms_ = now_ms;
|
|
197
|
+
}
|
|
198
|
+
const double t = current_time();
|
|
199
|
+
PumpVideo(t);
|
|
200
|
+
PumpAudio();
|
|
201
|
+
events |= CheckEnded(now_ms);
|
|
202
|
+
return events;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
void invalidate_texture() override {}
|
|
206
|
+
|
|
207
|
+
private:
|
|
208
|
+
bool OpenVideo(AVStream* stream) {
|
|
209
|
+
const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
|
|
210
|
+
if (!codec) return false;
|
|
211
|
+
vctx_ = avcodec_alloc_context3(codec);
|
|
212
|
+
if (!vctx_) return false;
|
|
213
|
+
if (avcodec_parameters_to_context(vctx_, stream->codecpar) < 0) return false;
|
|
214
|
+
vctx_->pkt_timebase = stream->time_base;
|
|
215
|
+
if (avcodec_open2(vctx_, codec, nullptr) < 0) return false;
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
bool OpenAudio(AVStream* stream) {
|
|
220
|
+
const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
|
|
221
|
+
if (!codec) return false;
|
|
222
|
+
actx_ = avcodec_alloc_context3(codec);
|
|
223
|
+
if (!actx_) return false;
|
|
224
|
+
if (avcodec_parameters_to_context(actx_, stream->codecpar) < 0) return false;
|
|
225
|
+
actx_->pkt_timebase = stream->time_base;
|
|
226
|
+
if (avcodec_open2(actx_, codec, nullptr) < 0) return false;
|
|
227
|
+
|
|
228
|
+
AVChannelLayout out_layout;
|
|
229
|
+
av_channel_layout_default(&out_layout, kAudioChannels);
|
|
230
|
+
if (swr_alloc_set_opts2(&swr_, &out_layout, AV_SAMPLE_FMT_FLT, kAudioRate,
|
|
231
|
+
&actx_->ch_layout, actx_->sample_fmt, actx_->sample_rate,
|
|
232
|
+
0, nullptr) < 0 ||
|
|
233
|
+
!swr_ || swr_init(swr_) < 0) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
av_channel_layout_uninit(&out_layout);
|
|
237
|
+
|
|
238
|
+
/* Audio is best effort: a headless CI box has no device. Video still
|
|
239
|
+
* plays, and audio is silently dropped. */
|
|
240
|
+
audio_ready_ = audio_ != nullptr && audio_->open();
|
|
241
|
+
if (audio_ready_) ApplyGain();
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
void ApplyGain() {
|
|
246
|
+
if (audio_ready_) audio_->set_gain(muted_ ? 0.0f : static_cast<float>(volume_));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
double queued_seconds() const {
|
|
250
|
+
if (!audio_ready_) return 0.0;
|
|
251
|
+
return audio_->queued_seconds();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
double stream_pts(const AVStream* stream, int64_t ts) const {
|
|
255
|
+
return static_cast<double>(ts) * av_q2d(stream->time_base);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void Seek(double seconds) {
|
|
259
|
+
double target = seconds;
|
|
260
|
+
if (duration_ > 0) target = std::min(duration_, std::max(0.0, target));
|
|
261
|
+
if (target < 0) target = 0;
|
|
262
|
+
clock_base_pts_ = target;
|
|
263
|
+
clock_base_ms_ = NowMs();
|
|
264
|
+
ended_ = false;
|
|
265
|
+
started_ = true;
|
|
266
|
+
|
|
267
|
+
if (fmt_) {
|
|
268
|
+
const int seek_stream = vstream_ >= 0 ? vstream_ : astream_;
|
|
269
|
+
const int64_t ts = static_cast<int64_t>(target / av_q2d(fmt_->streams[seek_stream]->time_base));
|
|
270
|
+
if (av_seek_frame(fmt_, seek_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) {
|
|
271
|
+
avformat_flush(fmt_);
|
|
272
|
+
}
|
|
273
|
+
if (vctx_) avcodec_flush_buffers(vctx_);
|
|
274
|
+
if (actx_) avcodec_flush_buffers(actx_);
|
|
275
|
+
}
|
|
276
|
+
if (audio_ready_) audio_->clear();
|
|
277
|
+
video_queue_.clear();
|
|
278
|
+
current_valid_ = false;
|
|
279
|
+
drop_before_ = vstream_ >= 0 ? target : -1;
|
|
280
|
+
reader_eof_ = false;
|
|
281
|
+
video_flushed_ = false;
|
|
282
|
+
audio_flushed_ = false;
|
|
283
|
+
video_drained_ = false;
|
|
284
|
+
audio_drained_ = false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
bool ReadPacket() {
|
|
288
|
+
const int ret = av_read_frame(fmt_, pkt_);
|
|
289
|
+
if (ret < 0) {
|
|
290
|
+
reader_eof_ = true;
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (pkt_->stream_index == vstream_) {
|
|
294
|
+
avcodec_send_packet(vctx_, pkt_);
|
|
295
|
+
DrainVideo();
|
|
296
|
+
} else if (pkt_->stream_index == astream_) {
|
|
297
|
+
avcodec_send_packet(actx_, pkt_);
|
|
298
|
+
DrainAudio();
|
|
299
|
+
}
|
|
300
|
+
av_packet_unref(pkt_);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
void DrainVideo() {
|
|
305
|
+
while (avcodec_receive_frame(vctx_, vframe_) == 0) {
|
|
306
|
+
const double pts = stream_pts(fmt_->streams[vstream_], vframe_->best_effort_timestamp);
|
|
307
|
+
Image image;
|
|
308
|
+
ConvertVideo(vframe_, &image);
|
|
309
|
+
video_queue_.push_back({std::move(image), pts});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
void ConvertVideo(const AVFrame* frame, Image* out) {
|
|
314
|
+
const int w = frame->width;
|
|
315
|
+
const int h = frame->height;
|
|
316
|
+
if (!sws_ || sws_width_ != w || sws_height_ != h || sws_pix_fmt_ != frame->format) {
|
|
317
|
+
if (sws_) sws_freeContext(sws_);
|
|
318
|
+
sws_ = sws_getContext(w, h, static_cast<AVPixelFormat>(frame->format), w, h, AV_PIX_FMT_RGBA,
|
|
319
|
+
SWS_BILINEAR, nullptr, nullptr, nullptr);
|
|
320
|
+
sws_width_ = w;
|
|
321
|
+
sws_height_ = h;
|
|
322
|
+
sws_pix_fmt_ = frame->format;
|
|
323
|
+
}
|
|
324
|
+
if (!sws_) return;
|
|
325
|
+
out->width = w;
|
|
326
|
+
out->height = h;
|
|
327
|
+
out->pixels.resize(static_cast<size_t>(w) * h * 4);
|
|
328
|
+
uint8_t* dst[4] = {out->pixels.data(), nullptr, nullptr, nullptr};
|
|
329
|
+
int dst_linesize[4] = {w * 4, 0, 0, 0};
|
|
330
|
+
sws_scale(sws_, frame->data, frame->linesize, 0, h, dst, dst_linesize);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
void DrainAudio() {
|
|
334
|
+
while (avcodec_receive_frame(actx_, aframe_) == 0) {
|
|
335
|
+
if (!audio_ready_) continue;
|
|
336
|
+
const int capacity = swr_get_out_samples(swr_, aframe_->nb_samples);
|
|
337
|
+
if (capacity <= 0) continue;
|
|
338
|
+
audio_buf_.resize(static_cast<size_t>(capacity) * kAudioChannels * sizeof(float));
|
|
339
|
+
uint8_t* out = audio_buf_.data();
|
|
340
|
+
const int got = swr_convert(swr_, &out, capacity,
|
|
341
|
+
reinterpret_cast<const uint8_t* const*>(aframe_->extended_data),
|
|
342
|
+
aframe_->nb_samples);
|
|
343
|
+
if (got > 0) {
|
|
344
|
+
const int bytes = got * kAudioChannels * static_cast<int>(sizeof(float));
|
|
345
|
+
audio_->write(audio_buf_.data(), static_cast<size_t>(bytes));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
void PumpVideo(double t) {
|
|
351
|
+
if (vstream_ < 0 || video_drained_) return;
|
|
352
|
+
while (true) {
|
|
353
|
+
if (video_queue_.empty()) {
|
|
354
|
+
if (reader_eof_) {
|
|
355
|
+
if (!video_flushed_) {
|
|
356
|
+
avcodec_send_packet(vctx_, nullptr);
|
|
357
|
+
video_flushed_ = true;
|
|
358
|
+
DrainVideo();
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
video_drained_ = true;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (!ReadPacket() && video_queue_.empty() && reader_eof_) continue;
|
|
365
|
+
if (video_queue_.empty()) continue;
|
|
366
|
+
}
|
|
367
|
+
QueuedFrame& front = video_queue_.front();
|
|
368
|
+
if (drop_before_ >= 0 && front.pts < drop_before_ - 0.001) {
|
|
369
|
+
video_queue_.erase(video_queue_.begin());
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (front.pts <= t + 0.01) {
|
|
373
|
+
current_ = std::move(front.image);
|
|
374
|
+
current_valid_ = true;
|
|
375
|
+
++generation_;
|
|
376
|
+
video_queue_.erase(video_queue_.begin());
|
|
377
|
+
drop_before_ = -1;
|
|
378
|
+
} else {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
void PumpAudio() {
|
|
385
|
+
if (astream_ < 0 || audio_drained_) return;
|
|
386
|
+
while (queued_seconds() < kAudioAhead) {
|
|
387
|
+
if (reader_eof_) {
|
|
388
|
+
if (!audio_flushed_) {
|
|
389
|
+
avcodec_send_packet(actx_, nullptr);
|
|
390
|
+
audio_flushed_ = true;
|
|
391
|
+
DrainAudio();
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
audio_drained_ = true;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (!ReadPacket()) return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
unsigned CheckEnded(double now_ms) {
|
|
402
|
+
if (ended_) return 0;
|
|
403
|
+
if (!reader_eof_) return 0;
|
|
404
|
+
if (vstream_ >= 0 && !video_drained_) return 0;
|
|
405
|
+
if (astream_ >= 0 && !audio_drained_) return 0;
|
|
406
|
+
if (astream_ >= 0 && queued_seconds() > 0.05) return 0;
|
|
407
|
+
if (vstream_ >= 0 && duration_ > 0 && current_time() < duration_ - 0.05) return 0;
|
|
408
|
+
if (loop_) {
|
|
409
|
+
Seek(0);
|
|
410
|
+
play();
|
|
411
|
+
return kMediaEventPlay;
|
|
412
|
+
}
|
|
413
|
+
ended_ = true;
|
|
414
|
+
paused_ = true;
|
|
415
|
+
clock_base_pts_ = duration_ > 0 ? duration_ : current_time();
|
|
416
|
+
last_timeupdate_ms_ = now_ms;
|
|
417
|
+
return kMediaEventEnded;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
uint64_t id_ = 0;
|
|
421
|
+
std::string source_;
|
|
422
|
+
AVFormatContext* fmt_ = nullptr;
|
|
423
|
+
AVCodecContext* vctx_ = nullptr;
|
|
424
|
+
AVCodecContext* actx_ = nullptr;
|
|
425
|
+
int vstream_ = -1;
|
|
426
|
+
int astream_ = -1;
|
|
427
|
+
AVPacket* pkt_ = nullptr;
|
|
428
|
+
AVFrame* vframe_ = nullptr;
|
|
429
|
+
AVFrame* aframe_ = nullptr;
|
|
430
|
+
SwsContext* sws_ = nullptr;
|
|
431
|
+
SwrContext* swr_ = nullptr;
|
|
432
|
+
int sws_width_ = 0;
|
|
433
|
+
int sws_height_ = 0;
|
|
434
|
+
int sws_pix_fmt_ = -1;
|
|
435
|
+
|
|
436
|
+
std::vector<QueuedFrame> video_queue_;
|
|
437
|
+
Image current_;
|
|
438
|
+
bool current_valid_ = false;
|
|
439
|
+
uint64_t generation_ = 0;
|
|
440
|
+
double drop_before_ = -1;
|
|
441
|
+
|
|
442
|
+
std::unique_ptr<AudioSink> audio_;
|
|
443
|
+
bool audio_ready_ = false;
|
|
444
|
+
std::vector<uint8_t> audio_buf_;
|
|
445
|
+
|
|
446
|
+
bool reader_eof_ = false;
|
|
447
|
+
bool video_flushed_ = false;
|
|
448
|
+
bool audio_flushed_ = false;
|
|
449
|
+
bool video_drained_ = false;
|
|
450
|
+
bool audio_drained_ = false;
|
|
451
|
+
|
|
452
|
+
double duration_ = 0;
|
|
453
|
+
double volume_ = 1.0;
|
|
454
|
+
bool muted_ = false;
|
|
455
|
+
bool loop_ = false;
|
|
456
|
+
bool autoplay_ = false;
|
|
457
|
+
bool paused_ = true;
|
|
458
|
+
bool ended_ = false;
|
|
459
|
+
bool started_ = false;
|
|
460
|
+
double clock_base_pts_ = 0;
|
|
461
|
+
double clock_base_ms_ = 0;
|
|
462
|
+
double last_timeupdate_ms_ = -1e9;
|
|
463
|
+
unsigned pending_events_ = 0;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
} // namespace
|
|
467
|
+
|
|
468
|
+
MediaPlayer* OpenMediaPlayer(const char* path, std::string* error,
|
|
469
|
+
std::unique_ptr<AudioSink> audio) {
|
|
470
|
+
std::unique_ptr<FfmpegPlayer> player(new FfmpegPlayer(std::move(audio)));
|
|
471
|
+
if (!player->Open(path, error)) return nullptr;
|
|
472
|
+
return player.release();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
MediaPlayer* MediaPlayerById(uint64_t id) {
|
|
476
|
+
auto& table = PlayerTable();
|
|
477
|
+
const auto it = table.find(id);
|
|
478
|
+
return it == table.end() ? nullptr : it->second;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
void ShutdownMediaPlayers() {
|
|
482
|
+
/* Move the table out before destroying it: `~FfmpegPlayer` erases its own
|
|
483
|
+
* entry, and a destructor mutating the container it is being cleared from
|
|
484
|
+
* would invalidate the clearing loop. With an empty table the erases are
|
|
485
|
+
* no-ops. */
|
|
486
|
+
auto pending = std::move(PlayerTable());
|
|
487
|
+
PlayerTable().clear();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
} // namespace xtgui
|
|
491
|
+
|
|
492
|
+
extern "C" XtMediaPlayerHandle xt_media_backend_open(const char* path, char* error,
|
|
493
|
+
size_t error_size, void* audio_sink) {
|
|
494
|
+
std::unique_ptr<xtgui::AudioSink> audio(static_cast<xtgui::AudioSink*>(audio_sink));
|
|
495
|
+
std::string message;
|
|
496
|
+
xtgui::MediaPlayer* player = xtgui::OpenMediaPlayer(path != nullptr ? path : "", &message, std::move(audio));
|
|
497
|
+
if (player == nullptr && error != nullptr && error_size > 0) {
|
|
498
|
+
std::snprintf(error, error_size, "%s", message.c_str());
|
|
499
|
+
}
|
|
500
|
+
return player;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
extern "C" XtMediaPlayerHandle xt_media_backend_by_id(uint64_t id) {
|
|
504
|
+
return xtgui::MediaPlayerById(id);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
extern "C" void xt_media_backend_shutdown(void) { xtgui::ShutdownMediaPlayers(); }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc media extension — registration bridge.
|
|
3
|
+
*
|
|
4
|
+
* The decoders live in `media.a`, a prebuilt C++ archive. A static archive is
|
|
5
|
+
* only searched for members that satisfy an undefined symbol, so a backend that
|
|
6
|
+
* merely had a constructor would never be extracted. This tiny C translation
|
|
7
|
+
* unit is compiled and linked *directly* (it is the media extension's
|
|
8
|
+
* `runtimeSources()` entry): its constructor references every backend entry
|
|
9
|
+
* point, which pulls the archive member in, and installs the table into the GUI
|
|
10
|
+
* engine before `main` runs.
|
|
11
|
+
*
|
|
12
|
+
* The bridge is intentionally C-only and has no FFmpeg (or SDL) dependency. If
|
|
13
|
+
* the `gui` extension is not enabled, `xt_media_install` is unresolved; the
|
|
14
|
+
* registry rejects that combination first with a "media requires gui"
|
|
15
|
+
* diagnostic (see `src/extensions/registry.ts`).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#include "../ext_gui/media_api.h"
|
|
19
|
+
|
|
20
|
+
static const XtMediaApi kMediaApi = {
|
|
21
|
+
xt_media_backend_open,
|
|
22
|
+
xt_media_backend_by_id,
|
|
23
|
+
xt_media_backend_shutdown,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
27
|
+
__attribute__((constructor)) static void xt_media_register(void) {
|
|
28
|
+
xt_media_install(&kMediaApi);
|
|
29
|
+
}
|
|
30
|
+
#elif defined(_MSC_VER)
|
|
31
|
+
/* COFF has no `constructor` attribute. `media.lib` is not produced on Windows
|
|
32
|
+
* yet; keep the table referenced so the translation unit is not empty. */
|
|
33
|
+
void xt_media_register(void);
|
|
34
|
+
void xt_media_register(void) { xt_media_install(&kMediaApi); }
|
|
35
|
+
#endif
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
/** True when `c` separates path segments on this platform. */
|
|
33
33
|
static int xt_path_is_sep(char c) {
|
|
34
34
|
#if defined(_WIN32)
|
|
35
|
-
return c ==
|
|
35
|
+
return c == XT_ARTIFACT_PATH_SEPARATOR || c == '\\';
|
|
36
36
|
#else
|
|
37
|
-
return c ==
|
|
37
|
+
return c == XT_ARTIFACT_PATH_SEPARATOR;
|
|
38
38
|
#endif
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -57,7 +57,7 @@ static int xt_path_is_absolute(const char *path) {
|
|
|
57
57
|
if (path[0] != '\0' && path[1] == ':') return root == 3;
|
|
58
58
|
return xt_path_is_sep(path[0]) ? 1 : 0;
|
|
59
59
|
#else
|
|
60
|
-
return path[0] ==
|
|
60
|
+
return path[0] == XT_ARTIFACT_PATH_SEPARATOR;
|
|
61
61
|
#endif
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -129,11 +129,11 @@ static void xt_path_normalize(const char *input, char *out, size_t size) {
|
|
|
129
129
|
|
|
130
130
|
size_t position = 0;
|
|
131
131
|
for (size_t p = 0; p < prefixLen && position + 1 < size; p++) out[position++] = prefix[p];
|
|
132
|
-
if (absolute && position + 1 < size) out[position++] =
|
|
132
|
+
if (absolute && position + 1 < size) out[position++] = XT_ARTIFACT_PATH_SEPARATOR;
|
|
133
133
|
for (size_t s = 0; s < count; s++) {
|
|
134
134
|
size_t segmentLength = strlen(segments[s]);
|
|
135
135
|
if (position + segmentLength + 2 > size) break;
|
|
136
|
-
if (s > 0) out[position++] =
|
|
136
|
+
if (s > 0) out[position++] = XT_ARTIFACT_PATH_SEPARATOR;
|
|
137
137
|
memcpy(out + position, segments[s], segmentLength);
|
|
138
138
|
position += segmentLength;
|
|
139
139
|
}
|
|
@@ -325,14 +325,14 @@ static xt_value xt_path_relative(const char *from, const char *to) {
|
|
|
325
325
|
result[0] = '\0';
|
|
326
326
|
size_t position = 0;
|
|
327
327
|
for (size_t i = common; i < fromParts.count && position + 3 < sizeof(result); i++) {
|
|
328
|
-
if (position > 0) result[position++] =
|
|
328
|
+
if (position > 0) result[position++] = XT_ARTIFACT_PATH_SEPARATOR;
|
|
329
329
|
result[position++] = '.';
|
|
330
330
|
result[position++] = '.';
|
|
331
331
|
}
|
|
332
332
|
for (size_t i = common; i < toParts.count; i++) {
|
|
333
333
|
size_t segmentLength = strlen(toParts.segments[i]);
|
|
334
334
|
if (position + segmentLength + 2 > sizeof(result)) break;
|
|
335
|
-
if (position > 0) result[position++] =
|
|
335
|
+
if (position > 0) result[position++] = XT_ARTIFACT_PATH_SEPARATOR;
|
|
336
336
|
memcpy(result + position, toParts.segments[i], segmentLength);
|
|
337
337
|
position += segmentLength;
|
|
338
338
|
}
|
|
@@ -58,9 +58,11 @@ xt_value xt_url_path_to_file_url(int32_t argc, xt_value *argv) {
|
|
|
58
58
|
const char *prefix = "file://";
|
|
59
59
|
for (; *prefix && j < sizeof(href) - 1; prefix++) href[j++] = *prefix;
|
|
60
60
|
#if defined(_WIN32)
|
|
61
|
-
if (j < sizeof(href) - 1) href[j++] =
|
|
61
|
+
if (j < sizeof(href) - 1) href[j++] = XT_ARTIFACT_PATH_SEPARATOR;
|
|
62
62
|
#endif
|
|
63
|
-
for (const char *p = path; *p && j < sizeof(href) - 1; p++)
|
|
63
|
+
for (const char *p = path; *p && j < sizeof(href) - 1; p++) {
|
|
64
|
+
href[j++] = (*p == '\\') ? XT_ARTIFACT_PATH_SEPARATOR : *p;
|
|
65
|
+
}
|
|
64
66
|
href[j] = 0;
|
|
65
67
|
|
|
66
68
|
xt_value url = xt_object_new();
|
package/runtime/rt.h
CHANGED
|
@@ -31,6 +31,19 @@ extern "C" {
|
|
|
31
31
|
|
|
32
32
|
typedef uint64_t xt_value;
|
|
33
33
|
|
|
34
|
+
/* Separator for paths the runtime renders into program-visible artifacts: the
|
|
35
|
+
* `path` module's results and `file://` URLs. This is deliberately NOT selected
|
|
36
|
+
* by `#if defined(_WIN32)`: every host must emit the same bytes (Windows
|
|
37
|
+
* accepts `/` everywhere), which is what keeps output comparable across
|
|
38
|
+
* platforms and reproducible in the self-hosting fixpoint. Native filesystem
|
|
39
|
+
* paths — diagnostics, `import.meta.filename`, `process.cwd()` — keep the host
|
|
40
|
+
* separator and never use this constant. */
|
|
41
|
+
#define XT_ARTIFACT_PATH_SEPARATOR '/'
|
|
42
|
+
|
|
43
|
+
#if XT_ARTIFACT_PATH_SEPARATOR != '/'
|
|
44
|
+
#error "XT_ARTIFACT_PATH_SEPARATOR is the canonical artifact separator; it must not vary by platform"
|
|
45
|
+
#endif
|
|
46
|
+
|
|
34
47
|
/* Tag layout (top 16 bits). Values whose tag bits are not 0xFFF8.. are
|
|
35
48
|
* doubles, so ordinary numbers travel unboxed. */
|
|
36
49
|
#define XT_TAG_MASK 0xFFFF000000000000ULL
|
|
@@ -221,6 +234,8 @@ xt_value xt_object_ctor(int32_t argc, xt_value *argv);
|
|
|
221
234
|
xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *argv);
|
|
222
235
|
/** Implements `Math.<name>(...)`; the name is interned by the compiler. */
|
|
223
236
|
xt_value xt_math_call(xt_value name, int32_t argc, xt_value *argv);
|
|
237
|
+
/* The first-class `Math` object (function members as native closures). */
|
|
238
|
+
xt_value xt_math_object(int32_t argc, xt_value *argv);
|
|
224
239
|
/** Implements `JSON.parse` / `JSON.stringify`. */
|
|
225
240
|
xt_value xt_json_parse(int32_t argc, xt_value *argv);
|
|
226
241
|
xt_value xt_json_stringify(int32_t argc, xt_value *argv);
|
|
@@ -270,7 +270,8 @@ static const char *const XT_OBJECT_STATICS[] = {
|
|
|
270
270
|
"keys", "values", "entries", "assign", "getOwnPropertyNames",
|
|
271
271
|
"getOwnPropertySymbols", "groupBy", "freeze", "isFrozen", "fromEntries",
|
|
272
272
|
"getPrototypeOf", "setPrototypeOf", "hasOwn", "is", "create",
|
|
273
|
-
"defineProperty", "
|
|
273
|
+
"defineProperty", "defineProperties", "getOwnPropertyDescriptor",
|
|
274
|
+
"getOwnPropertyDescriptors",
|
|
274
275
|
};
|
|
275
276
|
static const char *const XT_NUMBER_STATICS[] = {
|
|
276
277
|
"isInteger", "isSafeInteger", "isFinite", "isNaN", "parseFloat", "parseInt",
|