@needle-tools/engine 2.29.0-pre → 2.31.0-pre

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 (101) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/needle-engine.d.ts +847 -757
  3. package/dist/needle-engine.js +347 -343
  4. package/dist/needle-engine.js.map +4 -4
  5. package/dist/needle-engine.min.js +16 -12
  6. package/dist/needle-engine.min.js.map +4 -4
  7. package/lib/engine/engine.d.ts +1 -0
  8. package/lib/engine/engine_physics.d.ts +19 -8
  9. package/lib/engine/engine_physics.js +57 -48
  10. package/lib/engine/engine_physics.js.map +1 -1
  11. package/lib/engine/engine_serialization.d.ts +1 -0
  12. package/lib/engine/engine_serialization.js +1 -0
  13. package/lib/engine/engine_serialization.js.map +1 -1
  14. package/lib/engine/engine_setup.d.ts +5 -0
  15. package/lib/engine/engine_setup.js +6 -0
  16. package/lib/engine/engine_setup.js.map +1 -1
  17. package/lib/engine/engine_utils.d.ts +1 -1
  18. package/lib/engine/engine_utils.js +25 -8
  19. package/lib/engine/engine_utils.js.map +1 -1
  20. package/lib/engine/extensions/NEEDLE_deferred_texture.d.ts +1 -1
  21. package/lib/engine/extensions/NEEDLE_deferred_texture.js +26 -14
  22. package/lib/engine/extensions/NEEDLE_deferred_texture.js.map +1 -1
  23. package/lib/engine/extensions/extension_utils.js +24 -13
  24. package/lib/engine/extensions/extension_utils.js.map +1 -1
  25. package/lib/engine/extensions/extensions.js +3 -1
  26. package/lib/engine/extensions/extensions.js.map +1 -1
  27. package/lib/engine-components/Camera.js +7 -1
  28. package/lib/engine-components/Camera.js.map +1 -1
  29. package/lib/engine-components/Component.d.ts +10 -0
  30. package/lib/engine-components/Component.js +53 -0
  31. package/lib/engine-components/Component.js.map +1 -1
  32. package/lib/engine-components/OrbitControls.js +2 -1
  33. package/lib/engine-components/OrbitControls.js.map +1 -1
  34. package/lib/engine-components/Renderer.d.ts +1 -0
  35. package/lib/engine-components/Renderer.js +10 -3
  36. package/lib/engine-components/Renderer.js.map +1 -1
  37. package/lib/engine-components/Rigidbody.d.ts +1 -1
  38. package/lib/engine-components/Rigidbody.js +3 -3
  39. package/lib/engine-components/Rigidbody.js.map +1 -1
  40. package/lib/engine-components/ScreenCapture.d.ts +35 -5
  41. package/lib/engine-components/ScreenCapture.js +542 -25
  42. package/lib/engine-components/ScreenCapture.js.map +1 -1
  43. package/lib/engine-components/SpectatorCamera.js +29 -5
  44. package/lib/engine-components/SpectatorCamera.js.map +1 -1
  45. package/lib/engine-components/SyncedRoom.js +2 -0
  46. package/lib/engine-components/SyncedRoom.js.map +1 -1
  47. package/lib/engine-components/VideoPlayer.d.ts +14 -1
  48. package/lib/engine-components/VideoPlayer.js +86 -16
  49. package/lib/engine-components/VideoPlayer.js.map +1 -1
  50. package/lib/engine-components/Volume.d.ts +4 -0
  51. package/lib/engine-components/Volume.js +44 -3
  52. package/lib/engine-components/Volume.js.map +1 -1
  53. package/lib/engine-components/WebARSessionRoot.d.ts +9 -2
  54. package/lib/engine-components/WebARSessionRoot.js +69 -24
  55. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  56. package/lib/engine-components/WebXR.d.ts +6 -3
  57. package/lib/engine-components/WebXR.js +42 -7
  58. package/lib/engine-components/WebXR.js.map +1 -1
  59. package/lib/engine-components/WebXRAvatar.js +4 -0
  60. package/lib/engine-components/WebXRAvatar.js.map +1 -1
  61. package/lib/engine-components/WebXRController.js +13 -7
  62. package/lib/engine-components/WebXRController.js.map +1 -1
  63. package/lib/engine-components/ui/CanvasGroup.d.ts +1 -0
  64. package/lib/engine-components/ui/CanvasGroup.js +1 -0
  65. package/lib/engine-components/ui/CanvasGroup.js.map +1 -1
  66. package/lib/engine-components/ui/EventSystem.js +13 -4
  67. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  68. package/lib/engine-components/ui/Graphic.d.ts +1 -0
  69. package/lib/engine-components/ui/Graphic.js +2 -0
  70. package/lib/engine-components/ui/Graphic.js.map +1 -1
  71. package/lib/engine-components/ui/Interfaces.d.ts +2 -0
  72. package/lib/needle-engine.d.ts +1 -0
  73. package/lib/needle-engine.js +1 -0
  74. package/lib/needle-engine.js.map +1 -1
  75. package/package.json +2 -2
  76. package/src/engine/engine_physics.ts +74 -57
  77. package/src/engine/engine_serialization.ts +3 -1
  78. package/src/engine/engine_setup.ts +6 -0
  79. package/src/engine/engine_utils.ts +34 -8
  80. package/src/engine/extensions/NEEDLE_deferred_texture.ts +25 -19
  81. package/src/engine/extensions/extension_utils.ts +24 -12
  82. package/src/engine/extensions/extensions.ts +3 -2
  83. package/src/engine-components/Camera.ts +9 -2
  84. package/src/engine-components/Component.ts +58 -0
  85. package/src/engine-components/OrbitControls.ts +2 -1
  86. package/src/engine-components/Renderer.ts +11 -3
  87. package/src/engine-components/RigidBody.ts +2 -2
  88. package/src/engine-components/ScreenCapture.ts +610 -28
  89. package/src/engine-components/SpectatorCamera.ts +28 -5
  90. package/src/engine-components/SyncedRoom.ts +1 -0
  91. package/src/engine-components/VideoPlayer.ts +123 -23
  92. package/src/engine-components/Volume.ts +47 -4
  93. package/src/engine-components/WebARSessionRoot.ts +78 -28
  94. package/src/engine-components/WebXR.ts +50 -15
  95. package/src/engine-components/WebXRAvatar.ts +5 -0
  96. package/src/engine-components/WebXRController.ts +20 -14
  97. package/src/engine-components/ui/CanvasGroup.ts +2 -0
  98. package/src/engine-components/ui/EventSystem.ts +21 -15
  99. package/src/engine-components/ui/Graphic.ts +3 -0
  100. package/src/engine-components/ui/Interfaces.ts +2 -0
  101. package/src/needle-engine.ts +1 -0
@@ -1,47 +1,629 @@
1
1
  import { Behaviour, GameObject } from "./Component";
2
2
  import { VideoPlayer } from "./VideoPlayer";
3
+ import Peer from "peerjs"
4
+ import { Context } from "../engine/engine_setup";
5
+ import { RoomEvents } from "../engine/engine_networking";
6
+ import { UserJoinedOrLeftRoomModel } from "../engine/engine_networking";
7
+ import { serializeable } from "../engine/engine_serialization";
8
+ import { IModel } from "../engine/engine_networking";
9
+ import { IPointerClickHandler } from "./ui/PointerEvents";
10
+ import { EventDispatcher } from "three";
11
+ import { AudioSource } from "./AudioSource";
12
+ import { getWorldScale, setWorldScale } from "../engine/engine_three_utils";
3
13
 
4
- export class ScreenCapture extends Behaviour {
5
14
 
6
- streamOnAwake: boolean = true;
15
+ export enum ScreenCaptureDevice {
16
+ Screen = 0,
17
+ Camera = 1
18
+ }
7
19
 
8
- start() {
9
- if (this.streamOnAwake)
10
- this.open();
20
+ export enum ScreenCaptureMode {
21
+ Idle = 0,
22
+ Sending = 1,
23
+ Receiving = 2
24
+ }
25
+
26
+ export enum AspectMode {
27
+ None = 0,
28
+ AdjustHeight = 1,
29
+ AdjustWidth = 2,
30
+ }
31
+
32
+ function disposeStream(str: MediaStream | null | undefined) {
33
+ if (!str) return;
34
+ for (const cap of str.getTracks())
35
+ cap.stop();
36
+ }
37
+
38
+ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
39
+
40
+ onPointerClick() {
41
+ if (this.isSending) {
42
+ this.close();
43
+ return;
44
+ }
45
+ this.share();
11
46
  }
12
47
 
13
- private requestOpen: boolean = false;
14
- private captureStream: MediaStream | null = null;
15
48
 
16
- async open(force: boolean = false) {
17
- if (this.captureStream && !force) return;
18
- this.close();
19
- this.requestOpen = true;
49
+
50
+
51
+ @serializeable()
52
+ device: ScreenCaptureDevice = ScreenCaptureDevice.Screen;
53
+
54
+ @serializeable()
55
+ aspectMode: AspectMode = AspectMode.AdjustHeight;
56
+
57
+
58
+ get isSending() {
59
+ return this._currentStream && this._currentMode === ScreenCaptureMode.Sending;
60
+ }
61
+ get isReceiving() {
62
+ return this._currentStream && this._currentMode === ScreenCaptureMode.Receiving;
63
+ }
64
+
65
+ private _net?: NetworkedVideo;
66
+ private _video?: VideoPlayer;
67
+ private _requestOpen: boolean = false;
68
+ private _currentStream: MediaStream | null = null;
69
+ private _currentMode: ScreenCaptureMode = ScreenCaptureMode.Idle;
70
+
71
+ awake() {
72
+ AudioSource.registerWaitForAllowAudio(() => {
73
+ if (this._video && this._currentStream && this._currentMode === ScreenCaptureMode.Receiving) {
74
+ this._video.setVideo(this._currentStream);
75
+ }
76
+ });
77
+ }
78
+
79
+ start() {
80
+ this._video = GameObject.getComponentInChildren(this.gameObject, VideoPlayer) ?? undefined;
81
+ if (!this._video) {
82
+ console.error("Missing VideoPlayer component");
83
+ return;
84
+ }
85
+ const handle = PeerHandle.getOrCreate(this.context, this.guid);
86
+ this._net = new NetworkedVideo(this.context, handle);
87
+ this._net.enable();
88
+ //@ts-ignore
89
+ this._net.addEventListener(PeerEvent.ReceiveVideo, this.onReceiveVideo.bind(this));
90
+ }
91
+
92
+ async share() {
93
+ this._requestOpen = true;
20
94
  try {
21
- const player = GameObject.getComponentInChildren(this.gameObject, VideoPlayer);
22
- if (player) {
95
+ if (this._video) {
96
+
97
+ const settings: MediaTrackConstraints = {
98
+ echoCancellation: true,
99
+ autoGainControl: false,
100
+ };
23
101
  const displayMediaOptions: DisplayMediaStreamConstraints = {
24
- video: true,
25
- audio: false
102
+ video: settings,
103
+ audio: settings,
26
104
  };
27
- this.captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
28
- if (this.captureStream && this.requestOpen) {
29
- //@ts-ignore
30
- player.clip = this.captureStream;
31
- player.create(true);
105
+
106
+ switch (this.device) {
107
+ case ScreenCaptureDevice.Camera:
108
+ this.tryShareUserCamera(displayMediaOptions);
109
+ break;
110
+
111
+ case ScreenCaptureDevice.Screen:
112
+ if (!navigator.mediaDevices.getDisplayMedia) {
113
+ console.error("No getDisplayMedia support");
114
+ return;
115
+ }
116
+ const myVideo = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
117
+ if (this._requestOpen) {
118
+ this.setVideo(myVideo, ScreenCaptureMode.Sending);
119
+ }
120
+ else disposeStream(myVideo);
121
+ break;
122
+ }
123
+ }
124
+ } catch (err: any) {
125
+ if (err.name === "NotAllowedError") {
126
+ // user cancelled stream selection
127
+ console.log("Selection cancelled");
128
+ this._requestOpen = false;
129
+ return;
130
+ }
131
+ console.error("Error opening video", err);
132
+ }
133
+ }
134
+
135
+ close() {
136
+ this._requestOpen = false;
137
+ if (this._currentStream) {
138
+ console.warn("Close current stream / disposing resources");
139
+ disposeStream(this._currentStream);
140
+ this._net?.stopSendingVideo(this._currentStream);
141
+ this._currentMode = ScreenCaptureMode.Idle;
142
+ this._currentStream = null;
143
+ }
144
+ }
145
+
146
+ private setVideo(stream: MediaStream, mode: ScreenCaptureMode) {
147
+ if (stream === this._currentStream) return;
148
+ this.close();
149
+ if (!stream || !this._video) return;
150
+ this._currentStream = stream;
151
+ this._requestOpen = true;
152
+ this._currentMode = mode;
153
+ this._video.setVideo(stream);
154
+ if (mode === ScreenCaptureMode.Sending)
155
+ this._net?.startSendingVideo(stream);
156
+
157
+ this._targetObjects = undefined;
158
+ this.updateAspect();
159
+ }
160
+
161
+ private onReceiveVideo(evt: ReceiveVideoEvent) {
162
+ this.setVideo(evt.stream, ScreenCaptureMode.Receiving);
163
+ }
164
+
165
+
166
+
167
+
168
+ private async tryShareUserCamera(opts: DisplayMediaStreamConstraints) {
169
+
170
+ // let newWindow = open('', 'example', 'width=300,height=300');
171
+ // if (window) {
172
+ // newWindow!.document.body.innerHTML = "Please allow access to your camera and microphone";
173
+ // }
174
+
175
+ // TODO: allow user to select device
176
+ const devices = (await navigator.mediaDevices.enumerateDevices()).filter(d => d.kind === "videoinput");
177
+ console.log("Request camera", devices);
178
+ for (const dev of devices) {
179
+ try {
180
+ if (!this._requestOpen) break;
181
+ if (dev.kind !== "videoinput") continue;
182
+ const id = dev.deviceId;
183
+ if (opts.video !== false) {
184
+ if (typeof opts.video === "undefined" || typeof opts.video === "boolean") {
185
+ opts.video = {};
186
+ }
187
+ opts.video.deviceId = id;
188
+ }
189
+ const userMedia = await navigator.mediaDevices.getUserMedia(opts);
190
+ if (this._requestOpen) {
191
+ this.setVideo(userMedia, ScreenCaptureMode.Sending);
192
+ }
193
+ else disposeStream(userMedia);
194
+ console.log("Selected camera", dev);
195
+ break;
196
+ }
197
+ catch (err) {
198
+ console.warn(err);
199
+ }
200
+ }
201
+ }
202
+ // private _cameraSelectionWindow : Window | null = null;
203
+ // private openWindowToSelectCamera(){
204
+
205
+ // }
206
+
207
+
208
+
209
+
210
+ private _targetObjects?: Array<THREE.Object3D>;
211
+
212
+ private updateAspect() {
213
+ this.startCoroutine(this.updateAspectImpl());
214
+ }
215
+
216
+ private _updateAspectRoutineId: number = -1;
217
+ private *updateAspectImpl() {
218
+ const id = ++this._updateAspectRoutineId;
219
+ while (this.aspectMode !== AspectMode.None && this._currentStream && id === this._updateAspectRoutineId) {
220
+ const video = this._video;
221
+ const stream = this._currentStream;
222
+ if (!video) return;
223
+ if (!stream) return;
224
+ let aspect: number | undefined = undefined;
225
+ for (const track of stream.getVideoTracks()) {
226
+ const settings = track.getSettings();
227
+ if (settings.width && settings.height) {
228
+ aspect = settings.width / settings.height;
229
+ break;
230
+ }
231
+ }
232
+ if (aspect === undefined) return;
233
+ if (!this._targetObjects)
234
+ this._targetObjects = video.getTargetObjects();
235
+ for (const obj of this._targetObjects) {
236
+ let worldAspect = 1;
237
+ if (obj.parent) {
238
+ const parentScale = getWorldScale(obj.parent);
239
+ worldAspect = parentScale.x / parentScale.y;
240
+ }
241
+ switch (this.aspectMode) {
242
+ case AspectMode.AdjustHeight:
243
+ obj.scale.y = 1 / aspect * obj.scale.x * worldAspect;
244
+ break;
245
+ case AspectMode.AdjustWidth:
246
+ obj.scale.x = aspect * obj.scale.y * worldAspect;
247
+ break;
32
248
  }
33
249
  }
34
- } catch (err) {
35
- console.error("Error: " + err);
250
+ yield;
36
251
  }
37
252
  }
253
+ }
254
+
255
+
256
+ /////// PEER
257
+
258
+ enum PeerEvent {
259
+ Connected = "peer-user-connected",
260
+ ReceiveVideo = "receive-video",
261
+ Disconnected = "peer-user-disconnected",
262
+ }
263
+
264
+ class ReceiveVideoEvent {
265
+ readonly type = PeerEvent.ReceiveVideo;
266
+ readonly stream: MediaStream;
267
+ readonly target: CallHandle;
268
+ constructor(stream: MediaStream, target: CallHandle) {
269
+ this.stream = stream
270
+ this.target = target;
271
+ }
272
+ }
273
+
274
+ class PeerUserConnectedModel implements IModel {
275
+ /** the peer handle id */
276
+ readonly guid: string;
277
+ readonly peerId: string;
278
+ // internal so server doesnt save it to persistent storage
279
+ readonly dontSave: boolean = true;
280
+ constructor(handle: PeerHandle, peerId: string) {
281
+ this.guid = handle.id;
282
+ this.peerId = peerId;
283
+ }
284
+ }
285
+
286
+ enum CallDirection {
287
+ Incoming = "incoming",
288
+ Outgoing = "outgoing",
289
+ }
290
+
291
+ class CallHandle extends EventDispatcher {
292
+ readonly userId: string;
293
+ readonly direction: CallDirection;
294
+ readonly call: Peer.MediaConnection;
295
+ get stream() { return this._stream; };
296
+
297
+ private _stream: MediaStream | null = null;
298
+ private _isDisposed: boolean = false;
38
299
 
39
300
  close() {
40
- this.requestOpen = false;
41
- if (this.captureStream) {
42
- for (const cap of this.captureStream.getTracks())
43
- cap.stop();
44
- this.captureStream = null;
301
+ if (this._isDisposed) return;
302
+ this._isDisposed = true;
303
+ this.call.close();
304
+ disposeStream(this._stream);
305
+ }
306
+
307
+ get isOpen() {
308
+ return this.call.peerConnection?.connectionState === "connected";// && this._stream?.active;
309
+ }
310
+
311
+ get isClosed() {
312
+ return !this.isOpen;
313
+ }
314
+
315
+ constructor(userId: string, call: Peer.MediaConnection, direction: CallDirection) {
316
+ super();
317
+ this.userId = userId;
318
+ this.call = call;
319
+ this.direction = direction;
320
+ this._stream = null;
321
+ call.on("stream", stream => {
322
+ console.log("Receive video", stream.getAudioTracks(), stream.getVideoTracks());
323
+ this._stream = stream;
324
+ if (direction === CallDirection.Incoming) {
325
+ const args: ReceiveVideoEvent = new ReceiveVideoEvent(stream, this);
326
+ this.dispatchEvent(args);
327
+ }
328
+ });
329
+ }
330
+ }
331
+
332
+ class PeerHandle extends EventDispatcher {
333
+
334
+ private static readonly instances: Map<string, PeerHandle> = new Map();
335
+
336
+ static getOrCreate(context: Context, guid: string): PeerHandle {
337
+ // if (id === undefined) {
338
+ // // randomId
339
+ // id = Math.random().toFixed(5);
340
+ // }
341
+ if (PeerHandle.instances.has(guid))
342
+ return PeerHandle.instances.get(guid)!;
343
+ const peer = new PeerHandle(context, guid);
344
+ PeerHandle.instances.set(guid, peer);
345
+ return peer;
346
+ }
347
+
348
+ getMyPeerId(): string | undefined {
349
+ if (this.context.connection.connectionId)
350
+ return this.getPeerIdFromUserId(this.context.connection.connectionId);
351
+ return undefined;
352
+ }
353
+
354
+ getPeerIdFromUserId(userConnectionId: string): string {
355
+ // we build the peer id ourselves so we dont need to wait for peer to report it
356
+ return this.id + "-" + userConnectionId;
357
+ }
358
+
359
+ getUserIdFromPeerId(peerId: string): string {
360
+ return peerId.substring(this.id.length + 1);
361
+ }
362
+
363
+ makeCall(peerId: string, stream: MediaStream): CallHandle | undefined {
364
+ const opts = { metadata: { userId: this.context.connection.connectionId } };
365
+ const call = this._peer?.call(peerId, stream, opts);
366
+ if (call)
367
+ return this.registerCall(call, CallDirection.Outgoing);
368
+ return undefined;
369
+ }
370
+
371
+ get peer(): Peer | undefined { return this._peer; }
372
+
373
+ readonly id: string;
374
+ readonly context: Context;
375
+ private _peer: Peer | undefined;
376
+ private _incomingCalls: CallHandle[] = [];
377
+ private _outgoingCalls: CallHandle[] = [];
378
+
379
+ private constructor(context: Context, id: string) {
380
+ super();
381
+ this.context = context;
382
+ this.id = id;
383
+ this.setupPeer();
384
+ navigator["getUserMedia"] = (
385
+ navigator["getUserMedia"] || navigator["webkitGetUserMedia"] ||
386
+ navigator["mozGetUserMedia"] || navigator["msGetUserMedia"]
387
+ );
388
+ }
389
+
390
+ private _enabled: boolean = false;
391
+ private _enabledPeer: boolean = false;
392
+ private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
393
+ private onUserJoinedOrLeftRoomFn: Function = this.onUserJoinedOrLeftRoom.bind(this);
394
+ private onPeerConnectFn: (id) => void = this.onPeerConnect.bind(this);
395
+ private onPeerReceiveCallFn: (call) => void = this.onPeerReceivingCall.bind(this);
396
+ // private _connectionPeerIdMap : Map<string, string> = new Map();
397
+
398
+ enable() {
399
+ if (this._enabled) return;
400
+ this._enabled = true;
401
+ this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onConnectRoomFn);
402
+ this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
403
+ this.context.connection.beginListen(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
404
+ this.subscribePeerEvents();
405
+ }
406
+
407
+ disable() {
408
+ if (!this._enabled) return;
409
+ this._enabled = false;
410
+ this.context.connection.stopListening(RoomEvents.JoinedRoom, this.onConnectRoomFn);
411
+ this.context.connection.stopListening(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
412
+ this.context.connection.stopListening(RoomEvents.UserLeftRoom, this.onUserJoinedOrLeftRoomFn);
413
+ this.unsubscribePeerEvents();
414
+ }
415
+
416
+ private onConnectRoom(): void {
417
+ this.setupPeer();
418
+ };
419
+
420
+ private onUserJoinedOrLeftRoom(_: UserJoinedOrLeftRoomModel): void {
421
+ };
422
+
423
+ private setupPeer() {
424
+ if (!this.context.connection.connectionId) return;
425
+ if (this._enabledPeer) return;
426
+ this._enabledPeer = true;
427
+ if (!this._peer) {
428
+ const peerId = this.getMyPeerId();
429
+ this._peer = new Peer(peerId);
430
+ }
431
+ if (this._enabled)
432
+ this.subscribePeerEvents();
433
+ }
434
+
435
+ private subscribePeerEvents() {
436
+ if (!this._peer) return;
437
+ this._peer.on("open", this.onPeerConnectFn);
438
+ this._peer.on("call", this.onPeerReceiveCallFn);
439
+ // this.context.connection.beginListen(PeerEvent.Connected, this.onRemotePeerConnect.bind(this));
440
+ }
441
+
442
+ private unsubscribePeerEvents() {
443
+ // TODO: unsubscribe
444
+ }
445
+
446
+ private onPeerConnect(id): void {
447
+ console.log("Peer connected as", id);
448
+ this.context.connection.send(PeerEvent.Connected, new PeerUserConnectedModel(this, id));
449
+ }
450
+
451
+ private onPeerReceivingCall(call: Peer.MediaConnection): void {
452
+ call.answer();
453
+ this.registerCall(call, CallDirection.Incoming);
454
+ }
455
+
456
+ private registerCall(call: Peer.MediaConnection, direction: CallDirection): CallHandle {
457
+
458
+ const meta = call.metadata;
459
+ if (!meta || !meta.userId) {
460
+ console.error("Missing call metadata", call);
461
+ }
462
+ const userId = meta.userId;
463
+
464
+ if (direction === CallDirection.Incoming) console.log("Receive call from", call.metadata);
465
+ else console.log("Make call to", call.metadata);
466
+
467
+ const arr = direction === CallDirection.Incoming ? this._incomingCalls : this._outgoingCalls;
468
+ const handle = new CallHandle(userId, call, direction);
469
+ arr.push(handle);
470
+ call.on("error", err => {
471
+ console.error("Call error", err);
472
+ });
473
+ call.on("close", () => {
474
+ console.log("Call ended", call.metadata);
475
+ call.close();
476
+ const index = arr.indexOf(handle);
477
+ if (index !== -1)
478
+ arr.splice(index, 1);
479
+ });
480
+
481
+ if (direction === CallDirection.Incoming) {
482
+
483
+ handle.addEventListener(PeerEvent.ReceiveVideo, e => {
484
+ this.dispatchEvent(e);
485
+ });
486
+
487
+ call.on("stream", () => {
488
+ // workaround for https://github.com/peers/peerjs/issues/636
489
+ let closeInterval = setInterval(() => {
490
+ if (!handle.isOpen) {
491
+ clearInterval(closeInterval);
492
+ handle.close();
493
+ }
494
+ }, 2000);
495
+ });
496
+ }
497
+ return handle;
498
+ }
499
+
500
+ // private onRemotePeerConnect(user: PeerUserConnectedModel) {
501
+ // console.log("other user connected", user);
502
+ // }
503
+ }
504
+
505
+
506
+ // type UserVideoCall = {
507
+ // call: Peer.MediaConnection;
508
+ // stream: MediaStream;
509
+ // userId: string;
510
+ // }
511
+
512
+ // type IncomingStreamArgs = {
513
+ // stream: MediaStream;
514
+ // userId: string;
515
+ // }
516
+
517
+ class NetworkedVideo extends EventDispatcher {
518
+
519
+ private readonly context: Context;
520
+ private readonly peer: PeerHandle;
521
+
522
+ // private _receiveVideoStreamListeners: Array<(info: IncomingStreamArgs) => void> = [];
523
+ private _sendingVideoStreams: Map<MediaStream, CallHandle[]> = new Map();
524
+
525
+ constructor(context: Context, peer: PeerHandle) {
526
+ super();
527
+ this.context = context;
528
+ this.peer = peer;
529
+ }
530
+
531
+ startSendingVideo(stream: MediaStream) {
532
+ if (!this._sendingVideoStreams.has(stream)) {
533
+ this._sendingVideoStreams.set(stream, []);
534
+ this.updateSendingCalls();
535
+ };
536
+ }
537
+
538
+ stopSendingVideo(_steam: MediaStream | undefined | null) {
539
+ if (_steam) {
540
+ const calls = this._sendingVideoStreams.get(_steam);
541
+ if (calls) {
542
+ for (const call of calls) {
543
+ call.close();
544
+ }
545
+ }
546
+ this._sendingVideoStreams.delete(_steam);
547
+ if (calls)
548
+ console.log("Currently sending", this._sendingVideoStreams);
549
+ }
550
+ }
551
+
552
+ private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
553
+ private onUserConnectedFn: Function = this.onUserConnected.bind(this);
554
+ private onUserLeftFn: Function = this.onUserLeft.bind(this);
555
+
556
+ enable() {
557
+ this.peer.enable();
558
+ this.context.connection.beginListen(PeerEvent.Connected, this.onUserConnectedFn);
559
+ this.peer.addEventListener("receive-video", this.onReceiveVideo.bind(this));
560
+ }
561
+
562
+ disable() {
563
+ this.peer.disable();
564
+ // this.context.connection.stopListening(RoomEvents.UserJoinedRoom, this.onUserConnectedFn);
565
+ // this.context.connection.stopListening(RoomEvents.UserLeftRoom, this.onUserLeftFn);
566
+ }
567
+
568
+ private onReceiveVideo(evt) {
569
+ console.log("RECEIVE VIDEO", evt);
570
+ this.dispatchEvent({ type: "receive-video", target: this, stream: evt.stream, userId: evt.userId });
571
+ }
572
+
573
+ private onConnectRoom() {
574
+
575
+ }
576
+
577
+ private onUserConnected(user: PeerUserConnectedModel) {
578
+ // console.log(this.peer.id, user.guid)
579
+ if (this.peer.id === user.guid) {
580
+ console.log("USER CONNECTED", user);
581
+ const stream = this._sendingVideoStreams.keys().next().value;
582
+ this.peer.makeCall(user.peerId, stream);
45
583
  }
46
584
  }
47
- }
585
+
586
+ private onUserLeft(_: UserJoinedOrLeftRoomModel) {
587
+ this.stopCallsToUsersThatAreNotInTheRoomAnymore();
588
+ }
589
+
590
+ private updateSendingCalls() {
591
+ let startedNewCall = false;
592
+ for (const stream of this._sendingVideoStreams.keys()) {
593
+ const calls = this._sendingVideoStreams.get(stream) || [];
594
+ for (const userId of this.context.connection.usersInRoom()) {
595
+ if (userId === this.context.connection.connectionId) continue;
596
+ const existing = calls.find(c => c.userId === userId);
597
+ if (!existing) {
598
+ const handle = this.peer.makeCall(this.peer.getPeerIdFromUserId(userId), stream);
599
+ if (handle) {
600
+ startedNewCall = true;
601
+ calls.push(handle);
602
+ }
603
+ }
604
+ }
605
+
606
+ this._sendingVideoStreams.set(stream, calls);
607
+ }
608
+ this.stopCallsToUsersThatAreNotInTheRoomAnymore();
609
+ if (startedNewCall) {
610
+ console.log("Currently sending", this._sendingVideoStreams);
611
+ }
612
+ }
613
+
614
+ private stopCallsToUsersThatAreNotInTheRoomAnymore() {
615
+ for (const stream of this._sendingVideoStreams.keys()) {
616
+ const calls = this._sendingVideoStreams.get(stream);
617
+ if (!calls) continue;
618
+ for (let i = calls.length - 1; i >= 0; i--) {
619
+ const call = calls[i];
620
+ if (!this.context.connection.userIsInRoom(call.userId)) {
621
+ call.close();
622
+ calls.splice(i, 1);
623
+ }
624
+ }
625
+ }
626
+ }
627
+
628
+ // const call = peer.call(peerId, stream);
629
+ }