@3dsource/angular-unreal-module 0.0.113-dev.0 → 0.0.113-dev.1

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, debounceTime, Subject, skip, share, from, fromEvent, throwError, defer, retry, tap as tap$1, startWith, takeUntil as takeUntil$1, throttleTime, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, pairwise, Subscription, bufferTime, BehaviorSubject, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1, concat } from 'rxjs';
4
+ import { merge, map, scan, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, from, fromEvent, throwError, defer, retry, tap as tap$1, takeUntil as takeUntil$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, delay, skip as skip$1 } from 'rxjs/operators';
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, takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
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';
@@ -208,7 +211,7 @@ const WS_TIMEOUT = 2000;
208
211
  const POLLING_TIME = 4000;
209
212
  const WS_OPEN_STATE = 1;
210
213
  const DEFAULT_AFK_TIMEOUT_PERIOD = 15;
211
- const DEFAULT_AFK_TIMEOUT = 120;
214
+ const DEFAULT_AFK_TIMEOUT = 10;
212
215
  const DATA_CHANNEL_CONNECTION_TIMEOUT = 8000; //ms;
213
216
  const SIGNALLING_PERCENT_VALUE = 56;
214
217
  const SCREEN_LOCKER_CONTAINER_ID = '3dsource_start_screen';
@@ -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`, (data = {
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 showAfkTimeoutPopup = createAction(scoped `show afk timeout popup`, props());
343
- const setAfkTimeout = createAction(scoped `set afk timeout`, props());
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
- afkTimeout: DEFAULT_AFK_TIMEOUT,
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: isVideoPlaying };
388
+ return { ...state, isVideoPlaying };
389
389
  }), on(setAwsInstance, (state, { instanceName, wsUrl, pollingUrl }) => {
390
- return { ...state, awsInstance: { instanceName, wsUrl, pollingUrl } };
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
- if (state.dataChannelConnected ||
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: { ...state.streamConfig, warnTimeout: state.afkTimeout },
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.afkTimeout,
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(showAfkTimeoutPopup, (state, { visible }) => {
548
+ }), on(showUserInactivityTimeoutPopup, (state, { visible }) => {
541
549
  return { ...state, showAfkTimeoutPopup: visible };
542
- }), on(setAfkTimeout, (state, { timeout }) => {
550
+ }), on(setUserInactivityTimeout, (state, { timeout }) => {
543
551
  return {
544
552
  ...state,
545
- afkTimeout: timeout,
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
- afkTimeout: state.afkTimeout,
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
- Object.entries(out.commandsInProgress).forEach(([, { id, timeStamp }]) => {
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;
@@ -1194,8 +1145,8 @@ const selectStreamConfig = createSelector(unrealFeature.selectStreamConfig, (con
1194
1145
  const selectWarnTimeout = createSelector(selectStreamConfig, (config) => config?.warnTimeout || DEFAULT_AFK_TIMEOUT);
1195
1146
  const selectIsExistMatchUrls = createSelector(unrealFeature.selectMatchUrls, (urls) => urls?.length > 0);
1196
1147
  const selectIsAutostart = createSelector(selectStreamConfig, (config) => config?.autoStart);
1197
- const selectShowReconnectPopup = createSelector(unrealFeature.selectCirrusConnected, unrealFeature.selectEstablishingConnection, (connected, establishing) => !connected && !establishing);
1198
- const selectShowLoader = createSelector(selectShowReconnectPopup, unrealFeature.selectViewportReady, unrealFeature.selectShowLoadingProgress, (showPopup, viewportReady, selectShowLoadingProgress) => !showPopup && !viewportReady && selectShowLoadingProgress);
1148
+ const selectShowReconnectPopup = createSelector(unrealFeature.selectCirrusConnected, unrealFeature.selectEstablishingConnection, (cirrusConnected, establishingConnection) => !cirrusConnected && !establishingConnection);
1149
+ const selectShowLoader = createSelector(selectShowReconnectPopup, unrealFeature.selectViewportReady, (showPopup, viewportReady) => !showPopup && !viewportReady);
1199
1150
  const selectIsVideoPlayingAndDataChannelConnected = createSelector(unrealFeature.selectIsVideoPlaying, unrealFeature.selectDataChannelConnected, (isVideoPlaying, isDataChannelConnected) => isVideoPlaying && isDataChannelConnected);
1200
1151
  const selectSignalingParameters = createSelector(unrealFeature.selectSsData, unrealFeature.selectAwsInstance, (data, instance) => {
1201
1152
  const info = [];
@@ -1209,29 +1160,37 @@ const selectSignalingParameters = createSelector(unrealFeature.selectSsData, unr
1209
1160
  return info.join('<br>');
1210
1161
  });
1211
1162
  const selectClientAndViewIds = createSelector(unrealFeature.selectStreamClientCompanyId, unrealFeature.selectStreamViewId, (clientId, viewId) => ({ clientId, viewId }));
1212
- const selectIsLoaderScreenVisible = createSelector(unrealFeature.selectViewportReady, unrealFeature.selectDataChannelConnected, unrealFeature.selectCirrusConnected, (viewReady, cirrusConnected, dataChannelConnected) => !viewReady || !cirrusConnected || !dataChannelConnected);
1163
+ const selectIsFullyConnected = createSelector(unrealFeature.selectViewportReady, unrealFeature.selectDataChannelConnected, unrealFeature.selectCirrusConnected, (viewportReady, dataChannelConnected, cirrusConnected) => viewportReady && dataChannelConnected && cirrusConnected);
1164
+ const selectIsLoaderScreenVisible = createSelector(selectIsFullyConnected, (fullyConnected) => !fullyConnected);
1213
1165
  const selectCirrusConnectionId = createSelector(unrealFeature.selectSsData, (data) => data?.connectionId);
1214
1166
  /**
1215
- * Returns Scene load progress as a float value in between 0-1
1167
+ * Returns Scene load progress as a float value between 0 and 1.
1168
+ *
1169
+ * Progress is split into two phases:
1170
+ * 1. **Signaling** (0 → ~56%): connection polling or orchestration progress.
1171
+ * Starts at 0.1%, so the bar is never visually empty while connecting.
1172
+ * 2. **Commands** (~56% → 100%): Unreal command progress after the data channel connects.
1173
+ *
1174
+ * The split point bumps by +1% once the WebSocket URL is known,
1175
+ * producing a small visual step that indicates the WS handshake succeeded.
1216
1176
  */
1217
1177
  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
1178
  const SIGNALLING_MAX = SIGNALLING_PERCENT_VALUE / 100;
1179
+ const ORCHESTRATION_WS_PATTERN = /^wss:\/\/orchestration-websocket/i;
1180
+ const isNewOrchestration = ORCHESTRATION_WS_PATTERN.test(awsInstance?.wsUrl ?? '');
1181
+ // Orchestration progress is 0–1 from the backend; map to 0–1 directly.
1182
+ // Once the data channel is connected, signaling is fully complete (1.0).
1183
+ const signalingProgress = isNewOrchestration
1184
+ ? dataChannelConnected
1185
+ ? 1
1186
+ : (awsInstance?.progressComplete ?? 0)
1187
+ : (statusPercentSignallingServer ?? 0) / 100;
1188
+ // +1% bump once WS URL is known (visual step for WS handshake)
1231
1189
  const splitPoint = awsInstance?.wsUrl
1232
1190
  ? SIGNALLING_MAX + 0.01
1233
1191
  : SIGNALLING_MAX;
1234
- const progressSignaling = lerp(0.001, splitPoint, (signalingPercentValue ?? 0) / 100);
1192
+ // 0.001 minimum so the bar is never completely empty while loading
1193
+ const progressSignaling = lerp(0.001, splitPoint, signalingProgress);
1235
1194
  const progressRest = lerp(0, 1 - splitPoint, commandProgress);
1236
1195
  return clampf(0, 1, progressSignaling + progressRest);
1237
1196
  });
@@ -1286,12 +1245,9 @@ class UnrealEffects {
1286
1245
  };
1287
1246
  this.disconnectReasonHandling$ = createEffect(() => {
1288
1247
  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
1248
+ // Skip DataChannelClosed if peer reconnection will handle this case
1291
1249
  if (reason === DisconnectReason.DataChannelClosed &&
1292
- this.reconnectConfig.enabled &&
1293
- this.reconnectConfig.onDataChannelClose &&
1294
- this.signallingService.isConnected()) {
1250
+ this.canPeerReconnect()) {
1295
1251
  Logger.info('[Reconnect] Skipping destroy for DataChannelClosed - peer reconnection will handle');
1296
1252
  return false;
1297
1253
  }
@@ -1323,11 +1279,25 @@ class UnrealEffects {
1323
1279
  })));
1324
1280
  });
1325
1281
  this.resetAfk$ = createEffect(() => {
1326
- return this.actions$.pipe(ofType(setConfig, setAfkTimeout, resetWarnTimeout, resetConfig), map(() => resetAfk()));
1282
+ return this.actions$.pipe(ofType(setConfig, setUserInactivityTimeout, resetWarnTimeout, resetConfig), map(() => resetAfk()));
1327
1283
  });
1328
1284
  this.abortEstablishingConnection$ = createEffect(() => {
1329
- return merge(this.store.select(unrealFeature.selectViewportReady).pipe(filter(Truthy)), this.actions$.pipe(ofType(setCirrusDisconnected, destroyUnrealScene, dropConnection))).pipe(tapLog('ABORT'), map(() => abortEstablishingConnection()));
1285
+ return merge(this.store.select(unrealFeature.selectViewportReady).pipe(filter(Truthy)), this.actions$.pipe(ofType(setCirrusDisconnected, destroyUnrealScene, dropConnection))).pipe(map(() => abortEstablishingConnection()));
1330
1286
  });
1287
+ this.iframeMessaging$ = createEffect(() => {
1288
+ const cirrusConnected$ = this.store
1289
+ .select(unrealFeature.selectCirrusConnected)
1290
+ .pipe(skip(1));
1291
+ // On disconnect: immediately show 'Connection closed.', nullify after 2s
1292
+ const cirrusStatus$ = cirrusConnected$.pipe(switchMap((connected) => connected
1293
+ ? of('Creating Socket...')
1294
+ : concat(of('Connection closed.'), of(null).pipe(delay(2000)))));
1295
+ // Skip initial false; emit null on every viewport state change
1296
+ const viewportReset$ = this.store
1297
+ .select(unrealFeature.selectViewportReady)
1298
+ .pipe(skip(1), delay(500), map(() => null));
1299
+ return merge(cirrusStatus$, this.store.select(unrealFeature.selectStatusMessage), viewportReset$).pipe(debounceTime(0), distinctUntilChanged(), tapLog('STATUS=>'), tap((status) => this.postToScreenLocker(status)));
1300
+ }, { dispatch: false });
1331
1301
  this.setSignalingTo100$ = createEffect(() => {
1332
1302
  return this.actions$.pipe(ofType(setAwsInstance), map(() => setStatusPercentSignallingServer({ percent: 100 })));
1333
1303
  });
@@ -1404,7 +1374,7 @@ class UnrealEffects {
1404
1374
  });
1405
1375
  this.commandTelemetryService.trackStartCommand('EXT-SetViewportReady');
1406
1376
  }), switchMap(() =>
1407
- // Wait for initial commands to be sent before sending loopback.
1377
+ // Wait for initial commands to be sent before sending the loopback.
1408
1378
  // commandStarted fires for each sent command; debounce ensures
1409
1379
  // we proceed only after no more commands are started for 100 ms.
1410
1380
  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,6 +1387,9 @@ class UnrealEffects {
1417
1387
  this.startStream$ = createEffect(() => {
1418
1388
  return this.actions$.pipe(ofType(startStream), map(() => initSignalling()));
1419
1389
  });
1390
+ this.resetAfkOnVideoStart$ = createEffect(() => {
1391
+ return this.actions$.pipe(ofType(videoStreamInitialized), filter(() => !!this.videoService.video?.paused), tap(() => Logger.info('Simulate Press Play')), map(() => resetAfk()));
1392
+ });
1420
1393
  /**
1421
1394
  * Effect: `forceViewportNotReady$`
1422
1395
  *
@@ -1444,9 +1417,7 @@ class UnrealEffects {
1444
1417
  */
1445
1418
  this.handleDataChannelClosed$ = createEffect(() => {
1446
1419
  return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.DataChannelClosed &&
1447
- this.reconnectConfig.enabled &&
1448
- this.reconnectConfig.onDataChannelClose &&
1449
- this.signallingService.isConnected()), tap(() => {
1420
+ this.canPeerReconnect()), tap(() => {
1450
1421
  Logger.warn('[Reconnect] DataChannel closed, initiating peer reconnection');
1451
1422
  }), map(() => reconnectPeer({
1452
1423
  attempt: 1,
@@ -1557,16 +1528,21 @@ class UnrealEffects {
1557
1528
  // Ensure a single in-flight request per attempt.
1558
1529
  exhaustMap((urls) => this.signallingService.connectToSignaling(urls)));
1559
1530
  }, { dispatch: false });
1560
- /**
1561
- * Effect: `logPeerReconnectSuccess$`
1562
- *
1563
- * Logs successful peer reconnection for telemetry.
1564
- */
1565
- this.logPeerReconnectSuccess$ = createEffect(() => {
1566
- return this.actions$.pipe(ofType(reconnectPeerSuccess), tap(() => {
1567
- Logger.info('[Reconnect] Peer connection restored successfully');
1568
- }));
1569
- }, { dispatch: false });
1531
+ }
1532
+ canPeerReconnect() {
1533
+ return (this.reconnectConfig.enabled &&
1534
+ this.reconnectConfig.onDataChannelClose &&
1535
+ this.signallingService.isConnected());
1536
+ }
1537
+ postToScreenLocker(text) {
1538
+ try {
1539
+ const el = document.getElementById(this.unrealInitialConfig?.screenLockerContainerId ??
1540
+ SCREEN_LOCKER_CONTAINER_ID);
1541
+ el?.contentWindow?.postMessage(text, '*');
1542
+ }
1543
+ catch {
1544
+ // Fails silently if cross-origin
1545
+ }
1570
1546
  }
1571
1547
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1572
1548
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealEffects }); }
@@ -1601,7 +1577,7 @@ class SubService {
1601
1577
  this.disconnect$ = this.store
1602
1578
  .select(unrealFeature.selectCirrusConnected)
1603
1579
  .pipe(skip(1), filter(Falsy), share());
1604
- this._destroy = this.destroyRef.onDestroy(() => {
1580
+ this.destroyRef.onDestroy(() => {
1605
1581
  this.destroy$.next();
1606
1582
  this.destroy$.complete();
1607
1583
  });
@@ -1612,7 +1588,7 @@ class AFKService extends SubService {
1612
1588
  constructor() {
1613
1589
  super();
1614
1590
  this.active = false; // Whether the AFK system is currently looking for inactivity.
1615
- this.countdown = 0; // The inactivity warning overlay has a countdown to show time until disconnect.
1591
+ this.countdown = 0;
1616
1592
  this.warnTimeout = this.store.selectSignal(selectWarnTimeout);
1617
1593
  this.isViewportReady = this.store.selectSignal(unrealFeature.selectViewportReady);
1618
1594
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
@@ -1620,6 +1596,8 @@ class AFKService extends SubService {
1620
1596
  this.afkResetTrigger = toSignal(merge(this.store.select(selectWarnTimeout), this.actions$.pipe(ofType(resetAfk))));
1621
1597
  /** Ticks when cirrus disconnects */
1622
1598
  this.disconnected = toSignal(this.disconnect$);
1599
+ /** Ticks when resetUserInactivityTimeout action fires */
1600
+ this.resetUserInactivityTrigger = toSignal(this.actions$.pipe(ofType(resetUserInactivityTimeout)));
1623
1601
  this.destroyRef.onDestroy(() => this.clearTimers());
1624
1602
  if (this.unrealConfig?.playwright) {
1625
1603
  return;
@@ -1647,6 +1625,11 @@ class AFKService extends SubService {
1647
1625
  untracked(() => this.stop());
1648
1626
  }
1649
1627
  });
1628
+ // Full reset (hide overlay + restart warning timer) on external request
1629
+ effect(() => {
1630
+ this.resetUserInactivityTrigger();
1631
+ untracked(() => this.reset());
1632
+ });
1650
1633
  }
1651
1634
  hideOverlay() {
1652
1635
  this.store.dispatch(setAfkTimerHide());
@@ -1724,11 +1707,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1724
1707
  type: Injectable
1725
1708
  }], ctorParameters: () => [] });
1726
1709
 
1710
+ class LoadingProgressService {
1711
+ constructor() {
1712
+ this.#store = inject(Store);
1713
+ this.loaderVisible = this.#store.selectSignal(selectShowLoader);
1714
+ this.percents = toSignal(this.#store
1715
+ .select(selectTotalProgress)
1716
+ .pipe(keepMaxUntilReset(toObservable(this.loaderVisible).pipe(filter(Falsy))), floatToSmoothPercents(), distinctUntilChanged()));
1717
+ }
1718
+ #store;
1719
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1720
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LoadingProgressService }); }
1721
+ }
1722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LoadingProgressService, decorators: [{
1723
+ type: Injectable
1724
+ }] });
1725
+
1727
1726
  class FreezeFrameService extends SubService {
1728
1727
  constructor() {
1729
1728
  super();
1730
1729
  this.receiving = false;
1731
1730
  this.size = 0;
1731
+ this.jpeg = undefined;
1732
1732
  this.freezeFrameOverlay = new Image();
1733
1733
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
1734
1734
  this.init();
@@ -1881,7 +1881,7 @@ class VideoService extends SubService {
1881
1881
  this.videoTrack$.next({ stream, pcClient });
1882
1882
  }
1883
1883
  else {
1884
- console.error('Video element is not defined.');
1884
+ Logger.error('Video element is not defined.');
1885
1885
  }
1886
1886
  }
1887
1887
  attachAudioStream(stream) {
@@ -1958,7 +1958,7 @@ class VideoService extends SubService {
1958
1958
  catch (err) {
1959
1959
  // Handle InvalidStateError or any transient WebRTC errors gracefully
1960
1960
  if (err instanceof DOMException && err.name === 'InvalidStateError') {
1961
- console.warn('Peer connection is closed or unusable, skipping stats.');
1961
+ Logger.warn('Peer connection is closed or unusable, skipping stats.');
1962
1962
  return null;
1963
1963
  }
1964
1964
  throw err;
@@ -2100,7 +2100,7 @@ class VideoService extends SubService {
2100
2100
  }
2101
2101
  createWebRtcAudio() {
2102
2102
  const audio = new Audio();
2103
- audio.id = 'streamingAudio';
2103
+ audio.id = STREAMING_AUDIO_ID;
2104
2104
  this.audio = audio;
2105
2105
  }
2106
2106
  playAudio(audio) {
@@ -2573,7 +2573,9 @@ class SignallingService extends SubService {
2573
2573
  return;
2574
2574
  }
2575
2575
  this.setHandlersFromStream();
2576
- this.action$.pipe(ofType(dataChannelReady)).subscribe(() => {
2576
+ this.action$
2577
+ .pipe(ofType(dataChannelReady), takeUntil(this.abort$))
2578
+ .subscribe(() => {
2577
2579
  this.send({
2578
2580
  type: 'p2pEstablished',
2579
2581
  source: 'front',
@@ -2583,7 +2585,7 @@ class SignallingService extends SubService {
2583
2585
  this.action$
2584
2586
  .pipe(ofType(dataChannelReady), switchMap(() => this.store
2585
2587
  .select(unrealFeature.selectViewportReady)
2586
- .pipe(filter(Truthy), take(1))))
2588
+ .pipe(filter(Truthy), take(1))), takeUntil(this.abort$))
2587
2589
  .subscribe(() => {
2588
2590
  this.send({
2589
2591
  type: 'streamPreparedForUserInteraction',
@@ -2615,10 +2617,10 @@ class SignallingService extends SubService {
2615
2617
  },
2616
2618
  // count: 10, // Optional: cap the number of attempts.
2617
2619
  }),
2618
- // abort$ sits OUTSIDE retry so it kills the entire retry loop,
2620
+ // abort$ sits OUTSIDE retry, so it kills the entire retry loop,
2619
2621
  // not just the current attempt. Each inner createWebSocket also
2620
2622
  // receives abort$ independently, so mid-connection attempts are
2621
- // cancelled too.
2623
+ // canceled too.
2622
2624
  takeUntil(this.abort$));
2623
2625
  }
2624
2626
  startEstablishingConnection() {
@@ -2644,7 +2646,7 @@ class SignallingService extends SubService {
2644
2646
  *
2645
2647
  * 1. **Telemetry** — Starts a `getSignaling` span for end-to-end timing.
2646
2648
  * 2. **Candidate URL selection** — A stateful generator (`urlGen`) is created
2647
- * from `urlsPool`. Each resubscription (e.g. via `retry`) advances to the next URL.
2649
+ * from `urlsPool`. Each resubscription (e.g., via `retry`) advances to the next URL.
2648
2650
  * 3. **Short-circuit for WS URLs** — If the picked candidate already matches
2649
2651
  * `ws://` or `wss://`, emit `{ wsUrl, pollingUrl: null, instanceName: null }`
2650
2652
  * immediately and complete (no HTTP request).
@@ -2656,7 +2658,7 @@ class SignallingService extends SubService {
2656
2658
  * while the request is in flight are **ignored** until completion, preventing overlap.
2657
2659
  * 6. **Per-attempt timeout** — `timeout(WS_TIMEOUT)` caps how long we wait for a response.
2658
2660
  * On timeout, the attempt errors and the outer `retry` schedules the next URL.
2659
- * 7. **Response validation & progress reporting** — A `filter`:
2661
+ * 7. **Response validation and progress reporting** — A `filter`:
2660
2662
  * - Calls `TelemetryStop('getSignaling', { ...data, multi: true })` on receipt.
2661
2663
  * - If `data.signallingServer === ''` or `data.error`:
2662
2664
  * shows a status message, optionally dispatches `setStatusPercentSignallingServer`
@@ -2673,7 +2675,7 @@ class SignallingService extends SubService {
2673
2675
  *
2674
2676
  * ### Concurrency semantics
2675
2677
  *
2676
- * - `exhaustMap` guarantees a **single** HTTP request per attempt; subsequent
2678
+ * - `exhaustMap` guarantees a **single** HTTP request per attempt; later
2677
2679
  * `{clientId, viewId}` emissions are ignored until the current request completes.
2678
2680
  *
2679
2681
  * ### Telemetry semantics
@@ -2707,7 +2709,7 @@ class SignallingService extends SubService {
2707
2709
  * @remarks
2708
2710
  * - The URL generator (`urlGen`) is created **outside** `defer`, so resubscriptions
2709
2711
  * 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
2712
+ * - `location.protocol` is used to derive `ws` vs. `wss`. If this may run in non-browser
2711
2713
  * contexts (e.g., SSR), guard or abstract this logic.
2712
2714
  * - Consider capping retries with `count` in `retry({ count, delay })` when appropriate.
2713
2715
  *
@@ -2740,7 +2742,7 @@ class SignallingService extends SubService {
2740
2742
  };
2741
2743
  }));
2742
2744
  }
2743
- // Old (HTTP-polling) orchestration flow — mark the roundtrip so
2745
+ // Old (HTTP-polling) orchestration flow — mark the round trip so
2744
2746
  // the signaling-phase timeline can show it as a distinct segment.
2745
2747
  this.store.dispatch(pollingStarted());
2746
2748
  return this.store.select(selectClientAndViewIds).pipe(
@@ -2914,6 +2916,8 @@ class WebRtcPlayerService extends SubService {
2914
2916
  constructor() {
2915
2917
  super();
2916
2918
  this.onDataChannelMessage$ = new Subject();
2919
+ this.pcClient = null;
2920
+ this.dcClient = null;
2917
2921
  this.signallingSrv = inject(SignallingService);
2918
2922
  this.videoService = inject(VideoService);
2919
2923
  this.commandTelemetryService = inject(CommandTelemetryService);
@@ -2927,29 +2931,37 @@ class WebRtcPlayerService extends SubService {
2927
2931
  this.iceCandidateBuffer = [];
2928
2932
  this.isReconnecting = this.store.selectSignal(unrealFeature.selectIsReconnecting);
2929
2933
  this.init();
2930
- this.actions$.pipe(ofType(dropDC)).subscribe(() => this.dcClient?.close());
2931
- this.actions$.pipe(ofType(dropPC)).subscribe(() => this.pcClient?.close());
2934
+ this.actions$
2935
+ .pipe(ofType(dropDC), takeUntil(this.destroy$))
2936
+ .subscribe(() => this.dcClient?.close());
2937
+ this.actions$
2938
+ .pipe(ofType(dropPC), takeUntil(this.destroy$))
2939
+ .subscribe(() => this.pcClient?.close());
2932
2940
  }
2933
2941
  init() {
2934
- this.signallingSrv.onConfig$.subscribe((config) => {
2942
+ this.signallingSrv.onConfig$
2943
+ .pipe(takeUntil(this.destroy$))
2944
+ .subscribe((config) => {
2935
2945
  this.videoService.create();
2936
2946
  this.setConfig(config);
2937
2947
  });
2938
2948
  // Server-initiated offer: set remote description and respond with answer
2939
- this.signallingSrv.onOffer$.pipe(filter(Truthy)).subscribe((offer) => {
2949
+ this.signallingSrv.onOffer$
2950
+ .pipe(filter(Truthy), takeUntil(this.destroy$))
2951
+ .subscribe((offer) => {
2940
2952
  void this.handleServerOffer(offer);
2941
2953
  });
2942
2954
  // Answer to a client-initiated offer
2943
2955
  this.signallingSrv.onWebRtcAnswer$
2944
- .pipe(filter(Truthy))
2956
+ .pipe(filter(Truthy), takeUntil(this.destroy$))
2945
2957
  .subscribe((answer) => {
2946
2958
  void this.handleServerAnswer(answer);
2947
2959
  });
2948
2960
  // ICE candidates — buffered until remote description is set
2949
- this.signallingSrv.onWebRtcIce$.subscribe((candidate) => {
2950
- this.commandTelemetryService.trackStopCommand('EXT-iceCandidate', true, {
2951
- multi: true,
2952
- });
2961
+ this.signallingSrv.onWebRtcIce$
2962
+ .pipe(takeUntil(this.destroy$))
2963
+ .subscribe((candidate) => {
2964
+ this.commandTelemetryService.trackStopCommand('EXT-iceCandidate', true, { multi: true });
2953
2965
  if (this.remoteDescriptionSet) {
2954
2966
  this.handleCandidateFromServer(candidate);
2955
2967
  }
@@ -2974,11 +2986,15 @@ class WebRtcPlayerService extends SubService {
2974
2986
  if (!this.pcClient) {
2975
2987
  this.setupPeerConnection(this.cfg);
2976
2988
  }
2989
+ const pc = this.pcClient;
2990
+ if (!pc) {
2991
+ return;
2992
+ }
2977
2993
  // Offer already defines media transceivers — don't add extras
2978
- await this.pcClient?.setRemoteDescription(offer);
2994
+ await pc.setRemoteDescription(offer);
2979
2995
  this.remoteDescriptionSet = true;
2980
2996
  this.flushIceCandidateBuffer();
2981
- await this.createAnswer(this.pcClient);
2997
+ await this.createAnswer(pc);
2982
2998
  }
2983
2999
  catch (e) {
2984
3000
  Logger.error('Failed to handle server offer', e);
@@ -3023,10 +3039,16 @@ class WebRtcPlayerService extends SubService {
3023
3039
  if (!this.pcClient) {
3024
3040
  this.setupPeerConnection(this.cfg);
3025
3041
  }
3042
+ const pc = this.pcClient;
3043
+ if (!pc) {
3044
+ return;
3045
+ }
3026
3046
  // Client offers: transceivers first, then DC, then offer
3027
- await this.setupTransceiversAsync(this.pcClient);
3028
- this.dcClient = this.createDataChannel(this.pcClient, 'UnrealDcConnection', { ordered: true });
3029
- await this.createOffer(this.pcClient);
3047
+ await this.setupTransceiversAsync(pc);
3048
+ this.dcClient = this.createDataChannel(pc, 'UnrealDcConnection', {
3049
+ ordered: true,
3050
+ });
3051
+ await this.createOffer(pc);
3030
3052
  }
3031
3053
  catch (e) {
3032
3054
  Logger.error('Failed to create WebRTC offer', e);
@@ -3085,7 +3107,7 @@ class WebRtcPlayerService extends SubService {
3085
3107
  * RFC 6184 that encodes three H.264 parameters:
3086
3108
  * - **Profile IDC** (`XX`) — the H.264 profile.
3087
3109
  * - **Profile IOP** (`YY`) — constraint flags that further narrow the profile.
3088
- * - **Level IDC** (`ZZ`) — the level, which caps resolution/framerate/bitrate.
3110
+ * - **Level IDC** (`ZZ`) — the level which caps resolution/framerate/bitrate.
3089
3111
  *
3090
3112
  * ### H.264 profiles relevant to Unreal Engine Pixel Streaming
3091
3113
  *
@@ -3103,12 +3125,12 @@ class WebRtcPlayerService extends SubService {
3103
3125
  * | `f4001f` | High 4:4:4 Predictive | 3.1 | Lossless capable; rare in browsers |
3104
3126
  *
3105
3127
  * UE Pixel Streaming defaults to Constrained Baseline (`42e01f`) or High (`6400xx`)
3106
- * depending on engine version and `WebRTC.H264Profile` CVar.
3128
+ * depending on the engine version and `WebRTC.H264Profile` CVar.
3107
3129
  *
3108
3130
  * We prioritize `42e01f` because it guarantees hardware decoding on
3109
3131
  * virtually all client devices (desktop, mobile, embedded), avoiding
3110
3132
  * fallback to software decoding or negotiation of a higher profile
3111
- * that the client GPU may not accelerate.
3133
+ * that the client GPU may not speed up.
3112
3134
  */
3113
3135
  prioritizeH264Profile(sdp, profileLevelId) {
3114
3136
  const lines = sdp.split('\r\n');
@@ -3517,7 +3539,7 @@ class FileReceiverService extends SubService {
3517
3539
  }
3518
3540
  this.store
3519
3541
  .select(unrealFeature.selectViewportReady)
3520
- .pipe(filter(Truthy))
3542
+ .pipe(filter(Truthy), takeUntil(this.destroy$))
3521
3543
  .subscribe(() => this.reset());
3522
3544
  }
3523
3545
  reset() {
@@ -3642,7 +3664,7 @@ class AggregatorService extends SubService {
3642
3664
  initialize() {
3643
3665
  this.store
3644
3666
  .select(unrealFeature.selectCirrusConnected)
3645
- .pipe(distinctUntilChanged())
3667
+ .pipe(distinctUntilChanged(), takeUntil(this.destroy$))
3646
3668
  .subscribe((connected) => {
3647
3669
  this.resetResponseList();
3648
3670
  if (connected) {
@@ -3652,7 +3674,9 @@ class AggregatorService extends SubService {
3652
3674
  const unlockViewport$ = this.store
3653
3675
  .select(unrealFeature.selectViewportReady)
3654
3676
  .pipe(filter(Truthy));
3655
- merge(unlockViewport$, this.disconnect$).subscribe(() => {
3677
+ merge(unlockViewport$, this.disconnect$)
3678
+ .pipe(takeUntil(this.destroy$))
3679
+ .subscribe(() => {
3656
3680
  this.removeLoadScreen();
3657
3681
  });
3658
3682
  if (!this.selectStreamConfig()?.autoStart ||
@@ -3664,22 +3688,6 @@ class AggregatorService extends SubService {
3664
3688
  addResponseEventListener(name, listener) {
3665
3689
  this.responseEventListeners.set(name, listener);
3666
3690
  }
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
3691
  removeLoadScreen() {
3684
3692
  document
3685
3693
  .getElementById(this.unrealConfig?.screenLockerContainerId ||
@@ -3747,12 +3755,8 @@ class AggregatorService extends SubService {
3747
3755
  break;
3748
3756
  }
3749
3757
  case ToClientMessage.Command: {
3750
- const commandAsString = decodeData(anyData);
3751
- Logger.colored(...COLOR_CODES.FROM_UNREAL, commandAsString);
3752
- const command = JSON.parse(commandAsString);
3753
- if (command.command === 'onScreenKeyboard') {
3754
- this.showOnScreenKeyboard(command);
3755
- }
3758
+ const command = JSON.parse(decodeData(anyData));
3759
+ Logger.colored(...COLOR_CODES.FROM_UNREAL, command);
3756
3760
  break;
3757
3761
  }
3758
3762
  case ToClientMessage.FileExtension:
@@ -3857,7 +3861,7 @@ class ConsoleExtensionsService extends SubService {
3857
3861
  command: MetaBoxCommand.FChangeResolutionCommand,
3858
3862
  payload: { resolution },
3859
3863
  });
3860
- return `Max Fps Set to ${resolution}`;
3864
+ return `Resolution set to ${resolution.x}x${resolution.y}`;
3861
3865
  };
3862
3866
  window.setMaxFps = (maxFps) => {
3863
3867
  this.store.dispatch(setMaxFps({ maxFps }));
@@ -3932,7 +3936,9 @@ class UnrealCommunicatorService {
3932
3936
  // of the store selector. The selector reads the current state and can fire on
3933
3937
  // stale `true` from a previous session when switching components (Angular
3934
3938
  // creates the new component before destroying the old one).
3935
- this.actions$.pipe(ofType(dataChannelConnected)).subscribe(() => {
3939
+ this.actions$
3940
+ .pipe(ofType(dataChannelConnected), takeUntil$1(this.destroy$))
3941
+ .subscribe(() => {
3936
3942
  this.requestInitialSettings();
3937
3943
  this.requestQualityControl();
3938
3944
  });
@@ -4048,7 +4054,15 @@ class InputService extends SubService {
4048
4054
  KeyboardNumericCode.NumpadDecimal,
4049
4055
  ];
4050
4056
  this.availableKeys = this.defaultKeys;
4057
+ this.options = InputOptions;
4051
4058
  this.reInit$ = new Subject();
4059
+ this.deactivatedVideoTrigger$ = NEVER;
4060
+ this.normalizeAndQuantizeUnsigned = (_x, _y) => ({
4061
+ inRange: false,
4062
+ x: 65535,
4063
+ y: 65535,
4064
+ });
4065
+ this.normalizeAndQuantizeSigned = (_x, _y) => ({ x: 0, y: 0 });
4052
4066
  this.videoBoundingRect = new DOMRect(0, 0, 1, 1);
4053
4067
  this.fingerIds = new Map();
4054
4068
  this.maxByteValue = 255;
@@ -4071,11 +4085,11 @@ class InputService extends SubService {
4071
4085
  const visibilityVisibleTrigger$ = fromEvent(document, 'visibilitychange').pipe(map$1(() => document.visibilityState === 'visible'), filter(Truthy), startWith(true));
4072
4086
  this.deactivatedVideoTrigger$ = merge(this.disconnect$, this.reInit$, visibilityHiddenTrigger$);
4073
4087
  merge(visibilityVisibleTrigger$, this.store.select(unrealFeature.selectViewportReady).pipe(filter(Truthy)))
4074
- .pipe(filter(() => !!this.video))
4088
+ .pipe(filter(() => !!this.video), takeUntil$1(this.destroy$))
4075
4089
  .subscribe(() => this.setup());
4076
4090
  }
4077
4091
  useKeyboardKeys(keys) {
4078
- this.availableKeys = keys || [];
4092
+ this.availableKeys = keys;
4079
4093
  }
4080
4094
  get video() {
4081
4095
  return this.videoService.video;
@@ -4137,49 +4151,47 @@ class InputService extends SubService {
4137
4151
  };
4138
4152
  }
4139
4153
  setupNormalizeAndQuantize(videoElement) {
4140
- if (videoElement) {
4141
- this.videoBoundingRect = videoElement.getBoundingClientRect();
4142
- this.offsetParams = this.getObjectFitOffset(videoElement);
4143
- // Unsigned XY positions are the ratio (0.0..1.0) along a viewport axis,
4144
- // quantized into an uint16 (0..65536).
4145
- // Signed XY deltas are the ratio (-1.0..1.0) along a viewport axis,
4146
- // quantized into an int16 (-32767..32767).
4147
- // This allows the browser viewport and client viewport to have a different
4148
- // size.
4149
- // Hack: Currently we set an out-of-range position to an extreme (65535)
4150
- // as we can't yet accurately detect mouse enter and leave events
4151
- // precisely inside a video with an aspect ratio which causes mattes.
4152
- this.normalizeAndQuantizeUnsigned = (x, y) => {
4153
- const normalizedX = x / videoElement.clientWidth;
4154
- const normalizedY = y / videoElement.clientHeight;
4155
- if (normalizedX < 0.0 ||
4156
- normalizedX > 1.0 ||
4157
- normalizedY < 0.0 ||
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);
4154
+ this.videoBoundingRect = videoElement.getBoundingClientRect();
4155
+ this.offsetParams = this.getObjectFitOffset(videoElement);
4156
+ // Unsigned XY positions are the ratio (0.0..1.0) along a viewport axis,
4157
+ // quantized into an uint16 (0..65536).
4158
+ // Signed XY deltas are the ratio (-1.0..1.0) along a viewport axis,
4159
+ // quantized into an int16 (-32767..32767).
4160
+ // This allows the browser viewport and client viewport to have a different
4161
+ // size.
4162
+ // Hack: Currently, we set an out-of-range position to an extreme (65535)
4163
+ // as we can't yet accurately detect mouse enter and leave events
4164
+ // precisely inside a video with an aspect ratio which causes mattes.
4165
+ this.normalizeAndQuantizeUnsigned = (x, y) => {
4166
+ const normalizedX = x / videoElement.clientWidth;
4167
+ const normalizedY = y / videoElement.clientHeight;
4168
+ if (normalizedX < 0.0 ||
4169
+ normalizedX > 1.0 ||
4170
+ normalizedY < 0.0 ||
4171
+ normalizedY > 1.0) {
4177
4172
  return {
4178
- x: normalizedX * 32767,
4179
- y: normalizedY * 32767,
4173
+ inRange: false,
4174
+ x: 65535,
4175
+ y: 65535,
4180
4176
  };
4177
+ }
4178
+ else {
4179
+ return {
4180
+ inRange: true,
4181
+ x: normalizedX * 65536,
4182
+ y: normalizedY * 65536,
4183
+ };
4184
+ }
4185
+ };
4186
+ // Signed.
4187
+ this.normalizeAndQuantizeSigned = (x, y) => {
4188
+ const normalizedX = x / (0.5 * videoElement.clientWidth);
4189
+ const normalizedY = y / (0.5 * videoElement.clientHeight);
4190
+ return {
4191
+ x: normalizedX * 32767,
4192
+ y: normalizedY * 32767,
4181
4193
  };
4182
- }
4194
+ };
4183
4195
  }
4184
4196
  sendInputData(data) {
4185
4197
  this.commandsSender.sendInputData(data);
@@ -4219,11 +4231,12 @@ class InputService extends SubService {
4219
4231
  this.fingerIds.set(touch.identifier, finger);
4220
4232
  };
4221
4233
  const forgetTouch = (touch) => {
4222
- this.fingers.push(this.fingerIds.get(touch.identifier));
4234
+ const finger = this.fingerIds.get(touch.identifier);
4235
+ if (finger) {
4236
+ this.fingers.push(finger);
4237
+ }
4223
4238
  // 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(function (a, b) {
4225
- return b - a;
4226
- });
4239
+ this.fingers.sort((a, b) => b - a);
4227
4240
  this.fingerIds.delete(touch.identifier);
4228
4241
  };
4229
4242
  const emitTouchData = (type, touches) => {
@@ -4234,21 +4247,24 @@ class InputService extends SubService {
4234
4247
  const touch = touches[t];
4235
4248
  const { x, y } = this.getTouchPositionWithOffset(touch);
4236
4249
  Logger.info(`F${this.fingerIds.get(touch.identifier)}=(${x}, ${y})`);
4237
- const coord = this.normalizeAndQuantizeUnsigned(x, y);
4250
+ const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4238
4251
  const data = new DataView(new ArrayBuffer(9));
4239
4252
  data.setUint8(0, type);
4240
4253
  data.setUint8(1, numTouches);
4241
- data.setUint16(byte, coord.x, true);
4254
+ data.setUint16(byte, normalizeCoordinates.x, true);
4242
4255
  byte += 2;
4243
- data.setUint16(byte, coord.y, true);
4256
+ data.setUint16(byte, normalizeCoordinates.y, true);
4244
4257
  byte += 2;
4245
- data.setUint8(byte, this.fingerIds.get(touch.identifier));
4258
+ const finger = this.fingerIds.get(touch.identifier);
4259
+ if (finger) {
4260
+ data.setUint8(byte, finger);
4261
+ }
4246
4262
  byte += 1;
4247
4263
  // force is between 0.0 and 1.0, so quantize into byte.
4248
4264
  // iOs bug: Forced to be always 0.5 on all devices as we do not use forced touch any way;/
4249
4265
  data.setUint8(byte, this.maxByteValue * 0.5);
4250
4266
  byte += 1;
4251
- data.setUint8(byte, coord.inRange ? 1 : 0);
4267
+ data.setUint8(byte, normalizeCoordinates.inRange ? 1 : 0);
4252
4268
  byte += 1;
4253
4269
  this.sendInputData(data.buffer);
4254
4270
  }
@@ -4291,42 +4307,42 @@ class InputService extends SubService {
4291
4307
  window.focus();
4292
4308
  }
4293
4309
  emitMouseMove(x, y, deltaX, deltaY) {
4294
- const coord = this.normalizeAndQuantizeUnsigned(x, y);
4310
+ const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4295
4311
  const delta = this.normalizeAndQuantizeSigned(deltaX, deltaY);
4296
- const Data = new DataView(new ArrayBuffer(9));
4297
- Data.setUint8(0, MessageType.MouseMove);
4298
- Data.setUint16(1, coord.x, true);
4299
- Data.setUint16(3, coord.y, true);
4300
- Data.setInt16(5, delta.x, true);
4301
- Data.setInt16(7, delta.y, true);
4302
- this.sendInputData(Data.buffer);
4312
+ const data = new DataView(new ArrayBuffer(9));
4313
+ data.setUint8(0, MessageType.MouseMove);
4314
+ data.setUint16(1, normalizeCoordinates.x, true);
4315
+ data.setUint16(3, normalizeCoordinates.y, true);
4316
+ data.setInt16(5, delta.x, true);
4317
+ data.setInt16(7, delta.y, true);
4318
+ this.sendInputData(data.buffer);
4303
4319
  }
4304
4320
  emitMouseDown(button, x, y) {
4305
- const coord = this.normalizeAndQuantizeUnsigned(x, y);
4306
- const Data = new DataView(new ArrayBuffer(6));
4307
- Data.setUint8(0, MessageType.MouseDown);
4308
- Data.setUint8(1, button);
4309
- Data.setUint16(2, coord.x, true);
4310
- Data.setUint16(4, coord.y, true);
4311
- this.sendInputData(Data.buffer);
4321
+ const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4322
+ const data = new DataView(new ArrayBuffer(6));
4323
+ data.setUint8(0, MessageType.MouseDown);
4324
+ data.setUint8(1, button);
4325
+ data.setUint16(2, normalizeCoordinates.x, true);
4326
+ data.setUint16(4, normalizeCoordinates.y, true);
4327
+ this.sendInputData(data.buffer);
4312
4328
  }
4313
4329
  emitMouseUp(button, x, y) {
4314
- const coord = this.normalizeAndQuantizeUnsigned(x, y);
4315
- const Data = new DataView(new ArrayBuffer(6));
4316
- Data.setUint8(0, MessageType.MouseUp);
4317
- Data.setUint8(1, button);
4318
- Data.setUint16(2, coord.x, true);
4319
- Data.setUint16(4, coord.y, true);
4320
- this.sendInputData(Data.buffer);
4330
+ const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4331
+ const data = new DataView(new ArrayBuffer(6));
4332
+ data.setUint8(0, MessageType.MouseUp);
4333
+ data.setUint8(1, button);
4334
+ data.setUint16(2, normalizeCoordinates.x, true);
4335
+ data.setUint16(4, normalizeCoordinates.y, true);
4336
+ this.sendInputData(data.buffer);
4321
4337
  }
4322
4338
  emitMouseWheel(delta, x, y) {
4323
- const coord = this.normalizeAndQuantizeUnsigned(x, y);
4324
- const Data = new DataView(new ArrayBuffer(7));
4325
- Data.setUint8(0, MessageType.MouseWheel);
4326
- Data.setInt16(1, delta, true);
4327
- Data.setUint16(3, coord.x, true);
4328
- Data.setUint16(5, coord.y, true);
4329
- this.sendInputData(Data.buffer);
4339
+ const normalizeCoordinates = this.normalizeAndQuantizeUnsigned(x, y);
4340
+ const data = new DataView(new ArrayBuffer(7));
4341
+ data.setUint8(0, MessageType.MouseWheel);
4342
+ data.setInt16(1, delta, true);
4343
+ data.setUint16(3, normalizeCoordinates.x, true);
4344
+ data.setUint16(5, normalizeCoordinates.y, true);
4345
+ this.sendInputData(data.buffer);
4330
4346
  }
4331
4347
  releaseMouseButtons(buttons, x, y) {
4332
4348
  if (buttons & MouseButtonsMask.PrimaryButton) {
@@ -4345,7 +4361,7 @@ class InputService extends SubService {
4345
4361
  this.emitMouseUp(MouseButton.FifthButton, x, y);
4346
4362
  }
4347
4363
  }
4348
- // If the user has any mouse buttons pressed then press them again.
4364
+ // If the user has any mouse buttons pressed, then press them again.
4349
4365
  pressMouseButtons(buttons, x, y) {
4350
4366
  if (buttons & MouseButtonsMask.PrimaryButton) {
4351
4367
  this.emitMouseDown(MouseButton.MainButton, x, y);
@@ -4364,58 +4380,53 @@ class InputService extends SubService {
4364
4380
  }
4365
4381
  }
4366
4382
  registerHoveringMouseEvents(htmlVideoElement) {
4367
- htmlVideoElement.onmousemove = (e) => {
4383
+ fromEvent(htmlVideoElement, 'mousemove')
4384
+ .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4385
+ .subscribe((e) => {
4368
4386
  const { x, y } = this.getMousePositionWithOffset(e);
4369
4387
  this.emitMouseMove(x, y, e.movementX, e.movementY);
4370
4388
  e.preventDefault();
4371
- };
4372
- htmlVideoElement.onmousedown = (e) => {
4389
+ });
4390
+ fromEvent(htmlVideoElement, 'mousedown')
4391
+ .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4392
+ .subscribe((e) => {
4373
4393
  const { x, y } = this.getMousePositionWithOffset(e);
4374
4394
  this.focusWindow();
4375
4395
  this.emitMouseDown(e.button, x, y);
4376
4396
  e.preventDefault();
4377
- };
4378
- htmlVideoElement.onmouseup = (e) => {
4397
+ });
4398
+ fromEvent(htmlVideoElement, 'mouseup')
4399
+ .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4400
+ .subscribe((e) => {
4379
4401
  const { x, y } = this.getMousePositionWithOffset(e);
4380
4402
  this.emitMouseUp(e.button, x, y);
4381
4403
  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
- // };
4404
+ });
4392
4405
  fromEvent(htmlVideoElement, 'wheel')
4393
4406
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4394
4407
  .subscribe((e) => {
4395
4408
  this.emitMouseWheel(e.deltaY * -120, e.offsetX, e.offsetY);
4396
4409
  e.preventDefault();
4397
4410
  });
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
4411
  }
4405
4412
  registerMouseEnterAndLeaveEvents(htmlVideoElement) {
4406
- htmlVideoElement.onmouseenter = (e) => {
4413
+ fromEvent(htmlVideoElement, 'mouseenter')
4414
+ .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4415
+ .subscribe((e) => {
4407
4416
  this.focusWindow();
4408
- const Data = new DataView(new ArrayBuffer(1));
4409
- Data.setUint8(0, MessageType.MouseEnter);
4410
- this.sendInputData(Data.buffer);
4411
- htmlVideoElement.pressMouseButtons(e);
4412
- };
4413
- htmlVideoElement.onmouseleave = (e) => {
4414
- const Data = new DataView(new ArrayBuffer(1));
4415
- Data.setUint8(0, MessageType.MouseLeave);
4416
- this.sendInputData(Data.buffer);
4417
- htmlVideoElement.releaseMouseButtons(e);
4418
- };
4417
+ const data = new DataView(new ArrayBuffer(1));
4418
+ data.setUint8(0, MessageType.MouseEnter);
4419
+ this.sendInputData(data.buffer);
4420
+ this.pressMouseButtons(e.buttons, e.offsetX, e.offsetY);
4421
+ });
4422
+ fromEvent(htmlVideoElement, 'mouseleave')
4423
+ .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
4424
+ .subscribe((e) => {
4425
+ const data = new DataView(new ArrayBuffer(1));
4426
+ data.setUint8(0, MessageType.MouseLeave);
4427
+ this.sendInputData(data.buffer);
4428
+ this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
4429
+ });
4419
4430
  }
4420
4431
  // Browser keys do not have a charCode so we only need to test keyCode.
4421
4432
  isKeyCodeBrowserKey(keyCode) {
@@ -5432,22 +5443,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
5432
5443
  }] });
5433
5444
 
5434
5445
  class AnalyticsService extends SubService {
5435
- #store = inject(Store);
5436
5446
  /** Signals monitorMouse inner subscriptions to stop (viewport lost / new session). */
5437
5447
  #stopMouse$ = new Subject();
5438
5448
  sendEventToMixPanel(name, params = {}) {
5439
5449
  Logger.log('Analytics Event:', name, params);
5440
- this.#store.dispatch(saveAnalyticsEvent({ event: { name, params } }));
5450
+ this.store.dispatch(saveAnalyticsEvent({ event: { name, params } }));
5441
5451
  }
5442
5452
  constructor() {
5443
5453
  super();
5444
- const abortSignal$ = this.#store
5454
+ const abortSignal$ = this.store
5445
5455
  .select(unrealFeature.selectViewportReady)
5446
5456
  .pipe(filter$1(Falsy), map(() => void 0));
5447
- this.#store
5457
+ this.store
5448
5458
  .select(unrealFeature.selectViewportReady)
5449
- .pipe(distinctUntilChanged(), filter$1(Truthy), tap(() => this.monitorMouse(abortSignal$)))
5450
- .subscribe();
5459
+ .pipe(distinctUntilChanged(), filter$1(Truthy), takeUntil$1(this.destroy$))
5460
+ .subscribe(() => this.monitorMouse(abortSignal$));
5451
5461
  }
5452
5462
  monitorMouse(abortSignal$) {
5453
5463
  this.#stopMouse$.next();
@@ -5920,12 +5930,7 @@ class UnrealCallbackService {
5920
5930
  * ```
5921
5931
  */
5922
5932
  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) => ({
5933
+ return this.actions$.pipe(ofType(unrealCommandCallback), map$1(({ json }) => 'commandCallback' in json ? json.commandCallback : json), filter((json) => json.command === command), map$1((data) => ({
5929
5934
  command: data.command,
5930
5935
  payload: 'payload' in data ? data.payload : null,
5931
5936
  })));
@@ -6057,15 +6062,15 @@ class ImageLoadingSrcComponent {
6057
6062
  this.imageLoadingSrc = this.#store.selectSignal(unrealFeature.selectImageLoadingSrc);
6058
6063
  }
6059
6064
  #store;
6060
- imageError() {
6065
+ loadingImageError() {
6061
6066
  this.#store.dispatch(setLoadingImageSrc({ src: '' }));
6062
6067
  }
6063
6068
  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)=\"imageError()\"\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 }); }
6069
+ 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
6070
  }
6066
6071
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
6067
6072
  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)=\"imageError()\"\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"] }]
6073
+ 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
6074
  }] });
6070
6075
 
6071
6076
  class StatGraphComponent {
@@ -6152,83 +6157,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6152
6157
 
6153
6158
  class UnrealStatusComponent {
6154
6159
  constructor() {
6155
- this.unrealInitialConfig = inject(UNREAL_CONFIG);
6156
- this.destroyRef = inject(DestroyRef);
6157
- this.store = inject(Store);
6158
- this.resetPercentageSignal$ = this.store
6159
- .select(selectShowLoader)
6160
- .pipe(filter(Falsy));
6161
- this.showLoader = this.store.selectSignal(selectShowLoader);
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
- }
6160
+ this.#store = inject(Store);
6161
+ this.#loadingProgress = inject(LoadingProgressService);
6162
+ this.awsInstance = this.#store.selectSignal(unrealFeature.selectAwsInstance);
6163
+ this.messagePercents = this.#loadingProgress.percents;
6164
+ this.showLoader = this.#loadingProgress.loaderVisible;
6165
+ this.showLoadingProgress = this.#store.selectSignal(unrealFeature.selectShowLoadingProgress);
6166
+ this.isShowLoadingPercentsPopup = computed(() => this.showLoader() && this.showLoadingProgress(), ...(ngDevMode ? [{ debugName: "isShowLoadingPercentsPopup" }] : /* istanbul ignore next */ []));
6225
6167
  }
6168
+ #store;
6169
+ #loadingProgress;
6226
6170
  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 (showLoader()) {\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 }); }
6171
+ 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
6172
  }
6229
6173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealStatusComponent, decorators: [{
6230
6174
  type: Component,
6231
- args: [{ selector: 'app-unreal-status', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceLoadingComponent], template: "@if (showLoader()) {\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"] }]
6175
+ 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
6176
  }] });
6233
6177
 
6234
6178
  class VideoStatsComponent {
@@ -6240,42 +6184,18 @@ class VideoStatsComponent {
6240
6184
  this.obs$ = this.videoService.videoStats$;
6241
6185
  this.viewportReady = this.store.selectSignal(unrealFeature.selectViewportReady);
6242
6186
  this.ssData = this.store.selectSignal(selectSignalingParameters);
6243
- this.fpsTick = toSignal(this.obs$.pipe(map$1((stat) => ({
6244
- value: stat.aggregatedStats.framesPerSecond || 0,
6245
- }))));
6246
- this.fpsAvgTick = toSignal(this.obs$.pipe(map$1((stat) => ({
6247
- value: stat.aggregatedStats.avgFrameRate,
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
- }))));
6187
+ this.fpsTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.framesPerSecond || 0 }))));
6188
+ this.fpsAvgTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.avgFrameRate }))));
6189
+ this.bitrateTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.bitrate }))));
6190
+ this.videoQP = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.VideoEncoderQP }))));
6191
+ this.bitrateAverageTick = toSignal(this.obs$.pipe(map$1((stat) => ({ value: stat.aggregatedStats.avgBitrate }))));
6258
6192
  this.videoStatus = toSignal(this.obs$.pipe(map$1((event) => this.isDevMode &&
6259
6193
  {
6260
6194
  ...event.aggregatedStats,
6261
6195
  quality: event.quality,
6262
- }), filter(Boolean), map$1((data) => {
6263
- return this.fields
6264
- .map((key) => {
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() {
6196
+ }), filter(Boolean), map$1((data) => this.fields
6197
+ .map((key) => ({ key, value: data[key] }))
6198
+ .filter((r) => !this.elementsToShow().includes(r.key)))));
6279
6199
  this.fields = [
6280
6200
  'kalmanBitrate',
6281
6201
  'bitrateDrop',
@@ -6299,26 +6219,21 @@ class VideoStatsComponent {
6299
6219
  'type',
6300
6220
  'codecs',
6301
6221
  'selectedPair',
6302
- //'pliCount',
6303
- //'sliCount',
6304
- //'transportId',
6305
- //'id',
6306
6222
  ];
6307
- this.updateGraph();
6308
- }
6309
- updateGraph() {
6310
- this.graphList = this.fields.map((key) => {
6311
- const stat = this.obs$.pipe(map$1((val) => {
6312
- const value = val.aggregatedStats[key];
6313
- return { value };
6314
- }));
6315
- return {
6316
- key,
6317
- color: Math.floor(Math.random() * 16777216).toString(),
6318
- stat,
6319
- };
6320
- });
6321
- this.graphList$ = of(this.graphList).pipe(combineLatestWith(this.trigger$), map$1(([graphList]) => graphList.filter((r) => this.elementsToShow.includes(r.key))));
6223
+ this.elementsToShow = signal([], ...(ngDevMode ? [{ debugName: "elementsToShow" }] : /* istanbul ignore next */ []));
6224
+ this.idKey = 'com.source3d.unreal.stat';
6225
+ this.graphListItems = this.fields.map((key) => ({
6226
+ key,
6227
+ color: '#' +
6228
+ Math.floor(Math.random() * 16777216)
6229
+ .toString(16)
6230
+ .padStart(6, '0'),
6231
+ stat: this.obs$.pipe(map$1((val) => ({
6232
+ value: val.aggregatedStats[key],
6233
+ }))),
6234
+ }));
6235
+ this.graphList = computed(() => this.graphListItems.filter((r) => this.elementsToShow().includes(r.key)), ...(ngDevMode ? [{ debugName: "graphList" }] : /* istanbul ignore next */ []));
6236
+ this.restoreFromStorage();
6322
6237
  }
6323
6238
  toggle() {
6324
6239
  this.collapsed.update((collapsed) => !collapsed);
@@ -6330,78 +6245,63 @@ class VideoStatsComponent {
6330
6245
  (typeof value === 'number' && isNaN(value))) {
6331
6246
  return;
6332
6247
  }
6333
- if (this.elementsToShow.includes(el)) {
6334
- this.elementsToShow = this.elementsToShow.filter((item) => item !== el);
6335
- }
6336
- else {
6337
- this.elementsToShow.push(el);
6338
- }
6248
+ this.elementsToShow.update((current) => current.includes(el)
6249
+ ? current.filter((item) => item !== el)
6250
+ : [...current, el]);
6339
6251
  this.writeFields();
6340
- this.trigger$.next();
6341
6252
  }
6342
- getFields() {
6253
+ restoreFromStorage() {
6343
6254
  try {
6344
- const { elements, collapsed } = JSON.parse(localStorage.getItem(this.idKey)) || [];
6345
- this.elementsToShow = elements || [];
6346
- this.collapsed.set(collapsed);
6255
+ const { elements, collapsed } = JSON.parse(localStorage.getItem(this.idKey)) || {};
6256
+ this.elementsToShow.set(elements || []);
6257
+ this.collapsed.set(collapsed ?? false);
6347
6258
  }
6348
6259
  catch {
6349
- this.elementsToShow = [];
6260
+ this.elementsToShow.set([]);
6350
6261
  }
6351
6262
  }
6352
6263
  writeFields() {
6353
6264
  localStorage.setItem(this.idKey, JSON.stringify({
6354
6265
  collapsed: this.collapsed(),
6355
- elements: this.elementsToShow,
6266
+ elements: this.elementsToShow(),
6356
6267
  }));
6357
6268
  }
6358
6269
  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$ | async; track graph) {\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 [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></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" }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6270
+ 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
6271
  }
6361
6272
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: VideoStatsComponent, decorators: [{
6362
6273
  type: Component,
6363
- args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, AsyncPipe, JsonPipe, SafePipe], 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$ | async; track graph) {\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 [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></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"] }]
6274
+ 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
6275
  }], ctorParameters: () => [] });
6365
6276
 
6366
6277
  class ResumeStreamButtonComponent {
6278
+ #store;
6367
6279
  constructor() {
6368
- this.actions$ = inject(Actions);
6369
6280
  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
6281
  this.streamConfig = this.#store.selectSignal(selectStreamConfig);
6282
+ this.isSecondStart = signal(!!(this.streamConfig()?.autoStart && location.href.match(/^https/gi)), ...(ngDevMode ? [{ debugName: "isSecondStart" }] : /* istanbul ignore next */ []));
6283
+ this.isLoaderScreenVisible = this.#store.selectSignal(selectIsLoaderScreenVisible);
6284
+ this.showReconnectPopup = this.#store.selectSignal(selectShowReconnectPopup);
6375
6285
  this.isExistMatchUrls = this.#store.selectSignal(selectIsExistMatchUrls);
6376
6286
  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
6287
  this.isShowResumeButton = computed(() => this.isLoaderScreenVisible() &&
6379
6288
  this.showReconnectPopup() &&
6380
6289
  this.isExistMatchUrls() &&
6381
6290
  this.showResumeStreamPopup(), ...(ngDevMode ? [{ debugName: "isShowResumeButton" }] : /* istanbul ignore next */ []));
6382
- }
6383
- #store;
6384
- #videoService;
6385
- #destroyRef;
6386
- ngOnInit() {
6387
- this.#store
6388
- .select(unrealFeature.selectCirrusConnected)
6389
- .pipe(skip$1(1), filter(Falsy), takeUntilDestroyed(this.#destroyRef))
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());
6291
+ this.cirrusConnected = this.#store.selectSignal(unrealFeature.selectCirrusConnected);
6292
+ let wasConnected = this.cirrusConnected();
6293
+ effect(() => {
6294
+ const connected = this.cirrusConnected();
6295
+ if (!connected && wasConnected) {
6296
+ this.disconnect();
6297
+ }
6298
+ wasConnected = connected;
6399
6299
  });
6400
6300
  }
6401
6301
  connect() {
6402
6302
  this.#store.dispatch(startStream({ config: { autoStart: true } }));
6403
6303
  }
6404
- onDisconnect() {
6304
+ disconnect() {
6405
6305
  this.isSecondStart.set(true);
6406
6306
  }
6407
6307
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -6410,13 +6310,13 @@ class ResumeStreamButtonComponent {
6410
6310
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
6411
6311
  type: Component,
6412
6312
  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
- }] });
6313
+ }], ctorParameters: () => [] });
6414
6314
 
6415
6315
  class IntroSrcComponent {
6416
6316
  #store;
6417
6317
  constructor() {
6418
- this.videoElement = viewChild('videoPlayerCover', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
6419
6318
  this.#store = inject(Store);
6319
+ this.videoElement = viewChild('videoPlayerCover', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
6420
6320
  this.imageIntroSrc = this.#store.selectSignal(unrealFeature.selectImageIntroSrc);
6421
6321
  this.videoIntroSrc = this.#store.selectSignal(unrealFeature.selectVideoIntroSrc);
6422
6322
  this.viewportReady = this.#store.selectSignal(unrealFeature.selectViewportReady);
@@ -6440,6 +6340,9 @@ class IntroSrcComponent {
6440
6340
  });
6441
6341
  });
6442
6342
  }
6343
+ loadingImageError() {
6344
+ this.#store.dispatch(resetIntroSrc());
6345
+ }
6443
6346
  createVideo(src, videoContainer) {
6444
6347
  if (typeof src !== 'string') {
6445
6348
  return;
@@ -6470,15 +6373,12 @@ class IntroSrcComponent {
6470
6373
  sourceElement.addEventListener('error', () => this.#store.dispatch(resetIntroSrc()));
6471
6374
  videoContainer.appendChild(videoElement);
6472
6375
  }
6473
- imageError() {
6474
- this.#store.dispatch(resetIntroSrc());
6475
- }
6476
6376
  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)=\"imageError()\" [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 }); }
6377
+ 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
6378
  }
6479
6379
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IntroSrcComponent, decorators: [{
6480
6380
  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)=\"imageError()\" [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"] }]
6381
+ 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
6382
  }], ctorParameters: () => [], propDecorators: { videoElement: [{ type: i0.ViewChild, args: ['videoPlayerCover', { isSignal: true }] }] } });
6483
6383
 
6484
6384
  class UnrealSceneComponent {
@@ -6498,19 +6398,19 @@ class UnrealSceneComponent {
6498
6398
  onMouseOut() {
6499
6399
  this.changeMouseOverScene.emit(false);
6500
6400
  }
6401
+ onContextMenu(event) {
6402
+ event.preventDefault();
6403
+ }
6501
6404
  constructor() {
6502
- this.isStudio = input(false, ...(ngDevMode ? [{ debugName: "isStudio" }] : /* istanbul ignore next */ []));
6503
6405
  this.useContainerAsSizeProvider = input(true, ...(ngDevMode ? [{ debugName: "useContainerAsSizeProvider" }] : /* istanbul ignore next */ []));
6504
- this.studioResolutionSize = input({
6406
+ this.isStudio = input(false, ...(ngDevMode ? [{ debugName: "isStudio" }] : /* istanbul ignore next */ []));
6407
+ this.resolutionSize = input({
6505
6408
  width: FULL_HD_WIDTH,
6506
6409
  height: FULL_HD_HEIGHT,
6507
- }, ...(ngDevMode ? [{ debugName: "studioResolutionSize" }] : /* istanbul ignore next */ []));
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 */ []));
6410
+ }, ...(ngDevMode ? [{ debugName: "resolutionSize" }] : /* istanbul ignore next */ []));
6513
6411
  this.changeMouseOverScene = output();
6412
+ this.videoElement = viewChild('videoContainer', ...(ngDevMode ? [{ debugName: "videoElement" }] : /* istanbul ignore next */ []));
6413
+ this.resolutionValues$ = toObservable(this.resolutionSize);
6514
6414
  this.width = FULL_HD_WIDTH;
6515
6415
  this.height = FULL_HD_HEIGHT;
6516
6416
  this.actions$ = inject(Actions);
@@ -6521,38 +6421,34 @@ class UnrealSceneComponent {
6521
6421
  this.maxResolution = Truthy(new URLSearchParams(location.search).get('maxresolution'));
6522
6422
  this.commandsSender = inject(UnrealCommunicatorService);
6523
6423
  this.videoService = inject(VideoService);
6524
- this.element = inject(ElementRef);
6525
6424
  this.destroyRef = inject(DestroyRef);
6526
6425
  this.destroyRef.onDestroy(() => this.#store.dispatch(destroyUnrealScene()));
6527
6426
  effect(() => {
6528
6427
  const videoElement = this.videoElement();
6529
6428
  const isExistMatchUrls = this.isExistMatchUrls();
6530
- if (videoElement && isExistMatchUrls) {
6531
- this.#store.dispatch(initSignalling());
6532
- }
6429
+ untracked(() => {
6430
+ if (videoElement && isExistMatchUrls) {
6431
+ this.#store.dispatch(initSignalling());
6432
+ }
6433
+ });
6533
6434
  });
6534
6435
  afterNextRender({
6535
6436
  read: () => {
6536
6437
  const videoElement = this.videoElement();
6537
6438
  if (videoElement) {
6538
6439
  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
6440
  this.listenResizeValues();
6545
6441
  }
6546
6442
  },
6547
6443
  });
6548
6444
  }
6549
6445
  listenResizeValues() {
6550
- const customSizeInputChanged$ = this.studioResolutionValues.pipe(filter(Truthy), distinctUntilChanged(), filter(({ width, height }) => this.width !== width || this.height !== height), map$1(({ width, height }) => ({ width, height })), tap(({ width, height }) => {
6446
+ const customSizeInputChanged$ = this.resolutionValues$.pipe(filter(({ width, height }) => this.width !== width || this.height !== height), tap(({ width, height }) => {
6551
6447
  this.width = width;
6552
6448
  this.height = height;
6553
6449
  }));
6554
6450
  const resizeRequired$ = merge(this.actions$.pipe(ofType(videoStreamInitialized)), fromEvent(window, 'resize'));
6555
- const videoStreamSizeChanged$ = this.videoService?.videoStats$.pipe(filter(() => !this.isFreezeFrameLoading()), map$1((stats) => ({
6451
+ const videoStreamSizeChanged$ = this.videoService.videoStats$.pipe(filter(() => !this.isFreezeFrameLoading()), map$1((stats) => ({
6556
6452
  w: stats.aggregatedStats.frameWidth || 0,
6557
6453
  h: stats.aggregatedStats.frameHeight || 0,
6558
6454
  })), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
@@ -6560,12 +6456,17 @@ class UnrealSceneComponent {
6560
6456
  merge(customSizeInputChanged$, resizeRequired$, videoStreamSizeChanged$)
6561
6457
  .pipe(debounceTime(DEBOUNCE_TO_MANY_RESIZE_CALLS), switchMap(() => {
6562
6458
  const key = Math.random();
6563
- // Monitoring the size of the video stream
6459
+ // Poll 3 times to capture late-arriving DOM measurements after layout settles
6564
6460
  return timer(0, 300).pipe(take(3), map$1(() => key));
6565
6461
  }), map$1((key) => ({ key, ...this.resizeValues })), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
6566
6462
  fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD) &&
6567
- previous.key === current.key), tap((size) => this.adaptVideo(size)), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
6568
- fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD)), combineLatestWith(this.#store
6463
+ previous.key === current.key), tap((size) => this.adaptVideo(size)),
6464
+ // Key excluded: only dispatch when actual pixel dimensions change
6465
+ distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
6466
+ fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD)),
6467
+ // Holds dispatch until data channel is up; retains true after disconnect
6468
+ // so resizes still reach Unreal — cleanup is handled by destroyUnrealScene
6469
+ combineLatestWith(this.#store
6569
6470
  .select(unrealFeature.selectDataChannelConnected)
6570
6471
  .pipe(filter(Truthy))), takeUntilDestroyed(this.destroyRef))
6571
6472
  .subscribe(([size]) => {
@@ -6593,11 +6494,13 @@ class UnrealSceneComponent {
6593
6494
  this.#store.dispatch(videoAdaptedToContainer());
6594
6495
  }
6595
6496
  getResizeValuesUniversal() {
6596
- let containerWidth = window.innerWidth || 2;
6597
- let containerHeight = window.innerHeight || 2;
6497
+ let containerWidth = window.innerWidth || MIN_CONTAINER_SIZE;
6498
+ let containerHeight = window.innerHeight || MIN_CONTAINER_SIZE;
6598
6499
  if (this.useContainerAsSizeProvider()) {
6599
- containerWidth = this.videoElement()?.nativeElement.offsetWidth || 2;
6600
- containerHeight = this.videoElement()?.nativeElement.offsetHeight || 2;
6500
+ containerWidth =
6501
+ this.videoElement()?.nativeElement.offsetWidth || MIN_CONTAINER_SIZE;
6502
+ containerHeight =
6503
+ this.videoElement()?.nativeElement.offsetHeight || MIN_CONTAINER_SIZE;
6601
6504
  }
6602
6505
  const pixelRatio = this.pixelRatio;
6603
6506
  return getResizeValuesUniversal({
@@ -6611,8 +6514,8 @@ class UnrealSceneComponent {
6611
6514
  const videoElement = this.videoElement()?.nativeElement;
6612
6515
  return getResizeStudioValues({
6613
6516
  pixelRatio: this.pixelRatio,
6614
- containerWidth: videoElement?.offsetWidth || 2,
6615
- containerHeight: videoElement?.offsetHeight || 2,
6517
+ containerWidth: videoElement?.offsetWidth || MIN_CONTAINER_SIZE,
6518
+ containerHeight: videoElement?.offsetHeight || MIN_CONTAINER_SIZE,
6616
6519
  studioWidth: this.width,
6617
6520
  studioHeight: this.height,
6618
6521
  maxResolution: this.maxResolution,
@@ -6630,7 +6533,7 @@ class UnrealSceneComponent {
6630
6533
  });
6631
6534
  }
6632
6535
  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 }); }
6536
+ 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
6537
  }
6635
6538
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: UnrealSceneComponent, decorators: [{
6636
6539
  type: Component,
@@ -6643,12 +6546,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6643
6546
  ImageLoadingSrcComponent,
6644
6547
  IntroSrcComponent,
6645
6548
  ], 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: { isStudio: [{ type: i0.Input, args: [{ isSignal: true, alias: "isStudio", required: false }] }], useContainerAsSizeProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "useContainerAsSizeProvider", required: false }] }], studioResolutionSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "studioResolutionSize", required: false }] }], videoElement: [{ type: i0.ViewChild, args: ['videoContainer', { isSignal: true }] }], changeMouseOverScene: [{ type: i0.Output, args: ["changeMouseOverScene"] }], onMouseOver: [{
6549
+ }], 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
6550
  type: HostListener,
6648
6551
  args: ['mouseover']
6649
6552
  }], onMouseOut: [{
6650
6553
  type: HostListener,
6651
6554
  args: ['mouseout']
6555
+ }], onContextMenu: [{
6556
+ type: HostListener,
6557
+ args: ['contextmenu', ['$event']]
6652
6558
  }] } });
6653
6559
 
6654
6560
  class WebrtcErrorModalComponent {
@@ -6659,9 +6565,6 @@ class WebrtcErrorModalComponent {
6659
6565
  }
6660
6566
  #dialogRef;
6661
6567
  #store;
6662
- close() {
6663
- this.#dialogRef.close();
6664
- }
6665
6568
  closeModalWithCirrusDisconnect() {
6666
6569
  if (!this.isCirrusConnected()) {
6667
6570
  this.#store.dispatch(disconnectStream({
@@ -6671,6 +6574,9 @@ class WebrtcErrorModalComponent {
6671
6574
  }
6672
6575
  this.close();
6673
6576
  }
6577
+ close() {
6578
+ this.#dialogRef.close();
6579
+ }
6674
6580
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6675
6581
  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
6582
  }
@@ -6727,12 +6633,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
6727
6633
 
6728
6634
  class LowBandwidthDetectorComponent {
6729
6635
  constructor() {
6730
- this.store = inject(Store);
6636
+ this.#store = inject(Store);
6731
6637
  this.dialog = inject(Dialog);
6732
6638
  this.destroyRef = inject(DestroyRef);
6733
6639
  this.videoService = inject(VideoService);
6734
6640
  this.isDevMode = inject(DevModeService).isDevMode;
6735
- this.viewportReady$ = this.store.select(unrealFeature.selectViewportReady);
6641
+ this.viewportReady$ = this.#store.select(unrealFeature.selectViewportReady);
6736
6642
  this.isLowBandwidth$ = of(true);
6737
6643
  this.lowBandwidthTrigger$ = this.isLowBandwidth$.pipe(distinctUntilChanged$1(), filter(Truthy));
6738
6644
  this.highBandwidthTrigger$ = this.isLowBandwidth$.pipe(distinctUntilChanged$1(), filter(Falsy));
@@ -6755,6 +6661,7 @@ class LowBandwidthDetectorComponent {
6755
6661
  this.isIndicatorExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isIndicatorExpanded" }] : /* istanbul ignore next */ []));
6756
6662
  this.isIndicatorExpandedTrigger$ = toObservable(this.isIndicatorExpanded).pipe(skip(1), distinctUntilChanged$1(), filter(Falsy));
6757
6663
  }
6664
+ #store;
6758
6665
  ngOnInit() {
6759
6666
  this.lowBandwidthTrigger$
6760
6667
  .pipe(takeUntilDestroyed(this.destroyRef))
@@ -6853,6 +6760,7 @@ function provideUnrealModule() {
6853
6760
  StreamStatusTelemetryService2,
6854
6761
  FpsMonitorService,
6855
6762
  WebRtcPlayerService,
6763
+ LoadingProgressService,
6856
6764
  provideEnvironmentInitializer(() => {
6857
6765
  inject(AggregatorService);
6858
6766
  inject(InputService);
@@ -6871,5 +6779,5 @@ function provideUnrealModule() {
6871
6779
  * Generated bundle index. Do not edit.
6872
6780
  */
6873
6781
 
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, getResizeStudioValuesOld, getResizeValuesUniversal, getResizeValuesUniversalOld, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectCommandsInProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectWarnTimeout, setAfkTimeout, 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, setViewportNotReady, setViewportReady, showAfkTimeoutPopup, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
6782
+ 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, selectCommandsInProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, 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
6783
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map