@needle-tools/engine 5.1.0-canary.c111c4f → 5.1.0-canary.cce79c8
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/CHANGELOG.md +52 -0
- package/components.needle.json +1 -1
- package/dist/needle-engine.bundle-B1w7P037.min.js +1733 -0
- package/dist/needle-engine.bundle-C4w-9vec.umd.cjs +1733 -0
- package/dist/{needle-engine.bundle-BuaGvme0.js → needle-engine.bundle-CPbnC8NP.js} +15153 -14701
- package/dist/needle-engine.d.ts +898 -271
- package/dist/needle-engine.js +584 -582
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three-examples.js +3778 -4289
- package/dist/three-examples.min.js +14 -301
- package/dist/three-examples.umd.cjs +14 -301
- package/lib/engine/api.d.ts +5 -0
- package/lib/engine/api.js +4 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +2 -10
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/debug/debug_spatial_console.d.ts +2 -0
- package/lib/engine/debug/debug_spatial_console.js +10 -7
- package/lib/engine/debug/debug_spatial_console.js.map +1 -1
- package/lib/engine/engine_addressables.d.ts +2 -0
- package/lib/engine/engine_addressables.js +6 -3
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_audio.d.ts +68 -0
- package/lib/engine/engine_audio.js +172 -0
- package/lib/engine/engine_audio.js.map +1 -1
- package/lib/engine/engine_components.js +1 -1
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.d.ts +17 -20
- package/lib/engine/engine_context.js +15 -17
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_disposable.d.ts +172 -0
- package/lib/engine/engine_disposable.js +136 -0
- package/lib/engine/engine_disposable.js.map +1 -0
- package/lib/engine/engine_gameobject.d.ts +1 -10
- package/lib/engine/engine_gameobject.js +22 -120
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +7 -69
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_init.js +13 -3
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.d.ts +1 -1
- package/lib/engine/engine_input.js +1 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
- package/lib/engine/engine_instantiate_resolve.js +372 -0
- package/lib/engine/engine_instantiate_resolve.js.map +1 -0
- package/lib/engine/engine_license.js +1 -1
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_mainloop_utils.js +7 -4
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +51 -37
- package/lib/engine/engine_networking.js +132 -82
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
- package/lib/engine/engine_networking.transport.websocket.js +38 -0
- package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
- package/lib/engine/engine_networking_blob.js +1 -1
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_networking_instantiate.js +2 -2
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_types.d.ts +39 -1
- package/lib/engine/engine_networking_types.js +7 -0
- package/lib/engine/engine_networking_types.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +11 -3
- package/lib/engine/engine_physics_rapier.js +88 -25
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_scenedata.d.ts +5 -7
- package/lib/engine/engine_scenedata.js +16 -13
- package/lib/engine/engine_scenedata.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.d.ts +10 -16
- package/lib/engine/engine_serialization_builtin_serializer.js +56 -46
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +1 -0
- package/lib/engine/engine_serialization_core.js +7 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_types.d.ts +19 -11
- package/lib/engine/engine_types.js +1 -1
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_util_decorator.js +7 -2
- package/lib/engine/engine_util_decorator.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +19 -5
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
- package/lib/engine/webcomponents/jsx.d.ts +51 -0
- package/lib/engine/webcomponents/logo-element.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +3 -4
- package/lib/engine/webcomponents/needle menu/needle-menu.js +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-button.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +10 -4
- package/lib/engine/webcomponents/needle-engine.js +1 -1
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
- package/lib/engine/xr/NeedleXRSession.js +50 -14
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/events.d.ts +1 -1
- package/lib/engine/xr/events.js.map +1 -1
- package/lib/engine-components/Animation.js +17 -16
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/Animator.d.ts +6 -0
- package/lib/engine-components/Animator.js +17 -12
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +113 -0
- package/lib/engine-components/AnimatorController.builder.js +195 -0
- package/lib/engine-components/AnimatorController.builder.js.map +1 -0
- package/lib/engine-components/AnimatorController.d.ts +4 -119
- package/lib/engine-components/AnimatorController.js +35 -233
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +19 -3
- package/lib/engine-components/AudioSource.js +121 -68
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +18 -9
- package/lib/engine-components/Collider.js +61 -14
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +72 -9
- package/lib/engine-components/Component.js +114 -10
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +7 -0
- package/lib/engine-components/DragControls.js +19 -7
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/EventList.d.ts +31 -9
- package/lib/engine-components/EventList.js +37 -76
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +4 -2
- package/lib/engine-components/Joints.js +19 -3
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.d.ts +0 -19
- package/lib/engine-components/Light.js +13 -97
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +1 -1
- package/lib/engine-components/Networking.js +1 -1
- package/lib/engine-components/OrbitControls.js +16 -11
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.js +2 -0
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +12 -4
- package/lib/engine-components/RigidBody.js +18 -4
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SeeThrough.js +2 -2
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -1
- package/lib/engine-components/api.js +1 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +3 -9
- package/lib/engine-components/codegen/components.js +3 -9
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/postprocessing/VolumeParameter.d.ts +2 -0
- package/lib/engine-components/postprocessing/VolumeParameter.js +4 -1
- package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +16 -6
- package/lib/engine-components/timeline/PlayableDirector.js +63 -61
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
- package/lib/engine-components/timeline/SignalAsset.js +1 -0
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +247 -0
- package/lib/engine-components/timeline/TimelineBuilder.js +400 -0
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineModels.js +3 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +23 -0
- package/lib/engine-components/timeline/TimelineTracks.js +71 -13
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/timeline/index.d.ts +2 -1
- package/lib/engine-components/timeline/index.js +2 -0
- package/lib/engine-components/timeline/index.js.map +1 -1
- package/lib/engine-components/ui/Canvas.d.ts +1 -1
- package/lib/engine-components/ui/Canvas.js +2 -8
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +1 -0
- package/lib/engine-components/ui/Text.js +10 -7
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.js +21 -12
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +4 -0
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/lib/needle-engine.d.ts +2 -0
- package/lib/needle-engine.js +1 -0
- package/lib/needle-engine.js.map +1 -1
- package/package.json +2 -83
- package/plugins/common/license.js +5 -2
- package/plugins/common/worker.js +9 -4
- package/plugins/dts-generator/dts.codegen.js +12 -6
- package/plugins/dts-generator/dts.writer.js +1 -1
- package/plugins/dts-generator/glb.extractor.js +12 -1
- package/plugins/dts-generator/glb.reader.js +42 -33
- package/plugins/types/userconfig.d.ts +12 -0
- package/plugins/vite/asap.js +17 -8
- package/plugins/vite/dependencies.js +29 -10
- package/plugins/vite/dependency-watcher.js +2 -2
- package/plugins/vite/dts-generator.d.ts +2 -2
- package/plugins/vite/dts-generator.js +6 -1
- package/plugins/vite/editor-connection.js +3 -3
- package/plugins/vite/local-files-core.js +3 -3
- package/plugins/vite/local-files-utils.d.ts +3 -1
- package/plugins/vite/local-files-utils.js +29 -5
- package/plugins/vite/reload.js +19 -18
- package/plugins/vite/server.js +2 -1
- package/src/engine/api.ts +7 -0
- package/src/engine/codegen/register_types.ts +2 -10
- package/src/engine/debug/debug_spatial_console.ts +10 -7
- package/src/engine/engine_addressables.ts +6 -3
- package/src/engine/engine_audio.ts +184 -0
- package/src/engine/engine_components.ts +1 -1
- package/src/engine/engine_context.ts +22 -23
- package/src/engine/engine_disposable.ts +214 -0
- package/src/engine/engine_gameobject.ts +54 -159
- package/src/engine/engine_gltf_builtin_components.ts +7 -76
- package/src/engine/engine_init.ts +13 -3
- package/src/engine/engine_input.ts +1 -1
- package/src/engine/engine_instantiate_resolve.ts +407 -0
- package/src/engine/engine_license.ts +1 -1
- package/src/engine/engine_mainloop_utils.ts +7 -4
- package/src/engine/engine_networking.transport.websocket.ts +45 -0
- package/src/engine/engine_networking.ts +161 -137
- package/src/engine/engine_networking_blob.ts +1 -1
- package/src/engine/engine_networking_instantiate.ts +2 -2
- package/src/engine/engine_networking_types.ts +41 -1
- package/src/engine/engine_physics_rapier.ts +82 -27
- package/src/engine/engine_scenedata.ts +16 -13
- package/src/engine/engine_serialization_builtin_serializer.ts +64 -52
- package/src/engine/engine_serialization_core.ts +9 -0
- package/src/engine/engine_types.ts +24 -15
- package/src/engine/engine_util_decorator.ts +7 -2
- package/src/engine/engine_utils.ts +16 -5
- package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
- package/src/engine/webcomponents/jsx.d.ts +51 -0
- package/src/engine/webcomponents/logo-element.ts +1 -0
- package/src/engine/webcomponents/needle menu/needle-menu.ts +3 -2
- package/src/engine/webcomponents/needle-button.ts +1 -0
- package/src/engine/webcomponents/needle-engine.ts +11 -4
- package/src/engine/xr/NeedleXRSession.ts +48 -13
- package/src/engine/xr/events.ts +1 -1
- package/src/engine-components/Animation.ts +19 -16
- package/src/engine-components/Animator.ts +18 -11
- package/src/engine-components/AnimatorController.builder.ts +261 -0
- package/src/engine-components/AnimatorController.ts +23 -292
- package/src/engine-components/AudioSource.ts +130 -79
- package/src/engine-components/Collider.ts +66 -18
- package/src/engine-components/Component.ts +118 -20
- package/src/engine-components/DragControls.ts +18 -11
- package/src/engine-components/EventList.ts +45 -83
- package/src/engine-components/Joints.ts +20 -4
- package/src/engine-components/Light.ts +13 -96
- package/src/engine-components/Networking.ts +1 -1
- package/src/engine-components/OrbitControls.ts +18 -9
- package/src/engine-components/ReflectionProbe.ts +2 -0
- package/src/engine-components/RigidBody.ts +18 -4
- package/src/engine-components/SeeThrough.ts +2 -2
- package/src/engine-components/api.ts +1 -1
- package/src/engine-components/codegen/components.ts +3 -9
- package/src/engine-components/postprocessing/VolumeParameter.ts +4 -1
- package/src/engine-components/timeline/PlayableDirector.ts +61 -64
- package/src/engine-components/timeline/SignalAsset.ts +4 -1
- package/src/engine-components/timeline/TimelineBuilder.ts +565 -0
- package/src/engine-components/timeline/TimelineModels.ts +5 -1
- package/src/engine-components/timeline/TimelineTracks.ts +74 -13
- package/src/engine-components/timeline/index.ts +2 -1
- package/src/engine-components/ui/Canvas.ts +2 -8
- package/src/engine-components/ui/Text.ts +12 -8
- package/src/engine-components/web/CursorFollow.ts +21 -13
- package/src/engine-components/webxr/WebXRImageTracking.ts +2 -0
- package/src/needle-engine.ts +1 -0
- package/dist/needle-engine.bundle-CayCdkzK.min.js +0 -1732
- package/dist/needle-engine.bundle-DVhVGqQR.umd.cjs +0 -1732
- package/lib/engine-components/AvatarLoader.d.ts +0 -80
- package/lib/engine-components/AvatarLoader.js +0 -232
- package/lib/engine-components/AvatarLoader.js.map +0 -1
- package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
- package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
- package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
- package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
- package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
- package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
- package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
- package/src/engine-components/AvatarLoader.ts +0 -264
- package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
- package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
- package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
- package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
- package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
|
@@ -98,7 +98,7 @@ function watchPackageJson(server, projectDir, packageJsonPath, cachePath) {
|
|
|
98
98
|
|
|
99
99
|
setTimeout(() => {
|
|
100
100
|
requireInstall = testIfInstallIsRequired(projectDir, packageJson);
|
|
101
|
-
}, 1000);
|
|
101
|
+
}, 1000).unref();
|
|
102
102
|
|
|
103
103
|
setInterval(() => {
|
|
104
104
|
if (!packageJson || lastEditTime === undefined) return;
|
|
@@ -149,7 +149,7 @@ function watchPackageJson(server, projectDir, packageJsonPath, cachePath) {
|
|
|
149
149
|
restart(server, projectDir, cachePath);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
}, 2000);
|
|
152
|
+
}, 2000).unref();
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/** @param {string} projectDir @param {PackageJson | undefined} packageJson @returns {boolean} */
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* @param {"build" | "serve"} _command Vite command (unused — runs in both modes)
|
|
3
3
|
* @param {import('../types/needleConfig').needleMeta | null | undefined} _config
|
|
4
4
|
* @param {import('../types').userSettings} [_userSettings]
|
|
5
|
-
* @returns {import('vite').Plugin}
|
|
5
|
+
* @returns {import('vite').Plugin | null}
|
|
6
6
|
*/
|
|
7
|
-
export function needleDtsGenerator(_command: "build" | "serve", _config: import("../types/needleConfig").needleMeta | null | undefined, _userSettings?: import("../types").userSettings): import("vite").Plugin;
|
|
7
|
+
export function needleDtsGenerator(_command: "build" | "serve", _config: import("../types/needleConfig").needleMeta | null | undefined, _userSettings?: import("../types").userSettings): import("vite").Plugin | null;
|
|
@@ -72,9 +72,14 @@ function ensureVscodeHtmlCustomData(projectRoot, htmlDataPath) {
|
|
|
72
72
|
* @param {"build" | "serve"} _command Vite command (unused — runs in both modes)
|
|
73
73
|
* @param {import('../types/needleConfig').needleMeta | null | undefined} _config
|
|
74
74
|
* @param {import('../types').userSettings} [_userSettings]
|
|
75
|
-
* @returns {import('vite').Plugin}
|
|
75
|
+
* @returns {import('vite').Plugin | null}
|
|
76
76
|
*/
|
|
77
77
|
export function needleDtsGenerator(_command, _config, _userSettings) {
|
|
78
|
+
|
|
79
|
+
if(_userSettings?.dts?.enabled === false) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
let projectRoot = process.cwd();
|
|
79
84
|
|
|
80
85
|
function resolveCodegenDir() {
|
|
@@ -27,7 +27,7 @@ export async function editorConnection(command, config, userSettings, pluginsArr
|
|
|
27
27
|
if (typeof config.generator === "string" && !config.generator.includes("Unity")) return;
|
|
28
28
|
|
|
29
29
|
if (!config) {
|
|
30
|
-
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync can not be installed automatically to vite: missing config", "warn"), 1000);
|
|
30
|
+
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync can not be installed automatically to vite: missing config", "warn"), 1000).unref();
|
|
31
31
|
return createPlugin(false);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -42,14 +42,14 @@ export async function editorConnection(command, config, userSettings, pluginsArr
|
|
|
42
42
|
// }
|
|
43
43
|
// }
|
|
44
44
|
if (needleEditorSettings && needleEditorSettings.enabled === false) {
|
|
45
|
-
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync is not enabled. Add a 'Needle Editor Sync' component to your scene to enable", "warn"), 1000);
|
|
45
|
+
setTimeout(() => needleLog("needle-editor-sync", "Needle Editor Sync is not enabled. Add a 'Needle Editor Sync' component to your scene to enable", "warn"), 1000).unref();
|
|
46
46
|
return createPlugin(false);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Check if the editor package is installed
|
|
50
50
|
let path = root + `/node_modules/${editorSyncPackageName}/plugins/index.js`;
|
|
51
51
|
if (existsSync(path) === false) {
|
|
52
|
-
setTimeout(() => needleLog("needle-editor-sync", `${editorSyncPackageName} is not installed: Add the "Needle Editor Sync" component to your scene if you want to send changes directly from the Unity Editor to web app`, "warn"), 1000);
|
|
52
|
+
setTimeout(() => needleLog("needle-editor-sync", `${editorSyncPackageName} is not installed: Add the "Needle Editor Sync" component to your scene if you want to send changes directly from the Unity Editor to web app`, "warn"), 1000).unref();
|
|
53
53
|
return createPlugin(false);
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -291,7 +291,7 @@ export function needleMakeFilesLocal(command, _config, userSettings) {
|
|
|
291
291
|
failedDownloads,
|
|
292
292
|
localizationStats,
|
|
293
293
|
}, activeHandlers);
|
|
294
|
-
src = fixRelativeNewURL(src);
|
|
294
|
+
src = fixRelativeNewURL(src, viteConfig?.base);
|
|
295
295
|
}
|
|
296
296
|
catch (err) {
|
|
297
297
|
needleLog("needle:local-files", "Error in transform: " + getErrMessage(err), "error");
|
|
@@ -303,7 +303,7 @@ export function needleMakeFilesLocal(command, _config, userSettings) {
|
|
|
303
303
|
},
|
|
304
304
|
renderChunk(code, chunk) {
|
|
305
305
|
if (!chunk.fileName?.endsWith(".js")) return null;
|
|
306
|
-
const fixed = fixRelativeNewURL(code);
|
|
306
|
+
const fixed = fixRelativeNewURL(code, viteConfig?.base);
|
|
307
307
|
if (fixed === code) return null;
|
|
308
308
|
return {
|
|
309
309
|
code: fixed,
|
|
@@ -314,7 +314,7 @@ export function needleMakeFilesLocal(command, _config, userSettings) {
|
|
|
314
314
|
for (const output of Object.values(bundle)) {
|
|
315
315
|
if (output.type !== "chunk") continue;
|
|
316
316
|
if (!output.fileName?.endsWith(".js")) continue;
|
|
317
|
-
const fixed = fixRelativeNewURL(output.code);
|
|
317
|
+
const fixed = fixRelativeNewURL(output.code, viteConfig?.base);
|
|
318
318
|
if (fixed !== output.code) output.code = fixed;
|
|
319
319
|
}
|
|
320
320
|
},
|
|
@@ -24,9 +24,11 @@ export function normalizeWebPath(path: string): string;
|
|
|
24
24
|
export function ensureTrailingSlash(path: string): string;
|
|
25
25
|
/**
|
|
26
26
|
* @param {string} src
|
|
27
|
+
* @param {string} [base] - Vite base path (e.g. "/" or "/app/"). When provided,
|
|
28
|
+
* ext/ paths are made absolute so they resolve correctly under SPA routing.
|
|
27
29
|
* @returns {string}
|
|
28
30
|
*/
|
|
29
|
-
export function fixRelativeNewURL(src: string): string;
|
|
31
|
+
export function fixRelativeNewURL(src: string, base?: string): string;
|
|
30
32
|
/**
|
|
31
33
|
* @param {string} src
|
|
32
34
|
* @returns {string}
|
|
@@ -113,25 +113,49 @@ export function ensureTrailingSlash(path) {
|
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* @param {string} src
|
|
116
|
+
* @param {string} [base] - Vite base path (e.g. "/" or "/app/"). When provided,
|
|
117
|
+
* ext/ paths are made absolute so they resolve correctly under SPA routing.
|
|
116
118
|
* @returns {string}
|
|
117
119
|
*/
|
|
118
|
-
export function fixRelativeNewURL(src) {
|
|
120
|
+
export function fixRelativeNewURL(src, base) {
|
|
121
|
+
/** @param {string} path */
|
|
122
|
+
function makeAbsolute(path) {
|
|
123
|
+
// Strip any leading ./ ../ or / prefix to get the clean ext/... path
|
|
124
|
+
const clean = path.replace(/^(?:\.\.?\/)+/, '').replace(/^\//, '');
|
|
125
|
+
if (base) return base.replace(/\/+$/, '') + '/' + clean;
|
|
126
|
+
return clean;
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
src = src.replace(
|
|
120
130
|
/(?<==\s*)(["'])((?:(?:\.{1,2}\/)|\/)?ext\/[^"']*\/)\1/g,
|
|
121
131
|
(/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
|
|
122
|
-
const
|
|
123
|
-
return `new URL(${quote}${
|
|
132
|
+
const resolved = makeAbsolute(path);
|
|
133
|
+
return `new URL(${quote}${resolved}${quote}, self.location?.href || ${quote}${quote}).href`;
|
|
124
134
|
}
|
|
125
135
|
);
|
|
126
136
|
|
|
127
137
|
src = src.replace(
|
|
128
138
|
/new\s+URL\s*\(\s*(["'`])((?:(?:\.{1,2}\/)|\/)?ext\/[^"'`]+)\1\s*\)/g,
|
|
129
139
|
(/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
|
|
130
|
-
const
|
|
131
|
-
return `new URL(${quote}${
|
|
140
|
+
const resolved = makeAbsolute(path);
|
|
141
|
+
return `new URL(${quote}${resolved}${quote}, self.location?.href)`;
|
|
132
142
|
}
|
|
133
143
|
);
|
|
134
144
|
|
|
145
|
+
// Make remaining ext/ string literals absolute (e.g. font paths passed as
|
|
146
|
+
// function arguments like addVariant("normal","normal","ext/fonts/...")).
|
|
147
|
+
// The regexes above already consumed paths inside new URL() calls and
|
|
148
|
+
// assignment-based patterns, so this only catches leftover bare strings.
|
|
149
|
+
if (base) {
|
|
150
|
+
src = src.replace(
|
|
151
|
+
/(["'`])((?:\.{1,2}\/)?ext\/[^"'`]+)\1/g,
|
|
152
|
+
(/** @type {string} */ _match, /** @type {string} */ quote, /** @type {string} */ path) => {
|
|
153
|
+
const resolved = makeAbsolute(path);
|
|
154
|
+
return `${quote}${resolved}${quote}`;
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
135
159
|
return src;
|
|
136
160
|
}
|
|
137
161
|
|
package/plugins/vite/reload.js
CHANGED
|
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, statSync } from 'fs';
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { needleLog } from './logging.js';
|
|
9
9
|
|
|
10
|
+
const pluginName = "needle-reload";
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
|
|
@@ -39,7 +40,7 @@ export function needleReload(_command, config, userSettings) {
|
|
|
39
40
|
const buildDirectory = projectConfig?.buildDirectory?.length ? process.cwd().replaceAll("\\", "/") + "/" + projectConfig?.buildDirectory : "";
|
|
40
41
|
if (buildDirectory?.length) {
|
|
41
42
|
const relativeBuildDirectory = path.relative(process.cwd(), buildDirectory).replaceAll("\\", "/") || ".";
|
|
42
|
-
setTimeout(() => needleLog(
|
|
43
|
+
setTimeout(() => needleLog(pluginName, "Build directory: " + relativeBuildDirectory), 100);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// These ignore patterns will be injected into user config to better control vite reloading
|
|
@@ -59,7 +60,7 @@ export function needleReload(_command, config, userSettings) {
|
|
|
59
60
|
// @ts-ignore - watch.ignored is guaranteed to be string[] by the guards above
|
|
60
61
|
config.server.watch.ignored.push(pattern);
|
|
61
62
|
if (userSettings?.debug === true)
|
|
62
|
-
setTimeout(() => needleLog(
|
|
63
|
+
setTimeout(() => needleLog(pluginName, "Updated server ignore patterns: " + JSON.stringify(config.server?.watch?.ignored)), 100);
|
|
63
64
|
},
|
|
64
65
|
/** @param {import('vite').HmrContext & {buildDirectory?: string}} args */
|
|
65
66
|
handleHotUpdate(args) {
|
|
@@ -118,7 +119,7 @@ function getHot(server) {
|
|
|
118
119
|
|
|
119
120
|
/** @param {import('vite').ViteDevServer} server @param {string} [file] */
|
|
120
121
|
function notifyClientWillReload(server, file) {
|
|
121
|
-
|
|
122
|
+
needleLog(pluginName, "Send reload notification");
|
|
122
123
|
getHot(server).send('needle:reload', { type: 'will-reload', file: file });
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -134,7 +135,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
|
|
|
134
135
|
|
|
135
136
|
// Dont reload the whole server when a file that is using hot reload changes
|
|
136
137
|
if (filesUsingHotReload.has(file)) {
|
|
137
|
-
|
|
138
|
+
needleLog(pluginName, "File is using hot reload: " + file);
|
|
138
139
|
return null;
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -153,7 +154,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
|
|
|
153
154
|
// instead of relying on the vite server watch ignore array
|
|
154
155
|
// we could here also match paths that we know we dont want to track
|
|
155
156
|
if (ignorePatterns.length > 0 && ignoreRegex.test(file)) {
|
|
156
|
-
|
|
157
|
+
needleLog(pluginName, "Ignore change in file: " + getFileNameLog(file));
|
|
157
158
|
return [];
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -163,7 +164,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
|
|
|
163
164
|
if (buildDirectory?.length) {
|
|
164
165
|
const dir = path.dirname(file).replaceAll("\\", "/");
|
|
165
166
|
if (dir.startsWith(buildDirectory)) {
|
|
166
|
-
|
|
167
|
+
needleLog(pluginName, "Ignore change in build directory: " + getFileNameLog(file));
|
|
167
168
|
return [];
|
|
168
169
|
}
|
|
169
170
|
}
|
|
@@ -178,13 +179,13 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
|
|
|
178
179
|
return [];
|
|
179
180
|
}
|
|
180
181
|
if (await testIfFileContentChanged(file, read) === false) {
|
|
181
|
-
|
|
182
|
+
needleLog(pluginName, "File content didnt change: " + getFileNameLog(file));
|
|
182
183
|
return [];
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
// these are known file types we export from integrations
|
|
187
|
-
const knownExportFileTypes = [
|
|
188
|
+
const knownExportFileTypes = [".glb", ".gltf", ".bin", "exr", ".ktx2", ".mp3", ".ogg", ".mp4", ".webm"];
|
|
188
189
|
if (!knownExportFileTypes.some((type) => file.endsWith(type)))
|
|
189
190
|
return null;
|
|
190
191
|
|
|
@@ -208,7 +209,7 @@ async function handleReload({ file, server, modules: _modules, read, buildDirect
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
needleLog(pluginName, "> Detected file change: " + getFileNameLog(file) + " (" + ((fileSize / (1024 * 1024)).toFixed(1)) + " MB)");
|
|
212
213
|
notifyClientWillReload(server);
|
|
213
214
|
scheduleReload(server);
|
|
214
215
|
return [];
|
|
@@ -223,8 +224,8 @@ async function scheduleReload(server, level = 0) {
|
|
|
223
224
|
const lockFile = path.join(process.cwd(), lockFileName);
|
|
224
225
|
if (existsSync(lockFile)) {
|
|
225
226
|
if (level === 0)
|
|
226
|
-
|
|
227
|
-
setTimeout(() => scheduleReload(server, level += 1), 300);
|
|
227
|
+
needleLog(pluginName, "Lock file exists, waiting for export to finish...");
|
|
228
|
+
setTimeout(() => scheduleReload(server, level += 1), 300).unref();
|
|
228
229
|
return;
|
|
229
230
|
}
|
|
230
231
|
|
|
@@ -234,13 +235,13 @@ async function scheduleReload(server, level = 0) {
|
|
|
234
235
|
if (timeDiff < 1000) {
|
|
235
236
|
// Sometimes file changes happen immediately after triggering a reload
|
|
236
237
|
// we dont want to reload again in that case
|
|
237
|
-
|
|
238
|
+
needleLog(pluginName, "Ignoring reload, last reload was too recent" + timeDiff + "ms ago");
|
|
238
239
|
return;
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
lastReloadTime = Date.now();
|
|
242
243
|
const readableTime = new Date(lastReloadTime).toLocaleTimeString();
|
|
243
|
-
|
|
244
|
+
needleLog(pluginName, "< Reloading... " + readableTime);
|
|
244
245
|
getHot(server).send({
|
|
245
246
|
type: 'full-reload',
|
|
246
247
|
path: '*'
|
|
@@ -299,10 +300,10 @@ function removeVersionQueryArgument(content) {
|
|
|
299
300
|
function insertScriptRegisterHotReloadCode(src, filePath) {
|
|
300
301
|
|
|
301
302
|
// We only want to inject the hot reload code in the needle-engine root file
|
|
302
|
-
if(!filePath.includes("/src/needle-engine.ts")) {
|
|
303
|
+
if (!filePath.includes("/src/needle-engine.ts")) {
|
|
303
304
|
return src;
|
|
304
305
|
}
|
|
305
|
-
|
|
306
|
+
needleLog(pluginName, "[Needle HMR] Hot reload is enabled");
|
|
306
307
|
// this code let's the engine know that we are in hot reload mode
|
|
307
308
|
const code = `
|
|
308
309
|
globalThis.NEEDLE_HOT_RELOAD_ENABLED = true;
|
|
@@ -338,12 +339,12 @@ function insertScriptHotReloadCode(src, filePath) {
|
|
|
338
339
|
return undefined;
|
|
339
340
|
// make import path from engine package
|
|
340
341
|
const fullPathToHotReload = process.cwd() + "/node_modules/@needle-tools/engine/src/engine/engine_hot_reload.ts";
|
|
341
|
-
//
|
|
342
|
+
// needleLog(pluginName, fullPathToHotReload);
|
|
342
343
|
const fileDirectory = path.dirname(filePath);
|
|
343
|
-
//
|
|
344
|
+
// needleLog(pluginName, "DIR", fileDirectory)
|
|
344
345
|
const relativePath = path.relative(fileDirectory, fullPathToHotReload);
|
|
345
346
|
importPath = relativePath.replace(/\\/g, "/");
|
|
346
|
-
//
|
|
347
|
+
// needleLog(pluginName, "importPath: ", importPath);
|
|
347
348
|
}
|
|
348
349
|
|
|
349
350
|
// console.log(importPath, ">", filePath);
|
package/plugins/vite/server.js
CHANGED
|
@@ -60,7 +60,8 @@ export function needleServer(command, config, userSettings) {
|
|
|
60
60
|
})
|
|
61
61
|
.catch((err) => console.error("ERR: [needle:server] 'open' package not found - please make sure to install 'open' in your package.json\n", err));
|
|
62
62
|
}
|
|
63
|
-
}, 100)
|
|
63
|
+
}, 100);
|
|
64
|
+
i.unref();
|
|
64
65
|
}
|
|
65
66
|
},
|
|
66
67
|
}
|
package/src/engine/api.ts
CHANGED
|
@@ -114,6 +114,9 @@ export * from "./engine_addressables.js";
|
|
|
114
114
|
/** Animation playback and control utilities */
|
|
115
115
|
export { AnimationUtils } from "./engine_animation.js";
|
|
116
116
|
|
|
117
|
+
/** Standalone audio clip for playback control */
|
|
118
|
+
export { AudioClip } from "./engine_audio.js";
|
|
119
|
+
|
|
117
120
|
/** Application-level state and utilities */
|
|
118
121
|
export { Application } from "./engine_application.js";
|
|
119
122
|
|
|
@@ -173,6 +176,10 @@ export * from "./engine_context_registry.js";
|
|
|
173
176
|
*/
|
|
174
177
|
export * from "./engine_coroutine.js"
|
|
175
178
|
|
|
179
|
+
/** DisposableStore for managing lifecycle-bound cleanup in components */
|
|
180
|
+
export { DisposableStore, on, isDisposable } from "./engine_disposable.js";
|
|
181
|
+
export type { DisposeFn, IDisposable } from "./engine_disposable.js";
|
|
182
|
+
|
|
176
183
|
/** Factory functions for creating primitives and objects */
|
|
177
184
|
export * from "./engine_create_objects.js";
|
|
178
185
|
|
|
@@ -7,11 +7,6 @@ import { Animation } from "../../engine-components/Animation.js";
|
|
|
7
7
|
import { Animator } from "../../engine-components/Animator.js";
|
|
8
8
|
import { AudioListener } from "../../engine-components/AudioListener.js";
|
|
9
9
|
import { AudioSource } from "../../engine-components/AudioSource.js";
|
|
10
|
-
import { Avatar_Brain_LookAt } from "../../engine-components/avatar/Avatar_Brain_LookAt.js";
|
|
11
|
-
import { Avatar_MouthShapes } from "../../engine-components/avatar/Avatar_MouthShapes.js";
|
|
12
|
-
import { Avatar_MustacheShake } from "../../engine-components/avatar/Avatar_MustacheShake.js";
|
|
13
|
-
import { AvatarBlink_Simple } from "../../engine-components/avatar/AvatarBlink_Simple.js";
|
|
14
|
-
import { AvatarEyeLook_Rotation } from "../../engine-components/avatar/AvatarEyeLook_Rotation.js";
|
|
15
10
|
import { AxesHelper } from "../../engine-components/AxesHelper.js";
|
|
16
11
|
import { BasicIKConstraint } from "../../engine-components/BasicIKConstraint.js";
|
|
17
12
|
import { BoxHelperComponent } from "../../engine-components/BoxHelperComponent.js";
|
|
@@ -110,6 +105,7 @@ import { AnimationTrackHandler } from "../../engine-components/timeline/Timeline
|
|
|
110
105
|
import { AudioTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
111
106
|
import { MarkerTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
112
107
|
import { SignalTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
108
|
+
import { ActivationTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
113
109
|
import { ControlTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
|
|
114
110
|
import { TransformGizmo } from "../../engine-components/TransformGizmo.js";
|
|
115
111
|
import { BaseUIComponent } from "../../engine-components/ui/BaseUIComponent.js";
|
|
@@ -167,11 +163,6 @@ export function initBuiltinTypes() {
|
|
|
167
163
|
TypeStore.add("Animator", Animator);
|
|
168
164
|
TypeStore.add("AudioListener", AudioListener);
|
|
169
165
|
TypeStore.add("AudioSource", AudioSource);
|
|
170
|
-
TypeStore.add("Avatar_Brain_LookAt", Avatar_Brain_LookAt);
|
|
171
|
-
TypeStore.add("Avatar_MouthShapes", Avatar_MouthShapes);
|
|
172
|
-
TypeStore.add("Avatar_MustacheShake", Avatar_MustacheShake);
|
|
173
|
-
TypeStore.add("AvatarBlink_Simple", AvatarBlink_Simple);
|
|
174
|
-
TypeStore.add("AvatarEyeLook_Rotation", AvatarEyeLook_Rotation);
|
|
175
166
|
TypeStore.add("AxesHelper", AxesHelper);
|
|
176
167
|
TypeStore.add("BasicIKConstraint", BasicIKConstraint);
|
|
177
168
|
TypeStore.add("BoxHelperComponent", BoxHelperComponent);
|
|
@@ -270,6 +261,7 @@ export function initBuiltinTypes() {
|
|
|
270
261
|
TypeStore.add("AudioTrackHandler", AudioTrackHandler);
|
|
271
262
|
TypeStore.add("MarkerTrackHandler", MarkerTrackHandler);
|
|
272
263
|
TypeStore.add("SignalTrackHandler", SignalTrackHandler);
|
|
264
|
+
TypeStore.add("ActivationTrackHandler", ActivationTrackHandler);
|
|
273
265
|
TypeStore.add("ControlTrackHandler", ControlTrackHandler);
|
|
274
266
|
TypeStore.add("TransformGizmo", TransformGizmo);
|
|
275
267
|
TypeStore.add("BaseUIComponent", BaseUIComponent);
|
|
@@ -12,13 +12,16 @@ import { onError } from "./debug_overlay.js";
|
|
|
12
12
|
|
|
13
13
|
let _isActive = false;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
/** @internal */
|
|
16
|
+
export function initSpatialConsole() {
|
|
17
|
+
// enable the spatial console if we receive an error while in dev session and in XR
|
|
18
|
+
onError((...args: any[]) => {
|
|
19
|
+
if (isDevEnvironment() && ContextRegistry.Current?.isInXR) {
|
|
20
|
+
enableSpatialConsole(true);
|
|
21
|
+
onLog("error", ...args);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
/** Enable a spatial debug console that follows the camera */
|
|
@@ -567,7 +567,6 @@ class AddressableSerializer extends TypeSerializer {
|
|
|
567
567
|
}
|
|
568
568
|
|
|
569
569
|
}
|
|
570
|
-
new AddressableSerializer();
|
|
571
570
|
|
|
572
571
|
|
|
573
572
|
|
|
@@ -703,7 +702,6 @@ export class ImageReferenceSerializer extends TypeSerializer {
|
|
|
703
702
|
return undefined;
|
|
704
703
|
}
|
|
705
704
|
}
|
|
706
|
-
new ImageReferenceSerializer();
|
|
707
705
|
|
|
708
706
|
|
|
709
707
|
|
|
@@ -772,4 +770,9 @@ export class FileReferenceSerializer extends TypeSerializer {
|
|
|
772
770
|
return undefined;
|
|
773
771
|
}
|
|
774
772
|
}
|
|
775
|
-
|
|
773
|
+
/** @internal */
|
|
774
|
+
export function initAddressableSerializers() {
|
|
775
|
+
new AddressableSerializer();
|
|
776
|
+
new ImageReferenceSerializer();
|
|
777
|
+
new FileReferenceSerializer();
|
|
778
|
+
}
|
|
@@ -21,4 +21,188 @@ export function ensureAudioContextIsResumed() {
|
|
|
21
21
|
}, 500);
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Represents an audio clip that can be loaded and played independently.
|
|
29
|
+
* The AudioClip class encapsulates the URL of the audio resource and provides
|
|
30
|
+
* methods for playback control (play, pause, stop) and querying duration.
|
|
31
|
+
*/
|
|
32
|
+
export class AudioClip {
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new AudioClip instance with the specified URL.
|
|
36
|
+
* @param url The URL of the audio resource to load. This can be a path to an audio file or a MediaStream URL.
|
|
37
|
+
*/
|
|
38
|
+
constructor(public readonly url: string) {
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Whether the clip is currently playing.
|
|
42
|
+
* @returns `true` if the clip is actively playing audio.
|
|
43
|
+
*/
|
|
44
|
+
get isPlaying(): boolean {
|
|
45
|
+
return this._audioElement !== undefined
|
|
46
|
+
&& !this._audioElement.paused
|
|
47
|
+
&& !this._audioElement.ended;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The total duration of the audio clip in seconds.
|
|
52
|
+
* Loads the audio metadata if not already available.
|
|
53
|
+
* @returns A promise that resolves with the duration in seconds.
|
|
54
|
+
*/
|
|
55
|
+
getDuration(): Promise<number> {
|
|
56
|
+
if (this._duration !== undefined) {
|
|
57
|
+
return Promise.resolve(this._duration);
|
|
58
|
+
}
|
|
59
|
+
return this.ensureAudioElement().then(audio => {
|
|
60
|
+
this._duration = audio.duration;
|
|
61
|
+
return audio.duration;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Plays the audio clip from the current position.
|
|
67
|
+
* @returns A promise that resolves when playback finishes, or rejects on error.
|
|
68
|
+
* If the clip is looping, the promise will never resolve on its own – call {@link stop} or {@link pause} to end playback.
|
|
69
|
+
*/
|
|
70
|
+
// #region Play
|
|
71
|
+
play(): Promise<void> {
|
|
72
|
+
return this.ensureAudioElement().then(audio => {
|
|
73
|
+
return new Promise<void>((resolve, reject) => {
|
|
74
|
+
const onEnded = () => {
|
|
75
|
+
cleanup();
|
|
76
|
+
resolve();
|
|
77
|
+
};
|
|
78
|
+
const onError = () => {
|
|
79
|
+
cleanup();
|
|
80
|
+
reject(new Error(`Playback error for ${this.url}`));
|
|
81
|
+
};
|
|
82
|
+
const onPause = () => {
|
|
83
|
+
// pause/stop also resolve the promise
|
|
84
|
+
cleanup();
|
|
85
|
+
resolve();
|
|
86
|
+
};
|
|
87
|
+
const cleanup = () => {
|
|
88
|
+
audio.removeEventListener("ended", onEnded);
|
|
89
|
+
audio.removeEventListener("error", onError);
|
|
90
|
+
audio.removeEventListener("pause", onPause);
|
|
91
|
+
};
|
|
92
|
+
audio.addEventListener("ended", onEnded);
|
|
93
|
+
audio.addEventListener("error", onError);
|
|
94
|
+
audio.addEventListener("pause", onPause);
|
|
95
|
+
audio.play().catch(err => {
|
|
96
|
+
cleanup();
|
|
97
|
+
reject(err);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Pauses playback at the current position.
|
|
105
|
+
* Call {@link play} to resume.
|
|
106
|
+
*/
|
|
107
|
+
// #region Pause/Stop
|
|
108
|
+
pause(): void {
|
|
109
|
+
this._audioElement?.pause();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Stops playback and resets the position to the beginning.
|
|
114
|
+
*/
|
|
115
|
+
stop(): void {
|
|
116
|
+
if (this._audioElement) {
|
|
117
|
+
this._audioElement.pause();
|
|
118
|
+
this._audioElement.currentTime = 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Whether the clip should loop when reaching the end. */
|
|
123
|
+
get loop(): boolean { return this._loop; }
|
|
124
|
+
set loop(value: boolean) {
|
|
125
|
+
this._loop = value;
|
|
126
|
+
if (this._audioElement) this._audioElement.loop = value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Playback volume from 0 (silent) to 1 (full). */
|
|
130
|
+
get volume(): number { return this._volume; }
|
|
131
|
+
set volume(value: number) {
|
|
132
|
+
this._volume = value;
|
|
133
|
+
if (this._audioElement) this._audioElement.volume = value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Current playback position in seconds. */
|
|
137
|
+
get currentTime(): number { return this._audioElement?.currentTime ?? 0; }
|
|
138
|
+
set currentTime(value: number) {
|
|
139
|
+
if (this._audioElement) this._audioElement.currentTime = value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Normalized playback progress from 0 to 1.
|
|
143
|
+
* @returns The current playback position as a value between 0 and 1, or 0 if the duration is unknown.
|
|
144
|
+
*/
|
|
145
|
+
get progress(): number {
|
|
146
|
+
if (!this._audioElement || !this._duration) return 0;
|
|
147
|
+
return this._audioElement.currentTime / this._duration;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Seeks to a normalized position (0–1) in the clip.
|
|
152
|
+
* @param position A value between 0 (start) and 1 (end).
|
|
153
|
+
*/
|
|
154
|
+
// #region Seek
|
|
155
|
+
seek(position: number): void {
|
|
156
|
+
if (this._audioElement && this._duration) {
|
|
157
|
+
this._audioElement.currentTime = Math.max(0, Math.min(1, position)) * this._duration;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The underlying HTMLAudioElement, or `undefined` if not yet created.
|
|
162
|
+
* Use this to connect the element to the Web Audio API via `createMediaElementSource()`.
|
|
163
|
+
* @returns The HTMLAudioElement if the clip has been loaded or played, otherwise `undefined`.
|
|
164
|
+
*/
|
|
165
|
+
get audioElement(): HTMLAudioElement | undefined { return this._audioElement; }
|
|
166
|
+
|
|
167
|
+
private _audioElement?: HTMLAudioElement;
|
|
168
|
+
private _duration?: number;
|
|
169
|
+
private _loadPromise?: Promise<HTMLAudioElement>;
|
|
170
|
+
private _loop: boolean = false;
|
|
171
|
+
private _volume: number = 1;
|
|
172
|
+
|
|
173
|
+
/** Lazily creates and loads the shared HTMLAudioElement. */
|
|
174
|
+
private ensureAudioElement(): Promise<HTMLAudioElement> {
|
|
175
|
+
if (this._audioElement && this._loadPromise) {
|
|
176
|
+
return this._loadPromise;
|
|
177
|
+
}
|
|
178
|
+
const audio = this._audioElement ?? new Audio(this.url);
|
|
179
|
+
this._audioElement = audio;
|
|
180
|
+
audio.loop = this._loop;
|
|
181
|
+
audio.volume = this._volume;
|
|
182
|
+
|
|
183
|
+
if (audio.readyState >= HTMLMediaElement.HAVE_METADATA) {
|
|
184
|
+
this._duration = audio.duration;
|
|
185
|
+
this._loadPromise = Promise.resolve(audio);
|
|
186
|
+
return this._loadPromise;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this._loadPromise = new Promise<HTMLAudioElement>((resolve, reject) => {
|
|
190
|
+
const onLoaded = () => {
|
|
191
|
+
cleanup();
|
|
192
|
+
this._duration = audio.duration;
|
|
193
|
+
resolve(audio);
|
|
194
|
+
};
|
|
195
|
+
const onError = (e: Event) => {
|
|
196
|
+
cleanup();
|
|
197
|
+
reject(new Error(`Failed to load audio clip from ${this.url}: ${e}`));
|
|
198
|
+
};
|
|
199
|
+
const cleanup = () => {
|
|
200
|
+
audio.removeEventListener("loadedmetadata", onLoaded);
|
|
201
|
+
audio.removeEventListener("error", onError);
|
|
202
|
+
};
|
|
203
|
+
audio.addEventListener("loadedmetadata", onLoaded);
|
|
204
|
+
audio.addEventListener("error", onError);
|
|
205
|
+
});
|
|
206
|
+
return this._loadPromise;
|
|
207
|
+
}
|
|
24
208
|
}
|
|
@@ -8,9 +8,9 @@ import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
|
|
|
8
8
|
import { Context, registerComponent } from "./engine_setup.js";
|
|
9
9
|
import type { ComponentInit, Constructor, ConstructorConcrete, IComponent, IGameObject } from "./engine_types.js";
|
|
10
10
|
import { $componentName } from "./engine_types.js";
|
|
11
|
+
import { TypeStore } from "./engine_typestore.js";
|
|
11
12
|
import { getParam } from "./engine_utils.js";
|
|
12
13
|
import { apply } from "./js-extensions/index.js";
|
|
13
|
-
import { TypeStore } from "./engine_typestore.js";
|
|
14
14
|
|
|
15
15
|
const COMPONENT_GUID_NAMESPACE = 'eff8ba80-635d-11ec-90d6-0242ac120003';
|
|
16
16
|
|