@needle-tools/engine 3.0.1-alpha.4 → 3.0.1-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/dist/needle-engine.js +24914 -23954
- package/dist/needle-engine.min.js +443 -443
- package/dist/needle-engine.umd.cjs +440 -440
- package/lib/engine/api.d.ts +1 -0
- package/lib/engine/api.js +1 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/debug/debug_overlay.js +3 -0
- package/lib/engine/debug/debug_overlay.js.map +1 -1
- package/lib/engine/engine_context.d.ts +2 -0
- package/lib/engine/engine_context.js +3 -0
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_element.d.ts +1 -1
- package/lib/engine/engine_element.js +2 -2
- package/lib/engine/engine_element.js.map +1 -1
- package/lib/engine/engine_element_loading.d.ts +7 -2
- package/lib/engine/engine_element_loading.js +67 -22
- package/lib/engine/engine_element_loading.js.map +1 -1
- package/lib/engine/engine_license.d.ts +1 -1
- package/lib/engine/engine_license.js +6 -19
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +1 -0
- package/lib/engine/engine_networking.js +3 -0
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +2 -3
- package/lib/engine-components/AudioSource.js +26 -31
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/CameraUtils.js.map +1 -1
- package/lib/engine-components/ParticleSystem.js +15 -6
- package/lib/engine-components/ParticleSystem.js.map +1 -1
- package/lib/engine-components/ParticleSystemModules.d.ts +1 -0
- package/lib/engine-components/ParticleSystemModules.js +22 -11
- package/lib/engine-components/ParticleSystemModules.js.map +1 -1
- package/lib/engine-components/ScreenCapture.d.ts +1 -0
- package/lib/engine-components/ScreenCapture.js +145 -46
- package/lib/engine-components/ScreenCapture.js.map +1 -1
- package/lib/engine-components/SyncedRoom.js +1 -2
- package/lib/engine-components/SyncedRoom.js.map +1 -1
- package/lib/engine-components/VideoPlayer.d.ts +3 -0
- package/lib/engine-components/VideoPlayer.js +29 -5
- package/lib/engine-components/VideoPlayer.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/DepthOfField.d.ts +1 -0
- package/lib/engine-components/postprocessing/Effects/DepthOfField.js +15 -1
- package/lib/engine-components/postprocessing/Effects/DepthOfField.js.map +1 -1
- package/lib/engine-components/postprocessing/VolumeParameter.d.ts +1 -0
- package/lib/engine-components/postprocessing/VolumeParameter.js +4 -0
- package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +3 -1
- package/lib/engine-components/timeline/PlayableDirector.js +40 -2
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +2 -2
- package/lib/engine-components/timeline/TimelineTracks.js +12 -14
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/ui/Text.js +0 -1
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/plugins/vite/index.js +2 -0
- package/plugins/vite/license.js +24 -0
- package/src/engine/api.ts +1 -1
- package/src/engine/codegen/register_types.js +2 -2
- package/src/engine/debug/debug_overlay.ts +2 -0
- package/src/engine/engine_context.ts +4 -1
- package/src/engine/engine_element.ts +2 -2
- package/src/engine/engine_element_loading.ts +68 -22
- package/src/engine/engine_license.ts +6 -17
- package/src/engine/engine_networking.ts +4 -0
- package/src/engine-components/AudioSource.ts +28 -37
- package/src/engine-components/CameraUtils.ts +2 -2
- package/src/engine-components/ParticleSystem.ts +18 -6
- package/src/engine-components/ParticleSystemModules.ts +23 -11
- package/src/engine-components/ScreenCapture.ts +149 -49
- package/src/engine-components/SyncedRoom.ts +1 -2
- package/src/engine-components/VideoPlayer.ts +27 -5
- package/src/engine-components/postprocessing/Effects/DepthOfField.ts +18 -6
- package/src/engine-components/postprocessing/VolumeParameter.ts +5 -0
- package/src/engine-components/timeline/PlayableDirector.ts +38 -2
- package/src/engine-components/timeline/TimelineTracks.ts +13 -16
- package/src/engine-components/ui/Text.ts +0 -1
- package/lib/engine/codegen/license.json +0 -1
- package/license-2447137e.js +0 -4
- package/src/engine/codegen/license.json +0 -1
|
@@ -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("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
283
|
+
if (debug)
|
|
284
|
+
console.log("Selected camera", dev);
|
|
244
285
|
break;
|
|
245
286
|
}
|
|
246
|
-
catch (err) {
|
|
247
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
563
|
-
this.peer.addEventListener(
|
|
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
|
-
|
|
569
|
-
// this.
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
-
|
|
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) {
|
|
@@ -105,8 +105,13 @@ export class VideoPlayer extends Behaviour {
|
|
|
105
105
|
get isPlaying(): boolean {
|
|
106
106
|
const video = this._videoElement;
|
|
107
107
|
if (video) {
|
|
108
|
-
|
|
109
|
-
&& video.readyState > video.HAVE_CURRENT_DATA
|
|
108
|
+
if(video.currentTime > 0 && !video.paused && !video.ended
|
|
109
|
+
&& video.readyState > video.HAVE_CURRENT_DATA)
|
|
110
|
+
return true;
|
|
111
|
+
else if(video.srcObject){
|
|
112
|
+
const stream = video.srcObject as MediaStream;
|
|
113
|
+
if(stream.active) return true;
|
|
114
|
+
}
|
|
110
115
|
}
|
|
111
116
|
return false;
|
|
112
117
|
}
|
|
@@ -134,6 +139,15 @@ export class VideoPlayer extends Behaviour {
|
|
|
134
139
|
return this._videoElement;
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
get muted() {
|
|
143
|
+
return this._videoElement?.muted ?? this._muted;
|
|
144
|
+
}
|
|
145
|
+
set muted(val: boolean) {
|
|
146
|
+
this._muted = val;
|
|
147
|
+
if (this._videoElement) this._videoElement.muted = val;
|
|
148
|
+
}
|
|
149
|
+
private _muted: boolean = false;
|
|
150
|
+
|
|
137
151
|
private _crossOrigin: string | null = "anonymous";
|
|
138
152
|
|
|
139
153
|
private audioOutputMode: VideoAudioOutputMode = VideoAudioOutputMode.AudioSource;
|
|
@@ -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.
|
|
174
|
+
this.play();
|
|
161
175
|
this.updateAspect();
|
|
162
176
|
}
|
|
163
177
|
}
|
|
@@ -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
|
-
|
|
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,7 +419,9 @@ 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
|
-
|
|
422
|
+
let muted = !this._receivedInput && this.audioOutputMode !== VideoAudioOutputMode.None;
|
|
423
|
+
if(!muted && this._muted) muted = true;
|
|
424
|
+
this._videoElement.muted = muted;
|
|
403
425
|
if (this.playOnAwake || this.playOnEnable)
|
|
404
426
|
this._videoElement.autoplay = true;
|
|
405
427
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { DepthOfFieldEffect } from "postprocessing";
|
|
2
|
-
import { PerspectiveCamera, Vector3 } from "three";
|
|
3
|
-
import { getWorldPosition } from "../../../engine/engine_three_utils";
|
|
4
2
|
import { serializable } from "../../../engine/engine_serialization";
|
|
5
3
|
import { Mathf } from "../../../engine/engine_math";
|
|
6
4
|
import { PostProcessingEffect } from "../PostProcessingEffect";
|
|
7
5
|
import { VolumeParameter } from "../VolumeParameter";
|
|
8
6
|
import { registerCustomEffectType } from "../VolumeProfile";
|
|
7
|
+
import { isMobileDevice } from "../../../engine/engine_utils";
|
|
9
8
|
|
|
10
9
|
export enum DepthOfFieldMode {
|
|
11
10
|
Off = 0,
|
|
@@ -20,7 +19,7 @@ export class DepthOfField extends PostProcessingEffect {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
@serializable()
|
|
23
|
-
mode
|
|
22
|
+
mode!: DepthOfFieldMode;
|
|
24
23
|
|
|
25
24
|
@serializable(VolumeParameter)
|
|
26
25
|
focusDistance!: VolumeParameter;
|
|
@@ -34,13 +33,16 @@ export class DepthOfField extends PostProcessingEffect {
|
|
|
34
33
|
@serializable(VolumeParameter)
|
|
35
34
|
gaussianMaxRadius!: VolumeParameter;
|
|
36
35
|
|
|
36
|
+
@serializable(VolumeParameter)
|
|
37
|
+
resolutionScale?: VolumeParameter;
|
|
38
|
+
|
|
37
39
|
init() {
|
|
38
40
|
this.focalLength.valueProcessor = v => {
|
|
39
41
|
const t = v / 300;
|
|
40
42
|
const max = 2;// this.context.mainCameraComponent?.farClipPlane ?? 10;
|
|
41
43
|
return Mathf.lerp(max, .01, t);
|
|
42
44
|
};
|
|
43
|
-
|
|
45
|
+
|
|
44
46
|
const maxBokehScale = 20;
|
|
45
47
|
this.aperture.valueProcessor = v => {
|
|
46
48
|
const t = 1 - v / 32;
|
|
@@ -49,7 +51,16 @@ export class DepthOfField extends PostProcessingEffect {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
onCreateEffect() {
|
|
52
|
-
if(this.mode === DepthOfFieldMode.Off) return undefined;
|
|
54
|
+
if (this.mode === DepthOfFieldMode.Off) return undefined;
|
|
55
|
+
|
|
56
|
+
const factor = 1 / window.devicePixelRatio;
|
|
57
|
+
|
|
58
|
+
if (this.resolutionScale === undefined) {
|
|
59
|
+
let defaultValue = 1;
|
|
60
|
+
if(isMobileDevice()) defaultValue = .6;
|
|
61
|
+
this.resolutionScale = new VolumeParameter(defaultValue * factor);
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
// console.log(this.focusDistance.overrideState, this.focusDistance.value);
|
|
54
65
|
// const depth = new DepthEffect({
|
|
55
66
|
// inverted: true,
|
|
@@ -59,7 +70,7 @@ export class DepthOfField extends PostProcessingEffect {
|
|
|
59
70
|
worldFocusRange: .2,
|
|
60
71
|
focalLength: 1,
|
|
61
72
|
bokehScale: 20,
|
|
62
|
-
resolutionScale:
|
|
73
|
+
resolutionScale: this.resolutionScale.value,
|
|
63
74
|
});
|
|
64
75
|
|
|
65
76
|
this.focusDistance.onValueChanged = v => {
|
|
@@ -68,6 +79,7 @@ export class DepthOfField extends PostProcessingEffect {
|
|
|
68
79
|
this.focalLength.onValueChanged = v => dof.circleOfConfusionMaterial.worldFocusRange = v;
|
|
69
80
|
this.aperture.onValueChanged = v => dof.bokehScale = v;
|
|
70
81
|
|
|
82
|
+
if (this.resolutionScale) this.resolutionScale.onValueChanged = v => dof.resolution.scale = v;
|
|
71
83
|
|
|
72
84
|
return [dof];
|
|
73
85
|
}
|
|
@@ -5,6 +5,11 @@ export declare type VolumeParameterValueProcessor = (value: any) => any;
|
|
|
5
5
|
|
|
6
6
|
export class VolumeParameter {
|
|
7
7
|
|
|
8
|
+
constructor(value?: any) {
|
|
9
|
+
this._value = value;
|
|
10
|
+
this._defaultValue = value;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
@serializable()
|
|
9
14
|
get overrideState(): boolean {
|
|
10
15
|
return this._active;
|
|
@@ -6,7 +6,7 @@ import { AudioSource } from '../AudioSource';
|
|
|
6
6
|
import { SignalReceiver } from './SignalAsset';
|
|
7
7
|
import * as Models from "./TimelineModels";
|
|
8
8
|
import * as Tracks from "./TimelineTracks";
|
|
9
|
-
import { deepClone, getParam } from '../../engine/engine_utils';
|
|
9
|
+
import { deepClone, delay, getParam } from '../../engine/engine_utils';
|
|
10
10
|
import { GuidsMap } from '../../engine/engine_types';
|
|
11
11
|
import { Object3D } from 'three';
|
|
12
12
|
import { isLocalNetwork } from '../../engine/engine_networking_utils';
|
|
@@ -87,6 +87,8 @@ export class PlayableDirector extends Behaviour {
|
|
|
87
87
|
get speed(): number { return this._speed; }
|
|
88
88
|
set speed(value: number) { this._speed = value; }
|
|
89
89
|
|
|
90
|
+
/** When enabled the timeline will wait for audio tracks to load at the current time before starting to play */
|
|
91
|
+
waitForAudio: boolean = true;
|
|
90
92
|
|
|
91
93
|
private _visibilityChangeEvt?: any;
|
|
92
94
|
private _clonedPlayableAsset: boolean = false;
|
|
@@ -151,13 +153,28 @@ export class PlayableDirector extends Behaviour {
|
|
|
151
153
|
this.setupAndCreateTrackHandlers();
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
play() {
|
|
156
|
+
async play() {
|
|
155
157
|
if (!this.isValid()) return;
|
|
156
158
|
const pauseChanged = this._isPaused == true;
|
|
157
159
|
this._isPaused = false;
|
|
158
160
|
if (pauseChanged) this.invokePauseChangedMethodsOnTracks();
|
|
159
161
|
if (this._isPlaying) return;
|
|
160
162
|
this._isPlaying = true;
|
|
163
|
+
if (this.waitForAudio) {
|
|
164
|
+
// Make sure audio tracks have loaded at the current time
|
|
165
|
+
const promises: Array<Promise<any>> = [];
|
|
166
|
+
for (const track of this._audioTracks) {
|
|
167
|
+
const promise = track.loadAudio(this._time, 1, 0);
|
|
168
|
+
if (promise)
|
|
169
|
+
promises.push(promise);
|
|
170
|
+
}
|
|
171
|
+
if (promises.length > 0) {
|
|
172
|
+
await Promise.all(promises);
|
|
173
|
+
if (!this._isPlaying) return;
|
|
174
|
+
}
|
|
175
|
+
while(this._audioTracks.length > 0 && this._isPlaying && !AudioSource.userInteractionRegistered && this.waitForAudio)
|
|
176
|
+
await delay(200);
|
|
177
|
+
}
|
|
161
178
|
this._internalUpdateRoutine = this.startCoroutine(this.internalUpdate());
|
|
162
179
|
}
|
|
163
180
|
|
|
@@ -363,6 +380,25 @@ export class PlayableDirector extends Behaviour {
|
|
|
363
380
|
console.warn("Missing binding", binding, track.name, track.type, this.name, this.playableAsset.name);
|
|
364
381
|
}
|
|
365
382
|
}
|
|
383
|
+
if (track.type === Models.TrackType.Control) {
|
|
384
|
+
for (let i = 0; i < track.clips.length; i++) {
|
|
385
|
+
const clip = track.clips[i];
|
|
386
|
+
let binding = clip.asset.sourceObject;
|
|
387
|
+
if (typeof binding === "string") {
|
|
388
|
+
if (this._guidsMap && this._guidsMap[binding])
|
|
389
|
+
binding = this._guidsMap[binding];
|
|
390
|
+
const obj = GameObject.findByGuid(binding, root);
|
|
391
|
+
if (obj === null || typeof obj !== "object") {
|
|
392
|
+
console.warn("Failed to resolve sourceObject binding", binding, track.name, clip);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
if (debug)
|
|
396
|
+
console.log("Resolved binding", binding, "to", obj);
|
|
397
|
+
clip.asset.sourceObject = obj;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
366
402
|
}
|
|
367
403
|
}
|
|
368
404
|
|