@needle-tools/engine 5.1.0-canary.deec6e4 → 5.1.0-canary.e6680fa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/CHANGELOG.md +99 -1
  2. package/SKILL.md +4 -1
  3. package/components.needle.json +1 -1
  4. package/dist/needle-engine.bundle-Bl_hyH5G.umd.cjs +1734 -0
  5. package/dist/needle-engine.bundle-Cduc1gj6.min.js +1734 -0
  6. package/dist/{needle-engine.bundle-CvtELXh0.js → needle-engine.bundle-DNcqT8nJ.js} +19415 -18452
  7. package/dist/needle-engine.d.ts +1588 -374
  8. package/dist/needle-engine.js +572 -569
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/dist/three.js +1 -0
  12. package/dist/three.min.js +21 -21
  13. package/dist/three.umd.cjs +16 -16
  14. package/lib/engine/api.d.ts +8 -1
  15. package/lib/engine/api.js +7 -1
  16. package/lib/engine/api.js.map +1 -1
  17. package/lib/engine/codegen/register_types.js +10 -18
  18. package/lib/engine/codegen/register_types.js.map +1 -1
  19. package/lib/engine/engine_audio.d.ts +68 -0
  20. package/lib/engine/engine_audio.js +172 -0
  21. package/lib/engine/engine_audio.js.map +1 -1
  22. package/lib/engine/engine_camera.fit.js +16 -4
  23. package/lib/engine/engine_camera.fit.js.map +1 -1
  24. package/lib/engine/engine_components.js +1 -1
  25. package/lib/engine/engine_components.js.map +1 -1
  26. package/lib/engine/engine_context.d.ts +21 -8
  27. package/lib/engine/engine_context.js +46 -16
  28. package/lib/engine/engine_context.js.map +1 -1
  29. package/lib/engine/engine_context_eventbus.d.ts +47 -0
  30. package/lib/engine/engine_context_eventbus.js +47 -0
  31. package/lib/engine/engine_context_eventbus.js.map +1 -0
  32. package/lib/engine/engine_disposable.d.ts +172 -0
  33. package/lib/engine/engine_disposable.js +136 -0
  34. package/lib/engine/engine_disposable.js.map +1 -0
  35. package/lib/engine/engine_gameobject.d.ts +1 -10
  36. package/lib/engine/engine_gameobject.js +22 -120
  37. package/lib/engine/engine_gameobject.js.map +1 -1
  38. package/lib/engine/engine_gltf_builtin_components.js +7 -69
  39. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  40. package/lib/engine/engine_init.js +7 -7
  41. package/lib/engine/engine_init.js.map +1 -1
  42. package/lib/engine/engine_input.d.ts +24 -5
  43. package/lib/engine/engine_input.js +3 -2
  44. package/lib/engine/engine_input.js.map +1 -1
  45. package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
  46. package/lib/engine/engine_instantiate_resolve.js +372 -0
  47. package/lib/engine/engine_instantiate_resolve.js.map +1 -0
  48. package/lib/engine/engine_license.d.ts +7 -7
  49. package/lib/engine/engine_license.js +186 -58
  50. package/lib/engine/engine_license.js.map +1 -1
  51. package/lib/engine/engine_mainloop_utils.js +7 -4
  52. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  53. package/lib/engine/engine_networking.d.ts +51 -37
  54. package/lib/engine/engine_networking.js +132 -82
  55. package/lib/engine/engine_networking.js.map +1 -1
  56. package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
  57. package/lib/engine/engine_networking.transport.websocket.js +38 -0
  58. package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
  59. package/lib/engine/engine_networking_blob.js +4 -4
  60. package/lib/engine/engine_networking_blob.js.map +1 -1
  61. package/lib/engine/engine_networking_instantiate.js +2 -2
  62. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  63. package/lib/engine/engine_networking_types.d.ts +39 -1
  64. package/lib/engine/engine_networking_types.js +7 -0
  65. package/lib/engine/engine_networking_types.js.map +1 -1
  66. package/lib/engine/engine_physics_rapier.d.ts +21 -3
  67. package/lib/engine/engine_physics_rapier.js +94 -25
  68. package/lib/engine/engine_physics_rapier.js.map +1 -1
  69. package/lib/engine/engine_pmrem.js +51 -3
  70. package/lib/engine/engine_pmrem.js.map +1 -1
  71. package/lib/engine/engine_scenedata.js +2 -2
  72. package/lib/engine/engine_scenedata.js.map +1 -1
  73. package/lib/engine/engine_serialization_builtin_serializer.js +28 -5
  74. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  75. package/lib/engine/engine_serialization_core.d.ts +1 -0
  76. package/lib/engine/engine_serialization_core.js +7 -0
  77. package/lib/engine/engine_serialization_core.js.map +1 -1
  78. package/lib/engine/engine_types.d.ts +29 -11
  79. package/lib/engine/engine_types.js +1 -1
  80. package/lib/engine/engine_types.js.map +1 -1
  81. package/lib/engine/engine_util_decorator.js +7 -2
  82. package/lib/engine/engine_util_decorator.js.map +1 -1
  83. package/lib/engine/engine_utils.d.ts +1 -1
  84. package/lib/engine/engine_utils.js +19 -5
  85. package/lib/engine/engine_utils.js.map +1 -1
  86. package/lib/engine/engine_utils_format.js +20 -14
  87. package/lib/engine/engine_utils_format.js.map +1 -1
  88. package/lib/engine/engine_utils_qrcode.js +2 -2
  89. package/lib/engine/engine_utils_qrcode.js.map +1 -1
  90. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  91. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  92. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  93. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  94. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +1 -1
  95. package/lib/engine/webcomponents/needle menu/needle-menu.js +6 -6
  96. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  97. package/lib/engine/webcomponents/needle-engine.d.ts +10 -4
  98. package/lib/engine/webcomponents/needle-engine.js +3 -3
  99. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  100. package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
  101. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  102. package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
  103. package/lib/engine/xr/NeedleXRSession.js +50 -14
  104. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  105. package/lib/engine/xr/TempXRContext.js +2 -2
  106. package/lib/engine/xr/TempXRContext.js.map +1 -1
  107. package/lib/engine/xr/events.d.ts +1 -1
  108. package/lib/engine/xr/events.js.map +1 -1
  109. package/lib/engine-components/Animation.js +17 -16
  110. package/lib/engine-components/Animation.js.map +1 -1
  111. package/lib/engine-components/AnimationBuilder.d.ts +158 -0
  112. package/lib/engine-components/AnimationBuilder.js +305 -0
  113. package/lib/engine-components/AnimationBuilder.js.map +1 -0
  114. package/lib/engine-components/Animator.d.ts +6 -0
  115. package/lib/engine-components/Animator.js +23 -13
  116. package/lib/engine-components/Animator.js.map +1 -1
  117. package/lib/engine-components/AnimatorController.builder.d.ts +191 -0
  118. package/lib/engine-components/AnimatorController.builder.js +263 -0
  119. package/lib/engine-components/AnimatorController.builder.js.map +1 -0
  120. package/lib/engine-components/AnimatorController.d.ts +2 -119
  121. package/lib/engine-components/AnimatorController.js +33 -232
  122. package/lib/engine-components/AnimatorController.js.map +1 -1
  123. package/lib/engine-components/AudioSource.d.ts +19 -3
  124. package/lib/engine-components/AudioSource.js +121 -68
  125. package/lib/engine-components/AudioSource.js.map +1 -1
  126. package/lib/engine-components/Camera.d.ts +6 -1
  127. package/lib/engine-components/Camera.js +16 -3
  128. package/lib/engine-components/Camera.js.map +1 -1
  129. package/lib/engine-components/CameraUtils.js +14 -6
  130. package/lib/engine-components/CameraUtils.js.map +1 -1
  131. package/lib/engine-components/Collider.d.ts +18 -9
  132. package/lib/engine-components/Collider.js +61 -14
  133. package/lib/engine-components/Collider.js.map +1 -1
  134. package/lib/engine-components/Component.d.ts +72 -9
  135. package/lib/engine-components/Component.js +114 -10
  136. package/lib/engine-components/Component.js.map +1 -1
  137. package/lib/engine-components/ContactShadows.d.ts +1 -0
  138. package/lib/engine-components/ContactShadows.js +14 -1
  139. package/lib/engine-components/ContactShadows.js.map +1 -1
  140. package/lib/engine-components/DragControls.d.ts +7 -0
  141. package/lib/engine-components/DragControls.js +19 -7
  142. package/lib/engine-components/DragControls.js.map +1 -1
  143. package/lib/engine-components/DropListener.js +4 -0
  144. package/lib/engine-components/DropListener.js.map +1 -1
  145. package/lib/engine-components/EventList.d.ts +31 -9
  146. package/lib/engine-components/EventList.js +37 -76
  147. package/lib/engine-components/EventList.js.map +1 -1
  148. package/lib/engine-components/Joints.d.ts +4 -2
  149. package/lib/engine-components/Joints.js +19 -3
  150. package/lib/engine-components/Joints.js.map +1 -1
  151. package/lib/engine-components/Light.js +9 -1
  152. package/lib/engine-components/Light.js.map +1 -1
  153. package/lib/engine-components/Networking.d.ts +1 -1
  154. package/lib/engine-components/Networking.js +1 -1
  155. package/lib/engine-components/OrbitControls.d.ts +1 -2
  156. package/lib/engine-components/OrbitControls.js +37 -14
  157. package/lib/engine-components/OrbitControls.js.map +1 -1
  158. package/lib/engine-components/RigidBody.d.ts +12 -4
  159. package/lib/engine-components/RigidBody.js +18 -4
  160. package/lib/engine-components/RigidBody.js.map +1 -1
  161. package/lib/engine-components/SceneSwitcher.js +3 -0
  162. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  163. package/lib/engine-components/SeeThrough.js +2 -2
  164. package/lib/engine-components/SeeThrough.js.map +1 -1
  165. package/lib/engine-components/VideoPlayer.d.ts +8 -2
  166. package/lib/engine-components/VideoPlayer.js +42 -19
  167. package/lib/engine-components/VideoPlayer.js.map +1 -1
  168. package/lib/engine-components/Voip.d.ts +16 -7
  169. package/lib/engine-components/Voip.js +90 -53
  170. package/lib/engine-components/Voip.js.map +1 -1
  171. package/lib/engine-components/api.d.ts +3 -1
  172. package/lib/engine-components/api.js +3 -1
  173. package/lib/engine-components/api.js.map +1 -1
  174. package/lib/engine-components/codegen/components.d.ts +7 -13
  175. package/lib/engine-components/codegen/components.js +7 -13
  176. package/lib/engine-components/codegen/components.js.map +1 -1
  177. package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
  178. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  179. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  180. package/lib/engine-components/timeline/PlayableDirector.d.ts +21 -11
  181. package/lib/engine-components/timeline/PlayableDirector.js +75 -67
  182. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  183. package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
  184. package/lib/engine-components/timeline/SignalAsset.js +1 -0
  185. package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
  186. package/lib/engine-components/timeline/TimelineBuilder.d.ts +413 -0
  187. package/lib/engine-components/timeline/TimelineBuilder.js +506 -0
  188. package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
  189. package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
  190. package/lib/engine-components/timeline/TimelineModels.js +3 -0
  191. package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
  192. package/lib/engine-components/timeline/TimelineTracks.d.ts +37 -6
  193. package/lib/engine-components/timeline/TimelineTracks.js +92 -26
  194. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  195. package/lib/engine-components/timeline/index.d.ts +2 -1
  196. package/lib/engine-components/timeline/index.js +2 -0
  197. package/lib/engine-components/timeline/index.js.map +1 -1
  198. package/lib/engine-components/ui/Canvas.d.ts +1 -1
  199. package/lib/engine-components/ui/Canvas.js +2 -8
  200. package/lib/engine-components/ui/Canvas.js.map +1 -1
  201. package/lib/engine-components/ui/Text.d.ts +1 -0
  202. package/lib/engine-components/ui/Text.js +10 -7
  203. package/lib/engine-components/ui/Text.js.map +1 -1
  204. package/lib/engine-components/web/CursorFollow.d.ts +0 -1
  205. package/lib/engine-components/web/CursorFollow.js +21 -13
  206. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  207. package/lib/engine-components/webxr/WebXRImageTracking.d.ts +62 -1
  208. package/lib/engine-components/webxr/WebXRImageTracking.js +59 -2
  209. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  210. package/package.json +2 -83
  211. package/plugins/common/cloud.js +6 -1
  212. package/plugins/common/license.js +55 -12
  213. package/plugins/common/worker.js +9 -4
  214. package/plugins/types/userconfig.d.ts +4 -1
  215. package/plugins/vite/asap.js +17 -8
  216. package/plugins/vite/build-pipeline.js +57 -20
  217. package/plugins/vite/dependencies.js +29 -10
  218. package/plugins/vite/dependency-watcher.js +2 -2
  219. package/plugins/vite/editor-connection.js +3 -3
  220. package/plugins/vite/license.js +42 -7
  221. package/plugins/vite/local-files-core.js +3 -3
  222. package/plugins/vite/local-files-utils.d.ts +3 -1
  223. package/plugins/vite/local-files-utils.js +29 -5
  224. package/plugins/vite/reload.js +1 -1
  225. package/plugins/vite/server.js +2 -1
  226. package/src/engine/api.ts +11 -1
  227. package/src/engine/codegen/register_types.ts +10 -18
  228. package/src/engine/engine_audio.ts +184 -0
  229. package/src/engine/engine_camera.fit.ts +15 -4
  230. package/src/engine/engine_components.ts +1 -1
  231. package/src/engine/engine_context.ts +52 -19
  232. package/src/engine/engine_context_eventbus.ts +73 -0
  233. package/src/engine/engine_disposable.ts +214 -0
  234. package/src/engine/engine_gameobject.ts +54 -159
  235. package/src/engine/engine_gltf_builtin_components.ts +7 -76
  236. package/src/engine/engine_init.ts +7 -7
  237. package/src/engine/engine_input.ts +28 -7
  238. package/src/engine/engine_instantiate_resolve.ts +407 -0
  239. package/src/engine/engine_license.ts +202 -56
  240. package/src/engine/engine_mainloop_utils.ts +7 -4
  241. package/src/engine/engine_networking.transport.websocket.ts +45 -0
  242. package/src/engine/engine_networking.ts +161 -137
  243. package/src/engine/engine_networking_blob.ts +4 -4
  244. package/src/engine/engine_networking_instantiate.ts +2 -2
  245. package/src/engine/engine_networking_types.ts +41 -1
  246. package/src/engine/engine_physics_rapier.ts +102 -33
  247. package/src/engine/engine_pmrem.ts +53 -3
  248. package/src/engine/engine_scenedata.ts +3 -3
  249. package/src/engine/engine_serialization_builtin_serializer.ts +32 -9
  250. package/src/engine/engine_serialization_core.ts +9 -0
  251. package/src/engine/engine_types.ts +46 -27
  252. package/src/engine/engine_util_decorator.ts +7 -2
  253. package/src/engine/engine_utils.ts +16 -5
  254. package/src/engine/engine_utils_format.ts +20 -14
  255. package/src/engine/engine_utils_qrcode.ts +2 -2
  256. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  257. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  258. package/src/engine/webcomponents/needle menu/needle-menu.ts +6 -6
  259. package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
  260. package/src/engine/webcomponents/needle-engine.ts +12 -6
  261. package/src/engine/xr/NeedleXRSession.ts +48 -13
  262. package/src/engine/xr/TempXRContext.ts +2 -2
  263. package/src/engine/xr/events.ts +1 -1
  264. package/src/engine-components/Animation.ts +19 -16
  265. package/src/engine-components/AnimationBuilder.ts +472 -0
  266. package/src/engine-components/Animator.ts +24 -12
  267. package/src/engine-components/AnimatorController.builder.ts +387 -0
  268. package/src/engine-components/AnimatorController.ts +20 -291
  269. package/src/engine-components/AudioSource.ts +130 -79
  270. package/src/engine-components/Camera.ts +16 -3
  271. package/src/engine-components/CameraUtils.ts +12 -5
  272. package/src/engine-components/Collider.ts +66 -18
  273. package/src/engine-components/Component.ts +118 -20
  274. package/src/engine-components/ContactShadows.ts +15 -1
  275. package/src/engine-components/DragControls.ts +18 -11
  276. package/src/engine-components/DropListener.ts +4 -0
  277. package/src/engine-components/EventList.ts +45 -83
  278. package/src/engine-components/Joints.ts +20 -4
  279. package/src/engine-components/Light.ts +10 -2
  280. package/src/engine-components/Networking.ts +1 -1
  281. package/src/engine-components/OrbitControls.ts +42 -16
  282. package/src/engine-components/RigidBody.ts +18 -4
  283. package/src/engine-components/SceneSwitcher.ts +3 -0
  284. package/src/engine-components/SeeThrough.ts +2 -2
  285. package/src/engine-components/VideoPlayer.ts +40 -17
  286. package/src/engine-components/Voip.ts +88 -53
  287. package/src/engine-components/api.ts +3 -1
  288. package/src/engine-components/codegen/components.ts +7 -13
  289. package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
  290. package/src/engine-components/timeline/PlayableDirector.ts +83 -81
  291. package/src/engine-components/timeline/SignalAsset.ts +4 -1
  292. package/src/engine-components/timeline/TimelineBuilder.ts +824 -0
  293. package/src/engine-components/timeline/TimelineModels.ts +5 -1
  294. package/src/engine-components/timeline/TimelineTracks.ts +96 -27
  295. package/src/engine-components/timeline/index.ts +2 -1
  296. package/src/engine-components/ui/Canvas.ts +2 -8
  297. package/src/engine-components/ui/Text.ts +12 -8
  298. package/src/engine-components/web/CursorFollow.ts +21 -14
  299. package/src/engine-components/webxr/WebXRImageTracking.ts +79 -7
  300. package/dist/needle-engine.bundle-1s2gOoKZ.min.js +0 -1732
  301. package/dist/needle-engine.bundle-j4nGJXCs.umd.cjs +0 -1732
  302. package/lib/engine-components/AvatarLoader.d.ts +0 -80
  303. package/lib/engine-components/AvatarLoader.js +0 -232
  304. package/lib/engine-components/AvatarLoader.js.map +0 -1
  305. package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
  306. package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
  307. package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
  308. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
  309. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
  310. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
  311. package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
  312. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
  313. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
  314. package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
  315. package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
  316. package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
  317. package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
  318. package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
  319. package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
  320. package/src/engine-components/AvatarLoader.ts +0 -264
  321. package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
  322. package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
  323. package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
  324. package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
  325. package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
  326. package/src/vite-env.d.ts +0 -16
@@ -582,7 +582,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
582
582
  };
583
583
 
584
584
  private _onPointerUpLate = (evt: NEPointerEvent) => {
585
- if (this.doubleClickToFocus && evt.isDoubleClick && !evt.used) {
585
+ if (this.doubleClickToFocus && evt.isDoubleClick && !evt.used && this.canFocusAtPointer()) {
586
586
  this.setTargetFromRaycast();
587
587
  }
588
588
  // Automatically update the camera focus
@@ -663,7 +663,15 @@ export class OrbitControls extends Behaviour implements ICameraController {
663
663
  }
664
664
  this._controls.enabled = true;
665
665
 
666
- if (this.context.input.getPointerDown(1) || this.context.input.getPointerDown(2) || this.context.input.mouseWheelChanged || (this.context.input.getPointerPressed(0) && this.context.input.getPointerPositionDelta(0)?.length() || 0 > .1)) {
666
+ // Interrupt programmatic transitions on meaningful new user interaction:
667
+ // - Middle/right button down (always intentional camera control)
668
+ // - Mouse wheel (zoom intent)
669
+ // - Left button drag start: getPointerDown(0) with a position delta — a bare click
670
+ // without movement shouldn't cancel an animation, but starting to drag should.
671
+ // Using getPointerDown (not getPointerPressed) ensures we only interrupt once at
672
+ // drag onset, not continuously every frame during a drag.
673
+ const leftDragStart = this.context.input.getPointerDown(0) && (this.context.input.getPointerPositionDelta(0)?.length() || 0) > .1;
674
+ if (this.context.input.getPointerDown(1) || this.context.input.getPointerDown(2) || this.context.input.mouseWheelChanged || leftDragStart) {
667
675
  this._inputs += 1;
668
676
  }
669
677
  if (this._inputs > 0 && this.allowInterrupt) {
@@ -708,7 +716,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
708
716
  }
709
717
  }
710
718
 
711
- const focusAtPointer = (this.middleClickToFocus && this.context.input.getPointerClicked(1));
719
+ const focusAtPointer = (this.middleClickToFocus && this.context.input.getPointerClicked(1) && this.canFocusAtPointer());
712
720
  if (focusAtPointer) {
713
721
  this.setTargetFromRaycast();
714
722
  }
@@ -758,19 +766,26 @@ export class OrbitControls extends Behaviour implements ICameraController {
758
766
 
759
767
  if (this.targetBounds) {
760
768
  // #region target bounds
761
- const targetVector = this._controls.target;
762
769
  const boundsCenter = this.targetBounds.worldPosition;
763
770
  const boundsHalfSize = getTempVector(this.targetBounds.worldScale).multiplyScalar(0.5);
764
771
  const min = getTempVector(boundsCenter).sub(boundsHalfSize);
765
772
  const max = getTempVector(boundsCenter).add(boundsHalfSize);
766
- const newTarget = getTempVector(this._controls.target).clamp(min, max);
767
- const duration = .1;
768
- if (duration <= 0) targetVector.copy(newTarget);
769
- else targetVector.lerp(newTarget, this.context.time.deltaTime / duration);
773
+
770
774
  if (this._lookTargetLerpActive) {
771
- if (duration <= 0) this._lookTargetEndPosition.copy(newTarget);
772
- else this._lookTargetEndPosition.lerp(newTarget, this.context.time.deltaTime / (duration * 5));
775
+ // During a programmatic transition (fitCamera / setLookTargetPosition with immediate: false),
776
+ // only clamp the destination. The look-target lerp (above) handles moving _controls.target
777
+ // towards the endpoint — we must not fight it by also lerping _controls.target here.
778
+ this._lookTargetEndPosition.clamp(min, max);
779
+ }
780
+ else {
781
+ // Interactive use (pan/orbit): smoothly push the target back into bounds
782
+ const targetVector = this._controls.target;
783
+ const newTarget = getTempVector(targetVector).clamp(min, max);
784
+ const duration = .1;
785
+ if (duration <= 0) targetVector.copy(newTarget);
786
+ else targetVector.lerp(newTarget, Math.min(1, this.context.time.deltaTime / duration));
773
787
  }
788
+
774
789
  if (debug) {
775
790
  Gizmos.DrawWireBox(boundsCenter, boundsHalfSize.multiplyScalar(2), 0xffaa00);
776
791
  }
@@ -847,7 +862,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
847
862
  this._controls.update(this.context.time.deltaTime);
848
863
 
849
864
  if (debug) {
850
- Gizmos.DrawWireSphere(this._controls.target, 0.1, 0x00ff00);
865
+ const distance = this._controls.getDistance();
866
+ Gizmos.DrawWireSphere(this._controls.target, 0.01 * distance, 0x00ff00);
851
867
  }
852
868
  }
853
869
  }
@@ -1022,7 +1038,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
1022
1038
 
1023
1039
  if (debug) {
1024
1040
  console.warn("OrbitControls: setLookTargetPosition", position, immediateOrDuration);
1025
- Gizmos.DrawWireSphere(this._lookTargetEndPosition, .2, 0xff0000, 2);
1041
+ const distance = this._controls.getDistance();
1042
+ Gizmos.DrawWireSphere(this._lookTargetEndPosition, 0.01 * distance, 0xff5500, 2);
1026
1043
  }
1027
1044
 
1028
1045
  if (immediateOrDuration === true) {
@@ -1063,6 +1080,12 @@ export class OrbitControls extends Behaviour implements ICameraController {
1063
1080
  if (this.lookAtTarget && this.lockLookAtTarget) this.lookAtTarget.worldPosition = this._controls.target;
1064
1081
  }
1065
1082
 
1083
+ private canFocusAtPointer(): boolean {
1084
+ // A locked lookAtTarget is the authoritative orbit center. Pointer focus would
1085
+ // otherwise move the assigned scene object via lerpLookTarget's back-sync.
1086
+ return !(this.lookAtTarget && this.lockLookAtTarget);
1087
+ }
1088
+
1066
1089
  private setTargetFromRaycast(ray?: Ray, immediateOrDuration: number | boolean = false): boolean {
1067
1090
  if (!this.controls) return false;
1068
1091
  const rc = ray ? this.context.physics.raycastFromRay(ray) : this.context.physics.raycast();
@@ -1086,13 +1109,16 @@ export class OrbitControls extends Behaviour implements ICameraController {
1086
1109
  // Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
1087
1110
  // Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
1088
1111
 
1089
- /**
1090
- * Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
1112
+ /**
1113
+ * Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
1091
1114
  * @param options The options for fitting the camera. Use to provide objects to fit to, fit direction and size and other settings.
1092
1115
  */
1093
1116
  fitCamera(options?: OrbitFitCameraOptions);
1094
- /** @deprecated Use fitCamera(options) */
1095
- fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
1117
+ // Deprecated overload commented out: it accepted Object3D as first arg, which caused
1118
+ // TypeScript autocomplete to show Object3D properties (position, worldPosition, etc.)
1119
+ // instead of OrbitFitCameraOptions. The implementation still handles Object3D at runtime
1120
+ // for backwards-compat — use fitCamera({ objects: [...] }) instead.
1121
+ // fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
1096
1122
  fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | OrbitFitCameraOptions, options?: OrbitFitCameraOptions): void {
1097
1123
 
1098
1124
 
@@ -204,12 +204,19 @@ export class Rigidbody extends Behaviour implements IRigidbody {
204
204
 
205
205
  get isRigidbody() { return true; }
206
206
 
207
- /** When true the mass will be automatically calculated by the attached colliders */
207
+ /** When true the mass is automatically computed from the attached colliders using `mass = density × volume`.
208
+ * Each collider's {@link Collider.density} determines how heavy it contributes to the total mass.
209
+ * Disable to set mass explicitly via the `mass` property.
210
+ */
208
211
  @validate()
209
212
  autoMass: boolean = true;
210
213
 
211
- /** By default the mass will be automatically calculated (see `autoMass`) by the physics engine using the collider sizes
212
- * To set the mass manually you can either set the `mass` value or set `autoMass` to `false`
214
+ /** The mass of the rigidbody in kg (when `autoMass` is disabled).
215
+ * When `autoMass` is enabled, reading this returns the computed mass from `density × volume` of all attached colliders.
216
+ * Setting this property automatically disables `autoMass`.
217
+ *
218
+ * **Prefer using {@link Collider.density}** with `autoMass` enabled instead — density scales
219
+ * naturally with collider size, while explicit mass stays fixed regardless of shape changes.
213
220
  */
214
221
  @serializable()
215
222
  set mass(value: number) {
@@ -403,9 +410,16 @@ export class Rigidbody extends Behaviour implements IRigidbody {
403
410
  this.context.physics.engine?.removeBody(this);
404
411
  }
405
412
 
406
- onValidate() {
413
+ onValidate(property?: string) {
407
414
  this._propertiesChanged = true;
415
+ if (property === "autoMass" && !this.autoMass) {
416
+ if (isDevEnvironment() && !Rigidbody._didWarnAutoMass) {
417
+ Rigidbody._didWarnAutoMass = true;
418
+ console.warn("[Rigidbody] autoMass disabled — consider using Collider.density instead of setting mass explicitly. Density scales naturally with collider size.");
419
+ }
420
+ }
408
421
  }
422
+ private static _didWarnAutoMass = false;
409
423
 
410
424
  // need to do this right before updating physics to prevent rendered object glitching through physical bodies
411
425
  * beforePhysics() {
@@ -747,6 +747,9 @@ export class SceneSwitcher extends Behaviour {
747
747
  const openedEvt = new CustomEvent<LoadSceneEvent>("scene-opened", { detail: { scene: scene, switcher: this, index: index } });
748
748
  this.dispatchEvent(openedEvt);
749
749
  this.sceneLoaded?.invoke(this);
750
+ if (this._currentSceneAsset) {
751
+ this.context.events.emit("scene-content-changed", { source: this, object: this._currentSceneAsset });
752
+ }
750
753
  return true;
751
754
  }
752
755
  }
@@ -249,10 +249,10 @@ export class SeeThrough extends Behaviour {
249
249
  }
250
250
 
251
251
  const materials = renderer.sharedMaterials;// : this.rendererMaterials.get(renderer);
252
- if (!materials) return;
252
+ if (!materials?.length) return;
253
253
 
254
254
  const block = MaterialPropertyBlock.get(renderer.gameObject);
255
- const currentOpacity = (block.getOverride("opacity")?.value ?? materials[0].opacity ?? 1);
255
+ const currentOpacity = (block.getOverride("opacity")?.value ?? materials[0]?.opacity ?? 1);
256
256
 
257
257
  let newAlpha = Mathf.lerp(currentOpacity, targetAlpha, duration <= 0 ? 1 : this.context.time.deltaTime / duration);;
258
258
  if (newAlpha >= 0.99) newAlpha = 1;
@@ -207,16 +207,13 @@ export class VideoPlayer extends Behaviour {
207
207
  */
208
208
  get isPlaying(): boolean {
209
209
  const video = this._videoElement;
210
- if (video) {
211
- if (video.currentTime > 0 && !video.paused && !video.ended
212
- && video.readyState > video.HAVE_CURRENT_DATA)
213
- return true;
214
- else if (video.srcObject) {
215
- const stream = video.srcObject as MediaStream;
216
- if (stream.active) return true;
217
- }
210
+ if (!video) return false;
211
+ if (video.paused || video.ended) return false;
212
+ if (video.srcObject) {
213
+ const stream = video.srcObject as MediaStream;
214
+ return stream.active;
218
215
  }
219
- return false;
216
+ return video.currentTime > 0 && video.readyState > video.HAVE_CURRENT_DATA;
220
217
  }
221
218
 
222
219
  get crossOrigin(): string | null {
@@ -430,26 +427,52 @@ export class VideoPlayer extends Behaviour {
430
427
 
431
428
  private _playErrors: number = 0;
432
429
 
433
- /** start playing the video source */
434
- play() {
430
+ /**
431
+ * Plays the assigned video clip, URL, or MediaStream.
432
+ * If a `clip` argument is passed, it is used as the new video source (mirroring {@link AudioSource.play}).
433
+ *
434
+ * @param clip - Optional video URL string or {@link MediaStream} to play. If omitted, plays the currently assigned source.
435
+ * @returns A promise that resolves to `true` when playback was successfully started, or `false` on error.
436
+ */
437
+ async play(clip?: string | MediaStream): Promise<boolean> {
438
+ // Defensive: if called from an event handler with a non-string/non-MediaStream argument
439
+ // (e.g. SpatialTrigger.onEnter passes a receiver object), ignore the arg and fall back to the assigned source.
440
+ // Same pattern as AudioSource.play.
441
+ if (clip !== undefined && typeof clip !== "string" && !(clip instanceof MediaStream)) {
442
+ if (isDevEnvironment()) console.warn("[VideoPlayer] Called play with unknown argument type. Using assigned source instead.", clip);
443
+ clip = undefined;
444
+ }
445
+
446
+ if (typeof clip === "string") {
447
+ this.setClipURL(clip);
448
+ }
449
+ else if (clip instanceof MediaStream) {
450
+ this.setVideo(clip);
451
+ }
452
+
435
453
  if (!this._videoElement) this.create(false);
436
454
  if (!this._videoElement) {
437
455
  if (debug) console.warn("Can not play: no video element found", this);
438
- return
456
+ return false;
439
457
  }
440
- if (this._isPlaying && !this._videoElement?.ended && !this._videoElement?.paused) return;
458
+ if (this._isPlaying && !this._videoElement?.ended && !this._videoElement?.paused) return true;
441
459
  this._isPlaying = true;
442
460
  if (!this._receivedInput) this._videoElement.muted = true;
443
461
  this.handleBeginPlaying(false);
444
462
 
445
463
  if (this.shouldUseM3U) {
446
464
  this.ensureM3UCanBePlayed();
447
- return;
465
+ return true;
448
466
  }
449
467
 
450
468
  if (debug) console.log("Video Play()", this.clip, this._videoElement, this.time);
451
469
  this._videoElement.currentTime = this.time;
452
- this._videoElement.play().catch(err => {
470
+ try {
471
+ await this._videoElement.play();
472
+ if (debug) console.log("play", this._videoElement, this.time);
473
+ return true;
474
+ }
475
+ catch (err: any) {
453
476
  if (this._playErrors++ < 10) console.error(err);
454
477
  else if (this._playErrors === 10) console.error("Multiple errors playing video, further errors will be suppressed. Use 'debugvideo' param to see all errors.");
455
478
  // https://developer.chrome.com/blog/play-request-was-interrupted/
@@ -459,8 +482,8 @@ export class VideoPlayer extends Behaviour {
459
482
  if (this._isPlaying && !this.destroyed && this.activeAndEnabled)
460
483
  this.play();
461
484
  }, 1000);
462
- });
463
- if (debug) console.log("play", this._videoElement, this.time);
485
+ return false;
486
+ }
464
487
  }
465
488
 
466
489
  /**
@@ -36,9 +36,9 @@ const debugParam = getParam("debugvoip");
36
36
  * voip.createMenuButton = true;
37
37
  *
38
38
  * // Manual control
39
- * voip.connect(); // Start sending audio
40
- * voip.disconnect(); // Stop sending
41
- * voip.setMuted(true); // Mute microphone
39
+ * voip.connect(); // Start sending your microphone
40
+ * voip.disconnect(); // Stop sending your microphone
41
+ * voip.setMuted(true); // Mute incoming audio (silence other users)
42
42
  * ```
43
43
  *
44
44
  * @summary Voice over IP for networked audio communication
@@ -84,9 +84,13 @@ export class Voip extends Behaviour {
84
84
  @serializable()
85
85
  get volume(): number { return this._volume; }
86
86
  set volume(val: number) {
87
- this._volume = val;
87
+ // HTMLMediaElement.volume throws IndexSizeError outside [0,1] — clamp before assigning.
88
+ // Reject NaN so we don't poison _volume and break serialization.
89
+ if (Number.isNaN(val)) return;
90
+ const clamped = Math.max(0, Math.min(1, val));
91
+ this._volume = clamped;
88
92
  for (const audio of this._incomingStreams.values()) {
89
- audio.volume = val;
93
+ audio.volume = clamped;
90
94
  }
91
95
  }
92
96
 
@@ -119,11 +123,11 @@ export class Voip extends Behaviour {
119
123
  }
120
124
 
121
125
  /**
122
- * Threshold for speaking detection (0–255). When a user's audio amplitude exceeds this,
123
- * they are considered "speaking". Default is 30.
126
+ * Normalized amplitude threshold for speaking detection (0–1). When a user's average
127
+ * audio amplitude exceeds this, they are considered "speaking". Default is 0.1.
124
128
  */
125
129
  @serializable()
126
- speakingThreshold: number = 30;
130
+ speakingThreshold: number = 0.1;
127
131
 
128
132
  /**
129
133
  * Event fired when a user's speaking state changes.
@@ -133,7 +137,11 @@ export class Voip extends Behaviour {
133
137
  onSpeakingChanged: EventList = new EventList();
134
138
 
135
139
  private _speakingStates = new Map<string, boolean>();
136
- private _analysers = new Map<string, { analyser: AnalyserNode, data: Uint8Array, context: AudioContext }>();
140
+ private _analysers = new Map<string, { source: MediaStreamAudioSourceNode, analyser: AnalyserNode, data: Uint8Array }>();
141
+ // Single shared AudioContext for all remote-user analysers. Browsers cap live AudioContexts at ~6 per tab,
142
+ // so one-per-user would break voice rooms of 7+ participants. Lazily created on first setupAnalyser.
143
+ private _sharedAudioContext?: AudioContext;
144
+ private _lastSpeakingPollMs = 0;
137
145
 
138
146
  private _net?: NetworkedStreams;
139
147
  private _menubutton?: HTMLElement;
@@ -142,12 +150,12 @@ export class Voip extends Behaviour {
142
150
  awake() {
143
151
  if (debugParam) this.debug = true;
144
152
  if (this.debug) {
145
- console.log("VOIP debugging: press 'v' to toggle mute or 'c' to toggle connect/disconnect");
153
+ console.log("VOIP debugging: press 'v' to toggle incoming mute, 'c' to toggle connect/disconnect");
146
154
  window.addEventListener("keydown", async (evt) => {
147
155
  const key = evt.key.toLowerCase();
148
156
  switch (key) {
149
157
  case "v":
150
- console.log("MUTE?", !this.isMuted)
158
+ console.log("VOIP: toggle incoming mute → ", !this.isMuted);
151
159
  this.setMuted(!this.isMuted);
152
160
  break;
153
161
  case "c":
@@ -156,15 +164,6 @@ export class Voip extends Behaviour {
156
164
  break;
157
165
  }
158
166
  });
159
- // mute unfocused
160
- window.addEventListener("blur", () => {
161
- console.log("VOIP: MUTE ON BLUR")
162
- this.setMuted(true);
163
- });
164
- window.addEventListener("focus", () => {
165
- console.log("VOIP: UNMUTE ON FOCUS")
166
- this.setMuted(false);
167
- });
168
167
  }
169
168
  }
170
169
 
@@ -201,10 +200,11 @@ export class Voip extends Behaviour {
201
200
  this.onEnabledChanged();
202
201
  this.updateButton();
203
202
  window.removeEventListener("visibilitychange", this.onVisibilityChanged);
204
- // Clean up analysers
203
+ // Clean up analysers and the shared AudioContext (lazily recreated on next setupAnalyser).
205
204
  for (const userId of [...this._analysers.keys()]) {
206
205
  this.cleanupAnalyser(userId);
207
206
  }
207
+ this.closeSharedAudioContext();
208
208
  }
209
209
 
210
210
  /** @internal */
@@ -215,6 +215,7 @@ export class Voip extends Behaviour {
215
215
  for (const userId of [...this._analysers.keys()]) {
216
216
  this.cleanupAnalyser(userId);
217
217
  }
218
+ this.closeSharedAudioContext();
218
219
  for (const incoming of this._incomingStreams.values()) {
219
220
  disposeStream(incoming.srcObject as MediaStream);
220
221
  }
@@ -225,6 +226,10 @@ export class Voip extends Behaviour {
225
226
  /** Set via the mic button (e.g. when the websocket connection closes and rejoins but the user was muted before we don't want to enable VOIP again automatically) */
226
227
  private _allowSending = true;
227
228
  private _outputStream: MediaStream | null = null;
229
+ // Tracks an in-flight connect() so concurrent callers coalesce onto the same promise
230
+ // instead of each running getUserMedia in parallel (which would leak a MediaStream and
231
+ // briefly transmit then kill the first acquired stream).
232
+ private _connectInFlight?: Promise<boolean>;
228
233
 
229
234
  /**
230
235
  * @returns true if the component is currently sending audio
@@ -233,7 +238,18 @@ export class Voip extends Behaviour {
233
238
 
234
239
 
235
240
  /** Start sending audio. */
236
- async connect(audioSource?: MediaTrackConstraints) {
241
+ connect(audioSource?: MediaTrackConstraints): Promise<boolean> {
242
+ // Coalesce concurrent callers. Without this, two near-simultaneous connect() calls
243
+ // each call getUserMedia in parallel, the first stream gets disposed mid-broadcast
244
+ // by the second, and a MediaStream leaks.
245
+ if (this._connectInFlight) return this._connectInFlight;
246
+ this._connectInFlight = this._connectImpl(audioSource).finally(() => {
247
+ this._connectInFlight = undefined;
248
+ });
249
+ return this._connectInFlight;
250
+ }
251
+
252
+ private async _connectImpl(audioSource?: MediaTrackConstraints): Promise<boolean> {
237
253
  if (!this._net) {
238
254
  console.error("Cannot connect to voice chat - NetworkedStreams not initialized. Make sure the component is enabled before calling this method.");
239
255
  return false;
@@ -281,27 +297,25 @@ export class Voip extends Behaviour {
281
297
  }
282
298
 
283
299
  /**
284
- * Mute or unmute the audio stream (this will only mute incoming streams and not mute your own microphone. Use disconnect() to mute your own microphone)
300
+ * Mute or unmute the audio you hear from other users (incoming streams).
301
+ * This does NOT mute your own microphone — use {@link disconnect} to stop sending your microphone.
285
302
  */
286
303
  setMuted(mute: boolean) {
287
- const audio = this._outputStream?.getAudioTracks();
288
- if (audio) {
289
- for (const track of audio) {
290
- track.enabled = !mute
291
- }
304
+ for (const audio of this._incomingStreams.values()) {
305
+ audio.muted = mute;
292
306
  }
293
307
  }
294
308
 
295
- /** Returns true if the audio stream is currently muted */
309
+ /**
310
+ * Returns true if incoming audio is currently muted (you can't hear other users).
311
+ * When there are no incoming streams, returns false.
312
+ */
296
313
  get isMuted() {
297
- if (this._outputStream === null) return false;
298
- const audio = this._outputStream?.getAudioTracks();
299
- if (audio) {
300
- for (const track of audio) {
301
- if (!track.enabled) return true;
302
- }
314
+ if (this._incomingStreams.size === 0) return false;
315
+ for (const audio of this._incomingStreams.values()) {
316
+ if (!audio.muted) return false;
303
317
  }
304
- return false;
318
+ return true;
305
319
  }
306
320
 
307
321
  private async updateButton() {
@@ -381,7 +395,7 @@ export class Voip extends Behaviour {
381
395
  .catch((err) => {
382
396
  console.warn("VOIP failed getting audio stream", err);
383
397
  return null;
384
- });;
398
+ });
385
399
  }
386
400
 
387
401
  const stream = await getUserMedia(audio);
@@ -397,7 +411,13 @@ export class Voip extends Behaviour {
397
411
  if (nonBuiltInAudioSource) {
398
412
  const constraints = Object.assign({}, audio);
399
413
  constraints.deviceId = nonBuiltInAudioSource.deviceId;
400
- return await getUserMedia(constraints);
414
+ const externalStream = await getUserMedia(constraints);
415
+ if (externalStream) {
416
+ // Release the built-in mic stream we grabbed first — otherwise its tracks stay live.
417
+ disposeStream(stream);
418
+ return externalStream;
419
+ }
420
+ // External device acquisition failed — keep the original stream rather than returning null.
401
421
  }
402
422
  }
403
423
 
@@ -431,20 +451,25 @@ export class Voip extends Behaviour {
431
451
  update() {
432
452
  // Only run speaking detection if someone is listening
433
453
  if (!this.onSpeakingChanged || this.onSpeakingChanged.listenerCount <= 0) return;
454
+ // Rate-limit analysis to ~10Hz. Speaking-state is a coarse UI signal; running FFT per
455
+ // remote user every frame (60Hz) is wasteful and scales linearly with participant count.
456
+ const now = performance.now();
457
+ if (now - this._lastSpeakingPollMs < 100) return;
458
+ this._lastSpeakingPollMs = now;
434
459
 
435
460
  for (const [userId, info] of this._analysers) {
436
461
  info.analyser.getByteFrequencyData(info.data as Uint8Array<ArrayBuffer>);
437
- // Average amplitude
462
+ // Average amplitude normalized to 0–1
438
463
  let sum = 0;
439
464
  for (let i = 0; i < info.data.length; i++) sum += info.data[i];
440
- const avg = sum / info.data.length;
465
+ const volume = sum / info.data.length / 255;
441
466
 
442
467
  const wasSpeaking = this._speakingStates.get(userId) ?? false;
443
- const isSpeaking = avg > this.speakingThreshold;
468
+ const isSpeaking = volume > this.speakingThreshold;
444
469
 
445
470
  if (isSpeaking !== wasSpeaking) {
446
471
  this._speakingStates.set(userId, isSpeaking);
447
- this.onSpeakingChanged.invoke({ userId, isSpeaking, volume: avg / 255 });
472
+ this.onSpeakingChanged.invoke({ userId, isSpeaking, volume });
448
473
  }
449
474
  }
450
475
  }
@@ -453,13 +478,16 @@ export class Voip extends Behaviour {
453
478
  // Only set up if someone is listening or might listen
454
479
  if (this._analysers.has(userId)) return;
455
480
  try {
456
- const audioCtx = new AudioContext();
457
- const source = audioCtx.createMediaStreamSource(stream);
458
- const analyser = audioCtx.createAnalyser();
481
+ if (!this._sharedAudioContext) {
482
+ this._sharedAudioContext = new AudioContext();
483
+ }
484
+ const ctx = this._sharedAudioContext;
485
+ const source = ctx.createMediaStreamSource(stream);
486
+ const analyser = ctx.createAnalyser();
459
487
  analyser.fftSize = 256;
460
488
  source.connect(analyser);
461
489
  const data = new Uint8Array(analyser.frequencyBinCount);
462
- this._analysers.set(userId, { analyser, data, context: audioCtx });
490
+ this._analysers.set(userId, { source, analyser, data });
463
491
  }
464
492
  catch (err) {
465
493
  if (this.debug) console.warn("VOIP: Failed to create analyser for", userId, err);
@@ -469,12 +497,20 @@ export class Voip extends Behaviour {
469
497
  private cleanupAnalyser(userId: string) {
470
498
  const info = this._analysers.get(userId);
471
499
  if (info) {
472
- info.context.close();
500
+ info.source.disconnect();
501
+ info.analyser.disconnect();
473
502
  this._analysers.delete(userId);
474
503
  }
475
504
  this._speakingStates.delete(userId);
476
505
  }
477
506
 
507
+ private closeSharedAudioContext() {
508
+ if (this._sharedAudioContext) {
509
+ this._sharedAudioContext.close();
510
+ this._sharedAudioContext = undefined;
511
+ }
512
+ }
513
+
478
514
  private onReceiveStream = (evt: StreamReceivedEvent) => {
479
515
  const userId = evt.target.userId;
480
516
  const stream = evt.stream;
@@ -512,12 +548,11 @@ export class Voip extends Behaviour {
512
548
 
513
549
  private onVisibilityChanged = () => {
514
550
  if (this.runInBackground) return;
515
- const visible = document.visibilityState === "visible";
516
- const muted = !visible;
551
+ const muted = document.visibilityState !== "visible";
552
+ // Mute incoming so we don't hear other users while tab is hidden.
517
553
  this.setMuted(muted);
518
- for (const element of this._incomingStreams) {
519
- const str = element[1];
520
- str.muted = muted;
521
- }
554
+ // Also disable our outgoing mic tracks (cheaper than disconnect/reconnect — keeps the mic permission).
555
+ const tracks = this._outputStream?.getAudioTracks();
556
+ if (tracks) for (const t of tracks) t.enabled = !muted;
522
557
  };
523
558
  }
@@ -34,12 +34,14 @@
34
34
  * @module Built-in Components
35
35
  */
36
36
 
37
+ export { AnimationBuilder, type AnimationKeyframe, type Tween, type AnimationInterpolation } from "./AnimationBuilder.js";
38
+ export { AnimatorControllerBuilder, type ConditionMode, type StateOptions, type TransitionOptions } from "./AnimatorController.builder.js";
37
39
  export * from "./codegen/components.js";
38
- export { AnimatorControllerBuilder, type ConditionMode, type StateOptions, type TransitionOptions } from "./AnimatorController.js";
39
40
  export { Collider } from "./Collider.js"; // export abstract type
40
41
  export { Behaviour, Component, GameObject } from "./Component.js";
41
42
 
42
43
  // We dont want to export everything in the extensions
44
+ export { AudioRolloffMode } from "./AudioSource.js";
43
45
  export { ClearFlags } from "./Camera.js"
44
46
  export { DragMode } from "./DragControls.js";
45
47
  export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
@@ -3,21 +3,13 @@
3
3
  export class __Ignore {}
4
4
  export { AlignmentConstraint } from "../AlignmentConstraint.js";
5
5
  export { Animation } from "../Animation.js";
6
+ export { AnimationBuilder } from "../AnimationBuilder.js";
6
7
  export { Keyframe } from "../AnimationCurve.js";
7
8
  export { AnimationCurve } from "../AnimationCurve.js";
8
9
  export { Animator } from "../Animator.js";
9
- export { AnimatorControllerBuilder } from "../AnimatorController.js";
10
10
  export { AnimatorController } from "../AnimatorController.js";
11
11
  export { AudioListener } from "../AudioListener.js";
12
12
  export { AudioSource } from "../AudioSource.js";
13
- export { Avatar_POI } from "../avatar/Avatar_Brain_LookAt.js";
14
- export { Avatar_Brain_LookAt } from "../avatar/Avatar_Brain_LookAt.js";
15
- export { Avatar_MouthShapes } from "../avatar/Avatar_MouthShapes.js";
16
- export { Avatar_MustacheShake } from "../avatar/Avatar_MustacheShake.js";
17
- export { AvatarBlink_Simple } from "../avatar/AvatarBlink_Simple.js";
18
- export { AvatarEyeLook_Rotation } from "../avatar/AvatarEyeLook_Rotation.js";
19
- export { AvatarModel } from "../AvatarLoader.js";
20
- export { AvatarLoader } from "../AvatarLoader.js";
21
13
  export { AxesHelper } from "../AxesHelper.js";
22
14
  export { BasicIKConstraint } from "../BasicIKConstraint.js";
23
15
  export { BoxHelperComponent } from "../BoxHelperComponent.js";
@@ -168,11 +160,13 @@ export { PlayableDirector } from "../timeline/PlayableDirector.js";
168
160
  export { SignalAsset } from "../timeline/SignalAsset.js";
169
161
  export { SignalReceiverEvent } from "../timeline/SignalAsset.js";
170
162
  export { SignalReceiver } from "../timeline/SignalAsset.js";
171
- export { AnimationTrackHandler } from "../timeline/TimelineTracks.js";
172
- export { AudioTrackHandler } from "../timeline/TimelineTracks.js";
173
- export { MarkerTrackHandler } from "../timeline/TimelineTracks.js";
163
+ export { TimelineBuilder } from "../timeline/TimelineBuilder.js";
164
+ export { TimelineAnimationTrack } from "../timeline/TimelineTracks.js";
165
+ export { TimelineAudioTrack } from "../timeline/TimelineTracks.js";
166
+ export { TimelineMarkerTrack } from "../timeline/TimelineTracks.js";
174
167
  export { SignalTrackHandler } from "../timeline/TimelineTracks.js";
175
- export { ControlTrackHandler } from "../timeline/TimelineTracks.js";
168
+ export { TimelineActivationTrack } from "../timeline/TimelineTracks.js";
169
+ export { TimelineControlTrack } from "../timeline/TimelineTracks.js";
176
170
  export { TransformGizmo } from "../TransformGizmo.js";
177
171
  export { BaseUIComponent } from "../ui/BaseUIComponent.js";
178
172
  export { UIRootComponent } from "../ui/BaseUIComponent.js";