@3dsource/angular-unreal-module 0.0.113-dev.0 → 0.0.113-dev.2
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.
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, ChangeDetectionStrategy, Component, Injectable, DestroyRef, effect, untracked, signal, computed, Pipe, input, ElementRef, viewChild, afterNextRender, HostListener, output, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
|
|
3
3
|
import { SourceButtonComponent, SourceLoadingComponent } from '@3dsource/source-ui-native';
|
|
4
|
-
import { merge, map, scan, concatMap, of, animationFrameScheduler, interval, Observable, switchMap, timer, take, timeout, mergeMap, EMPTY,
|
|
4
|
+
import { merge, map, scan, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, takeUntil as takeUntil$1, from, fromEvent, throwError, defer, retry, tap as tap$1, NEVER, startWith, throttleTime, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, pairwise, Subscription, bufferTime, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
5
5
|
import { createAction, props, createReducer, on, createFeature, createSelector, Store, provideState } from '@ngrx/store';
|
|
6
6
|
import { Actions, createEffect, ofType, provideEffects } from '@ngrx/effects';
|
|
7
|
-
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, takeUntil, catchError, withLatestFrom, exhaustMap, switchMap as switchMap$1, timeout as timeout$1, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1
|
|
7
|
+
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, takeUntil, catchError, withLatestFrom, exhaustMap, switchMap as switchMap$1, timeout as timeout$1, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
8
8
|
import { Logger, calculateMedian, clampf, lerp, getCanvasCached, getSnapshot, fitIntoRectangle, whereNot, Truthy, tapLog, Falsy, generateUuid, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, Semaphore, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
|
|
9
9
|
import { concatLatestFrom } from '@ngrx/operators';
|
|
10
10
|
import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
|
|
11
11
|
import { HttpClient } from '@angular/common/http';
|
|
12
12
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
13
13
|
import { MetaBoxCommand, EPayloadVersion } from '@3dsource/types-unreal';
|
|
14
|
-
import { toSignal,
|
|
14
|
+
import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
15
15
|
import { v4 } from 'uuid';
|
|
16
16
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
17
17
|
import { NgOptimizedImage, AsyncPipe, JsonPipe } from '@angular/common';
|
|
@@ -197,8 +197,11 @@ const DESTROY_DISCONNECT_REASONS = [
|
|
|
197
197
|
|
|
198
198
|
const DEBOUNCE_TO_MANY_RESIZE_CALLS = 100;
|
|
199
199
|
const SAME_SIZE_THRESHOLD = 1.01;
|
|
200
|
+
// Minimum container dimension in px — prevents division by zero in resize calculations
|
|
201
|
+
const MIN_CONTAINER_SIZE = 2;
|
|
200
202
|
const MINIMAL_FPS = 6;
|
|
201
203
|
const STREAMING_VIDEO_ID = 'streamingVideo';
|
|
204
|
+
const STREAMING_AUDIO_ID = 'streamingAudio';
|
|
202
205
|
const CONSOLE_COMMAND_ENABLE_MESSAGES = 'EnableAllScreenMessages';
|
|
203
206
|
const CONSOLE_COMMAND_DISABLE_MESSAGES = 'DisableAllScreenMessages';
|
|
204
207
|
const CONSOLE_COMMAND_PIXEL_QUALITY = 'PixelStreaming.FreezeFrameQuality 95';
|
|
@@ -229,7 +232,7 @@ const commandStarted = createAction(scoped$1 `command started`, props());
|
|
|
229
232
|
const commandCompleted = createAction(scoped$1 `command completed`, props());
|
|
230
233
|
|
|
231
234
|
const scoped = (templateString) => `[UNREAL] ${templateString[0]}`;
|
|
232
|
-
const trackMixpanelEvent = createAction(scoped `track
|
|
235
|
+
const trackMixpanelEvent = createAction(scoped `track Mixpanel event`, props());
|
|
233
236
|
const trackTelemetryData = createAction(scoped `track telemetry data`, props());
|
|
234
237
|
const setMaxFps = createAction(scoped `change fps`, props());
|
|
235
238
|
const destroyRemoteConnections = createAction(scoped `destroyRemoteConnections`, props());
|
|
@@ -275,9 +278,9 @@ const instanceReady = createAction(scoped `instance ready`);
|
|
|
275
278
|
const regionPingFetchStart = createAction(scoped `region ping fetch start`);
|
|
276
279
|
/**
|
|
277
280
|
* Dispatched once `RegionsPingService.getProviders()` returns the region
|
|
278
|
-
* list — i.e
|
|
281
|
+
* list — i.e., the fetch step is done and the actual per-region ping
|
|
279
282
|
* sweep is about to start. Splits the region-discovery waterfall into
|
|
280
|
-
* two legs: config fetch vs ping sweep. Observability-only.
|
|
283
|
+
* two legs: config fetch vs. ping sweep. Observability-only.
|
|
281
284
|
*/
|
|
282
285
|
const regionPingFetchEnd = createAction(scoped `region ping fetch end`);
|
|
283
286
|
/**
|
|
@@ -289,9 +292,9 @@ const regionPingFetchEnd = createAction(scoped `region ping fetch end`);
|
|
|
289
292
|
*/
|
|
290
293
|
const regionResolved = createAction(scoped `region resolved`, props());
|
|
291
294
|
/**
|
|
292
|
-
* Dispatched around the HTTP polling
|
|
295
|
+
* Dispatched around the HTTP polling roundup inside `getAwsInstance`
|
|
293
296
|
* (old orchestration flow). Only fires when polling is actually used
|
|
294
|
-
* (i.e
|
|
297
|
+
* (i.e., signaling URL is not a direct `ws://` / `wss://` endpoint).
|
|
295
298
|
* Observability-only — no state change.
|
|
296
299
|
*/
|
|
297
300
|
const pollingStarted = createAction(scoped `polling started`);
|
|
@@ -310,11 +313,7 @@ const setAfkTimerVisible = createAction(scoped `set afk timer visible`);
|
|
|
310
313
|
const setAfkTimerHide = createAction(scoped `set afk timer hide`);
|
|
311
314
|
const setAfkTimerCountdown = createAction(scoped `set afk timer count down`, props());
|
|
312
315
|
const showUnrealErrorMessage = createAction(scoped `show unreal error message`, props());
|
|
313
|
-
const initSignalling = createAction(scoped `init signalling`, (
|
|
314
|
-
resetDisconnectionReason: true,
|
|
315
|
-
}) => ({
|
|
316
|
-
resetDisconnectionReason: data.resetDisconnectionReason,
|
|
317
|
-
}));
|
|
316
|
+
const initSignalling = createAction(scoped `init signalling`, (resetDisconnectionReason = true) => ({ resetDisconnectionReason }));
|
|
318
317
|
const startStream = createAction(scoped `startStream`, props());
|
|
319
318
|
const resetConfig = createAction(scoped `reset config`);
|
|
320
319
|
const resetAfk = createAction(scoped `reset afk action`);
|
|
@@ -339,8 +338,9 @@ const dropDC = createAction(scoped `drop data channel`);
|
|
|
339
338
|
const dropPC = createAction(scoped `drop peer connection`);
|
|
340
339
|
const showLoadingProgress = createAction(scoped `show loading progress`, props());
|
|
341
340
|
const showResumeStreamPopup = createAction(scoped `show resume stream popup`, props());
|
|
342
|
-
const
|
|
343
|
-
const
|
|
341
|
+
const showUserInactivityTimeoutPopup = createAction(scoped `show user inactivity timeout popup`, props());
|
|
342
|
+
const setUserInactivityTimeout = createAction(scoped `set user inactivity timeout`, props());
|
|
343
|
+
const resetUserInactivityTimeout = createAction(scoped `reset user inactivity timeout`);
|
|
344
344
|
|
|
345
345
|
const initialState$1 = {
|
|
346
346
|
streamRequestContext: null,
|
|
@@ -372,7 +372,7 @@ const initialState$1 = {
|
|
|
372
372
|
autoStart: true,
|
|
373
373
|
warnTimeout: DEFAULT_AFK_TIMEOUT,
|
|
374
374
|
},
|
|
375
|
-
|
|
375
|
+
userInactivityTimeout: DEFAULT_AFK_TIMEOUT,
|
|
376
376
|
matchUrls: [],
|
|
377
377
|
streamClientCompanyId: '',
|
|
378
378
|
streamViewId: 'default',
|
|
@@ -385,9 +385,18 @@ const initialState$1 = {
|
|
|
385
385
|
showAfkTimeoutPopup: true,
|
|
386
386
|
};
|
|
387
387
|
const unrealReducer = createReducer(initialState$1, on(changeStatusMainVideoOnScene, (state, { isVideoPlaying }) => {
|
|
388
|
-
return { ...state, isVideoPlaying
|
|
388
|
+
return { ...state, isVideoPlaying };
|
|
389
389
|
}), on(setAwsInstance, (state, { instanceName, wsUrl, pollingUrl }) => {
|
|
390
|
-
return {
|
|
390
|
+
return {
|
|
391
|
+
...state,
|
|
392
|
+
awsInstance: {
|
|
393
|
+
...state.awsInstance,
|
|
394
|
+
instanceName,
|
|
395
|
+
wsUrl,
|
|
396
|
+
pollingUrl,
|
|
397
|
+
progressComplete: 0,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
391
400
|
}), on(setViewportReady, (state) => {
|
|
392
401
|
return {
|
|
393
402
|
...state,
|
|
@@ -414,15 +423,11 @@ const unrealReducer = createReducer(initialState$1, on(changeStatusMainVideoOnSc
|
|
|
414
423
|
},
|
|
415
424
|
};
|
|
416
425
|
}), on(disconnectStream, (state, errorMessage) => {
|
|
417
|
-
|
|
418
|
-
state.disconnectReason === DisconnectReason.Destroy
|
|
426
|
+
const alreadyHandled = state.dataChannelConnected ||
|
|
427
|
+
state.disconnectReason === DisconnectReason.Destroy;
|
|
428
|
+
if (alreadyHandled)
|
|
419
429
|
return state;
|
|
420
|
-
}
|
|
421
|
-
return {
|
|
422
|
-
...state,
|
|
423
|
-
errorMessage,
|
|
424
|
-
statusMessage: null,
|
|
425
|
-
};
|
|
430
|
+
return { ...state, errorMessage, statusMessage: null };
|
|
426
431
|
}), on(setFreezeFrameFromVideo, (state, freezeFrameFromVideo) => {
|
|
427
432
|
return {
|
|
428
433
|
...state,
|
|
@@ -449,14 +454,17 @@ const unrealReducer = createReducer(initialState$1, on(changeStatusMainVideoOnSc
|
|
|
449
454
|
}), on(resetWarnTimeout, (state) => {
|
|
450
455
|
return {
|
|
451
456
|
...state,
|
|
452
|
-
streamConfig: {
|
|
457
|
+
streamConfig: {
|
|
458
|
+
...state.streamConfig,
|
|
459
|
+
warnTimeout: state.userInactivityTimeout,
|
|
460
|
+
},
|
|
453
461
|
};
|
|
454
462
|
}), on(resetConfig, (state) => {
|
|
455
463
|
return {
|
|
456
464
|
...state,
|
|
457
465
|
streamConfig: {
|
|
458
466
|
...initialState$1.streamConfig,
|
|
459
|
-
warnTimeout: state.
|
|
467
|
+
warnTimeout: state.userInactivityTimeout,
|
|
460
468
|
},
|
|
461
469
|
};
|
|
462
470
|
}), on(initSignalling, (state, { resetDisconnectionReason = true }) => {
|
|
@@ -537,12 +545,12 @@ const unrealReducer = createReducer(initialState$1, on(changeStatusMainVideoOnSc
|
|
|
537
545
|
return { ...state, showLoadingProgress: visible };
|
|
538
546
|
}), on(showResumeStreamPopup, (state, { visible }) => {
|
|
539
547
|
return { ...state, showResumeStreamPopup: visible };
|
|
540
|
-
}), on(
|
|
548
|
+
}), on(showUserInactivityTimeoutPopup, (state, { visible }) => {
|
|
541
549
|
return { ...state, showAfkTimeoutPopup: visible };
|
|
542
|
-
}), on(
|
|
550
|
+
}), on(setUserInactivityTimeout, (state, { timeout }) => {
|
|
543
551
|
return {
|
|
544
552
|
...state,
|
|
545
|
-
|
|
553
|
+
userInactivityTimeout: timeout,
|
|
546
554
|
streamConfig: { ...state.streamConfig, warnTimeout: timeout },
|
|
547
555
|
};
|
|
548
556
|
}), on(setCirrusDisconnected, (state) => {
|
|
@@ -560,7 +568,7 @@ const unrealReducer = createReducer(initialState$1, on(changeStatusMainVideoOnSc
|
|
|
560
568
|
environmentId: state.environmentId,
|
|
561
569
|
streamRequestContext: state.streamRequestContext,
|
|
562
570
|
streamConfig: state.streamConfig,
|
|
563
|
-
|
|
571
|
+
userInactivityTimeout: state.userInactivityTimeout,
|
|
564
572
|
};
|
|
565
573
|
}), on(dropConnection, (state) => {
|
|
566
574
|
return { ...state, establishingConnection: false };
|
|
@@ -1079,63 +1087,6 @@ function getResizeValuesUniversal(params) {
|
|
|
1079
1087
|
return { w: makeEven(w), h: makeEven(h), x: 0, y: 0, scale: w / maxSpace.w };
|
|
1080
1088
|
}
|
|
1081
1089
|
|
|
1082
|
-
//TODO: Remove this legacy method after confirming that `getResizeValuesUniversal`
|
|
1083
|
-
// works well in all scenarios and updating any dependent code to use it instead.
|
|
1084
|
-
function getResizeValuesUniversalOld(params) {
|
|
1085
|
-
const { pixelRatio, containerWidth, containerHeight, maxResolution } = params;
|
|
1086
|
-
const maxSpace = {
|
|
1087
|
-
w: containerWidth * pixelRatio,
|
|
1088
|
-
h: containerHeight * pixelRatio,
|
|
1089
|
-
};
|
|
1090
|
-
const isVertical = containerWidth < containerHeight;
|
|
1091
|
-
let maxH = FULL_HD_HEIGHT;
|
|
1092
|
-
let maxW = FULL_HD_WIDTH;
|
|
1093
|
-
if (maxResolution) {
|
|
1094
|
-
maxH = FULL_HD_HEIGHT * 2;
|
|
1095
|
-
maxW = FULL_HD_WIDTH * 2;
|
|
1096
|
-
}
|
|
1097
|
-
const maxLimits = isVertical
|
|
1098
|
-
? { w: maxH, h: maxW, x: 0, y: 0 }
|
|
1099
|
-
: { w: maxW, h: maxH, x: 0, y: 0 };
|
|
1100
|
-
const props = fitIntoRectangle({ x: 0, y: 0, w: containerWidth, h: containerHeight }, { x: 0, y: 0, ...maxSpace });
|
|
1101
|
-
let out = { ...props };
|
|
1102
|
-
const fitIntoMax = fitIntoRectangle(props, maxLimits);
|
|
1103
|
-
if (fitIntoMax.scale < 1) {
|
|
1104
|
-
// If reducing size to 1920x1080 is required, do it
|
|
1105
|
-
out = fitIntoMax;
|
|
1106
|
-
}
|
|
1107
|
-
else {
|
|
1108
|
-
// Otherwise, use the lowest resolution because the layout size is smaller than 1920x1080
|
|
1109
|
-
out.scale = 1;
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Ensure that the size is always even.
|
|
1113
|
-
*/
|
|
1114
|
-
out.w = makeEven(out.w);
|
|
1115
|
-
out.h = makeEven(out.h);
|
|
1116
|
-
return { ...out };
|
|
1117
|
-
}
|
|
1118
|
-
function getResizeStudioValuesOld(params) {
|
|
1119
|
-
const { pixelRatio, containerWidth, containerHeight, studioWidth, studioHeight, } = params;
|
|
1120
|
-
const maxSpace = {
|
|
1121
|
-
w: containerWidth * pixelRatio,
|
|
1122
|
-
h: containerHeight * pixelRatio,
|
|
1123
|
-
};
|
|
1124
|
-
const videoSizePixelPerfect = fitIntoRectangle({ x: 0, y: 0, w: studioWidth, h: studioHeight }, { x: 0, y: 0, ...maxSpace });
|
|
1125
|
-
const out = fitIntoRectangle(videoSizePixelPerfect, {
|
|
1126
|
-
x: 0,
|
|
1127
|
-
y: 0,
|
|
1128
|
-
w: Math.min(maxSpace.w, FULL_HD_WIDTH),
|
|
1129
|
-
h: Math.min(maxSpace.h, FULL_HD_HEIGHT),
|
|
1130
|
-
});
|
|
1131
|
-
/**
|
|
1132
|
-
* Ensure that the size is always even.
|
|
1133
|
-
*/
|
|
1134
|
-
out.w = makeEven(out.w);
|
|
1135
|
-
out.h = makeEven(out.h);
|
|
1136
|
-
return out;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
1090
|
const removeExileCommands = (state, idToRemove) => {
|
|
1140
1091
|
const exileTimeout = 10000;
|
|
1141
1092
|
const out = { ...state };
|
|
@@ -1143,11 +1094,11 @@ const removeExileCommands = (state, idToRemove) => {
|
|
|
1143
1094
|
out.totalCommandsCompleted++;
|
|
1144
1095
|
const time = new Date().getTime();
|
|
1145
1096
|
const markForDelete = [];
|
|
1146
|
-
|
|
1097
|
+
for (const { id, timeStamp } of out.commandsInProgress) {
|
|
1147
1098
|
if (time - timeStamp > exileTimeout) {
|
|
1148
1099
|
markForDelete.push(id);
|
|
1149
1100
|
}
|
|
1150
|
-
}
|
|
1101
|
+
}
|
|
1151
1102
|
out.totalCommandsCompleted += markForDelete.length;
|
|
1152
1103
|
out.commandsInProgress = out.commandsInProgress.filter((command) => !markForDelete.includes(command.id));
|
|
1153
1104
|
return out;
|
|
@@ -1180,7 +1131,6 @@ const commandsFeature = createFeature({
|
|
|
1180
1131
|
reducer: commandsReducer,
|
|
1181
1132
|
});
|
|
1182
1133
|
|
|
1183
|
-
const selectCommandsInProgress = createSelector(commandsFeature.selectCommandsInProgress, (commandsInProgress) => commandsInProgress?.length ?? 0);
|
|
1184
1134
|
const selectCommandProgress = createSelector(commandsFeature.selectTotalCommandsStarted, commandsFeature.selectTotalCommandsCompleted, (totalCommandsStarted, totalCommandsCompleted) => totalCommandsStarted ? totalCommandsCompleted / totalCommandsStarted : 0);
|
|
1185
1135
|
|
|
1186
1136
|
const selectFreezeFrameDataUrlFromVideo = createSelector(unrealFeature.selectFreezeFrameFromVideo, (freezeFrameFromVideo) => freezeFrameFromVideo?.dataUrl || null);
|
|
@@ -1194,9 +1144,8 @@ const selectStreamConfig = createSelector(unrealFeature.selectStreamConfig, (con
|
|
|
1194
1144
|
const selectWarnTimeout = createSelector(selectStreamConfig, (config) => config?.warnTimeout || DEFAULT_AFK_TIMEOUT);
|
|
1195
1145
|
const selectIsExistMatchUrls = createSelector(unrealFeature.selectMatchUrls, (urls) => urls?.length > 0);
|
|
1196
1146
|
const selectIsAutostart = createSelector(selectStreamConfig, (config) => config?.autoStart);
|
|
1197
|
-
const selectShowReconnectPopup = createSelector(unrealFeature.selectCirrusConnected, unrealFeature.selectEstablishingConnection, (
|
|
1198
|
-
const selectShowLoader = createSelector(selectShowReconnectPopup, unrealFeature.selectViewportReady,
|
|
1199
|
-
const selectIsVideoPlayingAndDataChannelConnected = createSelector(unrealFeature.selectIsVideoPlaying, unrealFeature.selectDataChannelConnected, (isVideoPlaying, isDataChannelConnected) => isVideoPlaying && isDataChannelConnected);
|
|
1147
|
+
const selectShowReconnectPopup = createSelector(unrealFeature.selectCirrusConnected, unrealFeature.selectEstablishingConnection, (cirrusConnected, establishingConnection) => !cirrusConnected && !establishingConnection);
|
|
1148
|
+
const selectShowLoader = createSelector(selectShowReconnectPopup, unrealFeature.selectViewportReady, (showPopup, viewportReady) => !showPopup && !viewportReady);
|
|
1200
1149
|
const selectSignalingParameters = createSelector(unrealFeature.selectSsData, unrealFeature.selectAwsInstance, (data, instance) => {
|
|
1201
1150
|
const info = [];
|
|
1202
1151
|
if (data) {
|
|
@@ -1209,29 +1158,37 @@ const selectSignalingParameters = createSelector(unrealFeature.selectSsData, unr
|
|
|
1209
1158
|
return info.join('<br>');
|
|
1210
1159
|
});
|
|
1211
1160
|
const selectClientAndViewIds = createSelector(unrealFeature.selectStreamClientCompanyId, unrealFeature.selectStreamViewId, (clientId, viewId) => ({ clientId, viewId }));
|
|
1212
|
-
const
|
|
1161
|
+
const selectIsFullyConnected = createSelector(unrealFeature.selectViewportReady, unrealFeature.selectDataChannelConnected, unrealFeature.selectCirrusConnected, (viewportReady, dataChannelConnected, cirrusConnected) => viewportReady && dataChannelConnected && cirrusConnected);
|
|
1162
|
+
const selectIsLoaderScreenVisible = createSelector(selectIsFullyConnected, (fullyConnected) => !fullyConnected);
|
|
1213
1163
|
const selectCirrusConnectionId = createSelector(unrealFeature.selectSsData, (data) => data?.connectionId);
|
|
1214
1164
|
/**
|
|
1215
|
-
* Returns Scene load progress as a float value
|
|
1165
|
+
* Returns Scene load progress as a float value between 0 and 1.
|
|
1166
|
+
*
|
|
1167
|
+
* Progress is split into two phases:
|
|
1168
|
+
* 1. **Signaling** (0 → ~56%): connection polling or orchestration progress.
|
|
1169
|
+
* Starts at 0.1%, so the bar is never visually empty while connecting.
|
|
1170
|
+
* 2. **Commands** (~56% → 100%): Unreal command progress after the data channel connects.
|
|
1171
|
+
*
|
|
1172
|
+
* The split point bumps by +1% once the WebSocket URL is known,
|
|
1173
|
+
* producing a small visual step that indicates the WS handshake succeeded.
|
|
1216
1174
|
*/
|
|
1217
1175
|
const selectTotalProgress = createSelector(unrealFeature.selectStatusPercentSignallingServer, selectCommandProgress, unrealFeature.selectAwsInstance, unrealFeature.selectDataChannelConnected, (statusPercentSignallingServer, commandProgress, awsInstance, dataChannelConnected) => {
|
|
1218
|
-
// TODO make better mechanism to detect is new orchestration
|
|
1219
|
-
const isNewOrchestration = String(awsInstance?.wsUrl).match(/^wss:\/\/orchestration-websocket/i);
|
|
1220
|
-
const newOrchestrationProgressComplete = dataChannelConnected
|
|
1221
|
-
? 100
|
|
1222
|
-
: (awsInstance?.progressComplete || 0) * 100;
|
|
1223
|
-
/**
|
|
1224
|
-
* Set 57% {@link SIGNALLING_PERCENT_VALUE} immediately when signalingServer?.signalingUrl is available
|
|
1225
|
-
* Up to 56% {@link SIGNALLING_PERCENT_VALUE} - 1 of the progress is reserved for signaling server polling
|
|
1226
|
-
*/
|
|
1227
|
-
const signalingPercentValue = isNewOrchestration
|
|
1228
|
-
? newOrchestrationProgressComplete
|
|
1229
|
-
: statusPercentSignallingServer;
|
|
1230
1176
|
const SIGNALLING_MAX = SIGNALLING_PERCENT_VALUE / 100;
|
|
1177
|
+
const ORCHESTRATION_WS_PATTERN = /^wss:\/\/orchestration-websocket/i;
|
|
1178
|
+
const isNewOrchestration = ORCHESTRATION_WS_PATTERN.test(awsInstance?.wsUrl ?? '');
|
|
1179
|
+
// Orchestration progress is 0–1 from the backend; map to 0–1 directly.
|
|
1180
|
+
// Once the data channel is connected, signaling is fully complete (1.0).
|
|
1181
|
+
const signalingProgress = isNewOrchestration
|
|
1182
|
+
? dataChannelConnected
|
|
1183
|
+
? 1
|
|
1184
|
+
: (awsInstance?.progressComplete ?? 0)
|
|
1185
|
+
: (statusPercentSignallingServer ?? 0) / 100;
|
|
1186
|
+
// +1% bump once WS URL is known (visual step for WS handshake)
|
|
1231
1187
|
const splitPoint = awsInstance?.wsUrl
|
|
1232
1188
|
? SIGNALLING_MAX + 0.01
|
|
1233
1189
|
: SIGNALLING_MAX;
|
|
1234
|
-
|
|
1190
|
+
// 0.001 minimum so the bar is never completely empty while loading
|
|
1191
|
+
const progressSignaling = lerp(0.001, splitPoint, signalingProgress);
|
|
1235
1192
|
const progressRest = lerp(0, 1 - splitPoint, commandProgress);
|
|
1236
1193
|
return clampf(0, 1, progressSignaling + progressRest);
|
|
1237
1194
|
});
|
|
@@ -1284,37 +1241,12 @@ class UnrealEffects {
|
|
|
1284
1241
|
onDataChannelClose: this.unrealInitialConfig?.reconnect?.onDataChannelClose ??
|
|
1285
1242
|
DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE,
|
|
1286
1243
|
};
|
|
1287
|
-
this.disconnectReasonHandling$ = createEffect(() => {
|
|
1288
|
-
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => {
|
|
1289
|
-
// Skip DataChannelClosed if peer reconnection is enabled and signaling is connected
|
|
1290
|
-
// The peer reconnection effect will handle this case
|
|
1291
|
-
if (reason === DisconnectReason.DataChannelClosed &&
|
|
1292
|
-
this.reconnectConfig.enabled &&
|
|
1293
|
-
this.reconnectConfig.onDataChannelClose &&
|
|
1294
|
-
this.signallingService.isConnected()) {
|
|
1295
|
-
Logger.info('[Reconnect] Skipping destroy for DataChannelClosed - peer reconnection will handle');
|
|
1296
|
-
return false;
|
|
1297
|
-
}
|
|
1298
|
-
return DESTROY_DISCONNECT_REASONS.includes(reason);
|
|
1299
|
-
}), tap(({ reason, message }) => {
|
|
1300
|
-
Logger.log(`Disconnect Call=> reason:'${reason}', message:'${message}'`);
|
|
1301
|
-
}), map(({ reason }) => destroyRemoteConnections({ reason })));
|
|
1302
|
-
});
|
|
1303
1244
|
this.destroyConnections$ = createEffect(() => {
|
|
1304
1245
|
return this.actions$.pipe(ofType(destroyUnrealScene), tap(() => {
|
|
1305
1246
|
this.commandsSender.destroy();
|
|
1306
1247
|
this.videoService.setContainer(null);
|
|
1307
1248
|
}), map(() => destroyRemoteConnections({ reason: DisconnectReason.Destroy })));
|
|
1308
1249
|
});
|
|
1309
|
-
this.destroyRemoteConnections$ = createEffect(() => {
|
|
1310
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections), tap(({ reason }) => {
|
|
1311
|
-
this.signallingService.close({
|
|
1312
|
-
code: WSCloseCode_FORCE_CIRRUS_CLOSE,
|
|
1313
|
-
reason,
|
|
1314
|
-
});
|
|
1315
|
-
this.webRtcPlayerService.closePC();
|
|
1316
|
-
}), filter(({ reason }) => reason === DisconnectReason.DropConnection), map(() => dropConnection()));
|
|
1317
|
-
});
|
|
1318
1250
|
this.changeStreamResolution$ = createEffect(() => {
|
|
1319
1251
|
return this.actions$.pipe(ofType(changeStreamResolutionAction), filter((size) => !!(size?.width && size?.height)), concatLatestFrom(() => this.store.select(unrealFeature.selectStreamResolution)), filter(([newSizes, savedSizes]) => !(newSizes.width === savedSizes.width &&
|
|
1320
1252
|
newSizes.height === savedSizes.height)), map(([sizes]) => changeStreamResolutionSuccessAction({
|
|
@@ -1323,11 +1255,25 @@ class UnrealEffects {
|
|
|
1323
1255
|
})));
|
|
1324
1256
|
});
|
|
1325
1257
|
this.resetAfk$ = createEffect(() => {
|
|
1326
|
-
return this.actions$.pipe(ofType(setConfig,
|
|
1258
|
+
return this.actions$.pipe(ofType(setConfig, setUserInactivityTimeout, resetWarnTimeout, resetConfig), map(() => resetAfk()));
|
|
1327
1259
|
});
|
|
1328
1260
|
this.abortEstablishingConnection$ = createEffect(() => {
|
|
1329
|
-
return
|
|
1261
|
+
return this.actions$.pipe(ofType(setCirrusDisconnected, dropConnection, destroyUnrealScene), map(() => abortEstablishingConnection()));
|
|
1330
1262
|
});
|
|
1263
|
+
this.iframeMessaging$ = createEffect(() => {
|
|
1264
|
+
const cirrusConnected$ = this.store
|
|
1265
|
+
.select(unrealFeature.selectCirrusConnected)
|
|
1266
|
+
.pipe(skip(1));
|
|
1267
|
+
// On disconnect: immediately show 'Connection closed.', nullify after 2s
|
|
1268
|
+
const cirrusStatus$ = cirrusConnected$.pipe(switchMap((connected) => connected
|
|
1269
|
+
? of('Creating Socket...')
|
|
1270
|
+
: concat(of('Connection closed.'), of(null).pipe(delay(2000)))));
|
|
1271
|
+
// Skip initial false; emit null on every viewport state change
|
|
1272
|
+
const viewportReset$ = this.store
|
|
1273
|
+
.select(unrealFeature.selectViewportReady)
|
|
1274
|
+
.pipe(skip(1), delay(500), map(() => null));
|
|
1275
|
+
return merge(cirrusStatus$, this.store.select(unrealFeature.selectStatusMessage), viewportReset$).pipe(debounceTime(0), distinctUntilChanged(), tapLog('STATUS=>'), tap((status) => this.postToScreenLocker(status)));
|
|
1276
|
+
}, { dispatch: false });
|
|
1331
1277
|
this.setSignalingTo100$ = createEffect(() => {
|
|
1332
1278
|
return this.actions$.pipe(ofType(setAwsInstance), map(() => setStatusPercentSignallingServer({ percent: 100 })));
|
|
1333
1279
|
});
|
|
@@ -1377,8 +1323,35 @@ class UnrealEffects {
|
|
|
1377
1323
|
}));
|
|
1378
1324
|
}));
|
|
1379
1325
|
});
|
|
1326
|
+
/**
|
|
1327
|
+
* Effect: `forceViewportNotReady$`
|
|
1328
|
+
*
|
|
1329
|
+
* Ensures the viewport is explicitly marked as *not ready* to prevent
|
|
1330
|
+
* a temporary black screen flash when disconnecting.
|
|
1331
|
+
*
|
|
1332
|
+
* ### Why
|
|
1333
|
+
* - The video stream is disconnected immediately.
|
|
1334
|
+
* - The DataChannel closes slightly later.
|
|
1335
|
+
* - Closing the DataChannel resets the Unreal scene,
|
|
1336
|
+
* which also sets `viewPortReady` to `false`.
|
|
1337
|
+
*
|
|
1338
|
+
* Without this effect, the order of these events may cause
|
|
1339
|
+
* a visible flicker (video flashing to black).
|
|
1340
|
+
*/
|
|
1341
|
+
this.forceViewportNotReady$ = createEffect(() => {
|
|
1342
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), concatLatestFrom(() => this.store.select(unrealFeature.selectViewportReady)), filter(([, viewportReady]) => viewportReady), map(() => setViewportNotReady()));
|
|
1343
|
+
});
|
|
1380
1344
|
this.resetCirrusOnDestroy$ = createEffect(() => {
|
|
1381
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections), map(() => setCirrusDisconnected()));
|
|
1345
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), concatLatestFrom(() => this.store.select(unrealFeature.selectCirrusConnected)), filter(([, cirrusConnected]) => cirrusConnected), map(() => setCirrusDisconnected()));
|
|
1346
|
+
});
|
|
1347
|
+
this.destroyRemoteConnections$ = createEffect(() => {
|
|
1348
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), tap(({ reason }) => {
|
|
1349
|
+
this.signallingService.close({
|
|
1350
|
+
code: WSCloseCode_FORCE_CIRRUS_CLOSE,
|
|
1351
|
+
reason,
|
|
1352
|
+
});
|
|
1353
|
+
this.webRtcPlayerService.closePC();
|
|
1354
|
+
}), filter(({ reason }) => reason === DisconnectReason.DropConnection), map(() => dropConnection()));
|
|
1382
1355
|
});
|
|
1383
1356
|
this.destroyConnectionAndRestart$ = createEffect(() => {
|
|
1384
1357
|
return this.actions$.pipe(ofType(setCirrusDisconnected), concatLatestFrom(() => this.store.select(unrealFeature.selectDisconnectReason)), filter(([, reason]) => reason === DisconnectReason.DataChannelTimeout), map(() => initSignalling()));
|
|
@@ -1404,7 +1377,7 @@ class UnrealEffects {
|
|
|
1404
1377
|
});
|
|
1405
1378
|
this.commandTelemetryService.trackStartCommand('EXT-SetViewportReady');
|
|
1406
1379
|
}), switchMap(() =>
|
|
1407
|
-
// Wait for initial commands to be sent before sending loopback.
|
|
1380
|
+
// Wait for initial commands to be sent before sending the loopback.
|
|
1408
1381
|
// commandStarted fires for each sent command; debounce ensures
|
|
1409
1382
|
// we proceed only after no more commands are started for 100 ms.
|
|
1410
1383
|
this.actions$.pipe(ofType(commandStarted), debounceTime(100), take(1), switchMap(() => this.unrealCallbackService.observeCommandResponse({ command: MetaBoxCommand.FLoopBackCommand }, (data) => this.commandsSender.sendCommandToUnreal(data))), switchMap(() => this.videoService.videoStats$), filter(({ aggregatedStats: s }) => !!s.framesPerSecond &&
|
|
@@ -1417,23 +1390,8 @@ class UnrealEffects {
|
|
|
1417
1390
|
this.startStream$ = createEffect(() => {
|
|
1418
1391
|
return this.actions$.pipe(ofType(startStream), map(() => initSignalling()));
|
|
1419
1392
|
});
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
*
|
|
1423
|
-
* Ensures the viewport is explicitly marked as *not ready* to prevent
|
|
1424
|
-
* a temporary black screen flash when disconnecting.
|
|
1425
|
-
*
|
|
1426
|
-
* ### Why
|
|
1427
|
-
* - The video stream is disconnected immediately.
|
|
1428
|
-
* - The DataChannel closes slightly later.
|
|
1429
|
-
* - Closing the DataChannel resets the Unreal scene,
|
|
1430
|
-
* which also sets `viewPortReady` to `false`.
|
|
1431
|
-
*
|
|
1432
|
-
* Without this effect, the order of these events may cause
|
|
1433
|
-
* a visible flicker (video flashing to black).
|
|
1434
|
-
*/
|
|
1435
|
-
this.forceViewportNotReady$ = createEffect(() => {
|
|
1436
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections), map(() => setViewportNotReady()));
|
|
1393
|
+
this.resetAfkOnVideoStart$ = createEffect(() => {
|
|
1394
|
+
return this.actions$.pipe(ofType(videoStreamInitialized), filter(() => !!this.videoService.video?.paused), tap(() => Logger.info('Simulate Press Play')), map(() => resetAfk()));
|
|
1437
1395
|
});
|
|
1438
1396
|
/**
|
|
1439
1397
|
* Effect: `handleDataChannelClosed$`
|
|
@@ -1442,11 +1400,22 @@ class UnrealEffects {
|
|
|
1442
1400
|
* Only triggers if reconnection is enabled, onDataChannelClose is true,
|
|
1443
1401
|
* and the signaling WebSocket is still connected.
|
|
1444
1402
|
*/
|
|
1403
|
+
this.disconnectReasonHandling$ = createEffect(() => {
|
|
1404
|
+
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => {
|
|
1405
|
+
// Skip DataChannelClosed if peer reconnection will handle this case
|
|
1406
|
+
if (reason === DisconnectReason.DataChannelClosed &&
|
|
1407
|
+
this.canPeerReconnect()) {
|
|
1408
|
+
Logger.info('[Reconnect] Skipping destroy for DataChannelClosed - peer reconnection will handle');
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
return DESTROY_DISCONNECT_REASONS.includes(reason);
|
|
1412
|
+
}), tap(({ reason, message }) => {
|
|
1413
|
+
Logger.log(`Disconnect Call=> reason:'${reason}', message:'${message}'`);
|
|
1414
|
+
}), map(({ reason }) => destroyRemoteConnections({ reason })));
|
|
1415
|
+
});
|
|
1445
1416
|
this.handleDataChannelClosed$ = createEffect(() => {
|
|
1446
1417
|
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.DataChannelClosed &&
|
|
1447
|
-
this.
|
|
1448
|
-
this.reconnectConfig.onDataChannelClose &&
|
|
1449
|
-
this.signallingService.isConnected()), tap(() => {
|
|
1418
|
+
this.canPeerReconnect()), tap(() => {
|
|
1450
1419
|
Logger.warn('[Reconnect] DataChannel closed, initiating peer reconnection');
|
|
1451
1420
|
}), map(() => reconnectPeer({
|
|
1452
1421
|
attempt: 1,
|
|
@@ -1454,6 +1423,11 @@ class UnrealEffects {
|
|
|
1454
1423
|
delay: 0,
|
|
1455
1424
|
})));
|
|
1456
1425
|
});
|
|
1426
|
+
this.webrtcErrorModalComponent$ = createEffect(() => {
|
|
1427
|
+
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.WebRTCError), debounceTime(400), switchMap(() => this.store
|
|
1428
|
+
.select(unrealFeature.selectDataChannelConnected)
|
|
1429
|
+
.pipe(take(1), filter(Falsy))), tap(() => this.dialog.open(WebrtcErrorModalComponent)));
|
|
1430
|
+
}, { dispatch: false });
|
|
1457
1431
|
/**
|
|
1458
1432
|
* Effect: `resetStateForPeerReconnect$`
|
|
1459
1433
|
*
|
|
@@ -1525,11 +1499,6 @@ class UnrealEffects {
|
|
|
1525
1499
|
});
|
|
1526
1500
|
}));
|
|
1527
1501
|
}, { dispatch: false });
|
|
1528
|
-
this.webrtcErrorModalComponent$ = createEffect(() => {
|
|
1529
|
-
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.WebRTCError), debounceTime(400), switchMap(() => this.store
|
|
1530
|
-
.select(unrealFeature.selectDataChannelConnected)
|
|
1531
|
-
.pipe(take(1), filter(Falsy))), tap(() => this.dialog.open(WebrtcErrorModalComponent)));
|
|
1532
|
-
}, { dispatch: false });
|
|
1533
1502
|
this.sendStatisticsToUnreal$ = createEffect(() => {
|
|
1534
1503
|
return this.actions$.pipe(ofType(showUnrealErrorMessage), filter(({ code }) => code === 404), withLatestFrom(this.store.select(unrealFeature.selectAwsInstance), this.store.select(unrealFeature.selectStreamClientCompanyId), this.store.select(unrealFeature.selectStreamViewId)), switchMap(([action, instance, companyId, viewId]) => this.http
|
|
1535
1504
|
.post(this.unrealInitialConfig?.customErrorsEndpoint || '', {
|
|
@@ -1557,16 +1526,21 @@ class UnrealEffects {
|
|
|
1557
1526
|
// Ensure a single in-flight request per attempt.
|
|
1558
1527
|
exhaustMap((urls) => this.signallingService.connectToSignaling(urls)));
|
|
1559
1528
|
}, { dispatch: false });
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1529
|
+
}
|
|
1530
|
+
canPeerReconnect() {
|
|
1531
|
+
return (this.reconnectConfig.enabled &&
|
|
1532
|
+
this.reconnectConfig.onDataChannelClose &&
|
|
1533
|
+
this.signallingService.isConnected());
|
|
1534
|
+
}
|
|
1535
|
+
postToScreenLocker(text) {
|
|
1536
|
+
try {
|
|
1537
|
+
const el = document.getElementById(this.unrealInitialConfig?.screenLockerContainerId ??
|
|
1538
|
+
SCREEN_LOCKER_CONTAINER_ID);
|
|
1539
|
+
el?.contentWindow?.postMessage(text, '*');
|
|
1540
|
+
}
|
|
1541
|
+
catch {
|
|
1542
|
+
// Fails silently if cross-origin
|
|
1543
|
+
}
|
|
1570
1544
|
}
|
|
1571
1545
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1572
1546
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealEffects }); }
|
|
@@ -1601,7 +1575,7 @@ class SubService {
|
|
|
1601
1575
|
this.disconnect$ = this.store
|
|
1602
1576
|
.select(unrealFeature.selectCirrusConnected)
|
|
1603
1577
|
.pipe(skip(1), filter(Falsy), share());
|
|
1604
|
-
this.
|
|
1578
|
+
this.destroyRef.onDestroy(() => {
|
|
1605
1579
|
this.destroy$.next();
|
|
1606
1580
|
this.destroy$.complete();
|
|
1607
1581
|
});
|
|
@@ -1612,13 +1586,13 @@ class AFKService extends SubService {
|
|
|
1612
1586
|
constructor() {
|
|
1613
1587
|
super();
|
|
1614
1588
|
this.active = false; // Whether the AFK system is currently looking for inactivity.
|
|
1615
|
-
this.countdown = 0;
|
|
1589
|
+
this.countdown = 0;
|
|
1616
1590
|
this.warnTimeout = this.store.selectSignal(selectWarnTimeout);
|
|
1591
|
+
this.isAfkTimerVisible = this.store.selectSignal(unrealFeature.selectIsAfkTimerVisible);
|
|
1617
1592
|
this.isViewportReady = this.store.selectSignal(unrealFeature.selectViewportReady);
|
|
1618
1593
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
1619
1594
|
/** Ticks every time warnTimeout changes or resetAfk action fires */
|
|
1620
1595
|
this.afkResetTrigger = toSignal(merge(this.store.select(selectWarnTimeout), this.actions$.pipe(ofType(resetAfk))));
|
|
1621
|
-
/** Ticks when cirrus disconnects */
|
|
1622
1596
|
this.disconnected = toSignal(this.disconnect$);
|
|
1623
1597
|
this.destroyRef.onDestroy(() => this.clearTimers());
|
|
1624
1598
|
if (this.unrealConfig?.playwright) {
|
|
@@ -1640,15 +1614,20 @@ class AFKService extends SubService {
|
|
|
1640
1614
|
untracked(() => this.startAfkWarningTimer());
|
|
1641
1615
|
}
|
|
1642
1616
|
});
|
|
1643
|
-
// Stop on disconnect
|
|
1644
1617
|
effect(() => {
|
|
1645
1618
|
const disconnected = this.disconnected();
|
|
1646
1619
|
if (disconnected !== undefined) {
|
|
1647
1620
|
untracked(() => this.stop());
|
|
1648
1621
|
}
|
|
1649
1622
|
});
|
|
1623
|
+
this.actions$
|
|
1624
|
+
.pipe(ofType(resetUserInactivityTimeout), takeUntil$1(this.destroy$))
|
|
1625
|
+
.subscribe(() => this.reset());
|
|
1650
1626
|
}
|
|
1651
1627
|
hideOverlay() {
|
|
1628
|
+
if (!this.isAfkTimerVisible()) {
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1652
1631
|
this.store.dispatch(setAfkTimerHide());
|
|
1653
1632
|
}
|
|
1654
1633
|
/**
|
|
@@ -1724,11 +1703,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
1724
1703
|
type: Injectable
|
|
1725
1704
|
}], ctorParameters: () => [] });
|
|
1726
1705
|
|
|
1706
|
+
class LoadingProgressService {
|
|
1707
|
+
constructor() {
|
|
1708
|
+
this.#store = inject(Store);
|
|
1709
|
+
this.loaderVisible = this.#store.selectSignal(selectShowLoader);
|
|
1710
|
+
this.percents = toSignal(this.#store
|
|
1711
|
+
.select(selectTotalProgress)
|
|
1712
|
+
.pipe(keepMaxUntilReset(toObservable(this.loaderVisible).pipe(filter(Falsy))), floatToSmoothPercents(), distinctUntilChanged()));
|
|
1713
|
+
}
|
|
1714
|
+
#store;
|
|
1715
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1716
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LoadingProgressService }); }
|
|
1717
|
+
}
|
|
1718
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LoadingProgressService, decorators: [{
|
|
1719
|
+
type: Injectable
|
|
1720
|
+
}] });
|
|
1721
|
+
|
|
1727
1722
|
class FreezeFrameService extends SubService {
|
|
1728
1723
|
constructor() {
|
|
1729
1724
|
super();
|
|
1730
1725
|
this.receiving = false;
|
|
1731
1726
|
this.size = 0;
|
|
1727
|
+
this.jpeg = undefined;
|
|
1732
1728
|
this.freezeFrameOverlay = new Image();
|
|
1733
1729
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
1734
1730
|
this.init();
|
|
@@ -1842,6 +1838,7 @@ const BITRATE_MONITOR = new DataFlowMonitor(FilterModel.yellowFlag, FilterModel.
|
|
|
1842
1838
|
class VideoService extends SubService {
|
|
1843
1839
|
constructor() {
|
|
1844
1840
|
super(...arguments);
|
|
1841
|
+
this.isVideoPlaying = this.store.selectSignal(unrealFeature.selectIsVideoPlaying);
|
|
1845
1842
|
this.video = null;
|
|
1846
1843
|
this.audio = null;
|
|
1847
1844
|
this.container = null;
|
|
@@ -1881,7 +1878,7 @@ class VideoService extends SubService {
|
|
|
1881
1878
|
this.videoTrack$.next({ stream, pcClient });
|
|
1882
1879
|
}
|
|
1883
1880
|
else {
|
|
1884
|
-
|
|
1881
|
+
Logger.error('Video element is not defined.');
|
|
1885
1882
|
}
|
|
1886
1883
|
}
|
|
1887
1884
|
attachAudioStream(stream) {
|
|
@@ -1958,7 +1955,7 @@ class VideoService extends SubService {
|
|
|
1958
1955
|
catch (err) {
|
|
1959
1956
|
// Handle InvalidStateError or any transient WebRTC errors gracefully
|
|
1960
1957
|
if (err instanceof DOMException && err.name === 'InvalidStateError') {
|
|
1961
|
-
|
|
1958
|
+
Logger.warn('Peer connection is closed or unusable, skipping stats.');
|
|
1962
1959
|
return null;
|
|
1963
1960
|
}
|
|
1964
1961
|
throw err;
|
|
@@ -2065,6 +2062,7 @@ class VideoService extends SubService {
|
|
|
2065
2062
|
if (!this.video) {
|
|
2066
2063
|
return;
|
|
2067
2064
|
}
|
|
2065
|
+
this.changeStatusMainVideoOnScene(false);
|
|
2068
2066
|
this.video = null;
|
|
2069
2067
|
this.audio = null;
|
|
2070
2068
|
this.videoTrack$.next(null);
|
|
@@ -2076,7 +2074,12 @@ class VideoService extends SubService {
|
|
|
2076
2074
|
while (this.container?.firstChild) {
|
|
2077
2075
|
this.container.removeChild(this.container.firstChild);
|
|
2078
2076
|
}
|
|
2079
|
-
|
|
2077
|
+
}
|
|
2078
|
+
changeStatusMainVideoOnScene(isVideoPlaying) {
|
|
2079
|
+
if (this.isVideoPlaying() === isVideoPlaying) {
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
this.store.dispatch(changeStatusMainVideoOnScene({ isVideoPlaying }));
|
|
2080
2083
|
}
|
|
2081
2084
|
createWebRtcVideo() {
|
|
2082
2085
|
dispatchResize();
|
|
@@ -2089,18 +2092,14 @@ class VideoService extends SubService {
|
|
|
2089
2092
|
video.disablePictureInPicture = true;
|
|
2090
2093
|
this.video = video;
|
|
2091
2094
|
this.container?.appendChild(this.video);
|
|
2092
|
-
this.video.addEventListener('play', () => this.
|
|
2095
|
+
this.video.addEventListener('play', () => this.changeStatusMainVideoOnScene(true));
|
|
2093
2096
|
fromEvent(this.video, 'loadedmetadata')
|
|
2094
|
-
.pipe(take(1), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'))
|
|
2095
|
-
.subscribe(() =>
|
|
2096
|
-
if (this.video) {
|
|
2097
|
-
this.store.dispatch(videoStreamInitialized());
|
|
2098
|
-
}
|
|
2099
|
-
});
|
|
2097
|
+
.pipe(take(1), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'), filter(() => !!this.video))
|
|
2098
|
+
.subscribe(() => this.store.dispatch(videoStreamInitialized()));
|
|
2100
2099
|
}
|
|
2101
2100
|
createWebRtcAudio() {
|
|
2102
2101
|
const audio = new Audio();
|
|
2103
|
-
audio.id =
|
|
2102
|
+
audio.id = STREAMING_AUDIO_ID;
|
|
2104
2103
|
this.audio = audio;
|
|
2105
2104
|
}
|
|
2106
2105
|
playAudio(audio) {
|
|
@@ -2573,7 +2572,9 @@ class SignallingService extends SubService {
|
|
|
2573
2572
|
return;
|
|
2574
2573
|
}
|
|
2575
2574
|
this.setHandlersFromStream();
|
|
2576
|
-
this.action
|
|
2575
|
+
this.action$
|
|
2576
|
+
.pipe(ofType(dataChannelReady), takeUntil(this.abort$))
|
|
2577
|
+
.subscribe(() => {
|
|
2577
2578
|
this.send({
|
|
2578
2579
|
type: 'p2pEstablished',
|
|
2579
2580
|
source: 'front',
|
|
@@ -2583,7 +2584,7 @@ class SignallingService extends SubService {
|
|
|
2583
2584
|
this.action$
|
|
2584
2585
|
.pipe(ofType(dataChannelReady), switchMap(() => this.store
|
|
2585
2586
|
.select(unrealFeature.selectViewportReady)
|
|
2586
|
-
.pipe(filter(Truthy), take(1))))
|
|
2587
|
+
.pipe(filter(Truthy), take(1))), takeUntil(this.abort$))
|
|
2587
2588
|
.subscribe(() => {
|
|
2588
2589
|
this.send({
|
|
2589
2590
|
type: 'streamPreparedForUserInteraction',
|
|
@@ -2615,10 +2616,10 @@ class SignallingService extends SubService {
|
|
|
2615
2616
|
},
|
|
2616
2617
|
// count: 10, // Optional: cap the number of attempts.
|
|
2617
2618
|
}),
|
|
2618
|
-
// abort$ sits OUTSIDE retry so it kills the entire retry loop,
|
|
2619
|
+
// abort$ sits OUTSIDE retry, so it kills the entire retry loop,
|
|
2619
2620
|
// not just the current attempt. Each inner createWebSocket also
|
|
2620
2621
|
// receives abort$ independently, so mid-connection attempts are
|
|
2621
|
-
//
|
|
2622
|
+
// canceled too.
|
|
2622
2623
|
takeUntil(this.abort$));
|
|
2623
2624
|
}
|
|
2624
2625
|
startEstablishingConnection() {
|
|
@@ -2644,7 +2645,7 @@ class SignallingService extends SubService {
|
|
|
2644
2645
|
*
|
|
2645
2646
|
* 1. **Telemetry** — Starts a `getSignaling` span for end-to-end timing.
|
|
2646
2647
|
* 2. **Candidate URL selection** — A stateful generator (`urlGen`) is created
|
|
2647
|
-
* from `urlsPool`. Each resubscription (e.g
|
|
2648
|
+
* from `urlsPool`. Each resubscription (e.g., via `retry`) advances to the next URL.
|
|
2648
2649
|
* 3. **Short-circuit for WS URLs** — If the picked candidate already matches
|
|
2649
2650
|
* `ws://` or `wss://`, emit `{ wsUrl, pollingUrl: null, instanceName: null }`
|
|
2650
2651
|
* immediately and complete (no HTTP request).
|
|
@@ -2656,7 +2657,7 @@ class SignallingService extends SubService {
|
|
|
2656
2657
|
* while the request is in flight are **ignored** until completion, preventing overlap.
|
|
2657
2658
|
* 6. **Per-attempt timeout** — `timeout(WS_TIMEOUT)` caps how long we wait for a response.
|
|
2658
2659
|
* On timeout, the attempt errors and the outer `retry` schedules the next URL.
|
|
2659
|
-
* 7. **Response validation
|
|
2660
|
+
* 7. **Response validation and progress reporting** — A `filter`:
|
|
2660
2661
|
* - Calls `TelemetryStop('getSignaling', { ...data, multi: true })` on receipt.
|
|
2661
2662
|
* - If `data.signallingServer === ''` or `data.error`:
|
|
2662
2663
|
* shows a status message, optionally dispatches `setStatusPercentSignallingServer`
|
|
@@ -2673,7 +2674,7 @@ class SignallingService extends SubService {
|
|
|
2673
2674
|
*
|
|
2674
2675
|
* ### Concurrency semantics
|
|
2675
2676
|
*
|
|
2676
|
-
* - `exhaustMap` guarantees a **single** HTTP request per attempt;
|
|
2677
|
+
* - `exhaustMap` guarantees a **single** HTTP request per attempt; later
|
|
2677
2678
|
* `{clientId, viewId}` emissions are ignored until the current request completes.
|
|
2678
2679
|
*
|
|
2679
2680
|
* ### Telemetry semantics
|
|
@@ -2707,7 +2708,7 @@ class SignallingService extends SubService {
|
|
|
2707
2708
|
* @remarks
|
|
2708
2709
|
* - The URL generator (`urlGen`) is created **outside** `defer`, so resubscriptions
|
|
2709
2710
|
* advance the pool. Creating it inside `defer` would restart from the first URL each retry.
|
|
2710
|
-
* - `location.protocol` is used to derive `ws` vs `wss`. If this may run in non-browser
|
|
2711
|
+
* - `location.protocol` is used to derive `ws` vs. `wss`. If this may run in non-browser
|
|
2711
2712
|
* contexts (e.g., SSR), guard or abstract this logic.
|
|
2712
2713
|
* - Consider capping retries with `count` in `retry({ count, delay })` when appropriate.
|
|
2713
2714
|
*
|
|
@@ -2740,7 +2741,7 @@ class SignallingService extends SubService {
|
|
|
2740
2741
|
};
|
|
2741
2742
|
}));
|
|
2742
2743
|
}
|
|
2743
|
-
// Old (HTTP-polling) orchestration flow — mark the
|
|
2744
|
+
// Old (HTTP-polling) orchestration flow — mark the round trip so
|
|
2744
2745
|
// the signaling-phase timeline can show it as a distinct segment.
|
|
2745
2746
|
this.store.dispatch(pollingStarted());
|
|
2746
2747
|
return this.store.select(selectClientAndViewIds).pipe(
|
|
@@ -2914,6 +2915,8 @@ class WebRtcPlayerService extends SubService {
|
|
|
2914
2915
|
constructor() {
|
|
2915
2916
|
super();
|
|
2916
2917
|
this.onDataChannelMessage$ = new Subject();
|
|
2918
|
+
this.pcClient = null;
|
|
2919
|
+
this.dcClient = null;
|
|
2917
2920
|
this.signallingSrv = inject(SignallingService);
|
|
2918
2921
|
this.videoService = inject(VideoService);
|
|
2919
2922
|
this.commandTelemetryService = inject(CommandTelemetryService);
|
|
@@ -2927,29 +2930,37 @@ class WebRtcPlayerService extends SubService {
|
|
|
2927
2930
|
this.iceCandidateBuffer = [];
|
|
2928
2931
|
this.isReconnecting = this.store.selectSignal(unrealFeature.selectIsReconnecting);
|
|
2929
2932
|
this.init();
|
|
2930
|
-
this.actions
|
|
2931
|
-
|
|
2933
|
+
this.actions$
|
|
2934
|
+
.pipe(ofType(dropDC), takeUntil(this.destroy$))
|
|
2935
|
+
.subscribe(() => this.dcClient?.close());
|
|
2936
|
+
this.actions$
|
|
2937
|
+
.pipe(ofType(dropPC), takeUntil(this.destroy$))
|
|
2938
|
+
.subscribe(() => this.pcClient?.close());
|
|
2932
2939
|
}
|
|
2933
2940
|
init() {
|
|
2934
|
-
this.signallingSrv.onConfig
|
|
2941
|
+
this.signallingSrv.onConfig$
|
|
2942
|
+
.pipe(takeUntil(this.destroy$))
|
|
2943
|
+
.subscribe((config) => {
|
|
2935
2944
|
this.videoService.create();
|
|
2936
2945
|
this.setConfig(config);
|
|
2937
2946
|
});
|
|
2938
2947
|
// Server-initiated offer: set remote description and respond with answer
|
|
2939
|
-
this.signallingSrv.onOffer
|
|
2948
|
+
this.signallingSrv.onOffer$
|
|
2949
|
+
.pipe(filter(Truthy), takeUntil(this.destroy$))
|
|
2950
|
+
.subscribe((offer) => {
|
|
2940
2951
|
void this.handleServerOffer(offer);
|
|
2941
2952
|
});
|
|
2942
2953
|
// Answer to a client-initiated offer
|
|
2943
2954
|
this.signallingSrv.onWebRtcAnswer$
|
|
2944
|
-
.pipe(filter(Truthy))
|
|
2955
|
+
.pipe(filter(Truthy), takeUntil(this.destroy$))
|
|
2945
2956
|
.subscribe((answer) => {
|
|
2946
2957
|
void this.handleServerAnswer(answer);
|
|
2947
2958
|
});
|
|
2948
2959
|
// ICE candidates — buffered until remote description is set
|
|
2949
|
-
this.signallingSrv.onWebRtcIce
|
|
2950
|
-
this.
|
|
2951
|
-
|
|
2952
|
-
});
|
|
2960
|
+
this.signallingSrv.onWebRtcIce$
|
|
2961
|
+
.pipe(takeUntil(this.destroy$))
|
|
2962
|
+
.subscribe((candidate) => {
|
|
2963
|
+
this.commandTelemetryService.trackStopCommand('EXT-iceCandidate', true, { multi: true });
|
|
2953
2964
|
if (this.remoteDescriptionSet) {
|
|
2954
2965
|
this.handleCandidateFromServer(candidate);
|
|
2955
2966
|
}
|
|
@@ -2974,11 +2985,15 @@ class WebRtcPlayerService extends SubService {
|
|
|
2974
2985
|
if (!this.pcClient) {
|
|
2975
2986
|
this.setupPeerConnection(this.cfg);
|
|
2976
2987
|
}
|
|
2988
|
+
const pc = this.pcClient;
|
|
2989
|
+
if (!pc) {
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2977
2992
|
// Offer already defines media transceivers — don't add extras
|
|
2978
|
-
await
|
|
2993
|
+
await pc.setRemoteDescription(offer);
|
|
2979
2994
|
this.remoteDescriptionSet = true;
|
|
2980
2995
|
this.flushIceCandidateBuffer();
|
|
2981
|
-
await this.createAnswer(
|
|
2996
|
+
await this.createAnswer(pc);
|
|
2982
2997
|
}
|
|
2983
2998
|
catch (e) {
|
|
2984
2999
|
Logger.error('Failed to handle server offer', e);
|
|
@@ -3023,10 +3038,16 @@ class WebRtcPlayerService extends SubService {
|
|
|
3023
3038
|
if (!this.pcClient) {
|
|
3024
3039
|
this.setupPeerConnection(this.cfg);
|
|
3025
3040
|
}
|
|
3041
|
+
const pc = this.pcClient;
|
|
3042
|
+
if (!pc) {
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3026
3045
|
// Client offers: transceivers first, then DC, then offer
|
|
3027
|
-
await this.setupTransceiversAsync(
|
|
3028
|
-
this.dcClient = this.createDataChannel(
|
|
3029
|
-
|
|
3046
|
+
await this.setupTransceiversAsync(pc);
|
|
3047
|
+
this.dcClient = this.createDataChannel(pc, 'UnrealDcConnection', {
|
|
3048
|
+
ordered: true,
|
|
3049
|
+
});
|
|
3050
|
+
await this.createOffer(pc);
|
|
3030
3051
|
}
|
|
3031
3052
|
catch (e) {
|
|
3032
3053
|
Logger.error('Failed to create WebRTC offer', e);
|
|
@@ -3085,7 +3106,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3085
3106
|
* RFC 6184 that encodes three H.264 parameters:
|
|
3086
3107
|
* - **Profile IDC** (`XX`) — the H.264 profile.
|
|
3087
3108
|
* - **Profile IOP** (`YY`) — constraint flags that further narrow the profile.
|
|
3088
|
-
* - **Level IDC** (`ZZ`) — the level
|
|
3109
|
+
* - **Level IDC** (`ZZ`) — the level which caps resolution/framerate/bitrate.
|
|
3089
3110
|
*
|
|
3090
3111
|
* ### H.264 profiles relevant to Unreal Engine Pixel Streaming
|
|
3091
3112
|
*
|
|
@@ -3103,12 +3124,12 @@ class WebRtcPlayerService extends SubService {
|
|
|
3103
3124
|
* | `f4001f` | High 4:4:4 Predictive | 3.1 | Lossless capable; rare in browsers |
|
|
3104
3125
|
*
|
|
3105
3126
|
* UE Pixel Streaming defaults to Constrained Baseline (`42e01f`) or High (`6400xx`)
|
|
3106
|
-
* depending on engine version and `WebRTC.H264Profile` CVar.
|
|
3127
|
+
* depending on the engine version and `WebRTC.H264Profile` CVar.
|
|
3107
3128
|
*
|
|
3108
3129
|
* We prioritize `42e01f` because it guarantees hardware decoding on
|
|
3109
3130
|
* virtually all client devices (desktop, mobile, embedded), avoiding
|
|
3110
3131
|
* fallback to software decoding or negotiation of a higher profile
|
|
3111
|
-
* that the client GPU may not
|
|
3132
|
+
* that the client GPU may not speed up.
|
|
3112
3133
|
*/
|
|
3113
3134
|
prioritizeH264Profile(sdp, profileLevelId) {
|
|
3114
3135
|
const lines = sdp.split('\r\n');
|
|
@@ -3517,7 +3538,7 @@ class FileReceiverService extends SubService {
|
|
|
3517
3538
|
}
|
|
3518
3539
|
this.store
|
|
3519
3540
|
.select(unrealFeature.selectViewportReady)
|
|
3520
|
-
.pipe(filter(Truthy))
|
|
3541
|
+
.pipe(filter(Truthy), takeUntil(this.destroy$))
|
|
3521
3542
|
.subscribe(() => this.reset());
|
|
3522
3543
|
}
|
|
3523
3544
|
reset() {
|
|
@@ -3642,7 +3663,7 @@ class AggregatorService extends SubService {
|
|
|
3642
3663
|
initialize() {
|
|
3643
3664
|
this.store
|
|
3644
3665
|
.select(unrealFeature.selectCirrusConnected)
|
|
3645
|
-
.pipe(distinctUntilChanged())
|
|
3666
|
+
.pipe(distinctUntilChanged(), takeUntil(this.destroy$))
|
|
3646
3667
|
.subscribe((connected) => {
|
|
3647
3668
|
this.resetResponseList();
|
|
3648
3669
|
if (connected) {
|
|
@@ -3652,7 +3673,9 @@ class AggregatorService extends SubService {
|
|
|
3652
3673
|
const unlockViewport$ = this.store
|
|
3653
3674
|
.select(unrealFeature.selectViewportReady)
|
|
3654
3675
|
.pipe(filter(Truthy));
|
|
3655
|
-
merge(unlockViewport$, this.disconnect$)
|
|
3676
|
+
merge(unlockViewport$, this.disconnect$)
|
|
3677
|
+
.pipe(takeUntil(this.destroy$))
|
|
3678
|
+
.subscribe(() => {
|
|
3656
3679
|
this.removeLoadScreen();
|
|
3657
3680
|
});
|
|
3658
3681
|
if (!this.selectStreamConfig()?.autoStart ||
|
|
@@ -3664,22 +3687,6 @@ class AggregatorService extends SubService {
|
|
|
3664
3687
|
addResponseEventListener(name, listener) {
|
|
3665
3688
|
this.responseEventListeners.set(name, listener);
|
|
3666
3689
|
}
|
|
3667
|
-
showOnScreenKeyboard(command) {
|
|
3668
|
-
if (command.showOnScreenKeyboard) {
|
|
3669
|
-
// Show the 'edit text' button.
|
|
3670
|
-
//editTextButton.classList.remove('hiddenState');
|
|
3671
|
-
// Place the 'edit text' button near the UE4 input widget.
|
|
3672
|
-
//let pos = unquantizeAndDenormalizeUnsigned(command.x, command.y);
|
|
3673
|
-
//editTextButton.style.top = pos.y.toString() + 'px';
|
|
3674
|
-
//editTextButton.style.left = (pos.x - 40).toString() + 'px';
|
|
3675
|
-
}
|
|
3676
|
-
else {
|
|
3677
|
-
// Hide the 'edit text' button.
|
|
3678
|
-
//editTextButton.classList.add('hiddenState');
|
|
3679
|
-
// Hide the on-screen keyboard.
|
|
3680
|
-
// hiddenInput.blur();
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
3690
|
removeLoadScreen() {
|
|
3684
3691
|
document
|
|
3685
3692
|
.getElementById(this.unrealConfig?.screenLockerContainerId ||
|
|
@@ -3747,12 +3754,8 @@ class AggregatorService extends SubService {
|
|
|
3747
3754
|
break;
|
|
3748
3755
|
}
|
|
3749
3756
|
case ToClientMessage.Command: {
|
|
3750
|
-
const
|
|
3751
|
-
Logger.colored(...COLOR_CODES.FROM_UNREAL,
|
|
3752
|
-
const command = JSON.parse(commandAsString);
|
|
3753
|
-
if (command.command === 'onScreenKeyboard') {
|
|
3754
|
-
this.showOnScreenKeyboard(command);
|
|
3755
|
-
}
|
|
3757
|
+
const command = JSON.parse(decodeData(anyData));
|
|
3758
|
+
Logger.colored(...COLOR_CODES.FROM_UNREAL, command);
|
|
3756
3759
|
break;
|
|
3757
3760
|
}
|
|
3758
3761
|
case ToClientMessage.FileExtension:
|
|
@@ -3857,7 +3860,7 @@ class ConsoleExtensionsService extends SubService {
|
|
|
3857
3860
|
command: MetaBoxCommand.FChangeResolutionCommand,
|
|
3858
3861
|
payload: { resolution },
|
|
3859
3862
|
});
|
|
3860
|
-
return `
|
|
3863
|
+
return `Resolution set to ${resolution.x}x${resolution.y}`;
|
|
3861
3864
|
};
|
|
3862
3865
|
window.setMaxFps = (maxFps) => {
|
|
3863
3866
|
this.store.dispatch(setMaxFps({ maxFps }));
|
|
@@ -3894,6 +3897,10 @@ class DevModeService {
|
|
|
3894
3897
|
get isDevMode() {
|
|
3895
3898
|
return Truthy(localStorage.getItem('devMode'));
|
|
3896
3899
|
}
|
|
3900
|
+
/**
|
|
3901
|
+
* Called from the browser console to toggle dev mode at runtime.
|
|
3902
|
+
* @preserve
|
|
3903
|
+
*/
|
|
3897
3904
|
setDevMode(value) {
|
|
3898
3905
|
if (value) {
|
|
3899
3906
|
localStorage.setItem('devMode', value.toString());
|
|
@@ -3932,7 +3939,9 @@ class UnrealCommunicatorService {
|
|
|
3932
3939
|
// of the store selector. The selector reads the current state and can fire on
|
|
3933
3940
|
// stale `true` from a previous session when switching components (Angular
|
|
3934
3941
|
// creates the new component before destroying the old one).
|
|
3935
|
-
this.actions
|
|
3942
|
+
this.actions$
|
|
3943
|
+
.pipe(ofType(dataChannelConnected), takeUntil$1(this.destroy$))
|
|
3944
|
+
.subscribe(() => {
|
|
3936
3945
|
this.requestInitialSettings();
|
|
3937
3946
|
this.requestQualityControl();
|
|
3938
3947
|
});
|
|
@@ -4048,7 +4057,15 @@ class InputService extends SubService {
|
|
|
4048
4057
|
KeyboardNumericCode.NumpadDecimal,
|
|
4049
4058
|
];
|
|
4050
4059
|
this.availableKeys = this.defaultKeys;
|
|
4060
|
+
this.options = InputOptions;
|
|
4051
4061
|
this.reInit$ = new Subject();
|
|
4062
|
+
this.deactivatedVideoTrigger$ = NEVER;
|
|
4063
|
+
this.normalizeAndQuantizeUnsigned = (_x, _y) => ({
|
|
4064
|
+
inRange: false,
|
|
4065
|
+
x: 65535,
|
|
4066
|
+
y: 65535,
|
|
4067
|
+
});
|
|
4068
|
+
this.normalizeAndQuantizeSigned = (_x, _y) => ({ x: 0, y: 0 });
|
|
4052
4069
|
this.videoBoundingRect = new DOMRect(0, 0, 1, 1);
|
|
4053
4070
|
this.fingerIds = new Map();
|
|
4054
4071
|
this.maxByteValue = 255;
|
|
@@ -4071,11 +4088,11 @@ class InputService extends SubService {
|
|
|
4071
4088
|
const visibilityVisibleTrigger$ = fromEvent(document, 'visibilitychange').pipe(map$1(() => document.visibilityState === 'visible'), filter(Truthy), startWith(true));
|
|
4072
4089
|
this.deactivatedVideoTrigger$ = merge(this.disconnect$, this.reInit$, visibilityHiddenTrigger$);
|
|
4073
4090
|
merge(visibilityVisibleTrigger$, this.store.select(unrealFeature.selectViewportReady).pipe(filter(Truthy)))
|
|
4074
|
-
.pipe(filter(() => !!this.video))
|
|
4091
|
+
.pipe(filter(() => !!this.video), takeUntil$1(this.destroy$))
|
|
4075
4092
|
.subscribe(() => this.setup());
|
|
4076
4093
|
}
|
|
4077
4094
|
useKeyboardKeys(keys) {
|
|
4078
|
-
this.availableKeys = keys
|
|
4095
|
+
this.availableKeys = keys;
|
|
4079
4096
|
}
|
|
4080
4097
|
get video() {
|
|
4081
4098
|
return this.videoService.video;
|
|
@@ -4137,49 +4154,47 @@ class InputService extends SubService {
|
|
|
4137
4154
|
};
|
|
4138
4155
|
}
|
|
4139
4156
|
setupNormalizeAndQuantize(videoElement) {
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
normalizedY > 1.0) {
|
|
4159
|
-
return {
|
|
4160
|
-
inRange: false,
|
|
4161
|
-
x: 65535,
|
|
4162
|
-
y: 65535,
|
|
4163
|
-
};
|
|
4164
|
-
}
|
|
4165
|
-
else {
|
|
4166
|
-
return {
|
|
4167
|
-
inRange: true,
|
|
4168
|
-
x: normalizedX * 65536,
|
|
4169
|
-
y: normalizedY * 65536,
|
|
4170
|
-
};
|
|
4171
|
-
}
|
|
4172
|
-
};
|
|
4173
|
-
// Signed.
|
|
4174
|
-
this.normalizeAndQuantizeSigned = (x, y) => {
|
|
4175
|
-
const normalizedX = x / (0.5 * videoElement.clientWidth);
|
|
4176
|
-
const normalizedY = y / (0.5 * videoElement.clientHeight);
|
|
4157
|
+
this.videoBoundingRect = videoElement.getBoundingClientRect();
|
|
4158
|
+
this.offsetParams = this.getObjectFitOffset(videoElement);
|
|
4159
|
+
// Unsigned XY positions are the ratio (0.0..1.0) along a viewport axis,
|
|
4160
|
+
// quantized into an uint16 (0..65536).
|
|
4161
|
+
// Signed XY deltas are the ratio (-1.0..1.0) along a viewport axis,
|
|
4162
|
+
// quantized into an int16 (-32767..32767).
|
|
4163
|
+
// This allows the browser viewport and client viewport to have a different
|
|
4164
|
+
// size.
|
|
4165
|
+
// Hack: Currently, we set an out-of-range position to an extreme (65535)
|
|
4166
|
+
// as we can't yet accurately detect mouse enter and leave events
|
|
4167
|
+
// precisely inside a video with an aspect ratio which causes mattes.
|
|
4168
|
+
this.normalizeAndQuantizeUnsigned = (x, y) => {
|
|
4169
|
+
const normalizedX = x / videoElement.clientWidth;
|
|
4170
|
+
const normalizedY = y / videoElement.clientHeight;
|
|
4171
|
+
if (normalizedX < 0.0 ||
|
|
4172
|
+
normalizedX > 1.0 ||
|
|
4173
|
+
normalizedY < 0.0 ||
|
|
4174
|
+
normalizedY > 1.0) {
|
|
4177
4175
|
return {
|
|
4178
|
-
|
|
4179
|
-
|
|
4176
|
+
inRange: false,
|
|
4177
|
+
x: 65535,
|
|
4178
|
+
y: 65535,
|
|
4180
4179
|
};
|
|
4180
|
+
}
|
|
4181
|
+
else {
|
|
4182
|
+
return {
|
|
4183
|
+
inRange: true,
|
|
4184
|
+
x: normalizedX * 65536,
|
|
4185
|
+
y: normalizedY * 65536,
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
// Signed.
|
|
4190
|
+
this.normalizeAndQuantizeSigned = (x, y) => {
|
|
4191
|
+
const normalizedX = x / (0.5 * videoElement.clientWidth);
|
|
4192
|
+
const normalizedY = y / (0.5 * videoElement.clientHeight);
|
|
4193
|
+
return {
|
|
4194
|
+
x: normalizedX * 32767,
|
|
4195
|
+
y: normalizedY * 32767,
|
|
4181
4196
|
};
|
|
4182
|
-
}
|
|
4197
|
+
};
|
|
4183
4198
|
}
|
|
4184
4199
|
sendInputData(data) {
|
|
4185
4200
|
this.commandsSender.sendInputData(data);
|
|
@@ -4219,11 +4234,12 @@ class InputService extends SubService {
|
|
|
4219
4234
|
this.fingerIds.set(touch.identifier, finger);
|
|
4220
4235
|
};
|
|
4221
4236
|
const forgetTouch = (touch) => {
|
|
4222
|
-
this.
|
|
4237
|
+
const finger = this.fingerIds.get(touch.identifier);
|
|
4238
|
+
if (finger) {
|
|
4239
|
+
this.fingers.push(finger);
|
|
4240
|
+
}
|
|
4223
4241
|
// Sort array back into descending order. This means if finger '1' were to lift after finger '0', we would ensure that 0 will be the first index to pop
|
|
4224
|
-
this.fingers.sort(
|
|
4225
|
-
return b - a;
|
|
4226
|
-
});
|
|
4242
|
+
this.fingers.sort((a, b) => b - a);
|
|
4227
4243
|
this.fingerIds.delete(touch.identifier);
|
|
4228
4244
|
};
|
|
4229
4245
|
const emitTouchData = (type, touches) => {
|
|
@@ -4234,21 +4250,24 @@ class InputService extends SubService {
|
|
|
4234
4250
|
const touch = touches[t];
|
|
4235
4251
|
const { x, y } = this.getTouchPositionWithOffset(touch);
|
|
4236
4252
|
Logger.info(`F${this.fingerIds.get(touch.identifier)}=(${x}, ${y})`);
|
|
4237
|
-
const
|
|
4253
|
+
const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
|
|
4238
4254
|
const data = new DataView(new ArrayBuffer(9));
|
|
4239
4255
|
data.setUint8(0, type);
|
|
4240
4256
|
data.setUint8(1, numTouches);
|
|
4241
|
-
data.setUint16(byte,
|
|
4257
|
+
data.setUint16(byte, normalizeCoordinates.x, true);
|
|
4242
4258
|
byte += 2;
|
|
4243
|
-
data.setUint16(byte,
|
|
4259
|
+
data.setUint16(byte, normalizeCoordinates.y, true);
|
|
4244
4260
|
byte += 2;
|
|
4245
|
-
|
|
4261
|
+
const finger = this.fingerIds.get(touch.identifier);
|
|
4262
|
+
if (finger) {
|
|
4263
|
+
data.setUint8(byte, finger);
|
|
4264
|
+
}
|
|
4246
4265
|
byte += 1;
|
|
4247
4266
|
// force is between 0.0 and 1.0, so quantize into byte.
|
|
4248
4267
|
// iOs bug: Forced to be always 0.5 on all devices as we do not use forced touch any way;/
|
|
4249
4268
|
data.setUint8(byte, this.maxByteValue * 0.5);
|
|
4250
4269
|
byte += 1;
|
|
4251
|
-
data.setUint8(byte,
|
|
4270
|
+
data.setUint8(byte, normalizeCoordinates.inRange ? 1 : 0);
|
|
4252
4271
|
byte += 1;
|
|
4253
4272
|
this.sendInputData(data.buffer);
|
|
4254
4273
|
}
|
|
@@ -4291,42 +4310,42 @@ class InputService extends SubService {
|
|
|
4291
4310
|
window.focus();
|
|
4292
4311
|
}
|
|
4293
4312
|
emitMouseMove(x, y, deltaX, deltaY) {
|
|
4294
|
-
const
|
|
4313
|
+
const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
|
|
4295
4314
|
const delta = this.normalizeAndQuantizeSigned(deltaX, deltaY);
|
|
4296
|
-
const
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
this.sendInputData(
|
|
4315
|
+
const data = new DataView(new ArrayBuffer(9));
|
|
4316
|
+
data.setUint8(0, MessageType.MouseMove);
|
|
4317
|
+
data.setUint16(1, normalizeCoordinates.x, true);
|
|
4318
|
+
data.setUint16(3, normalizeCoordinates.y, true);
|
|
4319
|
+
data.setInt16(5, delta.x, true);
|
|
4320
|
+
data.setInt16(7, delta.y, true);
|
|
4321
|
+
this.sendInputData(data.buffer);
|
|
4303
4322
|
}
|
|
4304
4323
|
emitMouseDown(button, x, y) {
|
|
4305
|
-
const
|
|
4306
|
-
const
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
this.sendInputData(
|
|
4324
|
+
const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
|
|
4325
|
+
const data = new DataView(new ArrayBuffer(6));
|
|
4326
|
+
data.setUint8(0, MessageType.MouseDown);
|
|
4327
|
+
data.setUint8(1, button);
|
|
4328
|
+
data.setUint16(2, normalizeCoordinates.x, true);
|
|
4329
|
+
data.setUint16(4, normalizeCoordinates.y, true);
|
|
4330
|
+
this.sendInputData(data.buffer);
|
|
4312
4331
|
}
|
|
4313
4332
|
emitMouseUp(button, x, y) {
|
|
4314
|
-
const
|
|
4315
|
-
const
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
this.sendInputData(
|
|
4333
|
+
const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
|
|
4334
|
+
const data = new DataView(new ArrayBuffer(6));
|
|
4335
|
+
data.setUint8(0, MessageType.MouseUp);
|
|
4336
|
+
data.setUint8(1, button);
|
|
4337
|
+
data.setUint16(2, normalizeCoordinates.x, true);
|
|
4338
|
+
data.setUint16(4, normalizeCoordinates.y, true);
|
|
4339
|
+
this.sendInputData(data.buffer);
|
|
4321
4340
|
}
|
|
4322
4341
|
emitMouseWheel(delta, x, y) {
|
|
4323
|
-
const
|
|
4324
|
-
const
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
this.sendInputData(
|
|
4342
|
+
const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
|
|
4343
|
+
const data = new DataView(new ArrayBuffer(7));
|
|
4344
|
+
data.setUint8(0, MessageType.MouseWheel);
|
|
4345
|
+
data.setInt16(1, delta, true);
|
|
4346
|
+
data.setUint16(3, normalizeCoordinates.x, true);
|
|
4347
|
+
data.setUint16(5, normalizeCoordinates.y, true);
|
|
4348
|
+
this.sendInputData(data.buffer);
|
|
4330
4349
|
}
|
|
4331
4350
|
releaseMouseButtons(buttons, x, y) {
|
|
4332
4351
|
if (buttons & MouseButtonsMask.PrimaryButton) {
|
|
@@ -4345,7 +4364,7 @@ class InputService extends SubService {
|
|
|
4345
4364
|
this.emitMouseUp(MouseButton.FifthButton, x, y);
|
|
4346
4365
|
}
|
|
4347
4366
|
}
|
|
4348
|
-
// If the user has any mouse buttons pressed then press them again.
|
|
4367
|
+
// If the user has any mouse buttons pressed, then press them again.
|
|
4349
4368
|
pressMouseButtons(buttons, x, y) {
|
|
4350
4369
|
if (buttons & MouseButtonsMask.PrimaryButton) {
|
|
4351
4370
|
this.emitMouseDown(MouseButton.MainButton, x, y);
|
|
@@ -4364,58 +4383,53 @@ class InputService extends SubService {
|
|
|
4364
4383
|
}
|
|
4365
4384
|
}
|
|
4366
4385
|
registerHoveringMouseEvents(htmlVideoElement) {
|
|
4367
|
-
htmlVideoElement
|
|
4386
|
+
fromEvent(htmlVideoElement, 'mousemove')
|
|
4387
|
+
.pipe(takeUntil$1(this.deactivatedVideoTrigger$))
|
|
4388
|
+
.subscribe((e) => {
|
|
4368
4389
|
const { x, y } = this.getMousePositionWithOffset(e);
|
|
4369
4390
|
this.emitMouseMove(x, y, e.movementX, e.movementY);
|
|
4370
4391
|
e.preventDefault();
|
|
4371
|
-
};
|
|
4372
|
-
htmlVideoElement
|
|
4392
|
+
});
|
|
4393
|
+
fromEvent(htmlVideoElement, 'mousedown')
|
|
4394
|
+
.pipe(takeUntil$1(this.deactivatedVideoTrigger$))
|
|
4395
|
+
.subscribe((e) => {
|
|
4373
4396
|
const { x, y } = this.getMousePositionWithOffset(e);
|
|
4374
4397
|
this.focusWindow();
|
|
4375
4398
|
this.emitMouseDown(e.button, x, y);
|
|
4376
4399
|
e.preventDefault();
|
|
4377
|
-
};
|
|
4378
|
-
htmlVideoElement
|
|
4400
|
+
});
|
|
4401
|
+
fromEvent(htmlVideoElement, 'mouseup')
|
|
4402
|
+
.pipe(takeUntil$1(this.deactivatedVideoTrigger$))
|
|
4403
|
+
.subscribe((e) => {
|
|
4379
4404
|
const { x, y } = this.getMousePositionWithOffset(e);
|
|
4380
4405
|
this.emitMouseUp(e.button, x, y);
|
|
4381
4406
|
e.preventDefault();
|
|
4382
|
-
};
|
|
4383
|
-
// When the context menu is shown, then it is safest to release the button
|
|
4384
|
-
// pressed when the event happened. This will guarantee we will
|
|
4385
|
-
// get at least one mouse up corresponding to a mouse down event. Otherwise,
|
|
4386
|
-
// the mouse can get stuck.
|
|
4387
|
-
// https://github.com/facebook/react/issues/5531
|
|
4388
|
-
// playerElement.oncontextmenu = (e) => {
|
|
4389
|
-
// e.preventDefault();
|
|
4390
|
-
// this.emitMouseUp(e.button, e.offsetX, e.offsetY);
|
|
4391
|
-
// };
|
|
4407
|
+
});
|
|
4392
4408
|
fromEvent(htmlVideoElement, 'wheel')
|
|
4393
4409
|
.pipe(takeUntil$1(this.deactivatedVideoTrigger$))
|
|
4394
4410
|
.subscribe((e) => {
|
|
4395
4411
|
this.emitMouseWheel(e.deltaY * -120, e.offsetX, e.offsetY);
|
|
4396
4412
|
e.preventDefault();
|
|
4397
4413
|
});
|
|
4398
|
-
htmlVideoElement.pressMouseButtons = (e) => {
|
|
4399
|
-
this.pressMouseButtons(e.buttons, e.offsetX, e.offsetY);
|
|
4400
|
-
};
|
|
4401
|
-
htmlVideoElement.releaseMouseButtons = (e) => {
|
|
4402
|
-
this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
|
|
4403
|
-
};
|
|
4404
4414
|
}
|
|
4405
4415
|
registerMouseEnterAndLeaveEvents(htmlVideoElement) {
|
|
4406
|
-
htmlVideoElement
|
|
4416
|
+
fromEvent(htmlVideoElement, 'mouseenter')
|
|
4417
|
+
.pipe(takeUntil$1(this.deactivatedVideoTrigger$))
|
|
4418
|
+
.subscribe((e) => {
|
|
4407
4419
|
this.focusWindow();
|
|
4408
|
-
const
|
|
4409
|
-
|
|
4410
|
-
this.sendInputData(
|
|
4411
|
-
|
|
4412
|
-
};
|
|
4413
|
-
htmlVideoElement
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4420
|
+
const data = new DataView(new ArrayBuffer(1));
|
|
4421
|
+
data.setUint8(0, MessageType.MouseEnter);
|
|
4422
|
+
this.sendInputData(data.buffer);
|
|
4423
|
+
this.pressMouseButtons(e.buttons, e.offsetX, e.offsetY);
|
|
4424
|
+
});
|
|
4425
|
+
fromEvent(htmlVideoElement, 'mouseleave')
|
|
4426
|
+
.pipe(takeUntil$1(this.deactivatedVideoTrigger$))
|
|
4427
|
+
.subscribe((e) => {
|
|
4428
|
+
const data = new DataView(new ArrayBuffer(1));
|
|
4429
|
+
data.setUint8(0, MessageType.MouseLeave);
|
|
4430
|
+
this.sendInputData(data.buffer);
|
|
4431
|
+
this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
|
|
4432
|
+
});
|
|
4419
4433
|
}
|
|
4420
4434
|
// Browser keys do not have a charCode so we only need to test keyCode.
|
|
4421
4435
|
isKeyCodeBrowserKey(keyCode) {
|
|
@@ -4716,7 +4730,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4716
4730
|
/**
|
|
4717
4731
|
* Whether a session has already started during the current page lifetime.
|
|
4718
4732
|
* Used to distinguish a fresh page load (bootstrap measurement is
|
|
4719
|
-
* meaningful) from a
|
|
4733
|
+
* meaningful) from a Résumé/restart (the document is already loaded,
|
|
4720
4734
|
* so "bootstrap" is a no-op). Only the first session gets the bootstrap
|
|
4721
4735
|
* phase anchored at navigation start (perf.now() === 0).
|
|
4722
4736
|
*/
|
|
@@ -4725,7 +4739,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4725
4739
|
* Rolling snapshot of the currently selected WebRTC ICE candidate pair
|
|
4726
4740
|
* (local/remote type + url). Scanned over `videoStats$` so the stream
|
|
4727
4741
|
* *keeps* the first non-null pair even if later stats emit null — WebRTC
|
|
4728
|
-
* doesn't
|
|
4742
|
+
* doesn't unselect a pair mid-session, but the source is nullable. The
|
|
4729
4743
|
* `startWith(null)` seed makes `withLatestFrom` on this stream safe even
|
|
4730
4744
|
* before the first video stat arrives (common at data-channel-open).
|
|
4731
4745
|
*/
|
|
@@ -4734,7 +4748,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4734
4748
|
* Correlation-matched Observable: emits once when the first `loopBack`
|
|
4735
4749
|
* command's corresponding `command completed` action fires. Built from
|
|
4736
4750
|
* the two related actions — the command-started captures the correlation
|
|
4737
|
-
* id
|
|
4751
|
+
* id; the command-completed with that id emits.
|
|
4738
4752
|
*/
|
|
4739
4753
|
this.loopbackReceived$ = this.actions$.pipe(ofType(commandStarted), filter$1(({ command }) => command === 'loopBack'), take(1), switchMap(({ id }) => this.actions$.pipe(ofType(commandCompleted), filter$1((b) => b.id === id), take(1))));
|
|
4740
4754
|
// Overlay-referenced milestones — hoisted as fields so both
|
|
@@ -4765,7 +4779,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4765
4779
|
/**
|
|
4766
4780
|
* Unified phase catalog — the single source of truth for everything
|
|
4767
4781
|
* this service tracks after `session_started`. Each entry is a phase;
|
|
4768
|
-
* its `milestones` array is ordered chronologically and the *last*
|
|
4782
|
+
* its `milestones` array is ordered chronologically, and the *last*
|
|
4769
4783
|
* entry is the phase closer. When that closer's observable fires, the
|
|
4770
4784
|
* service emits one generic `stream.phase_ended` wire event with every
|
|
4771
4785
|
* display field the viewer needs.
|
|
@@ -4871,7 +4885,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4871
4885
|
// time) into phase-3 params:
|
|
4872
4886
|
// - `iceCandidateCount` (total ICE candidates from server — too
|
|
4873
4887
|
// few hints at a restrictive network)
|
|
4874
|
-
// - `handshakeFlow` (server-offer vs client-offer; we only use
|
|
4888
|
+
// - `handshakeFlow` (server-offer vs. client-offer; we only use
|
|
4875
4889
|
// server-offer today, but the detector stays in place)
|
|
4876
4890
|
// - `instanceType` / `branchInfo` / version / path / app /
|
|
4877
4891
|
// `connectionId` (streamer identity, from `ssInfo` message)
|
|
@@ -4907,7 +4921,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4907
4921
|
this.loopbackReceivedEntry,
|
|
4908
4922
|
// CLOSER — flattens the selected ICE candidate pair into two
|
|
4909
4923
|
// string params (local / remote), and snapshots the running
|
|
4910
|
-
// command counter so phase 5 shows how many commands the client
|
|
4924
|
+
// command counter, so phase 5 shows how many commands the client
|
|
4911
4925
|
// fired at Unreal before the viewport was ready.
|
|
4912
4926
|
{
|
|
4913
4927
|
label: 'Viewport ready',
|
|
@@ -4951,7 +4965,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4951
4965
|
.select(unrealFeature.selectCirrusConnected)
|
|
4952
4966
|
.pipe(pairwise(), filter$1(([prev, curr]) => prev && !curr))
|
|
4953
4967
|
.subscribe(() => this.endSession('disconnected'));
|
|
4954
|
-
// Close the session when the tab is hidden / being evicted from
|
|
4968
|
+
// Close the session when the tab is hidden / being evicted from cache.
|
|
4955
4969
|
// `pagehide` fires in more cases than `beforeunload` (covers iOS Safari, bfcache).
|
|
4956
4970
|
fromEvent(window, 'pagehide').subscribe(() => this.endSession('pagehide'));
|
|
4957
4971
|
}
|
|
@@ -4966,7 +4980,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4966
4980
|
// - First session on this page → anchor at navigation start (0).
|
|
4967
4981
|
// `performance.now()` is already "ms since navigation start", so
|
|
4968
4982
|
// `session_started.duration` becomes the full page-load + idle-time
|
|
4969
|
-
// leading up to the first stream start (i.e
|
|
4983
|
+
// leading up to the first stream start (i.e., the real bootstrap).
|
|
4970
4984
|
// - Resume/restart sessions → anchor at the session's own start.
|
|
4971
4985
|
// The document is already loaded — a "bootstrap" phase would either
|
|
4972
4986
|
// balloon (to include all prior idle time) or be misleading, so we
|
|
@@ -5016,7 +5030,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5016
5030
|
this.session.handshakeFlow = 'client-offer';
|
|
5017
5031
|
}));
|
|
5018
5032
|
// Unreal-command counter — increments on every `commandStarted`
|
|
5019
|
-
// action.
|
|
5033
|
+
// action. Snapshot ted into the `viewport_ready` closer payload as
|
|
5020
5034
|
// phase-5's `commandCount`.
|
|
5021
5035
|
sub.add(this.actions$.pipe(ofType(commandStarted)).subscribe(() => {
|
|
5022
5036
|
if (this.session)
|
|
@@ -5117,7 +5131,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5117
5131
|
* Emit the unified `stream.phase_ended` wire event for a closer. Packs
|
|
5118
5132
|
* every display field the viewer consumes — phaseNo (derived from the
|
|
5119
5133
|
* catalog index), label, color, milestones, params, overlays. Boundary
|
|
5120
|
-
* labels aren't on the wire — the viewer derives them from
|
|
5134
|
+
* labels aren't on the wire — the viewer derives them from neighbor
|
|
5121
5135
|
* phases' `label` fields.
|
|
5122
5136
|
*/
|
|
5123
5137
|
emitPhaseEnded(bucket, bucketIdx, extrasValue) {
|
|
@@ -5188,7 +5202,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5188
5202
|
qpP95: qp.p95,
|
|
5189
5203
|
});
|
|
5190
5204
|
}
|
|
5191
|
-
// FPS low vs current target.
|
|
5205
|
+
// FPS low vs. current target.
|
|
5192
5206
|
const ref = s.maxFpsTarget > 0 ? s.maxFpsTarget : UNLIMITED_REFERENCE_FPS;
|
|
5193
5207
|
const fpsLowNow = fps.p50 < ref * FPS_LOW_RATIO;
|
|
5194
5208
|
if (fpsLowNow && !s.fpsLow) {
|
|
@@ -5318,7 +5332,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5318
5332
|
// Nearest-rank method: p-th percentile ≡ sorted[ceil(p/100 * n) - 1].
|
|
5319
5333
|
// Using `floor` would make p95 collapse to `max` for n=20
|
|
5320
5334
|
// (floor(0.95*20) = 19 = max index), hiding the max line behind p95
|
|
5321
|
-
// in charts. `ceil - 1` keeps them distinct:
|
|
5335
|
+
// in charts. `ceil - 1` keeps them distinct: p. 95 = sorted[18], max = sorted[19].
|
|
5322
5336
|
const pick = (p) => {
|
|
5323
5337
|
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
5324
5338
|
return sorted[Math.max(0, Math.min(sorted.length - 1, idx))];
|
|
@@ -5331,7 +5345,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5331
5345
|
};
|
|
5332
5346
|
}
|
|
5333
5347
|
track(event, data) {
|
|
5334
|
-
// `userId` is attached to every event so the server can bucket files
|
|
5348
|
+
// `userId` is attached to every event, so the server can bucket files
|
|
5335
5349
|
// under `data/<userId>/<sessionId>.jsonl` without maintaining a
|
|
5336
5350
|
// session→user index.
|
|
5337
5351
|
this.store.dispatch(trackTelemetryData({
|
|
@@ -5351,8 +5365,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5351
5365
|
/** Compact one side of an ICE candidate pair to `"type (url)"` or just
|
|
5352
5366
|
* `type` (host candidates have no URL). */
|
|
5353
5367
|
function fmtCandidate(c) {
|
|
5354
|
-
if (!c)
|
|
5368
|
+
if (!c) {
|
|
5355
5369
|
return null;
|
|
5370
|
+
}
|
|
5356
5371
|
return c.url ? `${c.type} (${c.url})` : c.type;
|
|
5357
5372
|
}
|
|
5358
5373
|
/**
|
|
@@ -5432,22 +5447,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5432
5447
|
}] });
|
|
5433
5448
|
|
|
5434
5449
|
class AnalyticsService extends SubService {
|
|
5435
|
-
#store = inject(Store);
|
|
5436
5450
|
/** Signals monitorMouse inner subscriptions to stop (viewport lost / new session). */
|
|
5437
5451
|
#stopMouse$ = new Subject();
|
|
5438
5452
|
sendEventToMixPanel(name, params = {}) {
|
|
5439
5453
|
Logger.log('Analytics Event:', name, params);
|
|
5440
|
-
this
|
|
5454
|
+
this.store.dispatch(saveAnalyticsEvent({ event: { name, params } }));
|
|
5441
5455
|
}
|
|
5442
5456
|
constructor() {
|
|
5443
5457
|
super();
|
|
5444
|
-
const abortSignal$ = this
|
|
5458
|
+
const abortSignal$ = this.store
|
|
5445
5459
|
.select(unrealFeature.selectViewportReady)
|
|
5446
5460
|
.pipe(filter$1(Falsy), map(() => void 0));
|
|
5447
|
-
this
|
|
5461
|
+
this.store
|
|
5448
5462
|
.select(unrealFeature.selectViewportReady)
|
|
5449
|
-
.pipe(distinctUntilChanged(), filter$1(Truthy),
|
|
5450
|
-
.subscribe();
|
|
5463
|
+
.pipe(distinctUntilChanged(), filter$1(Truthy), takeUntil$1(this.destroy$))
|
|
5464
|
+
.subscribe(() => this.monitorMouse(abortSignal$));
|
|
5451
5465
|
}
|
|
5452
5466
|
monitorMouse(abortSignal$) {
|
|
5453
5467
|
this.#stopMouse$.next();
|
|
@@ -5530,7 +5544,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5530
5544
|
* ## How it works
|
|
5531
5545
|
*
|
|
5532
5546
|
* The service subscribes to `VideoService.videoStats$` (emitted every 250 ms)
|
|
5533
|
-
* and collects samples of FPS, bitrate, and QP (
|
|
5547
|
+
* and collects samples of FPS, bitrate, and QP (Quantization Parameter).
|
|
5534
5548
|
* Every {@link SAMPLE_WINDOW} ticks (~2 s) it averages the collected samples
|
|
5535
5549
|
* and feeds them into a set of **fuzzy-logic membership functions** that
|
|
5536
5550
|
* classify each metric into overlapping quality bands.
|
|
@@ -5539,28 +5553,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5539
5553
|
*
|
|
5540
5554
|
* **1. FPS ratio** = `actualFPS / currentTargetFPS` (0..1+)
|
|
5541
5555
|
*
|
|
5542
|
-
* | Band
|
|
5556
|
+
* | Band | Range | Meaning |
|
|
5543
5557
|
* |--------|---------------------|-----------------------------------------------|
|
|
5544
|
-
* | low
|
|
5545
|
-
* |
|
|
5546
|
-
* | high
|
|
5558
|
+
* | low | ratio < 0.7 → 1.0, linear ramp 0.7..0.9 → 0 | Stream cannot keep up with the target |
|
|
5559
|
+
* | okays | triangle 0.8..1.0, peak at 0.95 | Stream roughly matches the target |
|
|
5560
|
+
* | high | ratio 0.9..1.0 → ramp, >1.0 → 1.0 | Stream meets or exceeds the target |
|
|
5547
5561
|
*
|
|
5548
5562
|
* **2. Bitrate load** = `currentBitrate / MAX_BITRATE` (0..1+)
|
|
5549
5563
|
*
|
|
5550
5564
|
* `MAX_BITRATE` = 20 Mbit/s — the reference ceiling for the encoder.
|
|
5551
5565
|
*
|
|
5552
|
-
* | Band
|
|
5566
|
+
* | Band | Range | Meaning |
|
|
5553
5567
|
* |--------|-------------------------------------------------|-----------------------------------------------|
|
|
5554
|
-
* | high
|
|
5555
|
-
* | not high | load < 0.5 → 0.0
|
|
5568
|
+
* | high | load 0.5..0.8 → ramp, >0.8 → 1.0 | Encoder is using most of the available bandwidth |
|
|
5569
|
+
* | not high | load < 0.5 → 0.0 | Bandwidth headroom is available |
|
|
5556
5570
|
*
|
|
5557
5571
|
* **3. Quality** — mapped from QP via `mapQpToQuality()` (see {@link Quality}):
|
|
5558
5572
|
*
|
|
5559
|
-
* | Quality
|
|
5573
|
+
* | Quality | QP range | Meaning |
|
|
5560
5574
|
* |----------|-----------|-------------------------------------------------|
|
|
5561
|
-
* | `lime`
|
|
5562
|
-
* | `orange` | QP 27..35 | Fair — encoder is under moderate pressure
|
|
5563
|
-
* | `red`
|
|
5575
|
+
* | `lime` | QP <= 26 | Good — encoder has enough headroom |
|
|
5576
|
+
* | `orange` | QP 27..35 | Fair — encoder is under moderate pressure |
|
|
5577
|
+
* | `red` | QP > 35 | Poor — heavy compression, visible artifacts |
|
|
5564
5578
|
*
|
|
5565
5579
|
* ### Decision rules (evaluated in priority order, first match wins)
|
|
5566
5580
|
*
|
|
@@ -5574,17 +5588,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5574
5588
|
*
|
|
5575
5589
|
* ## Cooldown-based throttling
|
|
5576
5590
|
*
|
|
5577
|
-
* The service continuously evaluates fuzzy rules every sample window.
|
|
5591
|
+
* The service continuously evaluates fuzzy rules in every sample window.
|
|
5578
5592
|
* After each FPS step change it enforces a cooldown before the next
|
|
5579
5593
|
* change can be applied. The cooldown duration depends on the
|
|
5580
5594
|
* (previous decision → next decision) pair:
|
|
5581
5595
|
*
|
|
5582
|
-
* | Previous
|
|
5596
|
+
* | Previous | Next | Cooldown |
|
|
5583
5597
|
* |-----------|-----------|-----------------------------------------------------|
|
|
5584
|
-
* | decrease
|
|
5585
|
-
* | decrease
|
|
5586
|
-
* | increase
|
|
5587
|
-
* | increase
|
|
5598
|
+
* | decrease | decrease | {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS} (2 s) |
|
|
5599
|
+
* | decrease | increase | {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} (10 s) |
|
|
5600
|
+
* | increase | decrease | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
|
|
5601
|
+
* | increase | increase | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
|
|
5588
5602
|
*
|
|
5589
5603
|
* This means the system reacts quickly after an increase (2 s in any
|
|
5590
5604
|
* direction) but is cautious about stepping back up after a decrease
|
|
@@ -5601,7 +5615,7 @@ class FpsMonitorService {
|
|
|
5601
5615
|
this.videoService = inject(VideoService);
|
|
5602
5616
|
this.store = inject(Store);
|
|
5603
5617
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
5604
|
-
/** Discrete FPS targets the controller can switch between (ascending order). Last 0 is unlimited framerate indicator*/
|
|
5618
|
+
/** Discrete FPS targets the controller can switch between (ascending order). Last 0 is an unlimited framerate indicator*/
|
|
5605
5619
|
this.FPS_STEPS = [30, 40, 50, 0];
|
|
5606
5620
|
/**
|
|
5607
5621
|
* Reference FPS used to compute the FPS ratio when `currentFpsTarget` is 0
|
|
@@ -5622,7 +5636,7 @@ class FpsMonitorService {
|
|
|
5622
5636
|
this.COOLDOWN_DECREASE_AFTER_DECREASE_MS = 2_000;
|
|
5623
5637
|
/**
|
|
5624
5638
|
* Cooldown (ms) for an increase following a previous decrease.
|
|
5625
|
-
* The stream needs more time to
|
|
5639
|
+
* The stream needs more time to stabilize before stepping back up.
|
|
5626
5640
|
*/
|
|
5627
5641
|
this.COOLDOWN_INCREASE_AFTER_DECREASE_MS = 10_000;
|
|
5628
5642
|
/**
|
|
@@ -5701,9 +5715,9 @@ class FpsMonitorService {
|
|
|
5701
5715
|
* Rules are evaluated in priority order — first match wins.
|
|
5702
5716
|
*
|
|
5703
5717
|
* Inputs:
|
|
5704
|
-
* - `fpsRatio`
|
|
5705
|
-
* - `bitrateLoad` = avgBitrate / 20 Mbit/s
|
|
5706
|
-
* - `quality`
|
|
5718
|
+
* - `fpsRatio` = avgFPS / currentTargetFPS (e.g., 45/60 = 0.75)
|
|
5719
|
+
* - `bitrateLoad` = avgBitrate / 20 Mbit/s (e.g., 12M/20M = 0.6)
|
|
5720
|
+
* - `quality` = mapQpToQuality(avgQP) → 'lime' | 'orange' | 'red'
|
|
5707
5721
|
*/
|
|
5708
5722
|
evaluateDecision(avg, currentTarget) {
|
|
5709
5723
|
const referenceTarget = currentTarget > 0 ? currentTarget : this.UNLIMITED_REFERENCE_FPS;
|
|
@@ -5720,17 +5734,17 @@ class FpsMonitorService {
|
|
|
5720
5734
|
return 'decrease';
|
|
5721
5735
|
}
|
|
5722
5736
|
// Rule 2: FPS looks acceptable numerically, but encoder quality is 'red'
|
|
5723
|
-
// (QP > 35 — heavy compression
|
|
5737
|
+
// (QP > 35 — heavy compression artifacts). Reducing target FPS frees
|
|
5724
5738
|
// encoder headroom and improves visual quality.
|
|
5725
5739
|
if (fpsOk > 0.4 && quality === 'red') {
|
|
5726
5740
|
return 'decrease';
|
|
5727
5741
|
}
|
|
5728
|
-
// Rule 3: FPS is ok, but bitrate is above ~50-80% of 20 Mbit/s ceiling
|
|
5742
|
+
// Rule 3: FPS is ok, but the bitrate is above ~50-80% of 20 Mbit/s ceiling
|
|
5729
5743
|
// and quality is 'orange' (QP 27..35). Near capacity — don't push higher.
|
|
5730
5744
|
if (fpsOk > 0.4 && bitrateHigh > 0.5 && quality === 'orange') {
|
|
5731
5745
|
return 'hold';
|
|
5732
5746
|
}
|
|
5733
|
-
// Rule 4: FPS meets/exceeds target (ratio >= ~0.95), quality is 'lime'
|
|
5747
|
+
// Rule 4: FPS meets/exceeds the target (ratio >= ~0.95), quality is 'lime'
|
|
5734
5748
|
// (QP <= 26), and bitrate is below 50% of ceiling — safe to step up.
|
|
5735
5749
|
// Note: the last FPS_STEPS value (0) means "no limit", so compare by
|
|
5736
5750
|
// position in FPS_STEPS rather than numerically.
|
|
@@ -5753,11 +5767,11 @@ class FpsMonitorService {
|
|
|
5753
5767
|
// ---------------------------------------------------------------------------
|
|
5754
5768
|
/**
|
|
5755
5769
|
* FPS ratio membership "low".
|
|
5756
|
-
* Input: ratio = actualFPS / targetFPS (e.g
|
|
5770
|
+
* Input: a ratio = actualFPS / targetFPS (e.g., 42/60 = 0.70).
|
|
5757
5771
|
*
|
|
5758
|
-
* ratio < 0.7
|
|
5759
|
-
* ratio 0.7..0.9 → linear ramp down (e.g
|
|
5760
|
-
* ratio > 0.9
|
|
5772
|
+
* ratio < 0.7 → 1.0 (clearly failing, e.g., 40/60)
|
|
5773
|
+
* ratio 0.7..0.9 → linear ramp down (e.g., 0.8 → 0.5)
|
|
5774
|
+
* a ratio > 0.9 → 0.0 (keeping up)
|
|
5761
5775
|
*/
|
|
5762
5776
|
membershipFpsLow(ratio) {
|
|
5763
5777
|
if (ratio < 0.7)
|
|
@@ -5770,10 +5784,10 @@ class FpsMonitorService {
|
|
|
5770
5784
|
* FPS ratio membership "ok" — triangle shape.
|
|
5771
5785
|
* Input: ratio = actualFPS / targetFPS.
|
|
5772
5786
|
*
|
|
5773
|
-
* ratio < 0.8
|
|
5774
|
-
* ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g
|
|
5787
|
+
* ratio < 0.8 → 0.0
|
|
5788
|
+
* ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g., 57/60)
|
|
5775
5789
|
* ratio 0.95..1.0 → ramp down
|
|
5776
|
-
* ratio > 1.0
|
|
5790
|
+
* a ratio > 1.0 → 0.0
|
|
5777
5791
|
*/
|
|
5778
5792
|
membershipFpsOk(ratio) {
|
|
5779
5793
|
if (ratio < 0.8)
|
|
@@ -5788,9 +5802,9 @@ class FpsMonitorService {
|
|
|
5788
5802
|
* FPS ratio membership "high".
|
|
5789
5803
|
* Input: ratio = actualFPS / targetFPS.
|
|
5790
5804
|
*
|
|
5791
|
-
* ratio < 0.9
|
|
5792
|
-
* ratio 0.9..1.0 → linear ramp up (e.g
|
|
5793
|
-
* ratio >= 1.0 → 1.0
|
|
5805
|
+
* ratio < 0.9 → 0.0
|
|
5806
|
+
* ratio 0.9..1.0 → linear ramp up (e.g., 0.95 → 0.5)
|
|
5807
|
+
* a ratio >= 1.0 → 1.0 (meeting or exceeding target, e.g., 60/60)
|
|
5794
5808
|
*/
|
|
5795
5809
|
membershipFpsHigh(ratio) {
|
|
5796
5810
|
if (ratio < 0.9)
|
|
@@ -5801,11 +5815,11 @@ class FpsMonitorService {
|
|
|
5801
5815
|
}
|
|
5802
5816
|
/**
|
|
5803
5817
|
* Bitrate load membership "high".
|
|
5804
|
-
* Input: load = currentBitrate / MAX_BITRATE (20 Mbit/s).
|
|
5818
|
+
* Input: a load = currentBitrate / MAX_BITRATE (20 Mbit/s).
|
|
5805
5819
|
*
|
|
5806
|
-
* load < 0.5
|
|
5807
|
-
* load 0.5..0.8 → linear ramp (e.g
|
|
5808
|
-
* load > 0.8
|
|
5820
|
+
* load < 0.5 → 0.0 (below 10 Mbit/s — plenty of headroom)
|
|
5821
|
+
* load 0.5..0.8 → linear ramp (e.g., 13M/20M = 0.65 → 0.5)
|
|
5822
|
+
* load > 0.8 → 1.0 (above 16 Mbit/s — near capacity)
|
|
5809
5823
|
*/
|
|
5810
5824
|
membershipBitrateHigh(load) {
|
|
5811
5825
|
if (load < 0.5)
|
|
@@ -5837,14 +5851,14 @@ class FpsMonitorService {
|
|
|
5837
5851
|
Logger.info(`[FpsMonitor] Decision: ${decision} → FPS: ${newTarget}, Quality: ${quality}`);
|
|
5838
5852
|
if (newTarget !== this.currentFpsTarget) {
|
|
5839
5853
|
this.currentFpsTarget = newTarget;
|
|
5840
|
-
this.store.dispatch(setMaxFps({ maxFps: newTarget }));
|
|
5854
|
+
// this.store.dispatch(setMaxFps({ maxFps: newTarget }));
|
|
5841
5855
|
}
|
|
5842
5856
|
this.lastDecisionTime = now;
|
|
5843
5857
|
this.samples = [];
|
|
5844
5858
|
}
|
|
5845
5859
|
/**
|
|
5846
|
-
* Get the next FPS target based on current target and decision.
|
|
5847
|
-
* Clamps to valid FPS_STEPS range.
|
|
5860
|
+
* Get the next FPS target based on the current target and decision.
|
|
5861
|
+
* Clamps to the valid FPS_STEPS range.
|
|
5848
5862
|
*/
|
|
5849
5863
|
getNextFpsTarget(currentTarget, decision) {
|
|
5850
5864
|
const currentIndex = this.FPS_STEPS.indexOf(currentTarget);
|
|
@@ -5920,12 +5934,7 @@ class UnrealCallbackService {
|
|
|
5920
5934
|
* ```
|
|
5921
5935
|
*/
|
|
5922
5936
|
fromUnrealCallback(command) {
|
|
5923
|
-
return this.actions$.pipe(ofType(unrealCommandCallback), map$1(({ json }) => {
|
|
5924
|
-
if ('commandCallback' in json) {
|
|
5925
|
-
return json.commandCallback;
|
|
5926
|
-
}
|
|
5927
|
-
return json;
|
|
5928
|
-
}), filter((json) => json.command === command), map$1((data) => ({
|
|
5937
|
+
return this.actions$.pipe(ofType(unrealCommandCallback), map$1(({ json }) => 'commandCallback' in json ? json.commandCallback : json), filter((json) => json.command === command), map$1((data) => ({
|
|
5929
5938
|
command: data.command,
|
|
5930
5939
|
payload: 'payload' in data ? data.payload : null,
|
|
5931
5940
|
})));
|
|
@@ -6057,15 +6066,15 @@ class ImageLoadingSrcComponent {
|
|
|
6057
6066
|
this.imageLoadingSrc = this.#store.selectSignal(unrealFeature.selectImageLoadingSrc);
|
|
6058
6067
|
}
|
|
6059
6068
|
#store;
|
|
6060
|
-
|
|
6069
|
+
loadingImageError() {
|
|
6061
6070
|
this.#store.dispatch(setLoadingImageSrc({ src: '' }));
|
|
6062
6071
|
}
|
|
6063
6072
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6064
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: ImageLoadingSrcComponent, isStandalone: true, selector: "app-image-loading-src", ngImport: i0, template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"
|
|
6073
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: ImageLoadingSrcComponent, isStandalone: true, selector: "app-image-loading-src", ngImport: i0, template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"loadingImageError()\"\n [ngSrc]=\"image\"\n fill\n loading=\"lazy\"\n alt=\"image loading src\"\n />\n }\n </div>\n </div>\n}\n", styles: [".preload{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.preload .back{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.preload .back img{width:100%;height:100%;object-fit:cover}.preload .back{background-color:#fff}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6065
6074
|
}
|
|
6066
6075
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
|
|
6067
6076
|
type: Component,
|
|
6068
|
-
args: [{ selector: 'app-image-loading-src', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgOptimizedImage], template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"
|
|
6077
|
+
args: [{ selector: 'app-image-loading-src', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgOptimizedImage], template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"loadingImageError()\"\n [ngSrc]=\"image\"\n fill\n loading=\"lazy\"\n alt=\"image loading src\"\n />\n }\n </div>\n </div>\n}\n", styles: [".preload{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.preload .back{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.preload .back img{width:100%;height:100%;object-fit:cover}.preload .back{background-color:#fff}\n"] }]
|
|
6069
6078
|
}] });
|
|
6070
6079
|
|
|
6071
6080
|
class StatGraphComponent {
|
|
@@ -6152,83 +6161,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
6152
6161
|
|
|
6153
6162
|
class UnrealStatusComponent {
|
|
6154
6163
|
constructor() {
|
|
6155
|
-
this
|
|
6156
|
-
this
|
|
6157
|
-
this.
|
|
6158
|
-
this.
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
this.
|
|
6162
|
-
/**
|
|
6163
|
-
* An observable that emits smoothed percentage values from 0 to 100.
|
|
6164
|
-
*/
|
|
6165
|
-
this.messagePercents = toSignal(this.store.select(selectTotalProgress).pipe(keepMaxUntilReset(this.resetPercentageSignal$), floatToSmoothPercents(), distinctUntilChanged(), map$1((progress) => progress || 0)));
|
|
6166
|
-
this.awsInstance = this.store.selectSignal(unrealFeature.selectAwsInstance);
|
|
6167
|
-
}
|
|
6168
|
-
ngOnInit() {
|
|
6169
|
-
/**
|
|
6170
|
-
* A subject that emits null values to trigger nullification.
|
|
6171
|
-
*/
|
|
6172
|
-
const nullify$ = new Subject();
|
|
6173
|
-
/**
|
|
6174
|
-
* An observable that emits null values with a delay of 2000 milliseconds.
|
|
6175
|
-
*/
|
|
6176
|
-
const nullifyObs$ = nullify$
|
|
6177
|
-
.asObservable()
|
|
6178
|
-
.pipe(delay(2000));
|
|
6179
|
-
/**
|
|
6180
|
-
* An observable that emits the status message from the Unreal feature store.
|
|
6181
|
-
*/
|
|
6182
|
-
const signalingStatus$ = this.store.select(unrealFeature.selectStatusMessage);
|
|
6183
|
-
/**
|
|
6184
|
-
* An observable that emits connection status messages based on the Cirrus connection state.
|
|
6185
|
-
* Then call side effect to nullify the message.
|
|
6186
|
-
*/
|
|
6187
|
-
const cirrusConnected$ = this.store
|
|
6188
|
-
.select(unrealFeature.selectCirrusConnected)
|
|
6189
|
-
.pipe(skip(1), switchMap((connected) => connected
|
|
6190
|
-
? of('Creating Socket...')
|
|
6191
|
-
: of('Connection closed.').pipe(tap(() => nullify$.next(null)))));
|
|
6192
|
-
/**
|
|
6193
|
-
* An observable that emits the viewport visibility state.
|
|
6194
|
-
*/
|
|
6195
|
-
const viewportTrigger$ = this.store.select(unrealFeature.selectViewportReady);
|
|
6196
|
-
/**
|
|
6197
|
-
* An observable that will nullify any message displayed after 500 mSec, when viewport changes its state.
|
|
6198
|
-
*/
|
|
6199
|
-
const streamToggle$ = merge(viewportTrigger$.pipe(filter(Falsy), skip(1)), viewportTrigger$.pipe(filter(Truthy))).pipe(delay(500), map$1(() => null));
|
|
6200
|
-
/**
|
|
6201
|
-
* A subscription that merges various status observables and emits debounced status via postMessage.
|
|
6202
|
-
*/
|
|
6203
|
-
merge(nullifyObs$, cirrusConnected$, signalingStatus$, streamToggle$)
|
|
6204
|
-
.pipe(tapLog('STATUS=>'), debounceTime$1(0), takeUntilDestroyed(this.destroyRef))
|
|
6205
|
-
.subscribe((status) => this.sendMessageToIndex(status));
|
|
6206
|
-
}
|
|
6207
|
-
/**
|
|
6208
|
-
* Sends a message to the embedded iframe identified by the default value '3dsource_start_screen' ID.
|
|
6209
|
-
* This function tries to post a message to the iframe's content window. If the content
|
|
6210
|
-
* window is not accessible (e.g., due to cross-origin policies), the function fails silently.
|
|
6211
|
-
*
|
|
6212
|
-
* @param {string} text The message to be sent to the iframe. This could be a status update
|
|
6213
|
-
* or any other relevant information that the iframe needs to process.
|
|
6214
|
-
*/
|
|
6215
|
-
sendMessageToIndex(text) {
|
|
6216
|
-
const objectElement = document.getElementById(this.unrealInitialConfig?.screenLockerContainerId ||
|
|
6217
|
-
SCREEN_LOCKER_CONTAINER_ID);
|
|
6218
|
-
try {
|
|
6219
|
-
const objectDocument = objectElement.contentWindow;
|
|
6220
|
-
objectDocument?.postMessage(text, '*');
|
|
6221
|
-
}
|
|
6222
|
-
catch {
|
|
6223
|
-
// Fails silently if the iframe's content window cannot be accessed
|
|
6224
|
-
}
|
|
6164
|
+
this.#store = inject(Store);
|
|
6165
|
+
this.#loadingProgress = inject(LoadingProgressService);
|
|
6166
|
+
this.awsInstance = this.#store.selectSignal(unrealFeature.selectAwsInstance);
|
|
6167
|
+
this.messagePercents = this.#loadingProgress.percents;
|
|
6168
|
+
this.showLoader = this.#loadingProgress.loaderVisible;
|
|
6169
|
+
this.showLoadingProgress = this.#store.selectSignal(unrealFeature.selectShowLoadingProgress);
|
|
6170
|
+
this.isShowLoadingPercentsPopup = computed(() => this.showLoader() && this.showLoadingProgress(), ...(ngDevMode ? [{ debugName: "isShowLoadingPercentsPopup" }] : /* istanbul ignore next */ []));
|
|
6225
6171
|
}
|
|
6172
|
+
#store;
|
|
6173
|
+
#loadingProgress;
|
|
6226
6174
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6227
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", ngImport: i0, template: "@if (
|
|
6175
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", ngImport: i0, template: "@if (isShowLoadingPercentsPopup()) {\n @let _awsInstance = awsInstance();\n <div class=\"status-box\">\n @if (_awsInstance?.message) {\n <p>{{ _awsInstance?.message }}</p>\n } @else {\n <div class=\"status-box__message\">\n <src-loading\n [size]=\"30\"\n [backgroundStrokeColor]=\"'#E5E7EB'\"\n [progressStrokeColor]=\"'#1F2937'\"\n />\n {{ messagePercents() }}%\n </div>\n }\n </div>\n}\n", styles: ["app-stat-graph{height:30px;width:100%}.status-box{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--src-surface-container-main, #fff);border:none;border-radius:var(--src-border-rounded-full, 9999px);box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814;pointer-events:none}.status-box p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color:var(--src-text-body-main, #1f2937);font-size:16px;font-weight:400;line-height:24px}.status-box__message .status-box__message_text{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.status-box__message src-loading{margin-right:12px}\n"], dependencies: [{ kind: "component", type: SourceLoadingComponent, selector: "src-loading", inputs: ["size", "progress", "lineCap", "backgroundStrokeColor", "progressStrokeColor", "strokeWidth", "data-testid", "role", "ariaLabel", "ariaValueNow", "ariaValueMin", "ariaValueMax"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6228
6176
|
}
|
|
6229
6177
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealStatusComponent, decorators: [{
|
|
6230
6178
|
type: Component,
|
|
6231
|
-
args: [{ selector: 'app-unreal-status', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceLoadingComponent], template: "@if (
|
|
6179
|
+
args: [{ selector: 'app-unreal-status', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceLoadingComponent], template: "@if (isShowLoadingPercentsPopup()) {\n @let _awsInstance = awsInstance();\n <div class=\"status-box\">\n @if (_awsInstance?.message) {\n <p>{{ _awsInstance?.message }}</p>\n } @else {\n <div class=\"status-box__message\">\n <src-loading\n [size]=\"30\"\n [backgroundStrokeColor]=\"'#E5E7EB'\"\n [progressStrokeColor]=\"'#1F2937'\"\n />\n {{ messagePercents() }}%\n </div>\n }\n </div>\n}\n", styles: ["app-stat-graph{height:30px;width:100%}.status-box{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--src-surface-container-main, #fff);border:none;border-radius:var(--src-border-rounded-full, 9999px);box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814;pointer-events:none}.status-box p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color:var(--src-text-body-main, #1f2937);font-size:16px;font-weight:400;line-height:24px}.status-box__message .status-box__message_text{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.status-box__message src-loading{margin-right:12px}\n"] }]
|
|
6232
6180
|
}] });
|
|
6233
6181
|
|
|
6234
6182
|
class VideoStatsComponent {
|
|
@@ -6240,42 +6188,18 @@ class VideoStatsComponent {
|
|
|
6240
6188
|
this.obs$ = this.videoService.videoStats$;
|
|
6241
6189
|
this.viewportReady = this.store.selectSignal(unrealFeature.selectViewportReady);
|
|
6242
6190
|
this.ssData = this.store.selectSignal(selectSignalingParameters);
|
|
6243
|
-
this.fpsTick = toSignal(this.obs$.pipe(map$1((stat) => ({
|
|
6244
|
-
|
|
6245
|
-
}))));
|
|
6246
|
-
this.
|
|
6247
|
-
|
|
6248
|
-
}))));
|
|
6249
|
-
this.bitrateTick = toSignal(this.obs$.pipe(map$1((stat) => ({
|
|
6250
|
-
value: stat.aggregatedStats.bitrate,
|
|
6251
|
-
}))));
|
|
6252
|
-
this.videoQP = toSignal(this.obs$.pipe(map$1((stat) => ({
|
|
6253
|
-
value: stat.aggregatedStats.VideoEncoderQP,
|
|
6254
|
-
}))));
|
|
6255
|
-
this.bitrateAverageTick = toSignal(this.obs$.pipe(map$1((stat) => ({
|
|
6256
|
-
value: stat.aggregatedStats.avgBitrate,
|
|
6257
|
-
}))));
|
|
6191
|
+
this.fpsTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.framesPerSecond || 0 }))));
|
|
6192
|
+
this.fpsAvgTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.avgFrameRate }))));
|
|
6193
|
+
this.bitrateTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.bitrate }))));
|
|
6194
|
+
this.videoQP = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.VideoEncoderQP }))));
|
|
6195
|
+
this.bitrateAverageTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.avgBitrate }))));
|
|
6258
6196
|
this.videoStatus = toSignal(this.obs$.pipe(map$1((event) => this.isDevMode &&
|
|
6259
6197
|
{
|
|
6260
6198
|
...event.aggregatedStats,
|
|
6261
6199
|
quality: event.quality,
|
|
6262
|
-
}), filter(Boolean), map$1((data) =>
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
return {
|
|
6266
|
-
key,
|
|
6267
|
-
value: data[key],
|
|
6268
|
-
};
|
|
6269
|
-
})
|
|
6270
|
-
.filter((r) => !this.elementsToShow.includes(r.key));
|
|
6271
|
-
})));
|
|
6272
|
-
this.graphList = [];
|
|
6273
|
-
this.idKey = 'com.source3d.unreal.stat';
|
|
6274
|
-
this.elementsToShow = [];
|
|
6275
|
-
this.trigger$ = new BehaviorSubject(undefined);
|
|
6276
|
-
this.getFields();
|
|
6277
|
-
}
|
|
6278
|
-
ngOnInit() {
|
|
6200
|
+
}), filter(Boolean), map$1((data) => this.fields
|
|
6201
|
+
.map((key) => ({ key, value: data[key] }))
|
|
6202
|
+
.filter((r) => !this.elementsToShow().includes(r.key)))));
|
|
6279
6203
|
this.fields = [
|
|
6280
6204
|
'kalmanBitrate',
|
|
6281
6205
|
'bitrateDrop',
|
|
@@ -6299,26 +6223,21 @@ class VideoStatsComponent {
|
|
|
6299
6223
|
'type',
|
|
6300
6224
|
'codecs',
|
|
6301
6225
|
'selectedPair',
|
|
6302
|
-
//'pliCount',
|
|
6303
|
-
//'sliCount',
|
|
6304
|
-
//'transportId',
|
|
6305
|
-
//'id',
|
|
6306
6226
|
];
|
|
6307
|
-
this.
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
key,
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
this.graphList$ = of(this.graphList).pipe(combineLatestWith(this.trigger$), map$1(([graphList]) => graphList.filter((r) => this.elementsToShow.includes(r.key))));
|
|
6227
|
+
this.elementsToShow = signal([], ...(ngDevMode ? [{ debugName: "elementsToShow" }] : /* istanbul ignore next */ []));
|
|
6228
|
+
this.idKey = 'com.source3d.unreal.stat';
|
|
6229
|
+
this.graphListItems = this.fields.map((key) => ({
|
|
6230
|
+
key,
|
|
6231
|
+
color: '#' +
|
|
6232
|
+
Math.floor(Math.random() * 16777216)
|
|
6233
|
+
.toString(16)
|
|
6234
|
+
.padStart(6, '0'),
|
|
6235
|
+
stat: this.obs$.pipe(map$1((val) => ({
|
|
6236
|
+
value: val.aggregatedStats[key],
|
|
6237
|
+
}))),
|
|
6238
|
+
}));
|
|
6239
|
+
this.graphList = computed(() => this.graphListItems.filter((r) => this.elementsToShow().includes(r.key)), ...(ngDevMode ? [{ debugName: "graphList" }] : /* istanbul ignore next */ []));
|
|
6240
|
+
this.restoreFromStorage();
|
|
6322
6241
|
}
|
|
6323
6242
|
toggle() {
|
|
6324
6243
|
this.collapsed.update((collapsed) => !collapsed);
|
|
@@ -6330,78 +6249,63 @@ class VideoStatsComponent {
|
|
|
6330
6249
|
(typeof value === 'number' && isNaN(value))) {
|
|
6331
6250
|
return;
|
|
6332
6251
|
}
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
else {
|
|
6337
|
-
this.elementsToShow.push(el);
|
|
6338
|
-
}
|
|
6252
|
+
this.elementsToShow.update((current) => current.includes(el)
|
|
6253
|
+
? current.filter((item) => item !== el)
|
|
6254
|
+
: [...current, el]);
|
|
6339
6255
|
this.writeFields();
|
|
6340
|
-
this.trigger$.next();
|
|
6341
6256
|
}
|
|
6342
|
-
|
|
6257
|
+
restoreFromStorage() {
|
|
6343
6258
|
try {
|
|
6344
|
-
const { elements, collapsed } = JSON.parse(localStorage.getItem(this.idKey)) ||
|
|
6345
|
-
this.elementsToShow
|
|
6346
|
-
this.collapsed.set(collapsed);
|
|
6259
|
+
const { elements, collapsed } = JSON.parse(localStorage.getItem(this.idKey)) || {};
|
|
6260
|
+
this.elementsToShow.set(elements || []);
|
|
6261
|
+
this.collapsed.set(collapsed ?? false);
|
|
6347
6262
|
}
|
|
6348
6263
|
catch {
|
|
6349
|
-
this.elementsToShow
|
|
6264
|
+
this.elementsToShow.set([]);
|
|
6350
6265
|
}
|
|
6351
6266
|
}
|
|
6352
6267
|
writeFields() {
|
|
6353
6268
|
localStorage.setItem(this.idKey, JSON.stringify({
|
|
6354
6269
|
collapsed: this.collapsed(),
|
|
6355
|
-
elements: this.elementsToShow,
|
|
6270
|
+
elements: this.elementsToShow(),
|
|
6356
6271
|
}));
|
|
6357
6272
|
}
|
|
6358
6273
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6359
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus() && viewportReady()) {\n <div class=\"settings-container\">\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\n <div class=\"forNerds\">\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\n <li>\n <app-stat-graph\n [stat]=\"fpsTick()!\"\n label=\"FPS (higher is better)\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"videoQP()!\"\n label=\"QP (lower is better)\"\n color=\"#D5ff07\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"bitrateTick()!\"\n label=\"Bitrate\"\n color=\"#D57F07\"\n />\n </li>\n </div>\n <div>\n @for (graph of graphList
|
|
6274
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus() && viewportReady()) {\n <div class=\"settings-container\">\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\n <div class=\"forNerds\">\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\n <li>\n <app-stat-graph\n [stat]=\"fpsTick()!\"\n label=\"FPS (higher is better)\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"videoQP()!\"\n label=\"QP (lower is better)\"\n color=\"#D5ff07\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"bitrateTick()!\"\n label=\"Bitrate\"\n color=\"#D57F07\"\n />\n </li>\n </div>\n <div>\n @for (graph of graphList(); track graph.key) {\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\n <app-stat-graph\n [label]=\"graph.key\"\n [stat]=\"(graph.stat | async)!\"\n [color]=\"graph.color\"\n />\n </li>\n }\n </div>\n </div>\n\n @for (el of videoStatus(); track el.key + $index) {\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\n <span>{{ el.key }}: </span>{{ el.value | json }}\n </div>\n }\n </div>\n </div>\n @if (!collapsed()) {\n <div class=\"ssInfo\">\n @for (line of ssData(); track line) {\n <div>{{ line }}</div>\n }\n </div>\n }\n}\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;width:50px;position:absolute;background:none;cursor:pointer;font-size:10px;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#fff;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}#aggregatedStats{max-height:calc(100% - 25px);overflow-y:auto;margin-top:25px;left:20px;z-index:31;max-width:400px;padding:10px;background-color:#fff3;border:1px solid grey}#aggregatedStats>*{color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}#aggregatedStats:empty{display:none}#aggregatedStats button{position:absolute;right:5px;top:5px}#aggregatedStats.min{display:none}#aggregatedStats .forNerds{width:100%}#aggregatedStats .forNerds li{list-style-type:none;height:60px}#aggregatedStats .forNerds li app-stat-graph{display:block;width:100%;height:100%}#aggregatedStats .stat span{font-weight:700}#aggregatedStats .stat,#aggregatedStats .graph{cursor:pointer}#aggregatedStats .static{pointer-events:all}.ssInfo{position:absolute;top:0;left:50%;transform:translate(-50%);color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;text-align:center;width:80%;pointer-events:none}\n"], dependencies: [{ kind: "component", type: StatGraphComponent, selector: "app-stat-graph", inputs: ["label", "stat", "color", "tickStep"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6360
6275
|
}
|
|
6361
6276
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: VideoStatsComponent, decorators: [{
|
|
6362
6277
|
type: Component,
|
|
6363
|
-
args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, AsyncPipe, JsonPipe
|
|
6278
|
+
args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, AsyncPipe, JsonPipe], template: "@if (videoStatus() && viewportReady()) {\n <div class=\"settings-container\">\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\n <div class=\"forNerds\">\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\n <li>\n <app-stat-graph\n [stat]=\"fpsTick()!\"\n label=\"FPS (higher is better)\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"videoQP()!\"\n label=\"QP (lower is better)\"\n color=\"#D5ff07\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"bitrateTick()!\"\n label=\"Bitrate\"\n color=\"#D57F07\"\n />\n </li>\n </div>\n <div>\n @for (graph of graphList(); track graph.key) {\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\n <app-stat-graph\n [label]=\"graph.key\"\n [stat]=\"(graph.stat | async)!\"\n [color]=\"graph.color\"\n />\n </li>\n }\n </div>\n </div>\n\n @for (el of videoStatus(); track el.key + $index) {\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\n <span>{{ el.key }}: </span>{{ el.value | json }}\n </div>\n }\n </div>\n </div>\n @if (!collapsed()) {\n <div class=\"ssInfo\">\n @for (line of ssData(); track line) {\n <div>{{ line }}</div>\n }\n </div>\n }\n}\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;width:50px;position:absolute;background:none;cursor:pointer;font-size:10px;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#fff;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}#aggregatedStats{max-height:calc(100% - 25px);overflow-y:auto;margin-top:25px;left:20px;z-index:31;max-width:400px;padding:10px;background-color:#fff3;border:1px solid grey}#aggregatedStats>*{color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}#aggregatedStats:empty{display:none}#aggregatedStats button{position:absolute;right:5px;top:5px}#aggregatedStats.min{display:none}#aggregatedStats .forNerds{width:100%}#aggregatedStats .forNerds li{list-style-type:none;height:60px}#aggregatedStats .forNerds li app-stat-graph{display:block;width:100%;height:100%}#aggregatedStats .stat span{font-weight:700}#aggregatedStats .stat,#aggregatedStats .graph{cursor:pointer}#aggregatedStats .static{pointer-events:all}.ssInfo{position:absolute;top:0;left:50%;transform:translate(-50%);color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;text-align:center;width:80%;pointer-events:none}\n"] }]
|
|
6364
6279
|
}], ctorParameters: () => [] });
|
|
6365
6280
|
|
|
6366
6281
|
class ResumeStreamButtonComponent {
|
|
6282
|
+
#store;
|
|
6367
6283
|
constructor() {
|
|
6368
|
-
this.actions$ = inject(Actions);
|
|
6369
6284
|
this.#store = inject(Store);
|
|
6370
|
-
this.#videoService = inject(VideoService);
|
|
6371
|
-
this.#destroyRef = inject(DestroyRef);
|
|
6372
|
-
this.showReconnectPopup = this.#store.selectSignal(selectShowReconnectPopup);
|
|
6373
|
-
this.isLoaderScreenVisible = this.#store.selectSignal(selectIsLoaderScreenVisible);
|
|
6374
6285
|
this.streamConfig = this.#store.selectSignal(selectStreamConfig);
|
|
6286
|
+
this.isSecondStart = signal(!!(this.streamConfig()?.autoStart && location.href.match(/^https/gi)), ...(ngDevMode ? [{ debugName: "isSecondStart" }] : /* istanbul ignore next */ []));
|
|
6287
|
+
this.isLoaderScreenVisible = this.#store.selectSignal(selectIsLoaderScreenVisible);
|
|
6288
|
+
this.showReconnectPopup = this.#store.selectSignal(selectShowReconnectPopup);
|
|
6375
6289
|
this.isExistMatchUrls = this.#store.selectSignal(selectIsExistMatchUrls);
|
|
6376
6290
|
this.showResumeStreamPopup = this.#store.selectSignal(unrealFeature.selectShowResumeStreamPopup);
|
|
6377
|
-
this.isSecondStart = signal(!!(this.streamConfig()?.autoStart && location.href.match(/^https/gi)), ...(ngDevMode ? [{ debugName: "isSecondStart" }] : /* istanbul ignore next */ []));
|
|
6378
6291
|
this.isShowResumeButton = computed(() => this.isLoaderScreenVisible() &&
|
|
6379
6292
|
this.showReconnectPopup() &&
|
|
6380
6293
|
this.isExistMatchUrls() &&
|
|
6381
6294
|
this.showResumeStreamPopup(), ...(ngDevMode ? [{ debugName: "isShowResumeButton" }] : /* istanbul ignore next */ []));
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
.subscribe(() => this.onDisconnect());
|
|
6391
|
-
}
|
|
6392
|
-
ngAfterViewInit() {
|
|
6393
|
-
this.actions$
|
|
6394
|
-
.pipe(ofType(videoStreamInitialized), takeUntilDestroyed(this.#destroyRef), filter(() => !!this.#videoService.video?.paused))
|
|
6395
|
-
.subscribe(() => {
|
|
6396
|
-
Logger.info('Simulate Press Play');
|
|
6397
|
-
this.#videoService.play();
|
|
6398
|
-
this.#store.dispatch(resetAfk());
|
|
6295
|
+
this.cirrusConnected = this.#store.selectSignal(unrealFeature.selectCirrusConnected);
|
|
6296
|
+
let wasConnected = this.cirrusConnected();
|
|
6297
|
+
effect(() => {
|
|
6298
|
+
const connected = this.cirrusConnected();
|
|
6299
|
+
if (!connected && wasConnected) {
|
|
6300
|
+
this.disconnect();
|
|
6301
|
+
}
|
|
6302
|
+
wasConnected = connected;
|
|
6399
6303
|
});
|
|
6400
6304
|
}
|
|
6401
6305
|
connect() {
|
|
6402
6306
|
this.#store.dispatch(startStream({ config: { autoStart: true } }));
|
|
6403
6307
|
}
|
|
6404
|
-
|
|
6308
|
+
disconnect() {
|
|
6405
6309
|
this.isSecondStart.set(true);
|
|
6406
6310
|
}
|
|
6407
6311
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
@@ -6410,13 +6314,13 @@ class ResumeStreamButtonComponent {
|
|
|
6410
6314
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
|
|
6411
6315
|
type: Component,
|
|
6412
6316
|
args: [{ selector: 'app-resume-stream-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceButtonComponent], template: "@if (isShowResumeButton()) {\n <div class=\"resume-box\">\n @if (isSecondStart()) {\n <div class=\"resume-box__text\">\n Your stream has been paused due to inactivity\n </div>\n }\n\n <src-button\n (onClick)=\"connect()\"\n [isFullWidth]=\"true\"\n [data-testid]=\"'connect-button'\"\n size=\"lg\"\n weight=\"primary\"\n class=\"connect-button\"\n >\n {{ isSecondStart() ? 'Resume' : 'Start' }}\n </src-button>\n </div>\n}\n", styles: [".resume-box{padding:16px;position:absolute;bottom:0;left:50%;gap:12px;z-index:6;display:flex;transform:translate(-50%,-50%);flex-direction:column;align-items:center;border-radius:var(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .connect-button{width:100%}.resume-box .resume-box__text{color:var(--src-color-grey-500, #6b7280);text-align:center;font-family:var(--src-font-family-body);font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"] }]
|
|
6413
|
-
}] });
|
|
6317
|
+
}], ctorParameters: () => [] });
|
|
6414
6318
|
|
|
6415
6319
|
class IntroSrcComponent {
|
|
6416
6320
|
#store;
|
|
6417
6321
|
constructor() {
|
|
6418
|
-
this.videoElement = viewChild('videoPlayerCover', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
|
|
6419
6322
|
this.#store = inject(Store);
|
|
6323
|
+
this.videoElement = viewChild('videoPlayerCover', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
|
|
6420
6324
|
this.imageIntroSrc = this.#store.selectSignal(unrealFeature.selectImageIntroSrc);
|
|
6421
6325
|
this.videoIntroSrc = this.#store.selectSignal(unrealFeature.selectVideoIntroSrc);
|
|
6422
6326
|
this.viewportReady = this.#store.selectSignal(unrealFeature.selectViewportReady);
|
|
@@ -6440,6 +6344,9 @@ class IntroSrcComponent {
|
|
|
6440
6344
|
});
|
|
6441
6345
|
});
|
|
6442
6346
|
}
|
|
6347
|
+
loadingImageError() {
|
|
6348
|
+
this.#store.dispatch(resetIntroSrc());
|
|
6349
|
+
}
|
|
6443
6350
|
createVideo(src, videoContainer) {
|
|
6444
6351
|
if (typeof src !== 'string') {
|
|
6445
6352
|
return;
|
|
@@ -6470,15 +6377,12 @@ class IntroSrcComponent {
|
|
|
6470
6377
|
sourceElement.addEventListener('error', () => this.#store.dispatch(resetIntroSrc()));
|
|
6471
6378
|
videoContainer.appendChild(videoElement);
|
|
6472
6379
|
}
|
|
6473
|
-
imageError() {
|
|
6474
|
-
this.#store.dispatch(resetIntroSrc());
|
|
6475
|
-
}
|
|
6476
6380
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6477
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: IntroSrcComponent, isStandalone: true, selector: "app-intro-src", viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoPlayerCover"], descendants: true, isSignal: true }], ngImport: i0, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"
|
|
6381
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: IntroSrcComponent, isStandalone: true, selector: "app-intro-src", viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoPlayerCover"], descendants: true, isSignal: true }], ngImport: i0, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\n </div>\n} @else if (videoIntroSrc()) {\n <div #videoPlayerCover class=\"backVideo\"></div>\n}\n", styles: [".backVideo{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backVideo video{position:absolute;width:100%;height:100%;object-fit:cover}.backImage{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backImage img{position:absolute;width:100%;height:100%;object-fit:cover}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6478
6382
|
}
|
|
6479
6383
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IntroSrcComponent, decorators: [{
|
|
6480
6384
|
type: Component,
|
|
6481
|
-
args: [{ selector: 'app-intro-src', imports: [NgOptimizedImage], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"
|
|
6385
|
+
args: [{ selector: 'app-intro-src', imports: [NgOptimizedImage], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\n </div>\n} @else if (videoIntroSrc()) {\n <div #videoPlayerCover class=\"backVideo\"></div>\n}\n", styles: [".backVideo{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backVideo video{position:absolute;width:100%;height:100%;object-fit:cover}.backImage{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backImage img{position:absolute;width:100%;height:100%;object-fit:cover}\n"] }]
|
|
6482
6386
|
}], ctorParameters: () => [], propDecorators: { videoElement: [{ type: i0.ViewChild, args: ['videoPlayerCover', { isSignal: true }] }] } });
|
|
6483
6387
|
|
|
6484
6388
|
class UnrealSceneComponent {
|
|
@@ -6498,19 +6402,19 @@ class UnrealSceneComponent {
|
|
|
6498
6402
|
onMouseOut() {
|
|
6499
6403
|
this.changeMouseOverScene.emit(false);
|
|
6500
6404
|
}
|
|
6405
|
+
onContextMenu(event) {
|
|
6406
|
+
event.preventDefault();
|
|
6407
|
+
}
|
|
6501
6408
|
constructor() {
|
|
6502
|
-
this.isStudio = input(false, ...(ngDevMode ? [{ debugName: "isStudio" }] : /* istanbul ignore next */ []));
|
|
6503
6409
|
this.useContainerAsSizeProvider = input(true, ...(ngDevMode ? [{ debugName: "useContainerAsSizeProvider" }] : /* istanbul ignore next */ []));
|
|
6504
|
-
this.
|
|
6410
|
+
this.isStudio = input(false, ...(ngDevMode ? [{ debugName: "isStudio" }] : /* istanbul ignore next */ []));
|
|
6411
|
+
this.resolutionSize = input({
|
|
6505
6412
|
width: FULL_HD_WIDTH,
|
|
6506
6413
|
height: FULL_HD_HEIGHT,
|
|
6507
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
6508
|
-
this.studioResolutionValues = toObservable(computed(() => {
|
|
6509
|
-
const values = this.studioResolutionSize();
|
|
6510
|
-
return { width: values.width, height: values.height };
|
|
6511
|
-
}));
|
|
6512
|
-
this.videoElement = viewChild('videoContainer', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
|
|
6414
|
+
}, ...(ngDevMode ? [{ debugName: "resolutionSize" }] : /* istanbul ignore next */ []));
|
|
6513
6415
|
this.changeMouseOverScene = output();
|
|
6416
|
+
this.videoElement = viewChild('videoContainer', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
|
|
6417
|
+
this.resolutionValues$ = toObservable(this.resolutionSize);
|
|
6514
6418
|
this.width = FULL_HD_WIDTH;
|
|
6515
6419
|
this.height = FULL_HD_HEIGHT;
|
|
6516
6420
|
this.actions$ = inject(Actions);
|
|
@@ -6521,38 +6425,34 @@ class UnrealSceneComponent {
|
|
|
6521
6425
|
this.maxResolution = Truthy(new URLSearchParams(location.search).get('maxresolution'));
|
|
6522
6426
|
this.commandsSender = inject(UnrealCommunicatorService);
|
|
6523
6427
|
this.videoService = inject(VideoService);
|
|
6524
|
-
this.element = inject(ElementRef);
|
|
6525
6428
|
this.destroyRef = inject(DestroyRef);
|
|
6526
6429
|
this.destroyRef.onDestroy(() => this.#store.dispatch(destroyUnrealScene()));
|
|
6527
6430
|
effect(() => {
|
|
6528
6431
|
const videoElement = this.videoElement();
|
|
6529
6432
|
const isExistMatchUrls = this.isExistMatchUrls();
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6433
|
+
untracked(() => {
|
|
6434
|
+
if (videoElement && isExistMatchUrls) {
|
|
6435
|
+
this.#store.dispatch(initSignalling());
|
|
6436
|
+
}
|
|
6437
|
+
});
|
|
6533
6438
|
});
|
|
6534
6439
|
afterNextRender({
|
|
6535
6440
|
read: () => {
|
|
6536
6441
|
const videoElement = this.videoElement();
|
|
6537
6442
|
if (videoElement) {
|
|
6538
6443
|
this.videoService.setContainer(videoElement.nativeElement);
|
|
6539
|
-
if (this.element?.nativeElement) {
|
|
6540
|
-
fromEvent(this.element?.nativeElement, 'contextmenu')
|
|
6541
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
6542
|
-
.subscribe((event) => event.preventDefault());
|
|
6543
|
-
}
|
|
6544
6444
|
this.listenResizeValues();
|
|
6545
6445
|
}
|
|
6546
6446
|
},
|
|
6547
6447
|
});
|
|
6548
6448
|
}
|
|
6549
6449
|
listenResizeValues() {
|
|
6550
|
-
const customSizeInputChanged$ = this.
|
|
6450
|
+
const customSizeInputChanged$ = this.resolutionValues$.pipe(filter(({ width, height }) => this.width !== width || this.height !== height), tap(({ width, height }) => {
|
|
6551
6451
|
this.width = width;
|
|
6552
6452
|
this.height = height;
|
|
6553
6453
|
}));
|
|
6554
6454
|
const resizeRequired$ = merge(this.actions$.pipe(ofType(videoStreamInitialized)), fromEvent(window, 'resize'));
|
|
6555
|
-
const videoStreamSizeChanged$ = this.videoService
|
|
6455
|
+
const videoStreamSizeChanged$ = this.videoService.videoStats$.pipe(filter(() => !this.isFreezeFrameLoading()), map$1((stats) => ({
|
|
6556
6456
|
w: stats.aggregatedStats.frameWidth || 0,
|
|
6557
6457
|
h: stats.aggregatedStats.frameHeight || 0,
|
|
6558
6458
|
})), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
|
|
@@ -6560,12 +6460,17 @@ class UnrealSceneComponent {
|
|
|
6560
6460
|
merge(customSizeInputChanged$, resizeRequired$, videoStreamSizeChanged$)
|
|
6561
6461
|
.pipe(debounceTime(DEBOUNCE_TO_MANY_RESIZE_CALLS), switchMap(() => {
|
|
6562
6462
|
const key = Math.random();
|
|
6563
|
-
//
|
|
6463
|
+
// Poll 3 times to capture late-arriving DOM measurements after layout settles
|
|
6564
6464
|
return timer(0, 300).pipe(take(3), map$1(() => key));
|
|
6565
6465
|
}), map$1((key) => ({ key, ...this.resizeValues })), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
|
|
6566
6466
|
fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD) &&
|
|
6567
|
-
previous.key === current.key), tap((size) => this.adaptVideo(size)),
|
|
6568
|
-
|
|
6467
|
+
previous.key === current.key), tap((size) => this.adaptVideo(size)),
|
|
6468
|
+
// Key excluded: only dispatch when actual pixel dimensions change
|
|
6469
|
+
distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
|
|
6470
|
+
fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD)),
|
|
6471
|
+
// Holds dispatch until data channel is up; retains true after disconnect
|
|
6472
|
+
// so resizes still reach Unreal — cleanup is handled by destroyUnrealScene
|
|
6473
|
+
combineLatestWith(this.#store
|
|
6569
6474
|
.select(unrealFeature.selectDataChannelConnected)
|
|
6570
6475
|
.pipe(filter(Truthy))), takeUntilDestroyed(this.destroyRef))
|
|
6571
6476
|
.subscribe(([size]) => {
|
|
@@ -6593,11 +6498,13 @@ class UnrealSceneComponent {
|
|
|
6593
6498
|
this.#store.dispatch(videoAdaptedToContainer());
|
|
6594
6499
|
}
|
|
6595
6500
|
getResizeValuesUniversal() {
|
|
6596
|
-
let containerWidth = window.innerWidth ||
|
|
6597
|
-
let containerHeight = window.innerHeight ||
|
|
6501
|
+
let containerWidth = window.innerWidth || MIN_CONTAINER_SIZE;
|
|
6502
|
+
let containerHeight = window.innerHeight || MIN_CONTAINER_SIZE;
|
|
6598
6503
|
if (this.useContainerAsSizeProvider()) {
|
|
6599
|
-
containerWidth =
|
|
6600
|
-
|
|
6504
|
+
containerWidth =
|
|
6505
|
+
this.videoElement()?.nativeElement.offsetWidth || MIN_CONTAINER_SIZE;
|
|
6506
|
+
containerHeight =
|
|
6507
|
+
this.videoElement()?.nativeElement.offsetHeight || MIN_CONTAINER_SIZE;
|
|
6601
6508
|
}
|
|
6602
6509
|
const pixelRatio = this.pixelRatio;
|
|
6603
6510
|
return getResizeValuesUniversal({
|
|
@@ -6611,8 +6518,8 @@ class UnrealSceneComponent {
|
|
|
6611
6518
|
const videoElement = this.videoElement()?.nativeElement;
|
|
6612
6519
|
return getResizeStudioValues({
|
|
6613
6520
|
pixelRatio: this.pixelRatio,
|
|
6614
|
-
containerWidth: videoElement?.offsetWidth ||
|
|
6615
|
-
containerHeight: videoElement?.offsetHeight ||
|
|
6521
|
+
containerWidth: videoElement?.offsetWidth || MIN_CONTAINER_SIZE,
|
|
6522
|
+
containerHeight: videoElement?.offsetHeight || MIN_CONTAINER_SIZE,
|
|
6616
6523
|
studioWidth: this.width,
|
|
6617
6524
|
studioHeight: this.height,
|
|
6618
6525
|
maxResolution: this.maxResolution,
|
|
@@ -6630,7 +6537,7 @@ class UnrealSceneComponent {
|
|
|
6630
6537
|
});
|
|
6631
6538
|
}
|
|
6632
6539
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6633
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: UnrealSceneComponent, isStandalone: true, selector: "app-unreal-scene", inputs: { isStudio: { classPropertyName: "isStudio", publicName: "isStudio", isSignal: true, isRequired: false, transformFunction: null }, useContainerAsSizeProvider: { classPropertyName: "useContainerAsSizeProvider", publicName: "useContainerAsSizeProvider", isSignal: true, isRequired: false, transformFunction: null }, studioResolutionSize: { classPropertyName: "studioResolutionSize", publicName: "studioResolutionSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\n <div #videoContainer id=\"player\"></div>\n\n <app-intro-src />\n <app-image-loading-src />\n <app-resume-stream-button />\n <app-unreal-status />\n <app-freeze-frame />\n <app-afk-timeout-modal />\n\n <ng-content select=\"app-pdf\"></ng-content>\n <ng-content select=\"app-show-case\"></ng-content>\n\n @if (isDevMode) {\n <app-video-stats />\n }\n</div>\n", styles: [".unreal-container{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#fff}.unreal-container #player{position:absolute;width:100%;height:100%;display:flex;justify-content:center;align-items:center;overflow:hidden}.unreal-container.select{cursor:url('data:image/svg+xml,<svg width=\"24\" height=\"24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.15 21.375a1.423 1.423 0 0 1-1.15.063 1.487 1.487 0 0 1-.85-.788l-3-6.45-2.325 3.25c-.283.4-.658.525-1.125.375-.467-.15-.7-.466-.7-.95V4.05c0-.417.188-.717.563-.9.375-.183.729-.142 1.062.125l10.1 7.95c.383.284.496.65.337 1.1-.158.45-.479.675-.962.675h-4.2l2.975 6.375c.183.384.204.767.063 1.15a1.487 1.487 0 0 1-.788.85Z\" fill=\"%23000\"/><path d=\"m12.697 20.861.002.005c.237.495.617.852 1.128 1.04.515.191 1.039.16 1.539-.08a1.987 1.987 0 0 0 1.04-1.128 1.922 1.922 0 0 0-.079-1.536v-.003L13.684 13.5H17.1c.32 0 .628-.075.89-.26.263-.184.438-.447.544-.749.106-.3.135-.617.04-.925a1.458 1.458 0 0 0-.545-.738L7.936 2.883a1.51 1.51 0 0 0-.768-.336 1.471 1.471 0 0 0-.825.154 1.495 1.495 0 0 0-.626.547 1.492 1.492 0 0 0-.217.802v12.825c0 .324.08.634.272.897.193.261.467.43.775.53.31.099.632.12.942.016.31-.103.555-.313.743-.578h.001l1.825-2.552 2.639 5.673Z\" stroke=\"%23fff\" stroke-opacity=\".7\"/></svg>') 6 2,auto}.unreal-container.orbit{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.998 21.6c-1.966 0-3.737-.53-5.312-1.588-1.575-1.058-2.738-2.437-3.488-4.137a.852.852 0 0 1 .013-.713.867.867 0 0 1 .512-.487.86.86 0 0 1 .688.037c.225.109.387.28.487.513.617 1.35 1.559 2.45 2.825 3.3 1.267.85 2.692 1.275 4.275 1.275 1.384 0 2.646-.33 3.788-.988a7.985 7.985 0 0 0 2.762-2.612h-.85a.873.873 0 0 1-.64-.257.863.863 0 0 1-.26-.638.88.88 0 0 1 .26-.643.864.864 0 0 1 .64-.262h3c.255 0 .47.086.642.259a.87.87 0 0 1 .258.64v3a.874.874 0 0 1-.257.642.863.863 0 0 1-.637.259.88.88 0 0 1-.643-.259.865.865 0 0 1-.263-.641v-.725a9.95 9.95 0 0 1-3.35 2.925c-1.35.733-2.833 1.1-4.45 1.1Zm0-17.4c-1.383 0-2.645.33-3.787.987A7.985 7.985 0 0 0 5.448 7.8h.85c.255 0 .47.086.642.257a.863.863 0 0 1 .258.638.88.88 0 0 1-.258.642.864.864 0 0 1-.642.263h-3a.87.87 0 0 1-.64-.259.87.87 0 0 1-.26-.641v-3c0-.255.086-.469.258-.641a.863.863 0 0 1 .637-.26.88.88 0 0 1 .643.26.864.864 0 0 1 .262.64v.726A9.95 9.95 0 0 1 7.548 3.5c1.35-.733 2.834-1.1 4.45-1.1 2 0 3.788.542 5.363 1.625 1.575 1.083 2.736 2.49 3.482 4.22a.724.724 0 0 1-.045.646.926.926 0 0 1-.525.437.87.87 0 0 1-.687-.04.977.977 0 0 1-.488-.513c-.616-1.35-1.558-2.45-2.825-3.3-1.266-.85-2.691-1.275-4.275-1.275Zm0 10.8a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 8.998 12c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 11.998 9c.834 0 1.542.292 2.125.875.584.583.875 1.292.875 2.125s-.291 1.542-.875 2.125a2.893 2.893 0 0 1-2.125.875Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"m2.739 16.072.002.005c.789 1.788 2.014 3.24 3.666 4.35 1.66 1.116 3.53 1.673 5.591 1.673 1.696 0 3.262-.386 4.69-1.16a10.54 10.54 0 0 0 2.714-2.097c.069.167.171.32.308.454.272.268.613.403.994.403.38 0 .722-.135.992-.406s.402-.614.402-.994v-3c0-.381-.134-.724-.405-.995a1.37 1.37 0 0 0-.995-.405h-3c-.383 0-.727.137-.997.411a1.38 1.38 0 0 0-.403.994c0 .38.135.723.407.992.238.237.532.369.858.397a7.552 7.552 0 0 1-2.027 1.685c-1.062.613-2.237.92-3.538.92-1.488 0-2.815-.397-3.996-1.19-1.19-.798-2.07-1.826-2.647-3.087-.147-.34-.393-.6-.727-.76a1.36 1.36 0 0 0-1.073-.058c-.37.133-.64.397-.8.754a1.351 1.351 0 0 0-.016 1.114Zm4.553-8.37a1.36 1.36 0 0 0-.858-.396A7.552 7.552 0 0 1 8.46 5.62c1.062-.613 2.237-.921 3.537-.921 1.489 0 2.816.398 3.997 1.19 1.19.8 2.07 1.827 2.646 3.088.148.34.394.6.728.76a1.37 1.37 0 0 0 1.068.063c.347-.12.62-.346.799-.668.187-.338.215-.71.072-1.072l-.003-.007-.003-.007c-.784-1.818-2.006-3.298-3.658-4.434C15.982 2.47 14.094 1.9 11.998 1.9c-1.695 0-3.261.385-4.688 1.16a10.536 10.536 0 0 0-2.715 2.096 1.364 1.364 0 0 0-.308-.453 1.38 1.38 0 0 0-.994-.403c-.38 0-.722.135-.992.406s-.403.614-.403.994v3c0 .38.135.724.406.995.27.27.614.405.994.405h3c.384 0 .727-.137.998-.412a1.38 1.38 0 0 0 .402-.993c0-.38-.135-.723-.406-.992ZM9.52 14.48a3.393 3.393 0 0 0 2.478 1.02c.965 0 1.8-.342 2.479-1.02a3.392 3.392 0 0 0 1.021-2.48c0-.963-.343-1.8-1.021-2.478A3.392 3.392 0 0 0 11.998 8.5c-.964 0-1.8.343-2.478 1.021A3.392 3.392 0 0 0 8.498 12c0 .964.344 1.8 1.022 2.479Z\"/></svg>') 12 12,auto}.unreal-container.pan{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.997 10.075a1.01 1.01 0 0 1-.738-.313 1.008 1.008 0 0 1-.312-.737V6.15l-.95.95a.998.998 0 0 1-.763.312 1.09 1.09 0 0 1-.763-.337 1.06 1.06 0 0 1-.324-.775c0-.3.108-.558.324-.775l2.776-2.775c.117-.116.237-.2.362-.25a1.04 1.04 0 0 1 .775 0c.125.05.246.134.363.25l2.8 2.8c.216.217.32.47.313.762a1.096 1.096 0 0 1-.338.763 1.058 1.058 0 0 1-.775.325c-.3 0-.558-.108-.775-.325l-.925-.925v2.875c0 .283-.104.53-.312.737a1.01 1.01 0 0 1-.738.313Zm0 11.5a1.04 1.04 0 0 1-.388-.075 1.103 1.103 0 0 1-.362-.25l-2.8-2.8a1.002 1.002 0 0 1-.313-.762c.009-.292.121-.546.337-.763.217-.217.475-.325.775-.325.3 0 .559.108.775.325l.926.925v-2.875c0-.283.104-.529.312-.737a1.01 1.01 0 0 1 .738-.313c.283 0 .529.104.738.313.208.208.312.454.312.737v2.875l.95-.95a.998.998 0 0 1 .762-.312c.292.008.546.12.763.337.216.217.324.475.324.775 0 .3-.108.559-.324.775l-2.775 2.775c-.117.116-.238.2-.363.25a1.04 1.04 0 0 1-.387.075Zm4.925-6.05a1.058 1.058 0 0 1-.326-.774c0-.3.109-.559.326-.776l.925-.925H14.97c-.283 0-.528-.104-.736-.312a1.01 1.01 0 0 1-.313-.738c0-.283.104-.53.313-.738.208-.208.453-.312.737-.312h2.875l-.95-.95a.998.998 0 0 1-.313-.762 1.09 1.09 0 0 1 .338-.764 1.06 1.06 0 0 1 .775-.324c.3 0 .558.108.775.324l2.774 2.776c.117.117.2.238.251.363a1.04 1.04 0 0 1 0 .775c-.05.124-.134.245-.25.362l-2.8 2.8c-.217.216-.471.32-.762.313a1.096 1.096 0 0 1-.763-.337Zm-11.4 0L2.746 12.75a1.103 1.103 0 0 1-.25-.362 1.04 1.04 0 0 1 0-.775c.05-.125.133-.246.25-.363l2.8-2.8c.216-.216.47-.32.762-.313.292.01.546.121.763.337.217.217.325.476.325.776 0 .3-.108.558-.325.775l-.925.925h2.875c.283 0 .529.104.737.312.208.209.313.455.313.738s-.105.53-.313.738a1.008 1.008 0 0 1-.737.312H6.147l.95.95c.217.217.32.471.312.762a1.09 1.09 0 0 1-.337.764 1.06 1.06 0 0 1-.775.324c-.3 0-.559-.108-.776-.324Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M9.22 7.912c.436.013.821-.15 1.13-.458l.097-.097v1.668c0 .422.16.793.458 1.09.298.299.67.46 1.092.46.422 0 .793-.161 1.091-.46.298-.297.459-.668.459-1.09V7.357l.071.072c.309.308.693.471 1.13.471.435 0 .82-.163 1.128-.471.301-.303.47-.676.483-1.102v-.001a1.501 1.501 0 0 0-.46-1.13l-2.799-2.8a1.6 1.6 0 0 0-.528-.36l-.003-.001a1.54 1.54 0 0 0-1.145 0l-.002.001a1.6 1.6 0 0 0-.529.36L8.118 5.172l-.001.001a1.56 1.56 0 0 0-.47 1.128c0 .435.163.82.47 1.128.303.303.676.472 1.103.484Zm0 0 .014-.5-.015.5Zm-1.311 6.865a1.497 1.497 0 0 0-.458-1.13l-.097-.097h1.668c.421 0 .793-.16 1.09-.458.298-.299.46-.67.46-1.092a1.51 1.51 0 0 0-.46-1.091 1.507 1.507 0 0 0-1.09-.459H7.354l.071-.072c.309-.308.472-.692.472-1.128 0-.436-.163-.82-.472-1.13a1.596 1.596 0 0 0-1.101-.482h-.002a1.501 1.501 0 0 0-1.129.459l-2.8 2.8a1.6 1.6 0 0 0-.36.528l-.001.003a1.54 1.54 0 0 0 0 1.144v.003c.081.2.208.375.362.529l2.774 2.775c.309.308.693.47 1.129.47.435 0 .82-.162 1.128-.47.303-.302.472-.676.484-1.102Zm0 0-.5-.014.5.015v-.001Zm3.038-8.627-.5.5.5.207V6.15Zm.475 15.814.002.001a1.54 1.54 0 0 0 1.145 0h.002c.2-.081.375-.208.53-.362l2.775-2.774a1.56 1.56 0 0 0 .47-1.129c0-.435-.162-.82-.47-1.128a1.589 1.589 0 0 0-1.103-.484 1.497 1.497 0 0 0-1.13.458l-.096.097v-1.668c0-.421-.161-.793-.459-1.09a1.509 1.509 0 0 0-1.091-.46c-.422 0-.794.161-1.092.46a1.507 1.507 0 0 0-.458 1.09v1.668l-.072-.072a1.558 1.558 0 0 0-1.129-.471c-.436 0-.82.163-1.128.471l-.001.001c-.301.302-.47.675-.483 1.101v.001c-.012.437.151.821.46 1.13l2.8 2.8c.153.153.328.28.528.36Zm5.146-6.085c.303.302.676.47 1.102.484a1.5 1.5 0 0 0 1.13-.46l2.8-2.8a1.6 1.6 0 0 0 .36-.528l.002-.003a1.54 1.54 0 0 0 0-1.144l-.001-.003a1.6 1.6 0 0 0-.361-.529l-2.774-2.775h-.001a1.56 1.56 0 0 0-1.128-.47c-.436 0-.82.162-1.128.47h-.001a1.59 1.59 0 0 0-.484 1.102c-.012.437.15.822.459 1.13l.097.097h-1.668c-.422 0-.793.16-1.09.459-.299.298-.46.67-.46 1.091 0 .422.161.793.46 1.092.297.297.668.458 1.09.458h1.668l-.072.072a1.558 1.558 0 0 0-.472 1.129c0 .435.164.82.472 1.128ZM6.147 13.05l.5.5.207-.5h-.707Z\"/></svg>') 12 12,auto}.unreal-container.dolly{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><mask id=\"a\" width=\"24\" height=\"24\" x=\"0\" y=\"0\" maskUnits=\"userSpaceOnUse\" style=\"mask-type:alpha\"><path fill=\"%23127392\" d=\"M0 0h24v24H0z\"/></mask><g mask=\"url(%23a)\"><path fill=\"%23000\" d=\"M12.002 20.575a1.026 1.026 0 0 1-.75-.325l-2.55-2.55a.933.933 0 0 1-.287-.713c.008-.275.113-.513.313-.713.2-.2.441-.3.725-.3.283 0 .524.1.724.3l.776.776V6.95l-.8.8c-.2.2-.438.296-.712.288a1.018 1.018 0 0 1-.713-.312c-.2-.2-.3-.442-.3-.726s.1-.525.3-.725l2.524-2.525a1.026 1.026 0 0 1 1.5 0l2.55 2.55c.2.2.296.438.289.713a1.023 1.023 0 0 1-.314.713c-.2.2-.441.3-.724.3a.988.988 0 0 1-.725-.3l-.775-.776v10.1l.8-.8c.2-.2.437-.296.712-.288.275.008.512.112.712.312.2.2.3.442.3.726s-.1.525-.3.725l-2.524 2.525a1.026 1.026 0 0 1-.75.325Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M13.553 8.153c.28.245.62.373 1 .373.415 0 .784-.153 1.078-.447.287-.287.446-.644.46-1.05v-.002a1.432 1.432 0 0 0-.435-1.08l-2.55-2.55-.354.353.354-.354a1.526 1.526 0 0 0-2.207 0L8.374 5.921A1.487 1.487 0 0 0 7.928 7c0 .416.152.785.446 1.08.288.287.645.446 1.052.457l4.127-.384Zm0 0v7.693m0-7.693a1.667 1.667 0 0 1-.079-.074l-3.021.075v7.693a1.482 1.482 0 0 0-1-.373c-.416 0-.785.153-1.079.447a1.522 1.522 0 0 0-.46 1.05v.002c-.011.417.14.786.435 1.08l2.55 2.55a1.527 1.527 0 0 0 2.207 0l2.525-2.524c.294-.294.446-.663.446-1.079 0-.416-.152-.785-.446-1.08a1.518 1.518 0 0 0-1.052-.457m0 0-.014.5.014-.5Zm0 0a1.424 1.424 0 0 0-1.027.383m0 0a1.583 1.583 0 0 0-.053.05l.354.354m-.3-.404v.104l.3.3m0 0-.3-.3v.6l.3-.3Z\"/></g></svg>%0A') 12 12,auto}.unreal-container.disabled:before{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}#playerUI{position:absolute;z-index:10;width:100%}#statsContainer{display:none;text-align:left;background-color:#000c}#stats{padding:6px;font-weight:700;font-size:14px;color:#0f0}canvas{position:absolute;image-rendering:crisp-edges}#overlay{position:absolute;top:0;right:2%;z-index:100;padding:4px;border-top-width:0;-webkit-border-bottom-right-radius:5px;-moz-border-radius-bottomright:5px;border-bottom-right-radius:5px;-webkit-border-bottom-left-radius:5px;-moz-border-radius-bottomleft:5px;border-bottom-left-radius:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none}.overlay{font-weight:lighter;font-family:var(--src-font-family-body)}#overlayButton:hover{cursor:pointer}#overlayButton{font-size:40px;text-align:right}#overlaySettings{display:none;width:300px;padding:4px}#videoMessageOverlay{position:absolute;z-index:20;width:100%;margin:auto;font-size:1.8em;font-family:var(--src-font-family-body)}#playButton{display:inline-block;height:auto}img#playButton{width:10%;max-width:241px}#UIInteraction{position:fixed}#UIInteractionButtonBoundary{padding:2px}#UIInteractionButton{cursor:pointer}#hiddenInput{position:absolute;left:-10%;width:0;opacity:0}#editTextButton{position:absolute;width:40px;height:40px}\n"], dependencies: [{ kind: "component", type: AfkTimeoutModalComponent, selector: "app-afk-timeout-modal" }, { kind: "component", type: UnrealStatusComponent, selector: "app-unreal-status" }, { kind: "component", type: FreezeFrameComponent, selector: "app-freeze-frame" }, { kind: "component", type: VideoStatsComponent, selector: "app-video-stats" }, { kind: "component", type: ResumeStreamButtonComponent, selector: "app-resume-stream-button" }, { kind: "component", type: ImageLoadingSrcComponent, selector: "app-image-loading-src" }, { kind: "component", type: IntroSrcComponent, selector: "app-intro-src" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6540
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: UnrealSceneComponent, isStandalone: true, selector: "app-unreal-scene", inputs: { useContainerAsSizeProvider: { classPropertyName: "useContainerAsSizeProvider", publicName: "useContainerAsSizeProvider", isSignal: true, isRequired: false, transformFunction: null }, isStudio: { classPropertyName: "isStudio", publicName: "isStudio", isSignal: true, isRequired: false, transformFunction: null }, resolutionSize: { classPropertyName: "resolutionSize", publicName: "resolutionSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()", "contextmenu": "onContextMenu($event)" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\n <div #videoContainer id=\"player\"></div>\n\n <app-intro-src />\n <app-image-loading-src />\n <app-resume-stream-button />\n <app-unreal-status />\n <app-freeze-frame />\n <app-afk-timeout-modal />\n\n <ng-content select=\"app-pdf\"></ng-content>\n <ng-content select=\"app-show-case\"></ng-content>\n\n @if (isDevMode) {\n <app-video-stats />\n }\n</div>\n", styles: [".unreal-container{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#fff}.unreal-container #player{position:absolute;width:100%;height:100%;display:flex;justify-content:center;align-items:center;overflow:hidden}.unreal-container.select{cursor:url('data:image/svg+xml,<svg width=\"24\" height=\"24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.15 21.375a1.423 1.423 0 0 1-1.15.063 1.487 1.487 0 0 1-.85-.788l-3-6.45-2.325 3.25c-.283.4-.658.525-1.125.375-.467-.15-.7-.466-.7-.95V4.05c0-.417.188-.717.563-.9.375-.183.729-.142 1.062.125l10.1 7.95c.383.284.496.65.337 1.1-.158.45-.479.675-.962.675h-4.2l2.975 6.375c.183.384.204.767.063 1.15a1.487 1.487 0 0 1-.788.85Z\" fill=\"%23000\"/><path d=\"m12.697 20.861.002.005c.237.495.617.852 1.128 1.04.515.191 1.039.16 1.539-.08a1.987 1.987 0 0 0 1.04-1.128 1.922 1.922 0 0 0-.079-1.536v-.003L13.684 13.5H17.1c.32 0 .628-.075.89-.26.263-.184.438-.447.544-.749.106-.3.135-.617.04-.925a1.458 1.458 0 0 0-.545-.738L7.936 2.883a1.51 1.51 0 0 0-.768-.336 1.471 1.471 0 0 0-.825.154 1.495 1.495 0 0 0-.626.547 1.492 1.492 0 0 0-.217.802v12.825c0 .324.08.634.272.897.193.261.467.43.775.53.31.099.632.12.942.016.31-.103.555-.313.743-.578h.001l1.825-2.552 2.639 5.673Z\" stroke=\"%23fff\" stroke-opacity=\".7\"/></svg>') 6 2,auto}.unreal-container.orbit{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.998 21.6c-1.966 0-3.737-.53-5.312-1.588-1.575-1.058-2.738-2.437-3.488-4.137a.852.852 0 0 1 .013-.713.867.867 0 0 1 .512-.487.86.86 0 0 1 .688.037c.225.109.387.28.487.513.617 1.35 1.559 2.45 2.825 3.3 1.267.85 2.692 1.275 4.275 1.275 1.384 0 2.646-.33 3.788-.988a7.985 7.985 0 0 0 2.762-2.612h-.85a.873.873 0 0 1-.64-.257.863.863 0 0 1-.26-.638.88.88 0 0 1 .26-.643.864.864 0 0 1 .64-.262h3c.255 0 .47.086.642.259a.87.87 0 0 1 .258.64v3a.874.874 0 0 1-.257.642.863.863 0 0 1-.637.259.88.88 0 0 1-.643-.259.865.865 0 0 1-.263-.641v-.725a9.95 9.95 0 0 1-3.35 2.925c-1.35.733-2.833 1.1-4.45 1.1Zm0-17.4c-1.383 0-2.645.33-3.787.987A7.985 7.985 0 0 0 5.448 7.8h.85c.255 0 .47.086.642.257a.863.863 0 0 1 .258.638.88.88 0 0 1-.258.642.864.864 0 0 1-.642.263h-3a.87.87 0 0 1-.64-.259.87.87 0 0 1-.26-.641v-3c0-.255.086-.469.258-.641a.863.863 0 0 1 .637-.26.88.88 0 0 1 .643.26.864.864 0 0 1 .262.64v.726A9.95 9.95 0 0 1 7.548 3.5c1.35-.733 2.834-1.1 4.45-1.1 2 0 3.788.542 5.363 1.625 1.575 1.083 2.736 2.49 3.482 4.22a.724.724 0 0 1-.045.646.926.926 0 0 1-.525.437.87.87 0 0 1-.687-.04.977.977 0 0 1-.488-.513c-.616-1.35-1.558-2.45-2.825-3.3-1.266-.85-2.691-1.275-4.275-1.275Zm0 10.8a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 8.998 12c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 11.998 9c.834 0 1.542.292 2.125.875.584.583.875 1.292.875 2.125s-.291 1.542-.875 2.125a2.893 2.893 0 0 1-2.125.875Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"m2.739 16.072.002.005c.789 1.788 2.014 3.24 3.666 4.35 1.66 1.116 3.53 1.673 5.591 1.673 1.696 0 3.262-.386 4.69-1.16a10.54 10.54 0 0 0 2.714-2.097c.069.167.171.32.308.454.272.268.613.403.994.403.38 0 .722-.135.992-.406s.402-.614.402-.994v-3c0-.381-.134-.724-.405-.995a1.37 1.37 0 0 0-.995-.405h-3c-.383 0-.727.137-.997.411a1.38 1.38 0 0 0-.403.994c0 .38.135.723.407.992.238.237.532.369.858.397a7.552 7.552 0 0 1-2.027 1.685c-1.062.613-2.237.92-3.538.92-1.488 0-2.815-.397-3.996-1.19-1.19-.798-2.07-1.826-2.647-3.087-.147-.34-.393-.6-.727-.76a1.36 1.36 0 0 0-1.073-.058c-.37.133-.64.397-.8.754a1.351 1.351 0 0 0-.016 1.114Zm4.553-8.37a1.36 1.36 0 0 0-.858-.396A7.552 7.552 0 0 1 8.46 5.62c1.062-.613 2.237-.921 3.537-.921 1.489 0 2.816.398 3.997 1.19 1.19.8 2.07 1.827 2.646 3.088.148.34.394.6.728.76a1.37 1.37 0 0 0 1.068.063c.347-.12.62-.346.799-.668.187-.338.215-.71.072-1.072l-.003-.007-.003-.007c-.784-1.818-2.006-3.298-3.658-4.434C15.982 2.47 14.094 1.9 11.998 1.9c-1.695 0-3.261.385-4.688 1.16a10.536 10.536 0 0 0-2.715 2.096 1.364 1.364 0 0 0-.308-.453 1.38 1.38 0 0 0-.994-.403c-.38 0-.722.135-.992.406s-.403.614-.403.994v3c0 .38.135.724.406.995.27.27.614.405.994.405h3c.384 0 .727-.137.998-.412a1.38 1.38 0 0 0 .402-.993c0-.38-.135-.723-.406-.992ZM9.52 14.48a3.393 3.393 0 0 0 2.478 1.02c.965 0 1.8-.342 2.479-1.02a3.392 3.392 0 0 0 1.021-2.48c0-.963-.343-1.8-1.021-2.478A3.392 3.392 0 0 0 11.998 8.5c-.964 0-1.8.343-2.478 1.021A3.392 3.392 0 0 0 8.498 12c0 .964.344 1.8 1.022 2.479Z\"/></svg>') 12 12,auto}.unreal-container.pan{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.997 10.075a1.01 1.01 0 0 1-.738-.313 1.008 1.008 0 0 1-.312-.737V6.15l-.95.95a.998.998 0 0 1-.763.312 1.09 1.09 0 0 1-.763-.337 1.06 1.06 0 0 1-.324-.775c0-.3.108-.558.324-.775l2.776-2.775c.117-.116.237-.2.362-.25a1.04 1.04 0 0 1 .775 0c.125.05.246.134.363.25l2.8 2.8c.216.217.32.47.313.762a1.096 1.096 0 0 1-.338.763 1.058 1.058 0 0 1-.775.325c-.3 0-.558-.108-.775-.325l-.925-.925v2.875c0 .283-.104.53-.312.737a1.01 1.01 0 0 1-.738.313Zm0 11.5a1.04 1.04 0 0 1-.388-.075 1.103 1.103 0 0 1-.362-.25l-2.8-2.8a1.002 1.002 0 0 1-.313-.762c.009-.292.121-.546.337-.763.217-.217.475-.325.775-.325.3 0 .559.108.775.325l.926.925v-2.875c0-.283.104-.529.312-.737a1.01 1.01 0 0 1 .738-.313c.283 0 .529.104.738.313.208.208.312.454.312.737v2.875l.95-.95a.998.998 0 0 1 .762-.312c.292.008.546.12.763.337.216.217.324.475.324.775 0 .3-.108.559-.324.775l-2.775 2.775c-.117.116-.238.2-.363.25a1.04 1.04 0 0 1-.387.075Zm4.925-6.05a1.058 1.058 0 0 1-.326-.774c0-.3.109-.559.326-.776l.925-.925H14.97c-.283 0-.528-.104-.736-.312a1.01 1.01 0 0 1-.313-.738c0-.283.104-.53.313-.738.208-.208.453-.312.737-.312h2.875l-.95-.95a.998.998 0 0 1-.313-.762 1.09 1.09 0 0 1 .338-.764 1.06 1.06 0 0 1 .775-.324c.3 0 .558.108.775.324l2.774 2.776c.117.117.2.238.251.363a1.04 1.04 0 0 1 0 .775c-.05.124-.134.245-.25.362l-2.8 2.8c-.217.216-.471.32-.762.313a1.096 1.096 0 0 1-.763-.337Zm-11.4 0L2.746 12.75a1.103 1.103 0 0 1-.25-.362 1.04 1.04 0 0 1 0-.775c.05-.125.133-.246.25-.363l2.8-2.8c.216-.216.47-.32.762-.313.292.01.546.121.763.337.217.217.325.476.325.776 0 .3-.108.558-.325.775l-.925.925h2.875c.283 0 .529.104.737.312.208.209.313.455.313.738s-.105.53-.313.738a1.008 1.008 0 0 1-.737.312H6.147l.95.95c.217.217.32.471.312.762a1.09 1.09 0 0 1-.337.764 1.06 1.06 0 0 1-.775.324c-.3 0-.559-.108-.776-.324Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M9.22 7.912c.436.013.821-.15 1.13-.458l.097-.097v1.668c0 .422.16.793.458 1.09.298.299.67.46 1.092.46.422 0 .793-.161 1.091-.46.298-.297.459-.668.459-1.09V7.357l.071.072c.309.308.693.471 1.13.471.435 0 .82-.163 1.128-.471.301-.303.47-.676.483-1.102v-.001a1.501 1.501 0 0 0-.46-1.13l-2.799-2.8a1.6 1.6 0 0 0-.528-.36l-.003-.001a1.54 1.54 0 0 0-1.145 0l-.002.001a1.6 1.6 0 0 0-.529.36L8.118 5.172l-.001.001a1.56 1.56 0 0 0-.47 1.128c0 .435.163.82.47 1.128.303.303.676.472 1.103.484Zm0 0 .014-.5-.015.5Zm-1.311 6.865a1.497 1.497 0 0 0-.458-1.13l-.097-.097h1.668c.421 0 .793-.16 1.09-.458.298-.299.46-.67.46-1.092a1.51 1.51 0 0 0-.46-1.091 1.507 1.507 0 0 0-1.09-.459H7.354l.071-.072c.309-.308.472-.692.472-1.128 0-.436-.163-.82-.472-1.13a1.596 1.596 0 0 0-1.101-.482h-.002a1.501 1.501 0 0 0-1.129.459l-2.8 2.8a1.6 1.6 0 0 0-.36.528l-.001.003a1.54 1.54 0 0 0 0 1.144v.003c.081.2.208.375.362.529l2.774 2.775c.309.308.693.47 1.129.47.435 0 .82-.162 1.128-.47.303-.302.472-.676.484-1.102Zm0 0-.5-.014.5.015v-.001Zm3.038-8.627-.5.5.5.207V6.15Zm.475 15.814.002.001a1.54 1.54 0 0 0 1.145 0h.002c.2-.081.375-.208.53-.362l2.775-2.774a1.56 1.56 0 0 0 .47-1.129c0-.435-.162-.82-.47-1.128a1.589 1.589 0 0 0-1.103-.484 1.497 1.497 0 0 0-1.13.458l-.096.097v-1.668c0-.421-.161-.793-.459-1.09a1.509 1.509 0 0 0-1.091-.46c-.422 0-.794.161-1.092.46a1.507 1.507 0 0 0-.458 1.09v1.668l-.072-.072a1.558 1.558 0 0 0-1.129-.471c-.436 0-.82.163-1.128.471l-.001.001c-.301.302-.47.675-.483 1.101v.001c-.012.437.151.821.46 1.13l2.8 2.8c.153.153.328.28.528.36Zm5.146-6.085c.303.302.676.47 1.102.484a1.5 1.5 0 0 0 1.13-.46l2.8-2.8a1.6 1.6 0 0 0 .36-.528l.002-.003a1.54 1.54 0 0 0 0-1.144l-.001-.003a1.6 1.6 0 0 0-.361-.529l-2.774-2.775h-.001a1.56 1.56 0 0 0-1.128-.47c-.436 0-.82.162-1.128.47h-.001a1.59 1.59 0 0 0-.484 1.102c-.012.437.15.822.459 1.13l.097.097h-1.668c-.422 0-.793.16-1.09.459-.299.298-.46.67-.46 1.091 0 .422.161.793.46 1.092.297.297.668.458 1.09.458h1.668l-.072.072a1.558 1.558 0 0 0-.472 1.129c0 .435.164.82.472 1.128ZM6.147 13.05l.5.5.207-.5h-.707Z\"/></svg>') 12 12,auto}.unreal-container.dolly{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><mask id=\"a\" width=\"24\" height=\"24\" x=\"0\" y=\"0\" maskUnits=\"userSpaceOnUse\" style=\"mask-type:alpha\"><path fill=\"%23127392\" d=\"M0 0h24v24H0z\"/></mask><g mask=\"url(%23a)\"><path fill=\"%23000\" d=\"M12.002 20.575a1.026 1.026 0 0 1-.75-.325l-2.55-2.55a.933.933 0 0 1-.287-.713c.008-.275.113-.513.313-.713.2-.2.441-.3.725-.3.283 0 .524.1.724.3l.776.776V6.95l-.8.8c-.2.2-.438.296-.712.288a1.018 1.018 0 0 1-.713-.312c-.2-.2-.3-.442-.3-.726s.1-.525.3-.725l2.524-2.525a1.026 1.026 0 0 1 1.5 0l2.55 2.55c.2.2.296.438.289.713a1.023 1.023 0 0 1-.314.713c-.2.2-.441.3-.724.3a.988.988 0 0 1-.725-.3l-.775-.776v10.1l.8-.8c.2-.2.437-.296.712-.288.275.008.512.112.712.312.2.2.3.442.3.726s-.1.525-.3.725l-2.524 2.525a1.026 1.026 0 0 1-.75.325Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M13.553 8.153c.28.245.62.373 1 .373.415 0 .784-.153 1.078-.447.287-.287.446-.644.46-1.05v-.002a1.432 1.432 0 0 0-.435-1.08l-2.55-2.55-.354.353.354-.354a1.526 1.526 0 0 0-2.207 0L8.374 5.921A1.487 1.487 0 0 0 7.928 7c0 .416.152.785.446 1.08.288.287.645.446 1.052.457l4.127-.384Zm0 0v7.693m0-7.693a1.667 1.667 0 0 1-.079-.074l-3.021.075v7.693a1.482 1.482 0 0 0-1-.373c-.416 0-.785.153-1.079.447a1.522 1.522 0 0 0-.46 1.05v.002c-.011.417.14.786.435 1.08l2.55 2.55a1.527 1.527 0 0 0 2.207 0l2.525-2.524c.294-.294.446-.663.446-1.079 0-.416-.152-.785-.446-1.08a1.518 1.518 0 0 0-1.052-.457m0 0-.014.5.014-.5Zm0 0a1.424 1.424 0 0 0-1.027.383m0 0a1.583 1.583 0 0 0-.053.05l.354.354m-.3-.404v.104l.3.3m0 0-.3-.3v.6l.3-.3Z\"/></g></svg>%0A') 12 12,auto}.unreal-container.disabled:before{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}#playerUI{position:absolute;z-index:10;width:100%}#statsContainer{display:none;text-align:left;background-color:#000c}#stats{padding:6px;font-weight:700;font-size:14px;color:#0f0}canvas{position:absolute;image-rendering:crisp-edges}#overlay{position:absolute;top:0;right:2%;z-index:100;padding:4px;border-top-width:0;-webkit-border-bottom-right-radius:5px;-moz-border-radius-bottomright:5px;border-bottom-right-radius:5px;-webkit-border-bottom-left-radius:5px;-moz-border-radius-bottomleft:5px;border-bottom-left-radius:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none}.overlay{font-weight:lighter;font-family:var(--src-font-family-body)}#overlayButton:hover{cursor:pointer}#overlayButton{font-size:40px;text-align:right}#overlaySettings{display:none;width:300px;padding:4px}#videoMessageOverlay{position:absolute;z-index:20;width:100%;margin:auto;font-size:1.8em;font-family:var(--src-font-family-body)}#playButton{display:inline-block;height:auto}img#playButton{width:10%;max-width:241px}#UIInteraction{position:fixed}#UIInteractionButtonBoundary{padding:2px}#UIInteractionButton{cursor:pointer}#hiddenInput{position:absolute;left:-10%;width:0;opacity:0}#editTextButton{position:absolute;width:40px;height:40px}\n"], dependencies: [{ kind: "component", type: AfkTimeoutModalComponent, selector: "app-afk-timeout-modal" }, { kind: "component", type: UnrealStatusComponent, selector: "app-unreal-status" }, { kind: "component", type: FreezeFrameComponent, selector: "app-freeze-frame" }, { kind: "component", type: VideoStatsComponent, selector: "app-video-stats" }, { kind: "component", type: ResumeStreamButtonComponent, selector: "app-resume-stream-button" }, { kind: "component", type: ImageLoadingSrcComponent, selector: "app-image-loading-src" }, { kind: "component", type: IntroSrcComponent, selector: "app-intro-src" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6634
6541
|
}
|
|
6635
6542
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealSceneComponent, decorators: [{
|
|
6636
6543
|
type: Component,
|
|
@@ -6643,12 +6550,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
6643
6550
|
ImageLoadingSrcComponent,
|
|
6644
6551
|
IntroSrcComponent,
|
|
6645
6552
|
], template: "<div class=\"frame unreal-container\">\n <div #videoContainer id=\"player\"></div>\n\n <app-intro-src />\n <app-image-loading-src />\n <app-resume-stream-button />\n <app-unreal-status />\n <app-freeze-frame />\n <app-afk-timeout-modal />\n\n <ng-content select=\"app-pdf\"></ng-content>\n <ng-content select=\"app-show-case\"></ng-content>\n\n @if (isDevMode) {\n <app-video-stats />\n }\n</div>\n", styles: [".unreal-container{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#fff}.unreal-container #player{position:absolute;width:100%;height:100%;display:flex;justify-content:center;align-items:center;overflow:hidden}.unreal-container.select{cursor:url('data:image/svg+xml,<svg width=\"24\" height=\"24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.15 21.375a1.423 1.423 0 0 1-1.15.063 1.487 1.487 0 0 1-.85-.788l-3-6.45-2.325 3.25c-.283.4-.658.525-1.125.375-.467-.15-.7-.466-.7-.95V4.05c0-.417.188-.717.563-.9.375-.183.729-.142 1.062.125l10.1 7.95c.383.284.496.65.337 1.1-.158.45-.479.675-.962.675h-4.2l2.975 6.375c.183.384.204.767.063 1.15a1.487 1.487 0 0 1-.788.85Z\" fill=\"%23000\"/><path d=\"m12.697 20.861.002.005c.237.495.617.852 1.128 1.04.515.191 1.039.16 1.539-.08a1.987 1.987 0 0 0 1.04-1.128 1.922 1.922 0 0 0-.079-1.536v-.003L13.684 13.5H17.1c.32 0 .628-.075.89-.26.263-.184.438-.447.544-.749.106-.3.135-.617.04-.925a1.458 1.458 0 0 0-.545-.738L7.936 2.883a1.51 1.51 0 0 0-.768-.336 1.471 1.471 0 0 0-.825.154 1.495 1.495 0 0 0-.626.547 1.492 1.492 0 0 0-.217.802v12.825c0 .324.08.634.272.897.193.261.467.43.775.53.31.099.632.12.942.016.31-.103.555-.313.743-.578h.001l1.825-2.552 2.639 5.673Z\" stroke=\"%23fff\" stroke-opacity=\".7\"/></svg>') 6 2,auto}.unreal-container.orbit{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.998 21.6c-1.966 0-3.737-.53-5.312-1.588-1.575-1.058-2.738-2.437-3.488-4.137a.852.852 0 0 1 .013-.713.867.867 0 0 1 .512-.487.86.86 0 0 1 .688.037c.225.109.387.28.487.513.617 1.35 1.559 2.45 2.825 3.3 1.267.85 2.692 1.275 4.275 1.275 1.384 0 2.646-.33 3.788-.988a7.985 7.985 0 0 0 2.762-2.612h-.85a.873.873 0 0 1-.64-.257.863.863 0 0 1-.26-.638.88.88 0 0 1 .26-.643.864.864 0 0 1 .64-.262h3c.255 0 .47.086.642.259a.87.87 0 0 1 .258.64v3a.874.874 0 0 1-.257.642.863.863 0 0 1-.637.259.88.88 0 0 1-.643-.259.865.865 0 0 1-.263-.641v-.725a9.95 9.95 0 0 1-3.35 2.925c-1.35.733-2.833 1.1-4.45 1.1Zm0-17.4c-1.383 0-2.645.33-3.787.987A7.985 7.985 0 0 0 5.448 7.8h.85c.255 0 .47.086.642.257a.863.863 0 0 1 .258.638.88.88 0 0 1-.258.642.864.864 0 0 1-.642.263h-3a.87.87 0 0 1-.64-.259.87.87 0 0 1-.26-.641v-3c0-.255.086-.469.258-.641a.863.863 0 0 1 .637-.26.88.88 0 0 1 .643.26.864.864 0 0 1 .262.64v.726A9.95 9.95 0 0 1 7.548 3.5c1.35-.733 2.834-1.1 4.45-1.1 2 0 3.788.542 5.363 1.625 1.575 1.083 2.736 2.49 3.482 4.22a.724.724 0 0 1-.045.646.926.926 0 0 1-.525.437.87.87 0 0 1-.687-.04.977.977 0 0 1-.488-.513c-.616-1.35-1.558-2.45-2.825-3.3-1.266-.85-2.691-1.275-4.275-1.275Zm0 10.8a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 8.998 12c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 11.998 9c.834 0 1.542.292 2.125.875.584.583.875 1.292.875 2.125s-.291 1.542-.875 2.125a2.893 2.893 0 0 1-2.125.875Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"m2.739 16.072.002.005c.789 1.788 2.014 3.24 3.666 4.35 1.66 1.116 3.53 1.673 5.591 1.673 1.696 0 3.262-.386 4.69-1.16a10.54 10.54 0 0 0 2.714-2.097c.069.167.171.32.308.454.272.268.613.403.994.403.38 0 .722-.135.992-.406s.402-.614.402-.994v-3c0-.381-.134-.724-.405-.995a1.37 1.37 0 0 0-.995-.405h-3c-.383 0-.727.137-.997.411a1.38 1.38 0 0 0-.403.994c0 .38.135.723.407.992.238.237.532.369.858.397a7.552 7.552 0 0 1-2.027 1.685c-1.062.613-2.237.92-3.538.92-1.488 0-2.815-.397-3.996-1.19-1.19-.798-2.07-1.826-2.647-3.087-.147-.34-.393-.6-.727-.76a1.36 1.36 0 0 0-1.073-.058c-.37.133-.64.397-.8.754a1.351 1.351 0 0 0-.016 1.114Zm4.553-8.37a1.36 1.36 0 0 0-.858-.396A7.552 7.552 0 0 1 8.46 5.62c1.062-.613 2.237-.921 3.537-.921 1.489 0 2.816.398 3.997 1.19 1.19.8 2.07 1.827 2.646 3.088.148.34.394.6.728.76a1.37 1.37 0 0 0 1.068.063c.347-.12.62-.346.799-.668.187-.338.215-.71.072-1.072l-.003-.007-.003-.007c-.784-1.818-2.006-3.298-3.658-4.434C15.982 2.47 14.094 1.9 11.998 1.9c-1.695 0-3.261.385-4.688 1.16a10.536 10.536 0 0 0-2.715 2.096 1.364 1.364 0 0 0-.308-.453 1.38 1.38 0 0 0-.994-.403c-.38 0-.722.135-.992.406s-.403.614-.403.994v3c0 .38.135.724.406.995.27.27.614.405.994.405h3c.384 0 .727-.137.998-.412a1.38 1.38 0 0 0 .402-.993c0-.38-.135-.723-.406-.992ZM9.52 14.48a3.393 3.393 0 0 0 2.478 1.02c.965 0 1.8-.342 2.479-1.02a3.392 3.392 0 0 0 1.021-2.48c0-.963-.343-1.8-1.021-2.478A3.392 3.392 0 0 0 11.998 8.5c-.964 0-1.8.343-2.478 1.021A3.392 3.392 0 0 0 8.498 12c0 .964.344 1.8 1.022 2.479Z\"/></svg>') 12 12,auto}.unreal-container.pan{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.997 10.075a1.01 1.01 0 0 1-.738-.313 1.008 1.008 0 0 1-.312-.737V6.15l-.95.95a.998.998 0 0 1-.763.312 1.09 1.09 0 0 1-.763-.337 1.06 1.06 0 0 1-.324-.775c0-.3.108-.558.324-.775l2.776-2.775c.117-.116.237-.2.362-.25a1.04 1.04 0 0 1 .775 0c.125.05.246.134.363.25l2.8 2.8c.216.217.32.47.313.762a1.096 1.096 0 0 1-.338.763 1.058 1.058 0 0 1-.775.325c-.3 0-.558-.108-.775-.325l-.925-.925v2.875c0 .283-.104.53-.312.737a1.01 1.01 0 0 1-.738.313Zm0 11.5a1.04 1.04 0 0 1-.388-.075 1.103 1.103 0 0 1-.362-.25l-2.8-2.8a1.002 1.002 0 0 1-.313-.762c.009-.292.121-.546.337-.763.217-.217.475-.325.775-.325.3 0 .559.108.775.325l.926.925v-2.875c0-.283.104-.529.312-.737a1.01 1.01 0 0 1 .738-.313c.283 0 .529.104.738.313.208.208.312.454.312.737v2.875l.95-.95a.998.998 0 0 1 .762-.312c.292.008.546.12.763.337.216.217.324.475.324.775 0 .3-.108.559-.324.775l-2.775 2.775c-.117.116-.238.2-.363.25a1.04 1.04 0 0 1-.387.075Zm4.925-6.05a1.058 1.058 0 0 1-.326-.774c0-.3.109-.559.326-.776l.925-.925H14.97c-.283 0-.528-.104-.736-.312a1.01 1.01 0 0 1-.313-.738c0-.283.104-.53.313-.738.208-.208.453-.312.737-.312h2.875l-.95-.95a.998.998 0 0 1-.313-.762 1.09 1.09 0 0 1 .338-.764 1.06 1.06 0 0 1 .775-.324c.3 0 .558.108.775.324l2.774 2.776c.117.117.2.238.251.363a1.04 1.04 0 0 1 0 .775c-.05.124-.134.245-.25.362l-2.8 2.8c-.217.216-.471.32-.762.313a1.096 1.096 0 0 1-.763-.337Zm-11.4 0L2.746 12.75a1.103 1.103 0 0 1-.25-.362 1.04 1.04 0 0 1 0-.775c.05-.125.133-.246.25-.363l2.8-2.8c.216-.216.47-.32.762-.313.292.01.546.121.763.337.217.217.325.476.325.776 0 .3-.108.558-.325.775l-.925.925h2.875c.283 0 .529.104.737.312.208.209.313.455.313.738s-.105.53-.313.738a1.008 1.008 0 0 1-.737.312H6.147l.95.95c.217.217.32.471.312.762a1.09 1.09 0 0 1-.337.764 1.06 1.06 0 0 1-.775.324c-.3 0-.559-.108-.776-.324Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M9.22 7.912c.436.013.821-.15 1.13-.458l.097-.097v1.668c0 .422.16.793.458 1.09.298.299.67.46 1.092.46.422 0 .793-.161 1.091-.46.298-.297.459-.668.459-1.09V7.357l.071.072c.309.308.693.471 1.13.471.435 0 .82-.163 1.128-.471.301-.303.47-.676.483-1.102v-.001a1.501 1.501 0 0 0-.46-1.13l-2.799-2.8a1.6 1.6 0 0 0-.528-.36l-.003-.001a1.54 1.54 0 0 0-1.145 0l-.002.001a1.6 1.6 0 0 0-.529.36L8.118 5.172l-.001.001a1.56 1.56 0 0 0-.47 1.128c0 .435.163.82.47 1.128.303.303.676.472 1.103.484Zm0 0 .014-.5-.015.5Zm-1.311 6.865a1.497 1.497 0 0 0-.458-1.13l-.097-.097h1.668c.421 0 .793-.16 1.09-.458.298-.299.46-.67.46-1.092a1.51 1.51 0 0 0-.46-1.091 1.507 1.507 0 0 0-1.09-.459H7.354l.071-.072c.309-.308.472-.692.472-1.128 0-.436-.163-.82-.472-1.13a1.596 1.596 0 0 0-1.101-.482h-.002a1.501 1.501 0 0 0-1.129.459l-2.8 2.8a1.6 1.6 0 0 0-.36.528l-.001.003a1.54 1.54 0 0 0 0 1.144v.003c.081.2.208.375.362.529l2.774 2.775c.309.308.693.47 1.129.47.435 0 .82-.162 1.128-.47.303-.302.472-.676.484-1.102Zm0 0-.5-.014.5.015v-.001Zm3.038-8.627-.5.5.5.207V6.15Zm.475 15.814.002.001a1.54 1.54 0 0 0 1.145 0h.002c.2-.081.375-.208.53-.362l2.775-2.774a1.56 1.56 0 0 0 .47-1.129c0-.435-.162-.82-.47-1.128a1.589 1.589 0 0 0-1.103-.484 1.497 1.497 0 0 0-1.13.458l-.096.097v-1.668c0-.421-.161-.793-.459-1.09a1.509 1.509 0 0 0-1.091-.46c-.422 0-.794.161-1.092.46a1.507 1.507 0 0 0-.458 1.09v1.668l-.072-.072a1.558 1.558 0 0 0-1.129-.471c-.436 0-.82.163-1.128.471l-.001.001c-.301.302-.47.675-.483 1.101v.001c-.012.437.151.821.46 1.13l2.8 2.8c.153.153.328.28.528.36Zm5.146-6.085c.303.302.676.47 1.102.484a1.5 1.5 0 0 0 1.13-.46l2.8-2.8a1.6 1.6 0 0 0 .36-.528l.002-.003a1.54 1.54 0 0 0 0-1.144l-.001-.003a1.6 1.6 0 0 0-.361-.529l-2.774-2.775h-.001a1.56 1.56 0 0 0-1.128-.47c-.436 0-.82.162-1.128.47h-.001a1.59 1.59 0 0 0-.484 1.102c-.012.437.15.822.459 1.13l.097.097h-1.668c-.422 0-.793.16-1.09.459-.299.298-.46.67-.46 1.091 0 .422.161.793.46 1.092.297.297.668.458 1.09.458h1.668l-.072.072a1.558 1.558 0 0 0-.472 1.129c0 .435.164.82.472 1.128ZM6.147 13.05l.5.5.207-.5h-.707Z\"/></svg>') 12 12,auto}.unreal-container.dolly{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><mask id=\"a\" width=\"24\" height=\"24\" x=\"0\" y=\"0\" maskUnits=\"userSpaceOnUse\" style=\"mask-type:alpha\"><path fill=\"%23127392\" d=\"M0 0h24v24H0z\"/></mask><g mask=\"url(%23a)\"><path fill=\"%23000\" d=\"M12.002 20.575a1.026 1.026 0 0 1-.75-.325l-2.55-2.55a.933.933 0 0 1-.287-.713c.008-.275.113-.513.313-.713.2-.2.441-.3.725-.3.283 0 .524.1.724.3l.776.776V6.95l-.8.8c-.2.2-.438.296-.712.288a1.018 1.018 0 0 1-.713-.312c-.2-.2-.3-.442-.3-.726s.1-.525.3-.725l2.524-2.525a1.026 1.026 0 0 1 1.5 0l2.55 2.55c.2.2.296.438.289.713a1.023 1.023 0 0 1-.314.713c-.2.2-.441.3-.724.3a.988.988 0 0 1-.725-.3l-.775-.776v10.1l.8-.8c.2-.2.437-.296.712-.288.275.008.512.112.712.312.2.2.3.442.3.726s-.1.525-.3.725l-2.524 2.525a1.026 1.026 0 0 1-.75.325Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M13.553 8.153c.28.245.62.373 1 .373.415 0 .784-.153 1.078-.447.287-.287.446-.644.46-1.05v-.002a1.432 1.432 0 0 0-.435-1.08l-2.55-2.55-.354.353.354-.354a1.526 1.526 0 0 0-2.207 0L8.374 5.921A1.487 1.487 0 0 0 7.928 7c0 .416.152.785.446 1.08.288.287.645.446 1.052.457l4.127-.384Zm0 0v7.693m0-7.693a1.667 1.667 0 0 1-.079-.074l-3.021.075v7.693a1.482 1.482 0 0 0-1-.373c-.416 0-.785.153-1.079.447a1.522 1.522 0 0 0-.46 1.05v.002c-.011.417.14.786.435 1.08l2.55 2.55a1.527 1.527 0 0 0 2.207 0l2.525-2.524c.294-.294.446-.663.446-1.079 0-.416-.152-.785-.446-1.08a1.518 1.518 0 0 0-1.052-.457m0 0-.014.5.014-.5Zm0 0a1.424 1.424 0 0 0-1.027.383m0 0a1.583 1.583 0 0 0-.053.05l.354.354m-.3-.404v.104l.3.3m0 0-.3-.3v.6l.3-.3Z\"/></g></svg>%0A') 12 12,auto}.unreal-container.disabled:before{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}#playerUI{position:absolute;z-index:10;width:100%}#statsContainer{display:none;text-align:left;background-color:#000c}#stats{padding:6px;font-weight:700;font-size:14px;color:#0f0}canvas{position:absolute;image-rendering:crisp-edges}#overlay{position:absolute;top:0;right:2%;z-index:100;padding:4px;border-top-width:0;-webkit-border-bottom-right-radius:5px;-moz-border-radius-bottomright:5px;border-bottom-right-radius:5px;-webkit-border-bottom-left-radius:5px;-moz-border-radius-bottomleft:5px;border-bottom-left-radius:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none}.overlay{font-weight:lighter;font-family:var(--src-font-family-body)}#overlayButton:hover{cursor:pointer}#overlayButton{font-size:40px;text-align:right}#overlaySettings{display:none;width:300px;padding:4px}#videoMessageOverlay{position:absolute;z-index:20;width:100%;margin:auto;font-size:1.8em;font-family:var(--src-font-family-body)}#playButton{display:inline-block;height:auto}img#playButton{width:10%;max-width:241px}#UIInteraction{position:fixed}#UIInteractionButtonBoundary{padding:2px}#UIInteractionButton{cursor:pointer}#hiddenInput{position:absolute;left:-10%;width:0;opacity:0}#editTextButton{position:absolute;width:40px;height:40px}\n"] }]
|
|
6646
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
6553
|
+
}], ctorParameters: () => [], propDecorators: { useContainerAsSizeProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "useContainerAsSizeProvider", required: false }] }], isStudio: [{ type: i0.Input, args: [{ isSignal: true, alias: "isStudio", required: false }] }], resolutionSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "resolutionSize", required: false }] }], changeMouseOverScene: [{ type: i0.Output, args: ["changeMouseOverScene"] }], videoElement: [{ type: i0.ViewChild, args: ['videoContainer', { isSignal: true }] }], onMouseOver: [{
|
|
6647
6554
|
type: HostListener,
|
|
6648
6555
|
args: ['mouseover']
|
|
6649
6556
|
}], onMouseOut: [{
|
|
6650
6557
|
type: HostListener,
|
|
6651
6558
|
args: ['mouseout']
|
|
6559
|
+
}], onContextMenu: [{
|
|
6560
|
+
type: HostListener,
|
|
6561
|
+
args: ['contextmenu', ['$event']]
|
|
6652
6562
|
}] } });
|
|
6653
6563
|
|
|
6654
6564
|
class WebrtcErrorModalComponent {
|
|
@@ -6659,9 +6569,6 @@ class WebrtcErrorModalComponent {
|
|
|
6659
6569
|
}
|
|
6660
6570
|
#dialogRef;
|
|
6661
6571
|
#store;
|
|
6662
|
-
close() {
|
|
6663
|
-
this.#dialogRef.close();
|
|
6664
|
-
}
|
|
6665
6572
|
closeModalWithCirrusDisconnect() {
|
|
6666
6573
|
if (!this.isCirrusConnected()) {
|
|
6667
6574
|
this.#store.dispatch(disconnectStream({
|
|
@@ -6671,6 +6578,9 @@ class WebrtcErrorModalComponent {
|
|
|
6671
6578
|
}
|
|
6672
6579
|
this.close();
|
|
6673
6580
|
}
|
|
6581
|
+
close() {
|
|
6582
|
+
this.#dialogRef.close();
|
|
6583
|
+
}
|
|
6674
6584
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6675
6585
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: WebrtcErrorModalComponent, isStandalone: true, selector: "app-webrtc-error-modal", ngImport: i0, template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"src-modal\">\n <header class=\"src-modal__header\"></header>\n <section class=\"src-modal__body\">\n <div [attr.data-testid]=\"'title'\" class=\"src-modal__title\">\n WebRTC error\n </div>\n <div style=\"text-align: left\">\n An internet connection type (WebRTC) appears to be blocked either by your\n browser settings or your current network. If WebRTC is blocked on your\n browser you may be able to adjust these settings yourself based on\n instructions here:\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\n >https://myownconference.com/blog/en/webrtc/</a\n >\n <br /><br />\n Trying a different web browser may help confirm this as well. If WebRTC is\n blocked by your network, try switching to a different network if possible\n or contact your network administrator.<br /><br />\n WebRTC is common, safe and increasingly utilised method for streaming real\n time 3D experiences via a web browser. It typically consumes no more\n bandwidth than streaming an HD video.\n </div>\n </section>\n <footer class=\"src-modal__footer\">\n <src-button\n (onClick)=\"closeModalWithCirrusDisconnect()\"\n [data-testid]=\"'close-webrtc-error-modal'\"\n weight=\"primary\"\n >\n Ok\n </src-button>\n </footer>\n</div>\n", styles: [".src-modal{--modalBodyPadding: 20px 8px 20px 20px;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto}.src-modal__body{width:100%}.src-modal__body p{color:var(--src-text-body-main, #1f2937);font-size:14px;font-style:normal;font-weight:400;line-height:24px;margin-top:0}.src-modal__body a{word-wrap:break-word;white-space:normal}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6676
6586
|
}
|
|
@@ -6727,12 +6637,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
6727
6637
|
|
|
6728
6638
|
class LowBandwidthDetectorComponent {
|
|
6729
6639
|
constructor() {
|
|
6730
|
-
this
|
|
6640
|
+
this.#store = inject(Store);
|
|
6731
6641
|
this.dialog = inject(Dialog);
|
|
6732
6642
|
this.destroyRef = inject(DestroyRef);
|
|
6733
6643
|
this.videoService = inject(VideoService);
|
|
6734
6644
|
this.isDevMode = inject(DevModeService).isDevMode;
|
|
6735
|
-
this.viewportReady$ = this
|
|
6645
|
+
this.viewportReady$ = this.#store.select(unrealFeature.selectViewportReady);
|
|
6736
6646
|
this.isLowBandwidth$ = of(true);
|
|
6737
6647
|
this.lowBandwidthTrigger$ = this.isLowBandwidth$.pipe(distinctUntilChanged$1(), filter(Truthy));
|
|
6738
6648
|
this.highBandwidthTrigger$ = this.isLowBandwidth$.pipe(distinctUntilChanged$1(), filter(Falsy));
|
|
@@ -6755,6 +6665,7 @@ class LowBandwidthDetectorComponent {
|
|
|
6755
6665
|
this.isIndicatorExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isIndicatorExpanded" }] : /* istanbul ignore next */ []));
|
|
6756
6666
|
this.isIndicatorExpandedTrigger$ = toObservable(this.isIndicatorExpanded).pipe(skip(1), distinctUntilChanged$1(), filter(Falsy));
|
|
6757
6667
|
}
|
|
6668
|
+
#store;
|
|
6758
6669
|
ngOnInit() {
|
|
6759
6670
|
this.lowBandwidthTrigger$
|
|
6760
6671
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
@@ -6853,6 +6764,7 @@ function provideUnrealModule() {
|
|
|
6853
6764
|
StreamStatusTelemetryService2,
|
|
6854
6765
|
FpsMonitorService,
|
|
6855
6766
|
WebRtcPlayerService,
|
|
6767
|
+
LoadingProgressService,
|
|
6856
6768
|
provideEnvironmentInitializer(() => {
|
|
6857
6769
|
inject(AggregatorService);
|
|
6858
6770
|
inject(InputService);
|
|
@@ -6871,5 +6783,5 @@ function provideUnrealModule() {
|
|
|
6871
6783
|
* Generated bundle index. Do not edit.
|
|
6872
6784
|
*/
|
|
6873
6785
|
|
|
6874
|
-
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, CommandsEffects, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MessageType, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, changeStreamResolutionSuccessAction, commandCompleted, commandStarted, commandsFeature, commandsReducer, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues,
|
|
6786
|
+
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, CommandsEffects, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, changeStreamResolutionSuccessAction, commandCompleted, commandStarted, commandsFeature, commandsReducer, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
6875
6787
|
//# sourceMappingURL=3dsource-angular-unreal-module.mjs.map
|