@needle-tools/engine 3.0.1-alpha.4 → 3.1.0-alpha

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 (146) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/needle-engine.js +13110 -12429
  3. package/dist/needle-engine.min.js +378 -365
  4. package/dist/needle-engine.umd.cjs +366 -353
  5. package/lib/engine/api.d.ts +2 -1
  6. package/lib/engine/api.js +2 -1
  7. package/lib/engine/api.js.map +1 -1
  8. package/lib/engine/codegen/register_types.js +8 -0
  9. package/lib/engine/codegen/register_types.js.map +1 -1
  10. package/lib/engine/debug/debug_overlay.js +3 -0
  11. package/lib/engine/debug/debug_overlay.js.map +1 -1
  12. package/lib/engine/engine_addressables.d.ts +21 -1
  13. package/lib/engine/engine_addressables.js +83 -7
  14. package/lib/engine/engine_addressables.js.map +1 -1
  15. package/lib/engine/engine_context.d.ts +2 -0
  16. package/lib/engine/engine_context.js +3 -0
  17. package/lib/engine/engine_context.js.map +1 -1
  18. package/lib/engine/engine_element.d.ts +1 -1
  19. package/lib/engine/engine_element.js +2 -2
  20. package/lib/engine/engine_element.js.map +1 -1
  21. package/lib/engine/engine_element_loading.d.ts +7 -2
  22. package/lib/engine/engine_element_loading.js +67 -22
  23. package/lib/engine/engine_element_loading.js.map +1 -1
  24. package/lib/engine/engine_license.d.ts +1 -1
  25. package/lib/engine/engine_license.js +6 -19
  26. package/lib/engine/engine_license.js.map +1 -1
  27. package/lib/engine/engine_networking.d.ts +1 -0
  28. package/lib/engine/engine_networking.js +3 -0
  29. package/lib/engine/engine_networking.js.map +1 -1
  30. package/lib/engine/engine_serialization.d.ts +2 -2
  31. package/lib/engine/engine_serialization.js +2 -3
  32. package/lib/engine/engine_serialization.js.map +1 -1
  33. package/lib/engine/engine_serialization_builtin_serializer.d.ts +5 -0
  34. package/lib/engine/engine_serialization_builtin_serializer.js +16 -0
  35. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  36. package/lib/engine/engine_serialization_core.d.ts +1 -0
  37. package/lib/engine/engine_serialization_core.js +26 -20
  38. package/lib/engine/engine_serialization_core.js.map +1 -1
  39. package/lib/engine/engine_utils.d.ts +9 -0
  40. package/lib/engine/engine_utils.js +33 -13
  41. package/lib/engine/engine_utils.js.map +1 -1
  42. package/lib/engine/extensions/NEEDLE_animator_controller_model.d.ts +1 -0
  43. package/lib/engine/extensions/NEEDLE_animator_controller_model.js.map +1 -1
  44. package/lib/engine/extensions/NEEDLE_progressive.js +2 -2
  45. package/lib/engine/extensions/NEEDLE_progressive.js.map +1 -1
  46. package/lib/engine-components/AnimatorController.js +22 -3
  47. package/lib/engine-components/AnimatorController.js.map +1 -1
  48. package/lib/engine-components/AudioSource.d.ts +2 -3
  49. package/lib/engine-components/AudioSource.js +28 -32
  50. package/lib/engine-components/AudioSource.js.map +1 -1
  51. package/lib/engine-components/CameraUtils.js.map +1 -1
  52. package/lib/engine-components/Component.js.map +1 -1
  53. package/lib/engine-components/ParticleSystem.d.ts +5 -2
  54. package/lib/engine-components/ParticleSystem.js +49 -10
  55. package/lib/engine-components/ParticleSystem.js.map +1 -1
  56. package/lib/engine-components/ParticleSystemModules.d.ts +2 -0
  57. package/lib/engine-components/ParticleSystemModules.js +23 -12
  58. package/lib/engine-components/ParticleSystemModules.js.map +1 -1
  59. package/lib/engine-components/ScreenCapture.d.ts +1 -0
  60. package/lib/engine-components/ScreenCapture.js +145 -46
  61. package/lib/engine-components/ScreenCapture.js.map +1 -1
  62. package/lib/engine-components/Skybox.js +2 -2
  63. package/lib/engine-components/Skybox.js.map +1 -1
  64. package/lib/engine-components/SyncedRoom.js +1 -2
  65. package/lib/engine-components/SyncedRoom.js.map +1 -1
  66. package/lib/engine-components/VideoPlayer.d.ts +4 -2
  67. package/lib/engine-components/VideoPlayer.js +35 -12
  68. package/lib/engine-components/VideoPlayer.js.map +1 -1
  69. package/lib/engine-components/WebXR.d.ts +4 -1
  70. package/lib/engine-components/WebXR.js +10 -2
  71. package/lib/engine-components/WebXR.js.map +1 -1
  72. package/lib/engine-components/WebXRController.js +2 -2
  73. package/lib/engine-components/WebXRController.js.map +1 -1
  74. package/lib/engine-components/WebXRImageTracking.d.ts +39 -0
  75. package/lib/engine-components/WebXRImageTracking.js +173 -0
  76. package/lib/engine-components/WebXRImageTracking.js.map +1 -0
  77. package/lib/engine-components/codegen/components.d.ts +4 -0
  78. package/lib/engine-components/codegen/components.js +4 -0
  79. package/lib/engine-components/codegen/components.js.map +1 -1
  80. package/lib/engine-components/postprocessing/Effects/DepthOfField.d.ts +2 -0
  81. package/lib/engine-components/postprocessing/Effects/DepthOfField.js +19 -1
  82. package/lib/engine-components/postprocessing/Effects/DepthOfField.js.map +1 -1
  83. package/lib/engine-components/postprocessing/Effects/TiltShiftEffect.d.ts +13 -0
  84. package/lib/engine-components/postprocessing/Effects/TiltShiftEffect.js +63 -0
  85. package/lib/engine-components/postprocessing/Effects/TiltShiftEffect.js.map +1 -0
  86. package/lib/engine-components/postprocessing/VolumeParameter.d.ts +1 -0
  87. package/lib/engine-components/postprocessing/VolumeParameter.js +4 -0
  88. package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
  89. package/lib/engine-components/timeline/PlayableDirector.d.ts +3 -1
  90. package/lib/engine-components/timeline/PlayableDirector.js +40 -2
  91. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  92. package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -2
  93. package/lib/engine-components/timeline/TimelineTracks.js +14 -16
  94. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  95. package/lib/engine-components/ui/Text.js +7 -8
  96. package/lib/engine-components/ui/Text.js.map +1 -1
  97. package/lib/include/three/ARButton.d.ts +1 -1
  98. package/lib/include/three/ARButton.js +2 -1
  99. package/lib/include/three/ARButton.js.map +1 -1
  100. package/lib/tsconfig.tsbuildinfo +1 -1
  101. package/package.json +3 -4
  102. package/plugins/vite/config.js +8 -0
  103. package/plugins/vite/copyfiles.js +12 -3
  104. package/plugins/vite/drop.js +1 -0
  105. package/plugins/vite/index.js +4 -0
  106. package/plugins/vite/license.js +24 -0
  107. package/plugins/vite/transform-codegen.js +45 -0
  108. package/src/engine/api.ts +2 -2
  109. package/src/engine/codegen/register_types.js +10 -2
  110. package/src/engine/debug/debug_overlay.ts +2 -0
  111. package/src/engine/engine_addressables.ts +102 -8
  112. package/src/engine/engine_context.ts +4 -1
  113. package/src/engine/engine_element.ts +2 -2
  114. package/src/engine/engine_element_loading.ts +68 -22
  115. package/src/engine/engine_license.ts +6 -17
  116. package/src/engine/engine_networking.ts +4 -0
  117. package/src/engine/engine_serialization.ts +5 -4
  118. package/src/engine/engine_serialization_builtin_serializer.ts +23 -3
  119. package/src/engine/engine_serialization_core.ts +27 -20
  120. package/src/engine/engine_utils.ts +35 -14
  121. package/src/engine/extensions/NEEDLE_animator_controller_model.ts +1 -0
  122. package/src/engine/extensions/NEEDLE_progressive.ts +2 -2
  123. package/src/engine-components/AnimatorController.ts +18 -3
  124. package/src/engine-components/AudioSource.ts +29 -38
  125. package/src/engine-components/CameraUtils.ts +2 -2
  126. package/src/engine-components/Component.ts +1 -1
  127. package/src/engine-components/ParticleSystem.ts +52 -10
  128. package/src/engine-components/ParticleSystemModules.ts +24 -12
  129. package/src/engine-components/ScreenCapture.ts +149 -49
  130. package/src/engine-components/Skybox.ts +2 -2
  131. package/src/engine-components/SyncedRoom.ts +1 -2
  132. package/src/engine-components/VideoPlayer.ts +33 -11
  133. package/src/engine-components/WebXR.ts +11 -2
  134. package/src/engine-components/WebXRController.ts +2 -2
  135. package/src/engine-components/WebXRImageTracking.ts +192 -0
  136. package/src/engine-components/codegen/components.ts +4 -0
  137. package/src/engine-components/postprocessing/Effects/DepthOfField.ts +21 -6
  138. package/src/engine-components/postprocessing/Effects/TiltShiftEffect.ts +56 -0
  139. package/src/engine-components/postprocessing/VolumeParameter.ts +5 -0
  140. package/src/engine-components/timeline/PlayableDirector.ts +38 -2
  141. package/src/engine-components/timeline/TimelineTracks.ts +15 -18
  142. package/src/engine-components/ui/Text.ts +7 -8
  143. package/src/include/three/ARButton.js +2 -2
  144. package/lib/engine/codegen/license.json +0 -1
  145. package/license-2447137e.js +0 -4
  146. package/src/engine/codegen/license.json +0 -1
@@ -35,7 +35,7 @@ export interface IParticleSystem {
35
35
 
36
36
  export enum ParticleSystemRenderMode {
37
37
  Billboard = 0,
38
- // Stretch = 1,
38
+ Stretch = 1,
39
39
  HorizontalBillboard = 2,
40
40
  VerticalBillboard = 3,
41
41
  Mesh = 4,
@@ -608,12 +608,10 @@ export class ShapeModule implements EmitterShape {
608
608
  this.randomConePoint(this.position, this.angle, radius, this.radiusThickness, this.arc, this.arcMode, this._vector);
609
609
  break;
610
610
  case ParticleSystemShapeType.Sphere:
611
- this.randomSpherePoint(this.position, radius, this.radiusThickness, this.arc, this._vector, this.scale);
611
+ this.randomSpherePoint(this.position, radius, this.radiusThickness, this.arc, this._vector);
612
612
  break;
613
613
  case ParticleSystemShapeType.Circle:
614
- this._temp.copy(this.scale);
615
- this._temp.z = 0;
616
- this.randomSpherePoint(this.position, radius, this.radiusThickness, this.arc, this._vector, this._temp);
614
+ this.randomCirclePoint(this.position, radius, this.radiusThickness, this.arc, this._vector);
617
615
  break;
618
616
  default:
619
617
  this._vector.set(0, 0, 0);
@@ -718,15 +716,27 @@ export class ShapeModule implements EmitterShape {
718
716
  dir.lerp(v, amount);
719
717
  }
720
718
 
721
- private randomSpherePoint(pos: Vec3, radius: number, thickness: number, arc: number, vec: Vec3, scale: Vec3) {
719
+ private randomSpherePoint(pos: Vec3, radius: number, thickness: number, arc: number, vec: Vec3) {
722
720
  const u = Math.random();
723
721
  const v = Math.random();
724
722
  const theta = 2 * Math.PI * u * (arc / 360);
725
723
  const phi = Math.acos(2 * v - 1);
726
724
  const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
727
- const x = pos.x + scale.x * (-r * Math.sin(phi) * Math.cos(theta));
728
- const y = pos.y + scale.y * (r * Math.sin(phi) * Math.sin(theta));
729
- const z = pos.z + scale.z * (r * Math.cos(phi));
725
+ const x = pos.x + (-r * Math.sin(phi) * Math.cos(theta));
726
+ const y = pos.y + (r * Math.sin(phi) * Math.sin(theta));
727
+ const z = pos.z + (r * Math.cos(phi));
728
+ vec.x = x;
729
+ vec.y = y;
730
+ vec.z = z;
731
+ }
732
+
733
+ private randomCirclePoint(pos:Vec3, radius:number, thickness:number, arg:number, vec:Vec3){
734
+ const u = Math.random();
735
+ const theta = 2 * Math.PI * u * (arg / 360);
736
+ const r = Mathf.lerp(1, 1 - (Math.pow(1 - Math.random(), Math.PI)), thickness) * (radius);
737
+ const x = pos.x + r * Math.cos(theta);
738
+ const y = pos.y + r * Math.sin(theta);
739
+ const z = pos.z;
730
740
  vec.x = x;
731
741
  vec.y = y;
732
742
  vec.z = z;
@@ -1096,6 +1106,8 @@ export class TextureSheetAnimationModule {
1096
1106
  switch (this.frameOverTime.mode) {
1097
1107
  case ParticleSystemCurveMode.Constant:
1098
1108
  case ParticleSystemCurveMode.TwoConstants:
1109
+ case ParticleSystemCurveMode.TwoCurves:
1110
+ case ParticleSystemCurveMode.Curve:
1099
1111
  return true;
1100
1112
  }
1101
1113
  }
@@ -1104,7 +1116,8 @@ export class TextureSheetAnimationModule {
1104
1116
 
1105
1117
  getStartIndex(): number {
1106
1118
  if (this.sampleOnceAtStart()) {
1107
- return this.frameOverTime.evaluate(Math.random())
1119
+ const start = Math.random();
1120
+ return start * (this.numTilesX * this.numTilesY);
1108
1121
  }
1109
1122
  return 0;
1110
1123
  }
@@ -1118,13 +1131,12 @@ export class TextureSheetAnimationModule {
1118
1131
 
1119
1132
  private getIndex(t01: number): number {
1120
1133
  const tiles = this.numTilesX * this.numTilesY;
1121
- // let pos = t01 * this.cycleCount;
1134
+ t01 = t01 * this.cycleCount;
1122
1135
  let index = this.frameOverTime.evaluate(t01 % 1);
1123
1136
  index *= this.frameOverTimeMultiplier;
1124
1137
  index *= tiles;
1125
1138
  index = index % tiles;
1126
1139
  index = Math.floor(index);
1127
- // console.log(index);
1128
1140
  return index;
1129
1141
  }
1130
1142
  }
@@ -1,6 +1,6 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import { VideoPlayer } from "./VideoPlayer";
3
- import Peer from "peerjs"
3
+ import Peer, { MediaConnection } from "peerjs"
4
4
  import { Context } from "../engine/engine_setup";
5
5
  import { RoomEvents } from "../engine/engine_networking";
6
6
  import { UserJoinedOrLeftRoomModel } from "../engine/engine_networking";
@@ -10,8 +10,9 @@ import { EventDispatcher } from "three";
10
10
  import { AudioSource } from "./AudioSource";
11
11
  import { getParam } from "../engine/engine_utils";
12
12
  import { IModel } from "../engine/engine_networking_types";
13
+ import { showBalloonWarning } from "../engine/debug";
13
14
 
14
- const debug = getParam("debugscreenshare");
15
+ const debug = getParam("debugscreensharing");
15
16
 
16
17
  export enum ScreenCaptureDevice {
17
18
  Screen = 0,
@@ -42,10 +43,11 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
42
43
  onPointerClick(evt : PointerEventData) {
43
44
  if(evt && evt.pointerId !== 0) return;
44
45
  if(this.context.connection.isInRoom === false) return;
45
- if (this.isReceiving) {
46
+ if (this.isReceiving && this.videoPlayer?.isPlaying) {
46
47
  if (this.videoPlayer)
47
48
  this.videoPlayer.screenspace = !this.videoPlayer.screenspace;
48
49
  return;
50
+
49
51
  }
50
52
  if (this.isSending) {
51
53
  this.close();
@@ -91,7 +93,7 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
91
93
 
92
94
  awake() {
93
95
  if (debug)
94
- console.log(this);
96
+ console.log("Screensharing", this.name, this);
95
97
  AudioSource.registerWaitForAllowAudio(() => {
96
98
  if (this.videoPlayer && this._currentStream && this._currentMode === ScreenCaptureMode.Receiving) {
97
99
  this.videoPlayer.setVideo(this._currentStream);
@@ -99,6 +101,11 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
99
101
  });
100
102
  }
101
103
 
104
+
105
+ onDisable(): void {
106
+ this.close();
107
+ }
108
+
102
109
  start() {
103
110
  if (!this.videoPlayer) {
104
111
  this.videoPlayer = GameObject.getComponent(this.gameObject, VideoPlayer) ?? undefined;
@@ -131,6 +138,20 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
131
138
  video: settings,
132
139
  audio: settings,
133
140
  };
141
+ const videoOptions = displayMediaOptions.video;
142
+ if (videoOptions !== undefined && typeof videoOptions !== "boolean") {
143
+ // Set default video settings
144
+ if (!videoOptions.width)
145
+ videoOptions.width = { max: 1920 };
146
+ if (!videoOptions.height)
147
+ videoOptions.height = { max: 1920 };
148
+ if (!videoOptions.aspectRatio)
149
+ videoOptions.aspectRatio = { ideal: 1.7777777778 };
150
+ if (!videoOptions.frameRate)
151
+ videoOptions.frameRate = { ideal: 24 };
152
+ if (!videoOptions.facingMode)
153
+ videoOptions.facingMode = { ideal: "user" };
154
+ }
134
155
 
135
156
  switch (this.device) {
136
157
  // Capture a connected camera
@@ -176,7 +197,8 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
176
197
  close() {
177
198
  this._requestOpen = false;
178
199
  if (this._currentStream) {
179
- console.warn("Close current stream / disposing resources");
200
+ if (debug)
201
+ console.warn("Close current stream / disposing resources, stream was active?", this._currentStream.active);
180
202
  this._net?.stopSendingVideo(this._currentStream);
181
203
  disposeStream(this._currentStream);
182
204
  this._currentMode = ScreenCaptureMode.Idle;
@@ -196,15 +218,32 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
196
218
  const isSending = mode === ScreenCaptureMode.Sending;
197
219
  if (isSending) {
198
220
  this._net?.startSendingVideo(stream);
221
+ }
199
222
 
223
+ // Mute audio for the video we are sending
224
+ if (mode === ScreenCaptureMode.Sending)
225
+ this.videoPlayer.muted = true;
226
+
227
+ for (const track of stream.getTracks()) {
228
+ track.addEventListener("ended", () => {
229
+ if (debug) console.log("Track ended", track);
230
+ this.close();
231
+ });
232
+
233
+ if (debug) {
234
+ if (track.kind === "video") {
235
+ if (isSending)
236
+ console.log("Video →", track.getSettings());
237
+ else
238
+ console.log("Video ←", track.getSettings());
239
+ }
240
+ }
200
241
  }
201
242
 
202
- stream.addEventListener("ended", () => {
203
- this.close();
204
- });
205
243
  }
206
244
 
207
245
  private onReceiveVideo(evt: ReceiveVideoEvent) {
246
+ if (evt.stream?.active !== true) return;
208
247
  this.setVideo(evt.stream, ScreenCaptureMode.Receiving);
209
248
  }
210
249
 
@@ -219,7 +258,8 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
219
258
 
220
259
  // TODO: allow user to select device
221
260
  const devices = (await navigator.mediaDevices.enumerateDevices()).filter(d => d.kind === "videoinput");
222
- console.log("Request camera", devices);
261
+ if (debug)
262
+ console.log("Request camera", devices);
223
263
  for (const dev of devices) {
224
264
  try {
225
265
  if (!this._requestOpen) break;
@@ -240,11 +280,20 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
240
280
  this.setVideo(userMedia, ScreenCaptureMode.Sending);
241
281
  }
242
282
  else disposeStream(userMedia);
243
- console.log("Selected camera", dev);
283
+ if (debug)
284
+ console.log("Selected camera", dev);
244
285
  break;
245
286
  }
246
- catch (err) {
247
- console.warn(err);
287
+ catch (err: any) {
288
+ // First message is firefox, second is chrome when the video source is already in use by another app
289
+ if (err.message === "Failed to allocate videosource" || err.message === "Could not start video source") {
290
+ showBalloonWarning("Failed to start video: Try another camera (Code " + err.code + ")");
291
+ console.warn(err);
292
+ continue;
293
+ }
294
+ else {
295
+ console.error("Failed to get user media", err.message, err.code, err);
296
+ }
248
297
  }
249
298
  }
250
299
  }
@@ -261,6 +310,7 @@ enum PeerEvent {
261
310
  Connected = "peer-user-connected",
262
311
  ReceiveVideo = "receive-video",
263
312
  Disconnected = "peer-user-disconnected",
313
+ UserJoined = "user-joined",
264
314
  }
265
315
 
266
316
  class ReceiveVideoEvent {
@@ -293,7 +343,7 @@ enum CallDirection {
293
343
  class CallHandle extends EventDispatcher {
294
344
  readonly userId: string;
295
345
  readonly direction: CallDirection;
296
- readonly call: Peer.MediaConnection;
346
+ readonly call: MediaConnection;
297
347
  get stream() { return this._stream; };
298
348
 
299
349
  private _stream: MediaStream | null = null;
@@ -314,14 +364,15 @@ class CallHandle extends EventDispatcher {
314
364
  return !this.isOpen;
315
365
  }
316
366
 
317
- constructor(userId: string, call: Peer.MediaConnection, direction: CallDirection) {
367
+ constructor(userId: string, call: MediaConnection, direction: CallDirection) {
318
368
  super();
319
369
  this.userId = userId;
320
370
  this.call = call;
321
371
  this.direction = direction;
322
372
  this._stream = null;
323
373
  call.on("stream", stream => {
324
- console.log("Receive video", stream.getAudioTracks(), stream.getVideoTracks());
374
+ if (debug)
375
+ console.log("Receive video", stream.getAudioTracks(), stream.getVideoTracks());
325
376
  this._stream = stream;
326
377
  if (direction === CallDirection.Incoming) {
327
378
  const args: ReceiveVideoEvent = new ReceiveVideoEvent(stream, this);
@@ -392,7 +443,7 @@ class PeerHandle extends EventDispatcher {
392
443
  private _enabled: boolean = false;
393
444
  private _enabledPeer: boolean = false;
394
445
  private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
395
- private onUserJoinedOrLeftRoomFn: Function = this.onUserJoinedOrLeftRoom.bind(this);
446
+ // private onUserJoinedOrLeftRoomFn: Function = this.onUserJoinedOrLeftRoom.bind(this);
396
447
  private onPeerConnectFn: (id) => void = this.onPeerConnect.bind(this);
397
448
  private onPeerReceiveCallFn: (call) => void = this.onPeerReceivingCall.bind(this);
398
449
  // private _connectionPeerIdMap : Map<string, string> = new Map();
@@ -401,8 +452,8 @@ class PeerHandle extends EventDispatcher {
401
452
  if (this._enabled) return;
402
453
  this._enabled = true;
403
454
  this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onConnectRoomFn);
404
- this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
405
- this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
455
+ // this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
456
+ // this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
406
457
  this.subscribePeerEvents();
407
458
  }
408
459
 
@@ -410,8 +461,8 @@ class PeerHandle extends EventDispatcher {
410
461
  if (!this._enabled) return;
411
462
  this._enabled = false;
412
463
  this.context.connection.stopListen(RoomEvents.JoinedRoom, this.onConnectRoomFn);
413
- this.context.connection.stopListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
414
- this.context.connection.stopListen(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
464
+ // this.context.connection.stopListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
465
+ // this.context.connection.stopListen(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
415
466
  this.unsubscribePeerEvents();
416
467
  }
417
468
 
@@ -419,8 +470,8 @@ class PeerHandle extends EventDispatcher {
419
470
  this.setupPeer();
420
471
  };
421
472
 
422
- private onUserJoinedOrLeftRoom(_: UserJoinedOrLeftRoomModel): void {
423
- };
473
+ // private onUserJoinedOrLeftRoom(_: UserJoinedOrLeftRoomModel): void {
474
+ // };
424
475
 
425
476
  private setupPeer() {
426
477
  if (!this.context.connection.connectionId) return;
@@ -428,7 +479,9 @@ class PeerHandle extends EventDispatcher {
428
479
  this._enabledPeer = true;
429
480
  if (!this._peer) {
430
481
  const peerId = this.getMyPeerId();
431
- this._peer = new Peer(peerId);
482
+ if(peerId)
483
+ this._peer = new Peer(peerId);
484
+ else console.error("Failed to setup peerjs because we dont have a connection id", this.context.connection.connectionId);
432
485
  }
433
486
  if (this._enabled)
434
487
  this.subscribePeerEvents();
@@ -439,10 +492,14 @@ class PeerHandle extends EventDispatcher {
439
492
  this._peer.on("open", this.onPeerConnectFn);
440
493
  this._peer.on("call", this.onPeerReceiveCallFn);
441
494
  // this.context.connection.beginListen(PeerEvent.Connected, this.onRemotePeerConnect.bind(this));
495
+ // TODO: make connection to all current active calls even if the user is not anymore in the needle room
442
496
  }
443
497
 
444
498
  private unsubscribePeerEvents() {
445
- // TODO: unsubscribe
499
+ if (!this._peer) return;
500
+ this._peer.off("open", this.onPeerConnectFn);
501
+ this._peer.off("call", this.onPeerReceiveCallFn);
502
+ // this.context.connection.stopListen(PeerEvent.Connected, this.onRemotePeerConnect.bind(this));
446
503
  }
447
504
 
448
505
  private onPeerConnect(id): void {
@@ -451,12 +508,12 @@ class PeerHandle extends EventDispatcher {
451
508
  this.context.connection.send(PeerEvent.Connected, new PeerUserConnectedModel(this, id));
452
509
  }
453
510
 
454
- private onPeerReceivingCall(call: Peer.MediaConnection): void {
511
+ private onPeerReceivingCall(call: MediaConnection): void {
455
512
  call.answer();
456
513
  this.registerCall(call, CallDirection.Incoming);
457
514
  }
458
515
 
459
- private registerCall(call: Peer.MediaConnection, direction: CallDirection): CallHandle {
516
+ private registerCall(call: MediaConnection, direction: CallDirection): CallHandle {
460
517
 
461
518
  const meta = call.metadata;
462
519
  if (!meta || !meta.userId) {
@@ -464,8 +521,8 @@ class PeerHandle extends EventDispatcher {
464
521
  }
465
522
  const userId = meta.userId;
466
523
 
467
- if (direction === CallDirection.Incoming) console.log("Receive call from", call.metadata);
468
- else console.log("Make call to", call.metadata);
524
+ if (direction === CallDirection.Incoming && debug) console.log("Receive call from", call.metadata);
525
+ else if (debug) console.log("Make call to", call.metadata);
469
526
 
470
527
  const arr = direction === CallDirection.Incoming ? this._incomingCalls : this._outgoingCalls;
471
528
  const handle = new CallHandle(userId, call, direction);
@@ -474,7 +531,8 @@ class PeerHandle extends EventDispatcher {
474
531
  console.error("Call error", err);
475
532
  });
476
533
  call.on("close", () => {
477
- console.log("Call ended", call.metadata);
534
+ if (debug)
535
+ console.log("Call ended", call.metadata);
478
536
  call.close();
479
537
  const index = arr.indexOf(handle);
480
538
  if (index !== -1)
@@ -489,8 +547,11 @@ class PeerHandle extends EventDispatcher {
489
547
 
490
548
  call.on("stream", () => {
491
549
  // workaround for https://github.com/peers/peerjs/issues/636
550
+ let intervalCounter = 0;
492
551
  let closeInterval = setInterval(() => {
493
- if (!handle.isOpen) {
552
+ const isFirstInterval = intervalCounter === 0;
553
+ if (!handle.isOpen && isFirstInterval) {
554
+ intervalCounter += 1;
494
555
  clearInterval(closeInterval);
495
556
  handle.close();
496
557
  }
@@ -542,46 +603,68 @@ class NetworkedVideo extends EventDispatcher {
542
603
  if (_steam) {
543
604
  const calls = this._sendingVideoStreams.get(_steam);
544
605
  if (calls) {
545
- console.log("Closing calls", calls);
606
+ if (debug)
607
+ console.log("Closing calls", calls);
546
608
  for (const call of calls) {
547
609
  call.close();
548
610
  }
549
611
  }
550
612
  this._sendingVideoStreams.delete(_steam);
551
- if (calls)
613
+ if (calls && debug)
552
614
  console.log("Currently sending", this._sendingVideoStreams);
553
615
  }
554
616
  }
555
617
 
556
- private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
557
- private onUserConnectedFn: Function = this.onUserConnected.bind(this);
558
- private onUserLeftFn: Function = this.onUserLeft.bind(this);
618
+ // private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
619
+ // private onUserConnectedFn: Function = this.onUserConnected.bind(this);
620
+ // private onUserLeftFn: Function = this.onUserLeft.bind(this);
559
621
 
560
622
  enable() {
561
623
  this.peer.enable();
562
- this.context.connection.beginListen(PeerEvent.Connected, this.onUserConnectedFn);
563
- this.peer.addEventListener("receive-video", this.onReceiveVideo.bind(this));
624
+ this.peer.addEventListener(PeerEvent.ReceiveVideo, this.onReceiveVideo);
625
+ // this.peer.addEventListener(PeerEvent.UserJoined, this.onUserJoinedPeer);
626
+ this.context.connection.beginListen(PeerEvent.Connected, this.onUserConnected);
627
+ this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onJoinedRoom);
628
+ this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onJoinedRoom);
629
+ this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.onUserLeft);
564
630
  }
565
631
 
566
632
  disable() {
567
633
  this.peer.disable();
568
- // this.context.connection.stopListening(RoomEvents.UserJoinedRoom, this.onUserConnectedFn);
569
- // this.context.connection.stopListening(RoomEvents.UserLeftRoom, this.onUserLeftFn);
570
- }
634
+ this.peer.removeEventListener(PeerEvent.ReceiveVideo, this.onReceiveVideo);
635
+ // this.peer.removeEventListener(PeerEvent.UserJoined, this.onUserJoinedPeer);
636
+ this.context.connection.stopListen(PeerEvent.Connected, this.onUserConnected);
637
+ this.context.connection.stopListen(RoomEvents.JoinedRoom, this.onJoinedRoom);
638
+ this.context.connection.stopListen(RoomEvents.UserJoinedRoom, this.onJoinedRoom);
639
+ this.context.connection.stopListen(RoomEvents.UserLeftRoom, this.onUserLeft);
640
+ }
641
+
642
+ // private onUserJoinedPeer = (evt) => {
643
+ // if (!this.context.connection.isConnected && evt.userId) {
644
+ // this.startCallWithUserIfNotAlready(evt.userId);
645
+ // }
646
+ // }
571
647
 
572
- private onReceiveVideo(evt) {
573
- console.log("RECEIVE VIDEO", evt);
574
- this.dispatchEvent({ type: "receive-video", target: this, stream: evt.stream, userId: evt.userId });
648
+ // When either we ourselves OR someone else is joining the room we want to make sure to re-establish all calls
649
+ // and if the user that joined is not yet receiving our video stream we want to start a stream with them
650
+ // https://github.com/needle-tools/needle-tiny/issues/697#issuecomment-1510425539
651
+ private onJoinedRoom = (evt) => {
652
+ if (debug) console.log(`${evt.userId} joined room and I'm currently sending ${this._sendingVideoStreams.size} streams`);
653
+ if (this._sendingVideoStreams.size > 0)
654
+ this.updateSendingCalls();
575
655
  }
576
656
 
577
- private onConnectRoom() {
578
-
657
+ private onReceiveVideo = (evt) => {
658
+ if (debug)
659
+ console.log("RECEIVE VIDEO", evt);
660
+ this.dispatchEvent({ type: PeerEvent.ReceiveVideo, target: this, stream: evt.stream, userId: evt.userId });
579
661
  }
580
662
 
581
- private onUserConnected(user: PeerUserConnectedModel) {
663
+ private onUserConnected = (user: PeerUserConnectedModel) => {
582
664
  // console.log(this.peer.id, user.guid)
583
665
  if (this.peer.id === user.guid) {
584
- console.log("USER CONNECTED", user);
666
+ if (debug)
667
+ console.log("USER CONNECTED", user.guid, user);
585
668
  const stream = this._sendingVideoStreams.keys().next().value;
586
669
  this.peer.makeCall(user.peerId, stream);
587
670
  }
@@ -598,7 +681,8 @@ class NetworkedVideo extends EventDispatcher {
598
681
  for (const userId of this.context.connection.usersInRoom()) {
599
682
  if (userId === this.context.connection.connectionId) continue;
600
683
  const existing = calls.find(c => c.userId === userId);
601
- if (!existing) {
684
+ if (!existing || existing.stream?.active === false) {
685
+ if (debug) console.log("Starting call to", userId)
602
686
  const handle = this.peer.makeCall(this.peer.getPeerIdFromUserId(userId), stream);
603
687
  if (handle) {
604
688
  startedNewCall = true;
@@ -610,11 +694,27 @@ class NetworkedVideo extends EventDispatcher {
610
694
  this._sendingVideoStreams.set(stream, calls);
611
695
  }
612
696
  this.stopCallsToUsersThatAreNotInTheRoomAnymore();
613
- if (startedNewCall) {
697
+ if (startedNewCall && debug) {
614
698
  console.log("Currently sending", this._sendingVideoStreams);
615
699
  }
616
700
  }
617
701
 
702
+ // private startCallWithUserIfNotAlready(userId: string) {
703
+ // for (const stream of this._sendingVideoStreams.keys()) {
704
+ // const calls = this._sendingVideoStreams.get(stream) || [];
705
+ // const existing = calls.find(c => c.userId === userId);
706
+ // if (!existing || existing.stream?.active === false) {
707
+ // if (debug) console.log("Starting call to", userId)
708
+ // const handle = this.peer.makeCall(this.peer.getPeerIdFromUserId(userId), stream);
709
+ // if (handle) {
710
+ // calls.push(handle);
711
+ // return true;
712
+ // }
713
+ // }
714
+ // }
715
+ // return false;
716
+ // }
717
+
618
718
  private stopCallsToUsersThatAreNotInTheRoomAnymore() {
619
719
  for (const stream of this._sendingVideoStreams.keys()) {
620
720
  const calls = this._sendingVideoStreams.get(stream);
@@ -5,7 +5,7 @@ import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader";
5
5
  import { EquirectangularRefractionMapping, sRGBEncoding, Texture, TextureLoader } from "three"
6
6
  import { syncField } from "../engine/engine_networking_auto";
7
7
  import { Camera } from "./Camera";
8
- import { getParam, getPath } from "../engine/engine_utils";
8
+ import { getParam, resolveUrl } from "../engine/engine_utils";
9
9
 
10
10
  const debug = getParam("debugskybox");
11
11
 
@@ -56,7 +56,7 @@ export class RemoteSkybox extends Behaviour {
56
56
  if(debug) console.log("Remote skybox url?: " + url);
57
57
 
58
58
  if (!url.startsWith("http") && !url.startsWith("www.") && !url.startsWith("data:")) {
59
- url = getPath(this.sourceId, url);
59
+ url = resolveUrl(this.sourceId, url);
60
60
  if(debug) console.log("Remote skybox resolved to " + url);
61
61
  }
62
62
 
@@ -104,8 +104,7 @@ export class SyncedRoom extends Behaviour {
104
104
  if (this.context.connection.isConnected) {
105
105
  if (this.context.time.time - this._lastPingTime > 3) {
106
106
  this._lastPingTime = this.context.time.time;
107
- // console.log("PING");
108
- this.context.connection.send("ping", { time: this.context.time.time });
107
+ this.context.connection.sendPing();
109
108
  }
110
109
 
111
110
  if (this.context.connection.isInRoom) {
@@ -3,7 +3,7 @@ import * as THREE from "three";
3
3
  import { serializable } from "../engine/engine_serialization_decorator";
4
4
  import { LinearFilter, Material, Mesh, Object3D, RawShaderMaterial, ShaderMaterial, Texture, TextureLoader, Vector2, Vector4, VideoTexture } from "three";
5
5
  import { awaitInput } from "../engine/engine_input_utils";
6
- import { getParam } from "../engine/engine_utils";
6
+ import { getParam, resolveUrl } from "../engine/engine_utils";
7
7
  import { Renderer } from "./Renderer";
8
8
  import { getWorldScale } from "../engine/engine_three_utils";
9
9
  import { ObjectUtils, PrimitiveType } from "../engine/engine_create_objects";
@@ -49,11 +49,12 @@ export class VideoPlayer extends Behaviour {
49
49
  renderer: THREE.Object3D | null = null;
50
50
  @serializable()
51
51
  playOnAwake: boolean = true;
52
- @serializable()
53
- playOnEnable?: boolean;
54
52
 
55
53
  @serializable()
56
54
  aspectMode: AspectMode = AspectMode.None;
55
+
56
+ @serializable(URL)
57
+ private clip?: string | MediaStream | null = null;
57
58
 
58
59
  @serializable()
59
60
  private renderMode?: VideoRenderMode;
@@ -105,8 +106,13 @@ export class VideoPlayer extends Behaviour {
105
106
  get isPlaying(): boolean {
106
107
  const video = this._videoElement;
107
108
  if (video) {
108
- return video.currentTime > 0 && !video.paused && !video.ended
109
- && video.readyState > video.HAVE_CURRENT_DATA;
109
+ if(video.currentTime > 0 && !video.paused && !video.ended
110
+ && video.readyState > video.HAVE_CURRENT_DATA)
111
+ return true;
112
+ else if(video.srcObject){
113
+ const stream = video.srcObject as MediaStream;
114
+ if(stream.active) return true;
115
+ }
110
116
  }
111
117
  return false;
112
118
  }
@@ -134,12 +140,20 @@ export class VideoPlayer extends Behaviour {
134
140
  return this._videoElement;
135
141
  }
136
142
 
143
+ get muted() {
144
+ return this._videoElement?.muted ?? this._muted;
145
+ }
146
+ set muted(val: boolean) {
147
+ this._muted = val;
148
+ if (this._videoElement) this._videoElement.muted = val;
149
+ }
150
+ private _muted: boolean = false;
151
+
137
152
  private _crossOrigin: string | null = "anonymous";
138
153
 
139
154
  private audioOutputMode: VideoAudioOutputMode = VideoAudioOutputMode.AudioSource;
140
155
 
141
156
  private source!: VideoSource;
142
- private clip?: string | MediaStream | null = null;
143
157
  private url?: string | null = null;
144
158
 
145
159
  private _videoElement: HTMLVideoElement | null = null;
@@ -157,7 +171,7 @@ export class VideoPlayer extends Behaviour {
157
171
  // TODO: how to prevent interruption error when another video is already playing
158
172
  this._videoElement.srcObject = video;
159
173
  if (this._isPlaying)
160
- this._videoElement.play();
174
+ this.play();
161
175
  this.updateAspect();
162
176
  }
163
177
  }
@@ -195,7 +209,7 @@ export class VideoPlayer extends Behaviour {
195
209
  }
196
210
 
197
211
  onEnable(): void {
198
- if (this.playOnEnable === true) {
212
+ if (this.playOnAwake === true) {
199
213
  this.handleBeginPlaying(true);
200
214
  }
201
215
  if (this.screenspace) {
@@ -245,7 +259,13 @@ export class VideoPlayer extends Behaviour {
245
259
  if (!this._receivedInput) this._videoElement.muted = true;
246
260
  this.updateVideoElementSettings();
247
261
  this._videoElement?.play().catch(err => {
248
- console.warn(err);
262
+ // https://developer.chrome.com/blog/play-request-was-interrupted/
263
+ if(debug)
264
+ console.error("Error playing video", err, "CODE=" + err.code, this.videoElement?.src, this);
265
+ setTimeout(() => {
266
+ if (this._isPlaying && !this.destroyed && this.activeAndEnabled)
267
+ this.play();
268
+ }, 1000);
249
269
  });
250
270
  if (debug) console.log("play", this._videoElement);
251
271
  }
@@ -399,8 +419,10 @@ export class VideoPlayer extends Behaviour {
399
419
  this._videoElement.playbackRate = this._playbackSpeed;
400
420
  // dont open in fullscreen on ios
401
421
  this._videoElement.playsInline = true;
402
- this._videoElement.muted = !this._receivedInput && this.audioOutputMode !== VideoAudioOutputMode.None;
403
- if (this.playOnAwake || this.playOnEnable)
422
+ let muted = !this._receivedInput && this.audioOutputMode !== VideoAudioOutputMode.None;
423
+ if(!muted && this._muted) muted = true;
424
+ this._videoElement.muted = muted;
425
+ if (this.playOnAwake)
404
426
  this._videoElement.autoplay = true;
405
427
  }
406
428