@3dsource/angular-unreal-module 0.0.35 → 0.0.37

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,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, ChangeDetectionStrategy, Component, Pipe, DestroyRef, signal, ElementRef, input, HostListener, Input, ViewChild, computed, output } from '@angular/core';
3
- import { filter, withLatestFrom, distinctUntilChanged, first, catchError, map, tap, delay, takeUntil, switchMap as switchMap$1, debounceTime, exhaustMap, skip as skip$1 } from 'rxjs/operators';
3
+ import { filter, withLatestFrom, distinctUntilChanged, switchMap, first, catchError, map as map$1, tap, delay, takeUntil, debounceTime, exhaustMap, skip as skip$1 } from 'rxjs/operators';
4
4
  import { createAction, props, createReducer, on, createFeature, Store, createSelector } from '@ngrx/store';
5
5
  import { Actions, createEffect, ofType } from '@ngrx/effects';
6
- import { skip, share, merge, timer, of, Subject, combineLatest, switchMap, timeout, retryWhen, take, fromEvent, from, tap as tap$1, interval, startWith, combineLatestWith, takeUntil as takeUntil$1, auditTime, map as map$1, EMPTY, debounceTime as debounceTime$1, scan, Observable, BehaviorSubject, first as first$1, distinctUntilChanged as distinctUntilChanged$1, concat } from 'rxjs';
6
+ import { skip, share, merge, Subject, interval, map, from, take, fromEvent, timer, of, combineLatest, switchMap as switchMap$1, timeout, retryWhen, tap as tap$1, startWith, combineLatestWith, takeUntil as takeUntil$1, auditTime, EMPTY, debounceTime as debounceTime$1, scan, Observable, BehaviorSubject, first as first$1, distinctUntilChanged as distinctUntilChanged$1, concat } from 'rxjs';
7
7
  import { concatLatestFrom, mapResponse } from '@ngrx/operators';
8
- import { Falsy, Truthy, Logger, generateUuid, tapLog, COLOR_CODES, calculateMedian, clampf, Signal, where, KeyboardNumericCode, InvertedKeyMap, Semaphore, isEmpty, lerp, smoothTransition, getCanvasCached, getSnapshot, whereNot, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB, fitIntoRectangle } from '@3dsource/utils';
8
+ import { Falsy, Truthy, Logger, calculateMedian, clampf, Signal, tapLog, generateUuid, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, Semaphore, isEmpty, lerp, smoothTransition, getCanvasCached, getSnapshot, whereNot, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB, fitIntoRectangle } from '@3dsource/utils';
9
9
  import { toSignal, takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
10
10
  import { HttpClient } from '@angular/common/http';
11
11
  import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
@@ -678,6 +678,309 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImpor
678
678
  type: Injectable
679
679
  }] });
680
680
 
681
+ class DataFlowMonitor {
682
+ /**
683
+ * Initializes the DataFlowMonitor monitor.
684
+ * @param yellowFlagThresholdPercentage - The percentage drop to trigger a YELLOW warning (default: 15%).
685
+ * @param redFlagThresholdPercentage - The percentage drop to trigger a RED warning (default: 30%).
686
+ * @param historyBufferLength - buffer length (default: 100).
687
+ * @param splitPoint - The point at which to split the history buffer into two halves (default: 0.5).
688
+ */
689
+ constructor(yellowFlagThresholdPercentage = 15, redFlagThresholdPercentage = 30, historyBufferLength = 100, splitPoint = 0.5) {
690
+ this.yellowFlagThresholdPercentage = yellowFlagThresholdPercentage;
691
+ this.redFlagThresholdPercentage = redFlagThresholdPercentage;
692
+ this.historyBufferLength = historyBufferLength;
693
+ this.splitPoint = splitPoint;
694
+ this.dataHistory = [];
695
+ }
696
+ reset() {
697
+ this.dataHistory.length = 0;
698
+ }
699
+ config(data) {
700
+ this.reset();
701
+ this.yellowFlagThresholdPercentage = data.yellowFlag;
702
+ this.redFlagThresholdPercentage = data.redFlag;
703
+ }
704
+ /**
705
+ * Adds a new bitrate measurement and checks for significant drops.
706
+ * @param currentValue - The current bitrate in kbps.
707
+ * @returns BitrateCheckResult indicating if a drop was detected.
708
+ */
709
+ addValue(currentValue) {
710
+ if (isNaN(currentValue) || currentValue < 1) {
711
+ return {};
712
+ }
713
+ const historyLength = this.dataHistory.length;
714
+ if (historyLength > this.historyBufferLength) {
715
+ this.dataHistory.shift();
716
+ }
717
+ this.dataHistory.push(currentValue);
718
+ if (historyLength < this.historyBufferLength) {
719
+ this.dataHistory.length = this.historyBufferLength;
720
+ this.dataHistory.fill(currentValue, 0, this.historyBufferLength);
721
+ }
722
+ const splitIndex = Math.floor(historyLength * this.splitPoint);
723
+ const firstHalf = this.dataHistory.slice(0, splitIndex);
724
+ const secondHalf = this.dataHistory.slice(splitIndex);
725
+ const firstHalfMedian = calculateMedian(firstHalf);
726
+ const secondHalfMedian = calculateMedian(secondHalf);
727
+ const dropPercentage = clampf(0, 100, ((firstHalfMedian - secondHalfMedian) / firstHalfMedian) * 100);
728
+ const isRedFlag = dropPercentage >= this.redFlagThresholdPercentage;
729
+ if (dropPercentage >= this.yellowFlagThresholdPercentage) {
730
+ return {
731
+ config: {
732
+ yellowFlagThresholdPercentage: this.yellowFlagThresholdPercentage,
733
+ redFlagThresholdPercentage: this.redFlagThresholdPercentage,
734
+ historyBufferLength: this.historyBufferLength,
735
+ splitPoint: this.splitPoint,
736
+ },
737
+ isDropDetected: true,
738
+ dropPercentage,
739
+ dataHistory: [...this.dataHistory],
740
+ activeMedian: secondHalfMedian,
741
+ quality: isRedFlag ? 'red' : 'orange',
742
+ message: `Significant flow drop detected: ${dropPercentage.toFixed(2)}% (from ${firstHalfMedian} to ${secondHalfMedian})`,
743
+ };
744
+ }
745
+ return {
746
+ config: {
747
+ yellowFlagThresholdPercentage: this.yellowFlagThresholdPercentage,
748
+ redFlagThresholdPercentage: this.redFlagThresholdPercentage,
749
+ historyBufferLength: this.historyBufferLength,
750
+ splitPoint: this.splitPoint,
751
+ },
752
+ isDropDetected: false,
753
+ dropPercentage,
754
+ dataHistory: [...this.dataHistory],
755
+ activeMedian: secondHalfMedian,
756
+ quality: 'lime',
757
+ message: 'Stable flow',
758
+ };
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Default LBM Filter Parameters
764
+ */
765
+ const DefaultFilterModel = {
766
+ minimumBitrate: 1600,
767
+ yellowFlag: 30,
768
+ redFlag: 50,
769
+ minimumFps: 5,
770
+ monitoringDelayTime: 5000,
771
+ initialBitrateEstimate: 1600,
772
+ initialErrorCovariance: 1,
773
+ processNoise: 3,
774
+ measurementNoise: 500,
775
+ panelOpen: false,
776
+ };
777
+ /**
778
+ * Global LBM Filter Parameters
779
+ */
780
+ const FilterModel = { ...DefaultFilterModel };
781
+ /**
782
+ * Bitrate Monitor Static Class
783
+ */
784
+ const BITRATE_MONITOR = new DataFlowMonitor(FilterModel.yellowFlag, FilterModel.redFlag, 20);
785
+
786
+ class VideoService extends SubService {
787
+ constructor() {
788
+ super();
789
+ this.latencyTestTimings = new LatencyTimings();
790
+ this.videoTrack$ = new Subject();
791
+ this.VideoEncoderQP = 0;
792
+ this.aggregatedStats = {};
793
+ this.kalmanFilter1D = new KalmanFilter1D(FilterModel.initialBitrateEstimate, FilterModel.initialErrorCovariance, FilterModel.processNoise, FilterModel.measurementNoise);
794
+ /**
795
+ * Aggregate video stats and emit it as videoStats$
796
+ */
797
+ this.videoStats$ = this.videoTrack$.pipe(
798
+ // IMPORTANT! DO NOT CHANGE THOSE NUMBERS, LBM Stats are based on those values
799
+ switchMap(({ pcClient }) => interval(250).pipe(map(() => pcClient))), switchMap((pcClient) => from(this.getStats(pcClient))), filter(Truthy), share());
800
+ Signal.on('setKalmanParams').subscribe((data) => {
801
+ this.kalmanFilter1D.config(data);
802
+ BITRATE_MONITOR.config(data);
803
+ });
804
+ }
805
+ setContainer(container) {
806
+ this.container = container;
807
+ }
808
+ setLatencyTimings(latencyTimings) {
809
+ this.latencyTestTimings.SetUETimings(latencyTimings);
810
+ }
811
+ setEncoder(data) {
812
+ this.VideoEncoderQP = Number(new TextDecoder('utf-16').decode(data));
813
+ }
814
+ create() {
815
+ this.disconnect$.pipe(take(1)).subscribe(() => this.destroy());
816
+ this.destroy();
817
+ this.createWebRtcVideo();
818
+ this.createWebRtcAudio();
819
+ }
820
+ attachVideoStream(stream, pcClient) {
821
+ if (this.video) {
822
+ this.video.srcObject = stream;
823
+ this.videoTrack$.next({ stream, pcClient });
824
+ }
825
+ else {
826
+ console.error('Video element is not defined.');
827
+ }
828
+ }
829
+ attachAudioStream(stream) {
830
+ // do nothing the video has the same media stream as the audio track we have here (they are linked)
831
+ if (this.video?.srcObject === stream) {
832
+ return;
833
+ }
834
+ // video element has some other media stream that is not associated with this audio track
835
+ else if (this.audio &&
836
+ this.video?.srcObject &&
837
+ this.video?.srcObject !== stream) {
838
+ this.audio.srcObject = stream;
839
+ this.playAudio(this.audio);
840
+ }
841
+ }
842
+ play() {
843
+ void this.video?.play();
844
+ }
845
+ async getStats(pcClient) {
846
+ if (!pcClient) {
847
+ return null;
848
+ }
849
+ const stats = await pcClient.getStats(null);
850
+ return this.generateAggregatedStatsFunction(stats);
851
+ }
852
+ generateAggregatedStatsFunction(stats) {
853
+ const newStat = {};
854
+ // store each type of codec we can get stats on
855
+ newStat.codecs = {};
856
+ newStat.currentRoundTripTime = -1;
857
+ stats.forEach((stat) => {
858
+ // Get the inbound-rtp for video
859
+ if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
860
+ Object.assign(newStat, stat);
861
+ newStat.bytesReceivedStart =
862
+ this.aggregatedStats && this.aggregatedStats.bytesReceivedStart
863
+ ? this.aggregatedStats.bytesReceivedStart
864
+ : stat.bytesReceived;
865
+ newStat.framesDecodedStart =
866
+ this.aggregatedStats && this.aggregatedStats.framesDecodedStart
867
+ ? this.aggregatedStats.framesDecodedStart
868
+ : stat.framesDecoded;
869
+ newStat.timestampStart =
870
+ this.aggregatedStats && this.aggregatedStats.timestampStart
871
+ ? this.aggregatedStats.timestampStart
872
+ : stat.timestamp;
873
+ if (this.aggregatedStats && this.aggregatedStats.timestamp) {
874
+ // Get the mimetype of the video codec being used
875
+ if (stat.codecId &&
876
+ this.aggregatedStats.codecs &&
877
+ Object.hasOwn(this.aggregatedStats.codecs, stat.codecId)) {
878
+ newStat.videoCodec = this.aggregatedStats.codecs[stat.codecId];
879
+ }
880
+ if (this.aggregatedStats.bytesReceived) {
881
+ // bitrate = bits received since last time / number of ms since last time
882
+ // This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
883
+ newStat.bitrate = Math.floor((8 *
884
+ (newStat.bytesReceived - this.aggregatedStats.bytesReceived)) /
885
+ (newStat.timestamp - this.aggregatedStats.timestamp));
886
+ }
887
+ if (this.aggregatedStats.bytesReceivedStart) {
888
+ newStat.avgBitrate = Math.floor((8 *
889
+ (newStat?.bytesReceived -
890
+ this.aggregatedStats.bytesReceivedStart)) /
891
+ (newStat.timestamp - this.aggregatedStats.timestampStart));
892
+ }
893
+ if (this.aggregatedStats.framesDecodedStart) {
894
+ newStat.avgFrameRate = Math.floor((newStat.framesDecoded -
895
+ this.aggregatedStats.framesDecodedStart) /
896
+ ((newStat.timestamp - this.aggregatedStats.timestampStart) /
897
+ 1000));
898
+ }
899
+ }
900
+ }
901
+ if (stat.type === 'candidate-pair' &&
902
+ Object.hasOwn(stat, 'currentRoundTripTime')) {
903
+ newStat.currentRoundTripTime = stat.currentRoundTripTime ?? 0;
904
+ }
905
+ // Store mimetype of each codec
906
+ if (Object.hasOwn(newStat, 'codecs') &&
907
+ stat.type === 'codec' &&
908
+ stat.mimeType &&
909
+ stat.id) {
910
+ const codecId = stat.id;
911
+ newStat.codecs[codecId] = stat.mimeType
912
+ .replace('video/', '')
913
+ .replace('audio/', '');
914
+ }
915
+ });
916
+ newStat.pixelRatio = +window.devicePixelRatio.toFixed(2);
917
+ if (this.aggregatedStats.receiveToCompositeMs) {
918
+ newStat.receiveToCompositeMs = this.aggregatedStats.receiveToCompositeMs;
919
+ this.latencyTestTimings.SetFrameDisplayDeltaTime(this.aggregatedStats.receiveToCompositeMs);
920
+ }
921
+ // Calculate duration of run
922
+ newStat.runTime = Math.floor((newStat.timestamp - newStat.timestampStart) / 1000);
923
+ newStat.kalmanBitrate = Math.floor(this.kalmanFilter1D.update(newStat.bitrate || FilterModel.initialBitrateEstimate));
924
+ newStat.dataFlowCheckResult = BITRATE_MONITOR.addValue(newStat.kalmanBitrate);
925
+ newStat.bitrateDrop = Math.floor(newStat.dataFlowCheckResult.dropPercentage);
926
+ this.aggregatedStats = newStat;
927
+ return this.onAggregatedStats(newStat);
928
+ }
929
+ onAggregatedStats(aggregatedStats) {
930
+ return {
931
+ quality: mapQpToQuality(this.VideoEncoderQP),
932
+ aggregatedStats: {
933
+ ...aggregatedStats,
934
+ VideoEncoderQP: this.VideoEncoderQP,
935
+ },
936
+ };
937
+ }
938
+ destroy() {
939
+ this.video = null;
940
+ this.audio = null;
941
+ while (this.container?.firstChild) {
942
+ this.container.removeChild(this.container.firstChild);
943
+ }
944
+ this.store.dispatch(changeStatusMainVideoOnScene({ isVideoPlaying: false }));
945
+ }
946
+ createWebRtcVideo() {
947
+ dispatchResize();
948
+ const video = document.createElement('video');
949
+ video.id = STREAMING_VIDEO_ID;
950
+ video.playsInline = true;
951
+ video.muted = true;
952
+ video.autoplay = true;
953
+ video.preload = 'auto';
954
+ video.disablePictureInPicture = true;
955
+ this.video = video;
956
+ this.container?.appendChild(this.video);
957
+ this.video.addEventListener('play', () => {
958
+ this.store.dispatch(changeStatusMainVideoOnScene({ isVideoPlaying: true }));
959
+ });
960
+ fromEvent(this.video, 'loadedmetadata')
961
+ .pipe(first(), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'))
962
+ .subscribe(() => sendSignal(UnrealInternalSignalEvents.OnVideoInitialized, this.video));
963
+ }
964
+ createWebRtcAudio() {
965
+ const audio = new Audio();
966
+ audio.id = 'streamingAudio';
967
+ this.audio = audio;
968
+ }
969
+ playAudio(audio) {
970
+ from(audio.play())
971
+ .pipe(catchError(() => fromEvent(document, 'click')), take(1))
972
+ .subscribe({
973
+ next: () => audio.play(),
974
+ error: () => ({}),
975
+ });
976
+ }
977
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
978
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoService }); }
979
+ }
980
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoService, decorators: [{
981
+ type: Injectable
982
+ }], ctorParameters: () => [] });
983
+
681
984
  class CommandTelemetryService {
682
985
  constructor() {
683
986
  this.appId = 'metabox-telemetry';
@@ -947,7 +1250,7 @@ class SignallingService extends SubService {
947
1250
  .pipe(tapLog('MatchMakerUrls changed:')),
948
1251
  this.store.select(selectIsAutostart).pipe(tapLog('Autostart is:')),
949
1252
  ])
950
- .pipe(filter(([, autoStart]) => autoStart), map(([matchMakerUrls]) => [...matchMakerUrls].filter(Truthy)), filter((urls) => urls.length > 0))
1253
+ .pipe(filter(([, autoStart]) => autoStart), map$1(([matchMakerUrls]) => [...matchMakerUrls].filter(Truthy)), filter((urls) => urls.length > 0))
951
1254
  .subscribe((urls) => this.connectToSignaling(urls));
952
1255
  this.store
953
1256
  .select(selectWsUrl)
@@ -1007,7 +1310,7 @@ class SignallingService extends SubService {
1007
1310
  if (!clientId || !viewId) {
1008
1311
  console.error('Client ID or View ID is not set');
1009
1312
  }
1010
- }), filter(([clientId, viewId]) => !!clientId && !!viewId), tapLog('Stream Id =>'), switchMap(([clientId, viewId]) => this.httpClient.get(`${signalingUrl}${clientId}/${viewId}`)), timeout(4000), filter((data) => {
1313
+ }), filter(([clientId, viewId]) => !!clientId && !!viewId), tapLog('Stream Id =>'), switchMap$1(([clientId, viewId]) => this.httpClient.get(`${signalingUrl}${clientId}/${viewId}`)), timeout(4000), filter((data) => {
1011
1314
  TelemetryStop('getSignaling', { ...data, multi: true });
1012
1315
  if (data.signallingServer === '' || data.error) {
1013
1316
  this.showStatusMessage(data?.mm_message || 'Server not found');
@@ -1154,106 +1457,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImpor
1154
1457
  type: Injectable
1155
1458
  }], ctorParameters: () => [] });
1156
1459
 
1157
- class VideoService extends SubService {
1158
- create() {
1159
- this.disconnect$.pipe(take(1)).subscribe(() => this.destroy());
1160
- this.destroy();
1161
- this.createWebRtcVideo();
1162
- this.createWebRtcAudio();
1163
- }
1164
- destroy() {
1165
- this.video = null;
1166
- this.audio = null;
1167
- while (this.container?.firstChild) {
1168
- this.container.removeChild(this.container.firstChild);
1169
- }
1170
- this.store.dispatch(changeStatusMainVideoOnScene({ isVideoPlaying: false }));
1171
- }
1172
- createWebRtcVideo() {
1173
- dispatchResize();
1174
- const video = document.createElement('video');
1175
- video.id = STREAMING_VIDEO_ID;
1176
- video.playsInline = true;
1177
- video.muted = true;
1178
- video.autoplay = true;
1179
- video.preload = 'auto';
1180
- video.disablePictureInPicture = true;
1181
- this.video = video;
1182
- this.container?.appendChild(this.video);
1183
- this.video.addEventListener('play', () => {
1184
- this.store.dispatch(changeStatusMainVideoOnScene({ isVideoPlaying: true }));
1185
- });
1186
- fromEvent(this.video, 'loadedmetadata')
1187
- .pipe(first(), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'))
1188
- .subscribe(() => sendSignal(UnrealInternalSignalEvents.OnVideoInitialized, this.video));
1189
- }
1190
- attachVideoStream(stream) {
1191
- if (this.video) {
1192
- this.video.srcObject = stream;
1193
- }
1194
- else {
1195
- console.error('Video element is not defined.');
1196
- }
1197
- }
1198
- attachAudioStream(stream) {
1199
- // do nothing the video has the same media stream as the audio track we have here (they are linked)
1200
- if (this.video?.srcObject === stream) {
1201
- return;
1202
- }
1203
- // video element has some other media stream that is not associated with this audio track
1204
- else if (this.audio &&
1205
- this.video?.srcObject &&
1206
- this.video?.srcObject !== stream) {
1207
- this.audio.srcObject = stream;
1208
- this.playAudio(this.audio);
1209
- }
1210
- }
1211
- createWebRtcAudio() {
1212
- const audio = new Audio();
1213
- audio.id = 'streamingAudio';
1214
- this.audio = audio;
1215
- }
1216
- playAudio(audio) {
1217
- from(audio.play())
1218
- .pipe(catchError(() => fromEvent(document, 'click')), take(1))
1219
- .subscribe({
1220
- next: () => audio.play(),
1221
- error: () => ({}),
1222
- });
1223
- }
1224
- play() {
1225
- void this.video?.play();
1226
- }
1227
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1228
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoService }); }
1229
- }
1230
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoService, decorators: [{
1231
- type: Injectable
1232
- }] });
1233
-
1234
1460
  class WebRtcPlayerService extends SubService {
1235
1461
  constructor() {
1236
- super(...arguments);
1237
- this.onAudioTrack$ = new Subject();
1238
- this.onVideoTrack$ = new Subject();
1462
+ super();
1239
1463
  this.onDataChannelMessage$ = new Subject();
1240
- this.onWebRtcCandidate$ = new Subject();
1241
- this.onWebRtcOffer$ = new Subject();
1464
+ this.signallingSrv = inject(SignallingService);
1465
+ this.videoService = inject(VideoService);
1242
1466
  this.cfg = {};
1243
1467
  this.useMic = false;
1244
1468
  this.forceTURN = false;
1245
- this.preferSFU = false;
1246
- this.autoPlayAudio = true;
1247
- this.startVideoMuted = true;
1248
1469
  this.forceMonoAudio = false;
1249
- this.forceMaxBundle = false;
1470
+ /**
1471
+ * Never called for now
1472
+ */
1473
+ this.destroy$ = new Subject();
1474
+ this.listenSignaling();
1475
+ }
1476
+ listenSignaling() {
1477
+ const iceCandidateBuffer = [];
1478
+ const processBuffer = () => {
1479
+ iceCandidateBuffer.forEach((cnd) => {
1480
+ this.handleCandidateFromServer(cnd);
1481
+ });
1482
+ iceCandidateBuffer.length = 0;
1483
+ };
1484
+ this.signallingSrv.onConfig$
1485
+ .pipe(takeUntil(this.destroy$))
1486
+ .subscribe((config) => {
1487
+ this.videoService.create();
1488
+ this.setConfig(config);
1489
+ });
1490
+ this.signallingSrv.onWebRtcAnswer$
1491
+ .pipe(filter(Truthy), takeUntil(this.destroy$))
1492
+ .subscribe((answer) => {
1493
+ this.receiveAnswer(answer);
1494
+ processBuffer();
1495
+ });
1496
+ this.signallingSrv.onOffer$
1497
+ .pipe(filter(Truthy), takeUntil(this.destroy$))
1498
+ .subscribe(() => {
1499
+ void this.createWebRtcOffer();
1500
+ });
1501
+ this.signallingSrv.onWebRtcIce$
1502
+ .pipe(withLatestFrom(this.signallingSrv.onWebRtcAnswer$), takeUntil(this.destroy$))
1503
+ .subscribe(([candidate, answerEstablished]) => {
1504
+ TelemetryStop('iceCandidate', {
1505
+ multi: true,
1506
+ });
1507
+ iceCandidateBuffer.push(candidate);
1508
+ if (answerEstablished) {
1509
+ processBuffer();
1510
+ }
1511
+ });
1250
1512
  }
1251
1513
  async createWebRtcOffer() {
1252
1514
  Logger.info('Creating offer');
1253
1515
  let offer;
1254
1516
  try {
1255
1517
  clearTimeout(this.webRtcErrorTimeout);
1256
- offer = await this.createOffer(this.cfg);
1518
+ offer = await this.createPeerConnection(this.cfg);
1257
1519
  this.webRtcErrorTimeout = setTimeout(() => {
1258
1520
  if (this.pcClient?.connectionState?.match(/connecting|failed/gi)) {
1259
1521
  this.dispatchWebRtcError('WebRTCError: Timeout');
@@ -1337,55 +1599,20 @@ class WebRtcPlayerService extends SubService {
1337
1599
  //**********************
1338
1600
  //Config setup
1339
1601
  //**********************
1340
- this.cfg =
1341
- typeof parOptions.peerConnectionOptions !== 'undefined'
1342
- ? parOptions.peerConnectionOptions
1343
- : {};
1344
- //this.cfg.sdpSemantics = 'unified-plan';
1345
- // If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
1346
- // However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
1347
- // tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
1348
- //this.cfg.offerExtmapAllowMixed = false;
1602
+ this.cfg = { ...parOptions.peerConnectionOptions };
1349
1603
  this.forceTURN = urlParams.has('ForceTURN');
1350
1604
  if (this.forceTURN) {
1351
1605
  Logger.info('Forcing TURN usage by setting ICE Transport Policy in peer connection config.');
1352
1606
  this.cfg.iceTransportPolicy = 'relay';
1353
1607
  }
1354
- this.cfg.bundlePolicy = 'balanced';
1355
- this.forceMaxBundle = urlParams.has('ForceMaxBundle');
1356
- if (this.forceMaxBundle) {
1357
- this.cfg.bundlePolicy = 'max-bundle';
1358
- }
1608
+ this.cfg.bundlePolicy = urlParams.has('ForceMaxBundle')
1609
+ ? 'max-bundle'
1610
+ : 'balanced';
1359
1611
  //**********************
1360
1612
  //Variables
1361
1613
  //**********************
1362
1614
  this.pcClient = null;
1363
1615
  this.dcClient = null;
1364
- this.sdpConstraints = {
1365
- offerToReceiveAudio: false, //Note: if you don't need audio you can get improved latency by turning this off.
1366
- offerToReceiveVideo: true,
1367
- };
1368
- // See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
1369
- this.dataChannelOptions = { ordered: true };
1370
- // This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
1371
- this.startVideoMuted =
1372
- typeof parOptions?.['startVideoMuted'] !== 'undefined'
1373
- ? !!parOptions?.['startVideoMuted']
1374
- : false;
1375
- this.autoPlayAudio =
1376
- typeof parOptions?.['autoPlayAudio'] !== 'undefined'
1377
- ? !!parOptions?.['autoPlayAudio']
1378
- : true;
1379
- // To force mono playback of WebRTC audio
1380
- this.forceMonoAudio = urlParams.has('ForceMonoAudio');
1381
- if (this.forceMonoAudio) {
1382
- Logger.info('Will attempt to force mono audio by munging the sdp in the browser.');
1383
- }
1384
- // To enable mic in browser use SSL/localhost and have ?useMic in the query string.
1385
- this.useMic = urlParams.has('useMic');
1386
- if (!this.useMic) {
1387
- Logger.info('Microphone access is not enabled. Pass ?useMic in the url to enable it.');
1388
- }
1389
1616
  // When ?useMic check for SSL or localhost
1390
1617
  const isLocalhostConnection = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
1391
1618
  const isHttpsConnection = location.protocol === 'https:';
@@ -1394,11 +1621,6 @@ class WebRtcPlayerService extends SubService {
1394
1621
  Logger.error('Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.');
1395
1622
  Logger.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
1396
1623
  }
1397
- // Prefer SFU or P2P connection
1398
- this.preferSFU = urlParams.has('preferSFU');
1399
- Logger.info(this.preferSFU
1400
- ? 'The browser will signal it would prefer an SFU connection. Remove ?preferSFU from the url to signal for P2P usage.'
1401
- : 'The browser will signal for a P2P connection. Pass ?preferSFU in the url to signal for SFU usage.');
1402
1624
  }
1403
1625
  dispatchWebRtcError(message) {
1404
1626
  Logger.error(message);
@@ -1407,409 +1629,188 @@ class WebRtcPlayerService extends SubService {
1407
1629
  message,
1408
1630
  }));
1409
1631
  }
1410
- async createOffer(config) {
1632
+ async createPeerConnection(config) {
1411
1633
  this.closePC();
1412
1634
  this.pcClient = new RTCPeerConnection(config);
1413
1635
  TelemetryStart('Browser ICE gathering');
1414
1636
  TelemetryStart('Browser ICE connection');
1415
1637
  TelemetryStart('Signaling state change');
1416
- fromEvent(this.pcClient, 'signalingstatechange')
1417
- .pipe(map(() => this.pcClient?.signalingState), takeUntil(this.disconnect$))
1638
+ this.handlePeerConnectionEvents(this.pcClient);
1639
+ await this.setupTransceiversAsync(this.pcClient);
1640
+ this.dcClient = this.createDataChannel(this.pcClient, 'cirrus', {
1641
+ // See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
1642
+ ordered: true,
1643
+ });
1644
+ this.disconnect$.pipe(first()).subscribe(() => {
1645
+ this.closePC();
1646
+ });
1647
+ await this.createOffer(this.pcClient);
1648
+ }
1649
+ handlePeerConnectionEvents(pcClient) {
1650
+ fromEvent(pcClient, 'signalingstatechange')
1651
+ .pipe(map$1(() => this.pcClient?.signalingState), takeUntil(this.disconnect$))
1418
1652
  .subscribe((state) => {
1419
1653
  Logger.info('[Signaling state change] |', state, '|');
1420
1654
  TelemetryStop('Signaling state change', { state, multi: true });
1421
1655
  });
1422
- fromEvent(this.pcClient, 'iceconnectionstatechange')
1423
- .pipe(map(() => this.pcClient?.iceConnectionState), takeUntil(this.disconnect$))
1656
+ fromEvent(pcClient, 'iceconnectionstatechange')
1657
+ .pipe(map$1(() => this.pcClient?.iceConnectionState), takeUntil(this.disconnect$))
1424
1658
  .subscribe((state) => {
1425
1659
  Logger.info('[Browser ICE connection] |', state, '|');
1426
1660
  TelemetryStop('Browser ICE connection', { state, multi: true });
1427
1661
  });
1428
- fromEvent(this.pcClient, 'icegatheringstatechange')
1429
- .pipe(map(() => this.pcClient?.iceGatheringState), takeUntil(this.disconnect$))
1662
+ fromEvent(pcClient, 'icegatheringstatechange')
1663
+ .pipe(map$1(() => this.pcClient?.iceGatheringState), takeUntil(this.disconnect$))
1430
1664
  .subscribe((state) => {
1431
1665
  Logger.info('[Browser ICE gathering] |', state, '|');
1432
1666
  TelemetryStop('Browser ICE gathering', { state, multi: true });
1433
1667
  });
1434
- fromEvent(this.pcClient, 'connectionstatechange')
1435
- .pipe(map(() => this.pcClient?.connectionState), takeUntil(this.disconnect$))
1668
+ fromEvent(pcClient, 'connectionstatechange')
1669
+ .pipe(map$1(() => this.pcClient?.connectionState), takeUntil(this.disconnect$))
1436
1670
  .subscribe((state) => {
1437
1671
  Logger.info('[RTCPeerConnection state] |', state, '|');
1438
1672
  });
1439
- fromEvent(this.pcClient, 'icecandidate')
1440
- .pipe(map((e) => e.candidate), filter(Truthy), tap$1((candidate) => Logger.info('[Browser ICE candidate]', '| Type=', candidate.type, '| Protocol=', candidate.protocol, '| Address=', candidate.address, '| Port=', candidate.port, '|')), takeUntil(this.disconnect$))
1441
- .subscribe((data) => this.onWebRtcCandidate$.next(data));
1442
- fromEvent(this.pcClient, 'datachannel')
1443
- .pipe(tap$1(() => Logger.info('Data channel created for us by browser as we are a receiving peer.')), map((e) => e.channel), takeUntil(this.disconnect$))
1673
+ fromEvent(pcClient, 'icecandidate')
1674
+ .pipe(map$1((e) => e.candidate), filter(Truthy), tap$1((candidate) => Logger.info('[Browser ICE candidate]', '| Type=', candidate.type, '| Protocol=', candidate.protocol, '| Address=', candidate.address, '| Port=', candidate.port, '|')), takeUntil(this.disconnect$))
1675
+ .subscribe((candidate) => this.signallingSrv.send({ type: 'iceCandidate', candidate }));
1676
+ fromEvent(pcClient, 'datachannel')
1677
+ .pipe(tap$1(() => Logger.info('Data channel created for us by browser as we are a receiving peer.')), map$1((e) => e.channel), takeUntil(this.disconnect$))
1444
1678
  .subscribe((datachannel) => {
1445
1679
  this.dcClient = this.setupDataChannelCallbacks(datachannel);
1446
1680
  });
1447
- const onTrack$ = fromEvent(this.pcClient, 'track').pipe(filter((e) => !!e.track), tap$1((e) => Logger.info('Got track - ' +
1681
+ const onTrack$ = fromEvent(pcClient, 'track').pipe(filter((e) => !!e.track), tap$1((e) => Logger.info('Got track - ' +
1448
1682
  e.track.kind +
1449
1683
  ' id=' +
1450
1684
  e.track.id +
1451
1685
  ' readyState=' +
1452
- e.track.readyState)), share());
1453
- onTrack$
1454
- .pipe(filter((e) => e.track.kind === 'audio'), map((e) => e.streams[0]), takeUntil(this.disconnect$))
1455
- .subscribe((data) => this.onAudioTrack$.next(data));
1456
- onTrack$
1457
- .pipe(filter((e) => e.track.kind === 'video'), map((e) => e.streams[0]), tap$1(() => Logger.info('Set video source from video track ontrack.')), takeUntil(this.disconnect$))
1458
- .subscribe((data) => this.onVideoTrack$.next(data));
1459
- await this.setupTransceiversAsync(this.pcClient);
1460
- this.dcClient = this.createDataChannel(this.pcClient, 'cirrus', this.dataChannelOptions);
1461
- this.disconnect$.pipe(first()).subscribe(() => {
1462
- this.closePC();
1463
- });
1464
- await this.handleCreateOffer();
1465
- }
1466
- async setupTransceiversAsync(pc) {
1467
- const hasTransceivers = pc.getTransceivers().length > 0;
1468
- // Setup a transceiver for getting UE video
1469
- pc.addTransceiver('video', { direction: 'recvonly' });
1470
- // Setup a transceiver for sending mic audio to UE and receiving audio from UE
1471
- if (!this.useMic) {
1472
- pc.addTransceiver('audio', { direction: 'recvonly' });
1473
- }
1474
- else {
1475
- const audioSendOptions = this.useMic
1476
- ? {
1477
- autoGainControl: false,
1478
- channelCount: 1,
1479
- echoCancellation: false,
1480
- latency: 0,
1481
- noiseSuppression: false,
1482
- sampleRate: 48000,
1483
- sampleSize: 16,
1484
- volume: 1.0,
1485
- }
1486
- : false;
1487
- // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
1488
- const stream = await navigator.mediaDevices.getUserMedia({
1489
- video: false,
1490
- audio: audioSendOptions,
1491
- });
1492
- if (stream) {
1493
- if (hasTransceivers) {
1494
- for (const transceiver of pc.getTransceivers()) {
1495
- if (transceiver &&
1496
- transceiver.receiver &&
1497
- transceiver.receiver.track &&
1498
- transceiver.receiver.track.kind === 'audio') {
1499
- for (const track of stream.getTracks()) {
1500
- if (track.kind && track.kind == 'audio') {
1501
- transceiver.sender.replaceTrack(track);
1502
- transceiver.direction = 'sendrecv';
1503
- }
1504
- }
1505
- }
1506
- }
1507
- }
1508
- else {
1509
- for (const track of stream.getTracks()) {
1510
- if (track.kind && track.kind == 'audio') {
1511
- pc.addTransceiver(track, { direction: 'sendrecv' });
1512
- }
1513
- }
1514
- }
1515
- }
1516
- else {
1517
- pc.addTransceiver('audio', { direction: 'recvonly' });
1518
- }
1519
- }
1520
- }
1521
- setupDataChannelCallbacks(datachannel) {
1522
- try {
1523
- TelemetryStart('DataChannel Connection');
1524
- // Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
1525
- datachannel.binaryType = 'arraybuffer';
1526
- fromEvent(datachannel, 'close')
1527
- .pipe(first())
1528
- .subscribe((e) => {
1529
- Logger.warn(`[DATACHANNEL] Data channel disconnected: ${datachannel.label}(${datachannel.id})`, e);
1530
- this.store.dispatch(destroyRemoteConnections({
1531
- disconnectReason: DisconnectReason.dataChannelClosed,
1532
- }));
1533
- });
1534
- fromEvent(datachannel, 'error')
1535
- .pipe(takeUntil(this.disconnect$))
1536
- .subscribe((e) => {
1537
- const message = `[DATACHANNEL] Data channel error: ${datachannel.label}(${datachannel.id})`;
1538
- Logger.error(message, e);
1539
- });
1540
- fromEvent(datachannel, 'open')
1541
- .pipe(tap$1(() => {
1542
- Logger.info(`[DATACHANNEL] Data channel connected: ${datachannel.label}(${datachannel.id})`);
1543
- }), takeUntil(this.disconnect$))
1544
- .subscribe(() => {
1545
- TelemetryStop('DataChannel Connection');
1546
- this.onDataChannelConnected();
1547
- });
1548
- fromEvent(datachannel, 'message')
1549
- .pipe(map((e) => e.data), takeUntil(this.disconnect$))
1550
- .subscribe((data) => this.onDataChannelMessage$.next(data));
1551
- return datachannel;
1552
- }
1553
- catch (e) {
1554
- Logger.warn('[DATACHANNEL] Datachannel setup caused an exception: ', e);
1555
- return null;
1556
- }
1557
- }
1558
- onDataChannelConnected() {
1559
- let text = 'Session connected, video stream starting.';
1560
- if (navigator.userAgent.includes('Firefox')) {
1561
- text +=
1562
- '\n If you are experiencing connection problems please try Google Chrome';
1563
- }
1564
- this.store.dispatch(setStatusMessage({ message: text }));
1565
- this.store.dispatch(setDataChannelConnected({ value: true }));
1566
- }
1567
- async handleCreateOffer() {
1568
- try {
1569
- const offer = await this.pcClient.createOffer(this.sdpConstraints);
1570
- this.mungeSDP(offer);
1571
- this.pcClient?.setLocalDescription(offer);
1572
- this.onWebRtcOffer$.next(offer);
1573
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1574
- }
1575
- catch (error) {
1576
- Logger.error("Couldn't create offer");
1577
- }
1578
- }
1579
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: WebRtcPlayerService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1580
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: WebRtcPlayerService }); }
1581
- }
1582
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: WebRtcPlayerService, decorators: [{
1583
- type: Injectable
1584
- }] });
1585
-
1586
- class DataFlowMonitor {
1587
- /**
1588
- * Initializes the DataFlowMonitor monitor.
1589
- * @param yellowFlagThresholdPercentage - The percentage drop to trigger a YELLOW warning (default: 15%).
1590
- * @param redFlagThresholdPercentage - The percentage drop to trigger a RED warning (default: 30%).
1591
- * @param historyBufferLength - buffer length (default: 100).
1592
- * @param splitPoint - The point at which to split the history buffer into two halves (default: 0.5).
1593
- */
1594
- constructor(yellowFlagThresholdPercentage = 15, redFlagThresholdPercentage = 30, historyBufferLength = 100, splitPoint = 0.5) {
1595
- this.yellowFlagThresholdPercentage = yellowFlagThresholdPercentage;
1596
- this.redFlagThresholdPercentage = redFlagThresholdPercentage;
1597
- this.historyBufferLength = historyBufferLength;
1598
- this.splitPoint = splitPoint;
1599
- this.dataHistory = [];
1600
- }
1601
- reset() {
1602
- this.dataHistory.length = 0;
1603
- }
1604
- config(data) {
1605
- this.reset();
1606
- this.yellowFlagThresholdPercentage = data.yellowFlag;
1607
- this.redFlagThresholdPercentage = data.redFlag;
1608
- }
1609
- /**
1610
- * Adds a new bitrate measurement and checks for significant drops.
1611
- * @param currentValue - The current bitrate in kbps.
1612
- * @returns BitrateCheckResult indicating if a drop was detected.
1613
- */
1614
- addValue(currentValue) {
1615
- if (isNaN(currentValue) || currentValue < 1) {
1616
- return {};
1617
- }
1618
- const historyLength = this.dataHistory.length;
1619
- if (historyLength > this.historyBufferLength) {
1620
- this.dataHistory.shift();
1621
- }
1622
- this.dataHistory.push(currentValue);
1623
- if (historyLength < this.historyBufferLength) {
1624
- this.dataHistory.length = this.historyBufferLength;
1625
- this.dataHistory.fill(currentValue, 0, this.historyBufferLength);
1626
- }
1627
- const splitIndex = Math.floor(historyLength * this.splitPoint);
1628
- const firstHalf = this.dataHistory.slice(0, splitIndex);
1629
- const secondHalf = this.dataHistory.slice(splitIndex);
1630
- const firstHalfMedian = calculateMedian(firstHalf);
1631
- const secondHalfMedian = calculateMedian(secondHalf);
1632
- const dropPercentage = clampf(0, 100, ((firstHalfMedian - secondHalfMedian) / firstHalfMedian) * 100);
1633
- const isRedFlag = dropPercentage >= this.redFlagThresholdPercentage;
1634
- if (dropPercentage >= this.yellowFlagThresholdPercentage) {
1635
- return {
1636
- config: {
1637
- yellowFlagThresholdPercentage: this.yellowFlagThresholdPercentage,
1638
- redFlagThresholdPercentage: this.redFlagThresholdPercentage,
1639
- historyBufferLength: this.historyBufferLength,
1640
- splitPoint: this.splitPoint,
1641
- },
1642
- isDropDetected: true,
1643
- dropPercentage,
1644
- dataHistory: [...this.dataHistory],
1645
- activeMedian: secondHalfMedian,
1646
- quality: isRedFlag ? 'red' : 'orange',
1647
- message: `Significant flow drop detected: ${dropPercentage.toFixed(2)}% (from ${firstHalfMedian} to ${secondHalfMedian})`,
1648
- };
1649
- }
1650
- return {
1651
- config: {
1652
- yellowFlagThresholdPercentage: this.yellowFlagThresholdPercentage,
1653
- redFlagThresholdPercentage: this.redFlagThresholdPercentage,
1654
- historyBufferLength: this.historyBufferLength,
1655
- splitPoint: this.splitPoint,
1656
- },
1657
- isDropDetected: false,
1658
- dropPercentage,
1659
- dataHistory: [...this.dataHistory],
1660
- activeMedian: secondHalfMedian,
1661
- quality: 'lime',
1662
- message: 'Stable flow',
1663
- };
1664
- }
1665
- }
1666
-
1667
- /**
1668
- * Default LBM Filter Parameters
1669
- */
1670
- const DefaultFilterModel = {
1671
- minimumBitrate: 1600,
1672
- yellowFlag: 30,
1673
- redFlag: 50,
1674
- minimumFps: 5,
1675
- monitoringDelayTime: 5000,
1676
- initialBitrateEstimate: 1600,
1677
- initialErrorCovariance: 1,
1678
- processNoise: 3,
1679
- measurementNoise: 500,
1680
- panelOpen: false,
1681
- };
1682
- /**
1683
- * Global LBM Filter Parameters
1684
- */
1685
- const FilterModel = { ...DefaultFilterModel };
1686
- /**
1687
- * Bitrate Monitor Static Class
1688
- */
1689
- const BITRATE_MONITOR = new DataFlowMonitor(FilterModel.yellowFlag, FilterModel.redFlag, 20);
1690
-
1691
- class VideoStreamStatusService extends SubService {
1692
- constructor() {
1693
- super();
1694
- this.latencyTestTimings = new LatencyTimings();
1695
- this.webrtcPlayer = inject(WebRtcPlayerService);
1696
- this.aggregatedStats = {};
1697
- this.VideoEncoderQP = 0;
1698
- this.kalmanFilter1D = new KalmanFilter1D(FilterModel.initialBitrateEstimate, FilterModel.initialErrorCovariance, FilterModel.processNoise, FilterModel.measurementNoise);
1699
- this.videoStats$ = this.aggregateStats();
1700
- Signal.on('setKalmanParams').subscribe((data) => {
1701
- this.kalmanFilter1D.config(data);
1702
- BITRATE_MONITOR.config(data);
1703
- });
1704
- }
1705
- /**
1706
- * Aggregate video stats and emit it as videoStats$
1707
- */
1708
- aggregateStats() {
1709
- return this.webrtcPlayer.onVideoTrack$.pipe(
1710
- // IMPORTANT! DO NOT CHANGE THOSE NUMBERS, LBM Stats are based on those values
1711
- switchMap$1(() => interval(250)), switchMap$1(() => from(this.getStats(this.webrtcPlayer.pcClient))), filter(Truthy), share());
1712
- }
1713
- setEncoder(data) {
1714
- this.VideoEncoderQP = Number(new TextDecoder('utf-16').decode(data));
1715
- }
1716
- async getStats(pcClient) {
1717
- if (!pcClient) {
1718
- return null;
1719
- }
1720
- const stats = await pcClient.getStats(null);
1721
- return this.generateAggregatedStatsFunction(stats);
1686
+ e.track.readyState)), share());
1687
+ onTrack$
1688
+ .pipe(filter((e) => e.track.kind === 'audio'), map$1((e) => e.streams[0]), takeUntil(this.disconnect$))
1689
+ .subscribe((stream) => this.videoService.attachAudioStream(stream));
1690
+ const videoTrack$ = onTrack$.pipe(filter((e) => e.track.kind === 'video'), map$1((e) => e.streams[0]), tap$1(() => Logger.info('Set video source from video track ontrack.')), takeUntil(this.disconnect$), share());
1691
+ videoTrack$.subscribe((stream) => {
1692
+ this.videoService.attachVideoStream(stream, this.pcClient);
1693
+ });
1722
1694
  }
1723
- generateAggregatedStatsFunction(stats) {
1724
- const newStat = {};
1725
- // store each type of codec we can get stats on
1726
- newStat.codecs = {};
1727
- newStat.currentRoundTripTime = -1;
1728
- stats.forEach((stat) => {
1729
- // Get the inbound-rtp for video
1730
- if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
1731
- Object.assign(newStat, stat);
1732
- newStat.bytesReceivedStart =
1733
- this.aggregatedStats && this.aggregatedStats.bytesReceivedStart
1734
- ? this.aggregatedStats.bytesReceivedStart
1735
- : stat.bytesReceived;
1736
- newStat.framesDecodedStart =
1737
- this.aggregatedStats && this.aggregatedStats.framesDecodedStart
1738
- ? this.aggregatedStats.framesDecodedStart
1739
- : stat.framesDecoded;
1740
- newStat.timestampStart =
1741
- this.aggregatedStats && this.aggregatedStats.timestampStart
1742
- ? this.aggregatedStats.timestampStart
1743
- : stat.timestamp;
1744
- if (this.aggregatedStats && this.aggregatedStats.timestamp) {
1745
- // Get the mimetype of the video codec being used
1746
- if (stat.codecId &&
1747
- this.aggregatedStats.codecs &&
1748
- Object.hasOwn(this.aggregatedStats.codecs, stat.codecId)) {
1749
- newStat.videoCodec = this.aggregatedStats.codecs[stat.codecId];
1750
- }
1751
- if (this.aggregatedStats.bytesReceived) {
1752
- // bitrate = bits received since last time / number of ms since last time
1753
- // This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
1754
- newStat.bitrate = Math.floor((8 *
1755
- (newStat.bytesReceived - this.aggregatedStats.bytesReceived)) /
1756
- (newStat.timestamp - this.aggregatedStats.timestamp));
1757
- }
1758
- if (this.aggregatedStats.bytesReceivedStart) {
1759
- newStat.avgBitrate = Math.floor((8 *
1760
- (newStat?.bytesReceived -
1761
- this.aggregatedStats.bytesReceivedStart)) /
1762
- (newStat.timestamp - this.aggregatedStats.timestampStart));
1695
+ async setupTransceiversAsync(pc) {
1696
+ const hasTransceivers = pc.getTransceivers().length > 0;
1697
+ // Setup a transceiver for getting UE video
1698
+ pc.addTransceiver('video', { direction: 'recvonly' });
1699
+ // Setup a transceiver for sending mic audio to UE and receiving audio from UE
1700
+ if (!this.useMic) {
1701
+ pc.addTransceiver('audio', { direction: 'recvonly' });
1702
+ }
1703
+ else {
1704
+ const audioSendOptions = this.useMic
1705
+ ? {
1706
+ autoGainControl: false,
1707
+ channelCount: 1,
1708
+ echoCancellation: false,
1709
+ latency: 0,
1710
+ noiseSuppression: false,
1711
+ sampleRate: 48000,
1712
+ sampleSize: 16,
1713
+ volume: 1.0,
1714
+ }
1715
+ : false;
1716
+ // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
1717
+ const stream = await navigator.mediaDevices.getUserMedia({
1718
+ video: false,
1719
+ audio: audioSendOptions,
1720
+ });
1721
+ if (stream) {
1722
+ if (hasTransceivers) {
1723
+ for (const transceiver of pc.getTransceivers()) {
1724
+ if (transceiver &&
1725
+ transceiver.receiver &&
1726
+ transceiver.receiver.track &&
1727
+ transceiver.receiver.track.kind === 'audio') {
1728
+ for (const track of stream.getTracks()) {
1729
+ if (track.kind && track.kind == 'audio') {
1730
+ transceiver.sender.replaceTrack(track);
1731
+ transceiver.direction = 'sendrecv';
1732
+ }
1733
+ }
1734
+ }
1763
1735
  }
1764
- if (this.aggregatedStats.framesDecodedStart) {
1765
- newStat.avgFrameRate = Math.floor((newStat.framesDecoded -
1766
- this.aggregatedStats.framesDecodedStart) /
1767
- ((newStat.timestamp - this.aggregatedStats.timestampStart) /
1768
- 1000));
1736
+ }
1737
+ else {
1738
+ for (const track of stream.getTracks()) {
1739
+ if (track.kind && track.kind == 'audio') {
1740
+ pc.addTransceiver(track, { direction: 'sendrecv' });
1741
+ }
1769
1742
  }
1770
1743
  }
1771
1744
  }
1772
- if (stat.type === 'candidate-pair' &&
1773
- Object.hasOwn(stat, 'currentRoundTripTime')) {
1774
- newStat.currentRoundTripTime = stat.currentRoundTripTime ?? 0;
1775
- }
1776
- // Store mimetype of each codec
1777
- if (Object.hasOwn(newStat, 'codecs') &&
1778
- stat.type === 'codec' &&
1779
- stat.mimeType &&
1780
- stat.id) {
1781
- const codecId = stat.id;
1782
- newStat.codecs[codecId] = stat.mimeType
1783
- .replace('video/', '')
1784
- .replace('audio/', '');
1745
+ else {
1746
+ pc.addTransceiver('audio', { direction: 'recvonly' });
1785
1747
  }
1786
- });
1787
- newStat.pixelRatio = +window.devicePixelRatio.toFixed(2);
1788
- if (this.aggregatedStats.receiveToCompositeMs) {
1789
- newStat.receiveToCompositeMs = this.aggregatedStats.receiveToCompositeMs;
1790
- this.latencyTestTimings.SetFrameDisplayDeltaTime(this.aggregatedStats.receiveToCompositeMs);
1791
1748
  }
1792
- // Calculate duration of run
1793
- newStat.runTime = Math.floor((newStat.timestamp - newStat.timestampStart) / 1000);
1794
- newStat.kalmanBitrate = Math.floor(this.kalmanFilter1D.update(newStat.bitrate || FilterModel.initialBitrateEstimate));
1795
- newStat.dataFlowCheckResult = BITRATE_MONITOR.addValue(newStat.kalmanBitrate);
1796
- newStat.bitrateDrop = Math.floor(newStat.dataFlowCheckResult.dropPercentage);
1797
- this.aggregatedStats = newStat;
1798
- return this.onAggregatedStats(newStat);
1799
1749
  }
1800
- onAggregatedStats(aggregatedStats) {
1801
- return {
1802
- quality: mapQpToQuality(this.VideoEncoderQP),
1803
- aggregatedStats: {
1804
- ...aggregatedStats,
1805
- VideoEncoderQP: this.VideoEncoderQP,
1806
- },
1807
- };
1750
+ setupDataChannelCallbacks(datachannel) {
1751
+ try {
1752
+ TelemetryStart('DataChannel Connection');
1753
+ // Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
1754
+ datachannel.binaryType = 'arraybuffer';
1755
+ fromEvent(datachannel, 'close')
1756
+ .pipe(first())
1757
+ .subscribe((e) => {
1758
+ Logger.warn(`[DATACHANNEL] Data channel disconnected: ${datachannel.label}(${datachannel.id})`, e);
1759
+ this.store.dispatch(destroyRemoteConnections({
1760
+ disconnectReason: DisconnectReason.dataChannelClosed,
1761
+ }));
1762
+ });
1763
+ fromEvent(datachannel, 'error')
1764
+ .pipe(takeUntil(this.disconnect$))
1765
+ .subscribe((e) => {
1766
+ const message = `[DATACHANNEL] Data channel error: ${datachannel.label}(${datachannel.id})`;
1767
+ Logger.error(message, e);
1768
+ });
1769
+ fromEvent(datachannel, 'open')
1770
+ .pipe(tap$1(() => {
1771
+ Logger.info(`[DATACHANNEL] Data channel connected: ${datachannel.label}(${datachannel.id})`);
1772
+ }), takeUntil(this.disconnect$))
1773
+ .subscribe(() => {
1774
+ TelemetryStop('DataChannel Connection');
1775
+ this.onDataChannelConnected();
1776
+ });
1777
+ fromEvent(datachannel, 'message')
1778
+ .pipe(map$1((e) => e.data), takeUntil(this.disconnect$))
1779
+ .subscribe((data) => this.onDataChannelMessage$.next(data));
1780
+ return datachannel;
1781
+ }
1782
+ catch (e) {
1783
+ Logger.warn('[DATACHANNEL] Datachannel setup caused an exception: ', e);
1784
+ return null;
1785
+ }
1786
+ }
1787
+ onDataChannelConnected() {
1788
+ let text = 'Session connected, video stream starting.';
1789
+ if (navigator.userAgent.includes('Firefox')) {
1790
+ text +=
1791
+ '\n If you are experiencing connection problems please try Google Chrome';
1792
+ }
1793
+ this.store.dispatch(setStatusMessage({ message: text }));
1794
+ this.store.dispatch(setDataChannelConnected({ value: true }));
1795
+ }
1796
+ async createOffer(pcClient) {
1797
+ try {
1798
+ const offer = await pcClient.createOffer({
1799
+ offerToReceiveAudio: false, //Note: if you don't need audio you can get improved latency by turning this off.
1800
+ offerToReceiveVideo: true,
1801
+ });
1802
+ this.mungeSDP(offer);
1803
+ pcClient?.setLocalDescription(offer);
1804
+ this.signallingSrv.send(offer);
1805
+ }
1806
+ catch {
1807
+ Logger.error("Couldn't create offer");
1808
+ }
1808
1809
  }
1809
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoStreamStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1810
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoStreamStatusService }); }
1810
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1811
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: WebRtcPlayerService }); }
1811
1812
  }
1812
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoStreamStatusService, decorators: [{
1813
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: WebRtcPlayerService, decorators: [{
1813
1814
  type: Injectable
1814
1815
  }], ctorParameters: () => [] });
1815
1816
 
@@ -1852,24 +1853,25 @@ const deepEqual = (a, b) => {
1852
1853
  return false;
1853
1854
  };
1854
1855
 
1856
+ const filteredLogs = [
1857
+ {
1858
+ command: 'onChangeSequence',
1859
+ },
1860
+ ];
1855
1861
  class AggregatorService extends SubService {
1856
- #unrealInitialConfig;
1857
1862
  constructor() {
1858
1863
  super();
1859
1864
  this.selectStreamConfig = toSignal(this.store.select(selectStreamConfig));
1860
1865
  this.videoService = inject(VideoService);
1861
1866
  this.webrtcPlayer = inject(WebRtcPlayerService);
1862
1867
  this.freezeFrame = inject(FreezeFrameService);
1863
- this.signallingSrv = inject(SignallingService);
1864
- this.stats = inject(VideoStreamStatusService);
1865
- this.#unrealInitialConfig = inject(UNREAL_CONFIG);
1868
+ this.unrealInitialConfig = inject(UNREAL_CONFIG);
1869
+ this.responseEventListeners = new Map();
1866
1870
  /**
1867
- * /Never called for now
1868
- * @
1871
+ * Never called for now
1869
1872
  */
1870
1873
  this.destroy$ = new Subject();
1871
1874
  this.listenWebRTC();
1872
- this.listenSignaling();
1873
1875
  this.initialize();
1874
1876
  }
1875
1877
  init() {
@@ -1885,13 +1887,10 @@ class AggregatorService extends SubService {
1885
1887
  this.startListenCallbacks();
1886
1888
  }
1887
1889
  });
1888
- this.unlockViewport$ = this.store
1890
+ const unlockViewport$ = this.store
1889
1891
  .select(unrealFeature.selectViewportReady)
1890
1892
  .pipe(filter(Truthy));
1891
- this.unlockViewport$.subscribe(() => {
1892
- this.removeLoadScreen();
1893
- });
1894
- this.disconnect$.subscribe(() => {
1893
+ merge(unlockViewport$, this.disconnect$).subscribe(() => {
1895
1894
  this.removeLoadScreen();
1896
1895
  });
1897
1896
  if (!this.selectStreamConfig()?.autoStart ||
@@ -1899,26 +1898,6 @@ class AggregatorService extends SubService {
1899
1898
  this.removeLoadScreen();
1900
1899
  }
1901
1900
  InputOptions.controlScheme = EControlSchemeType.HoveringMouse;
1902
- this.addExternalResponses();
1903
- }
1904
- /**
1905
- * @deprecated
1906
- * Hack for 3DS platform;
1907
- * TODO remove ASAP, use {@link BasicConfiguratorApiService.toMetaBoxHandlers.watchCallbacks}
1908
- * Add external responses to the list of responses
1909
- */
1910
- addExternalResponses() {
1911
- /*Signal.on('ADDRESPONSE')
1912
- .pipe(takeUntil(this.destroy$))
1913
- .subscribe((data: any) => {
1914
- this.addResponseEventListener(data.name!, (event) => {
1915
- this.externalApi.sendMessage({
1916
- action: FromMetaBoxApiAction.UnrealResponse,
1917
- name: data.name,
1918
- responseEvent: event,
1919
- });
1920
- });
1921
- });*/
1922
1901
  }
1923
1902
  addResponseEventListener(name, listener) {
1924
1903
  this.responseEventListeners.set(name, listener);
@@ -1939,24 +1918,9 @@ class AggregatorService extends SubService {
1939
1918
  // hiddenInput.blur();
1940
1919
  }
1941
1920
  }
1942
- exposeApi() {
1943
- // const globals: Window = window;
1944
- // if ('apiReady' in globals) {
1945
- // try {
1946
- // const api = {
1947
- // setStreamData: (streamData: StreamData) => {
1948
- // this.streamConfig.setConfig(streamData.params);
1949
- // },
1950
- // };
1951
- // globals.apiReady(api);
1952
- // } catch (e) {
1953
- // /!* empty *!/
1954
- // }
1955
- // }
1956
- }
1957
1921
  removeLoadScreen() {
1958
1922
  document
1959
- .getElementById(this.#unrealInitialConfig?.screenLockerContainerId ||
1923
+ .getElementById(this.unrealInitialConfig?.screenLockerContainerId ||
1960
1924
  SCREEN_LOCKER_CONTAINER_ID)
1961
1925
  ?.remove();
1962
1926
  }
@@ -1997,66 +1961,12 @@ class AggregatorService extends SubService {
1997
1961
  Logger.colored(...COLOR_CODES.FROM_UNREAL, json);
1998
1962
  }
1999
1963
  resetResponseList() {
2000
- this.responseEventListeners = new Map();
2001
- }
2002
- listenSignaling() {
2003
- const iceCandidateBuffer = [];
2004
- const processBuffer = () => {
2005
- iceCandidateBuffer.forEach((cnd) => {
2006
- this.webrtcPlayer.handleCandidateFromServer(cnd);
2007
- });
2008
- iceCandidateBuffer.length = 0;
2009
- };
2010
- this.signallingSrv.onConfig$
2011
- .pipe(takeUntil(this.destroy$))
2012
- .subscribe((config) => {
2013
- this.videoService.create();
2014
- this.webrtcPlayer.setConfig(config);
2015
- });
2016
- this.signallingSrv.onWebRtcAnswer$
2017
- .pipe(filter(Truthy), takeUntil(this.destroy$))
2018
- .subscribe((answer) => {
2019
- this.webrtcPlayer.receiveAnswer(answer);
2020
- processBuffer();
2021
- });
2022
- this.signallingSrv.onOffer$
2023
- .pipe(filter(Truthy), takeUntil(this.destroy$))
2024
- .subscribe(() => {
2025
- void this.webrtcPlayer.createWebRtcOffer();
2026
- });
2027
- this.signallingSrv.onWebRtcIce$
2028
- .pipe(withLatestFrom(this.signallingSrv.onWebRtcAnswer$), takeUntil(this.destroy$))
2029
- .subscribe(([candidate, answerEstablished]) => {
2030
- TelemetryStop('iceCandidate', {
2031
- multi: true,
2032
- });
2033
- iceCandidateBuffer.push(candidate);
2034
- if (answerEstablished) {
2035
- processBuffer();
2036
- }
2037
- });
1964
+ this.responseEventListeners.clear();
2038
1965
  }
2039
1966
  listenWebRTC() {
2040
- this.webrtcPlayer.onWebRtcOffer$
2041
- .pipe(takeUntil(this.destroy$))
2042
- .subscribe((offer) => this.signallingSrv.send(offer));
2043
- this.webrtcPlayer.onWebRtcCandidate$
2044
- .pipe(takeUntil(this.destroy$))
2045
- .subscribe((candidate) => this.signallingSrv.send(JSON.stringify({
2046
- type: 'iceCandidate',
2047
- candidate: candidate,
2048
- })));
2049
1967
  this.webrtcPlayer.onDataChannelMessage$
2050
1968
  .pipe(takeUntil(this.destroy$))
2051
1969
  .subscribe((data) => this.dataChannelMessageHandler(data));
2052
- this.webrtcPlayer.onAudioTrack$
2053
- .pipe(takeUntil(this.destroy$))
2054
- .subscribe((stream) => this.videoService.attachAudioStream(stream));
2055
- this.webrtcPlayer.onVideoTrack$
2056
- .pipe(takeUntil(this.destroy$))
2057
- .subscribe((stream) => {
2058
- this.videoService.attachVideoStream(stream);
2059
- });
2060
1970
  }
2061
1971
  dataChannelMessageHandler(data) {
2062
1972
  const view = new Uint8Array(data);
@@ -2097,13 +2007,13 @@ class AggregatorService extends SubService {
2097
2007
  this.freezeFrame.invalidate();
2098
2008
  break;
2099
2009
  case EToClientMessageType.VideoEncoderAvgQP:
2100
- this.stats.setEncoder(anyData);
2010
+ this.videoService.setEncoder(anyData);
2101
2011
  break;
2102
2012
  case EToClientMessageType.LatencyTest: {
2103
2013
  const latencyTimings = decodeData(anyData);
2104
2014
  Logger.colored(...COLOR_CODES.FROM_UNREAL, 'Got latency timings :', latencyTimings);
2105
2015
  Logger.colored(...COLOR_CODES.FROM_UNREAL, latencyTimings);
2106
- this.stats.latencyTestTimings.SetUETimings(latencyTimings);
2016
+ this.videoService.setLatencyTimings(latencyTimings);
2107
2017
  break;
2108
2018
  }
2109
2019
  case EToClientMessageType.InitialSettings:
@@ -2125,11 +2035,6 @@ class AggregatorService extends SubService {
2125
2035
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: AggregatorService, decorators: [{
2126
2036
  type: Injectable
2127
2037
  }], ctorParameters: () => [] });
2128
- const filteredLogs = [
2129
- {
2130
- command: 'onChangeSequence',
2131
- },
2132
- ];
2133
2038
 
2134
2039
  class ConsoleExtensionsService extends SubService {
2135
2040
  constructor() {
@@ -2161,7 +2066,7 @@ class ConsoleExtensionsService extends SubService {
2161
2066
  window.restartApp = () => {
2162
2067
  this.store
2163
2068
  .select(unrealFeature.selectAwsInstance)
2164
- .pipe(first(), filter((data) => !!data.instanceName), tapLog('Instance', `Restart initiated`), switchMap((data) => this.httpClient.get(`//${data.instanceName}/restartapp`)), catchError(() => of(null)))
2069
+ .pipe(first(), filter((data) => !!data.instanceName), tapLog('Instance', `Restart initiated`), switchMap$1((data) => this.httpClient.get(`//${data.instanceName}/restartapp`)), catchError(() => of(null)))
2165
2070
  .subscribe();
2166
2071
  };
2167
2072
  window.unrealHelp = () => {
@@ -2514,8 +2419,8 @@ class InputService extends SubService {
2514
2419
  offsetByY: 0,
2515
2420
  offsetByX: 0,
2516
2421
  };
2517
- const visibilityHiddenTrigger$ = fromEvent(document, 'visibilitychange').pipe(map(() => document.visibilityState === 'hidden'), filter(Truthy));
2518
- const visibilityVisibleTrigger$ = fromEvent(document, 'visibilitychange').pipe(map(() => document.visibilityState === 'visible'), filter(Truthy), startWith(true));
2422
+ const visibilityHiddenTrigger$ = fromEvent(document, 'visibilitychange').pipe(map$1(() => document.visibilityState === 'hidden'), filter(Truthy));
2423
+ const visibilityVisibleTrigger$ = fromEvent(document, 'visibilitychange').pipe(map$1(() => document.visibilityState === 'visible'), filter(Truthy), startWith(true));
2519
2424
  this.deactivatedVideoTrigger$ = merge(this.disconnect$, this.reInit$, visibilityHiddenTrigger$);
2520
2425
  fromSignal(UnrealInternalSignalEvents.OnVideoInitialized)
2521
2426
  .pipe(combineLatestWith(visibilityVisibleTrigger$, this.store
@@ -2974,7 +2879,7 @@ class RegionsPingService {
2974
2879
  }
2975
2880
  }
2976
2881
  getFastest(regionListUrl = this.unrealInitialConfig?.regionsPingUrl || '') {
2977
- return this.getProviders(regionListUrl).pipe(switchMap((providers) => this.getPingResult(providers)), catchError(() => of(null)), map((data) => data?.region_code));
2882
+ return this.getProviders(regionListUrl).pipe(switchMap$1((providers) => this.getPingResult(providers)), catchError(() => of(null)), map$1((data) => data?.region_code));
2978
2883
  }
2979
2884
  getPingResult(providers) {
2980
2885
  const regions = providers.regions;
@@ -3060,9 +2965,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImpor
3060
2965
  }], ctorParameters: () => [] });
3061
2966
 
3062
2967
  class StreamStatusTelemetryService {
3063
- #store = inject(Store);
3064
- #videoStatGenerator = inject(VideoStreamStatusService);
3065
2968
  constructor() {
2969
+ this.store = inject(Store);
2970
+ this.videoService = inject(VideoService);
3066
2971
  this.initTelemetry();
3067
2972
  }
3068
2973
  mapEventData(data, signalingServer, isLowBandwidth, lbmStats) {
@@ -3085,12 +2990,12 @@ class StreamStatusTelemetryService {
3085
2990
  };
3086
2991
  }
3087
2992
  initTelemetry() {
3088
- this.#videoStatGenerator.videoStats$
3089
- .pipe(withLatestFrom(this.#store.select(unrealFeature.selectAwsInstance), this.#store.select(unrealFeature.selectLowBandwidth), this.#store.select(unrealFeature.selectLowBandwidthStats)), auditTime(5000), map(([data, signalingServer, isLowBandwidth, lbmStats]) => this.mapEventData(data, signalingServer, isLowBandwidth, lbmStats)))
2993
+ this.videoService.videoStats$
2994
+ .pipe(withLatestFrom(this.store.select(unrealFeature.selectAwsInstance), this.store.select(unrealFeature.selectLowBandwidth), this.store.select(unrealFeature.selectLowBandwidthStats)), auditTime(5000), map$1(([data, signalingServer, isLowBandwidth, lbmStats]) => this.mapEventData(data, signalingServer, isLowBandwidth, lbmStats)))
3090
2995
  .subscribe((data) => this.trackEventToMixPanel(data));
3091
2996
  }
3092
2997
  trackEventToMixPanel(data) {
3093
- this.#store.dispatch(trackMixpanelEvent({ event: 'streamStatus', data }));
2998
+ this.store.dispatch(trackMixpanelEvent({ event: 'streamStatus', data }));
3094
2999
  }
3095
3000
  init() {
3096
3001
  // do nothing, just to not forget to initialize Service
@@ -3130,15 +3035,15 @@ class UnrealEffects {
3130
3035
  this.signallingService = inject(SignallingService);
3131
3036
  this.scrollStrategy = inject(ScrollStrategyOptions).block();
3132
3037
  this.commandsSender = inject(UnrealCommunicatorService);
3133
- this.videoStatGenerator = inject(VideoStreamStatusService);
3038
+ this.videoService = inject(VideoService);
3134
3039
  this.cirrusDisconnectEffect$ = createEffect(() => {
3135
- return this.actions$.pipe(ofType(setCirrusDisconnected), map$1(() => resetUnrealState()));
3040
+ return this.actions$.pipe(ofType(setCirrusDisconnected), map(() => resetUnrealState()));
3136
3041
  });
3137
3042
  this.destroyConnections$ = createEffect(() => {
3138
- return this.actions$.pipe(ofType(destroyConnectionsAndResetState), map$1(() => destroyRemoteConnections({ disconnectReason: DisconnectReason.reset })));
3043
+ return this.actions$.pipe(ofType(destroyConnectionsAndResetState), map(() => destroyRemoteConnections({ disconnectReason: DisconnectReason.reset })));
3139
3044
  });
3140
3045
  this.resetState$ = createEffect(() => {
3141
- return this.actions$.pipe(ofType(destroyConnectionsAndResetState), map$1(() => resetUnrealState()));
3046
+ return this.actions$.pipe(ofType(destroyConnectionsAndResetState), map(() => resetUnrealState()));
3142
3047
  });
3143
3048
  this.destroyRemoteConnections$ = createEffect(() => {
3144
3049
  return this.actions$.pipe(ofType(destroyRemoteConnections), tap(({ disconnectReason }) => {
@@ -3153,16 +3058,16 @@ class UnrealEffects {
3153
3058
  return this.actions$.pipe(ofType(changeStreamResolutionAction), filter((size) => !!(size?.width && size?.height)), concatLatestFrom(() => [
3154
3059
  this.store.select(unrealFeature.selectStreamResolution),
3155
3060
  ]), filter(([newSizes, savedSizes]) => !(newSizes.width === savedSizes.width &&
3156
- newSizes.height === savedSizes.height)), map$1(([sizes]) => changeStreamResolutionSuccessAction({
3061
+ newSizes.height === savedSizes.height)), map(([sizes]) => changeStreamResolutionSuccessAction({
3157
3062
  width: sizes.width,
3158
3063
  height: sizes.height,
3159
3064
  })));
3160
3065
  });
3161
3066
  this.forceLBMOff$ = createEffect(() => {
3162
- return fromEvent(document, 'visibilitychange').pipe(map$1(() => document.visibilityState === 'visible'), filter(Truthy), tapLog('forceLBMOff$'), map$1(() => changeLowBandwidth({ lowBandwidth: false })));
3067
+ return fromEvent(document, 'visibilitychange').pipe(map(() => document.visibilityState === 'visible'), filter(Truthy), tapLog('forceLBMOff$'), map(() => changeLowBandwidth({ lowBandwidth: false })));
3163
3068
  });
3164
3069
  this.resetFreezeFrameOnLowBandwidthTriggered$ = createEffect(() => {
3165
- return this.actions$.pipe(ofType(changeLowBandwidth), map$1((state) => state.lowBandwidth), distinctUntilChanged(), map$1(() => setFreezeFrame({ dataUrl: null, progress: null })));
3070
+ return this.actions$.pipe(ofType(changeLowBandwidth), map((state) => state.lowBandwidth), distinctUntilChanged(), map(() => setFreezeFrame({ dataUrl: null, progress: null })));
3166
3071
  });
3167
3072
  this.sendLbmStat$ = this.store
3168
3073
  .select(unrealFeature.selectViewportReady)
@@ -3184,10 +3089,10 @@ class UnrealEffects {
3184
3089
  .pipe(catchError(() => EMPTY))))))
3185
3090
  .subscribe();
3186
3091
  this.resetAfk$ = createEffect(() => {
3187
- return this.actions$.pipe(ofType(setConfig, resetWarnTimeout, resetConfig), map$1(() => resetAfkAction()));
3092
+ return this.actions$.pipe(ofType(setConfig, resetWarnTimeout, resetConfig), map(() => resetAfkAction()));
3188
3093
  });
3189
3094
  this.resetFreezeFrameOnViewportDestroy$ = createEffect(() => {
3190
- return this.actions$.pipe(ofType(setViewportReady), filter(Falsy), map$1(() => setFreezeFrame({ dataUrl: null, progress: null })));
3095
+ return this.actions$.pipe(ofType(setViewportReady), filter(Falsy), map(() => setFreezeFrame({ dataUrl: null, progress: null })));
3191
3096
  });
3192
3097
  this.setMaxFps$ = createEffect(() => {
3193
3098
  return this.actions$.pipe(ofType(setMaxFps), tap(({ maxFps }) => {
@@ -3210,7 +3115,7 @@ class UnrealEffects {
3210
3115
  this.store.select(unrealFeature.selectDataChannelConnected),
3211
3116
  ]), filter(([, , isCirrusConnected, isDataChannelConnected]) => {
3212
3117
  return isCirrusConnected && !isDataChannelConnected;
3213
- }), switchMap(([awsInstance, ssData]) => {
3118
+ }), switchMap$1(([awsInstance, ssData]) => {
3214
3119
  const error = `DataChannel connection timeout ${DATA_CHANNEL_CONNECTION_TIMEOUT}ms, requesting new signaling.`;
3215
3120
  Logger.error(error);
3216
3121
  return this.http
@@ -3225,25 +3130,25 @@ class UnrealEffects {
3225
3130
  Logger.error('Error sending timeout info:', error);
3226
3131
  return of(null);
3227
3132
  }));
3228
- }), map$1(() => destroyRemoteConnections({
3133
+ }), map(() => destroyRemoteConnections({
3229
3134
  disconnectReason: DisconnectReason.dataChannelTimeout,
3230
3135
  })));
3231
3136
  });
3232
3137
  this.destroyConnectionAndRestart$ = createEffect(() => {
3233
- return this.actions$.pipe(ofType(destroyRemoteConnections), filter(({ disconnectReason }) => disconnectReason === DisconnectReason.dataChannelTimeout), switchMap(() => this.actions$.pipe(ofType(resetUnrealState), first())), map$1(() => initSignalling()));
3138
+ return this.actions$.pipe(ofType(destroyRemoteConnections), filter(({ disconnectReason }) => disconnectReason === DisconnectReason.dataChannelTimeout), switchMap$1(() => this.actions$.pipe(ofType(resetUnrealState), first())), map(() => initSignalling()));
3234
3139
  });
3235
3140
  this.showUnrealError$ = createEffect(() => {
3236
- return this.actions$.pipe(ofType(showUnrealErrorMessage), map$1(({ code }) => getRtcErrorMessage(code)), distinctUntilChanged(), filter(Truthy), switchMap((content) => this.dialog.open(UnrealErrorModalComponent, {
3141
+ return this.actions$.pipe(ofType(showUnrealErrorMessage), map(({ code }) => getRtcErrorMessage(code)), distinctUntilChanged(), filter(Truthy), switchMap$1((content) => this.dialog.open(UnrealErrorModalComponent, {
3237
3142
  scrollStrategy: this.scrollStrategy,
3238
3143
  data: { content },
3239
- }).closed), map$1(() => showUnrealErrorMessage({ code: null })));
3144
+ }).closed), map(() => showUnrealErrorMessage({ code: null })));
3240
3145
  });
3241
3146
  this.sendStatisticsToUnreal$ = createEffect(() => {
3242
3147
  return this.actions$.pipe(ofType(showUnrealErrorMessage), filter(({ code }) => code === 404), concatLatestFrom(() => [
3243
3148
  this.store.select(unrealFeature.selectAwsInstance),
3244
3149
  this.store.select(unrealFeature.selectStreamClientCompanyId),
3245
3150
  this.store.select(unrealFeature.selectStreamViewId),
3246
- ]), switchMap(([action, instance, companyId, viewId]) => this.http
3151
+ ]), switchMap$1(([action, instance, companyId, viewId]) => this.http
3247
3152
  .post(this.unrealInitialConfig?.customErrorsEndpoint || '', {
3248
3153
  client: instance.pollingUrl,
3249
3154
  session_uuid: companyId,
@@ -3266,21 +3171,21 @@ class UnrealEffects {
3266
3171
  }));
3267
3172
  this.commandsSender.sendCommandToUnreal(getDebugModeCommand({ value: this.isDevMode }));
3268
3173
  TelemetryStart('SetViewportReady');
3269
- }), switchMap(() => this.store.select(selectCommandsInProgress).pipe(debounceTime$1(500), first(), switchMap(() => observeCommandResponse(getLoopBackCommand(), (data) => this.commandsSender.sendCommandToUnreal(data))), switchMap(() => this.videoStatGenerator.videoStats$), filter((videoStats) => !!videoStats.aggregatedStats.framesPerSecond &&
3174
+ }), switchMap$1(() => this.store.select(selectCommandsInProgress).pipe(debounceTime$1(500), first(), switchMap$1(() => observeCommandResponse(getLoopBackCommand(), (data) => this.commandsSender.sendCommandToUnreal(data))), switchMap$1(() => this.videoService.videoStats$), filter((videoStats) => !!videoStats.aggregatedStats.framesPerSecond &&
3270
3175
  videoStats.aggregatedStats.framesPerSecond > MINIMAL_FPS &&
3271
- (videoStats.aggregatedStats.framesDecoded || 0) > 15), first(), map$1(() => setLoopBackCommandIsCompleted()))));
3176
+ (videoStats.aggregatedStats.framesDecoded || 0) > 15), first(), map(() => setLoopBackCommandIsCompleted()))));
3272
3177
  });
3273
3178
  this.setViewportReadyBySetLoopBackCommandIsCompleted$ = createEffect(() => {
3274
- return this.actions$.pipe(ofType(setLoopBackCommandIsCompleted), tap(() => TelemetryStop('SetViewportReady', { ready: true })), map$1(() => setViewportReady({ value: true })));
3179
+ return this.actions$.pipe(ofType(setLoopBackCommandIsCompleted), tap(() => TelemetryStop('SetViewportReady', { ready: true })), map(() => setViewportReady({ value: true })));
3275
3180
  });
3276
3181
  this.listenUnrealCallbackByInitSignalling$ = createEffect(() => {
3277
- return this.actions$.pipe(ofType(initSignalling), switchMap(() => fromSignal(UnrealInternalSignalEvents.UnrealCallback).pipe(map$1(({ json }) => json?.commandCallback
3278
- ?.correlationId), filter(Truthy), map$1((id) => commandCompleted({ id })))));
3182
+ return this.actions$.pipe(ofType(initSignalling), switchMap$1(() => fromSignal(UnrealInternalSignalEvents.UnrealCallback).pipe(map(({ json }) => json?.commandCallback
3183
+ ?.correlationId), filter(Truthy), map((id) => commandCompleted({ id })))));
3279
3184
  });
3280
3185
  this.setConfigByInitSignalling$ = createEffect(() => {
3281
- return this.actions$.pipe(ofType(initSignalling), concatLatestFrom(() => this.store.select(unrealFeature.selectMatchUrls)), switchMap(() => this.store
3186
+ return this.actions$.pipe(ofType(initSignalling), concatLatestFrom(() => this.store.select(unrealFeature.selectMatchUrls)), switchMap$1(() => this.store
3282
3187
  .select(unrealFeature.selectMatchUrls)
3283
- .pipe(filter((item) => item?.length > 0))), map$1((matchMakerUrls) => {
3188
+ .pipe(filter((item) => item?.length > 0))), map((matchMakerUrls) => {
3284
3189
  if (isEmpty(matchMakerUrls)) {
3285
3190
  throw Error('Signalling URL(s) is empty');
3286
3191
  }
@@ -3428,7 +3333,7 @@ const clampAndKeepMaxPercents = () => {
3428
3333
  function observeCommandResponse(data, sender, timeOut = 60000, dispatchOnTimeout = true) {
3429
3334
  const correlationId = generateUuid();
3430
3335
  const out = { ...data, correlationId };
3431
- const observable = fromSignal(UnrealInternalSignalEvents.UnrealCallback).pipe(map(({ json }) => {
3336
+ const observable = fromSignal(UnrealInternalSignalEvents.UnrealCallback).pipe(map$1(({ json }) => {
3432
3337
  // Some callbacks do not have commandCallback wrapper,
3433
3338
  // so first we check if it exists,
3434
3339
  // then fallback to JSON itself
@@ -3461,7 +3366,7 @@ function decodeData(anyData) {
3461
3366
  function fromSignal(event) {
3462
3367
  return Signal.on(event).pipe(
3463
3368
  //tapLog('fromSignal', event), // <== for debugging, do not remove
3464
- map((data) => data.detail));
3369
+ map$1((data) => data.detail));
3465
3370
  }
3466
3371
  /**
3467
3372
  * UnrealInternalSignalEvents commands list wrapper sender
@@ -3472,12 +3377,12 @@ function sendSignal(event, ...args) {
3472
3377
  Signal.broadcast(event, { detail: args?.[0] || null });
3473
3378
  }
3474
3379
  function fromUnrealCallBackSignal(command) {
3475
- return fromSignal(UnrealInternalSignalEvents.UnrealCallback).pipe(map((data) => data?.json), map((json) => {
3380
+ return fromSignal(UnrealInternalSignalEvents.UnrealCallback).pipe(map$1((data) => data?.json), map$1((json) => {
3476
3381
  if ('commandCallback' in json) {
3477
3382
  return json?.commandCallback;
3478
3383
  }
3479
3384
  return json;
3480
- }), filter((json) => json.command === command), map((data) => data), map((data) => {
3385
+ }), filter((json) => json.command === command), map$1((data) => data), map$1((data) => {
3481
3386
  if ('payload' in data) {
3482
3387
  return {
3483
3388
  command: data.command,
@@ -3513,7 +3418,7 @@ const floatToSmoothPercents = () => {
3513
3418
  }), {
3514
3419
  previous: 0,
3515
3420
  current: 0,
3516
- }), switchMap(({ previous, current }) => {
3421
+ }), switchMap$1(({ previous, current }) => {
3517
3422
  if (Math.abs(current - previous) > 0.95) {
3518
3423
  // Assume that big difference seems like switch between 0 and 1 immediately after resetLoaderState(),
3519
3424
  // so it should go to 'current' without transitions
@@ -3521,7 +3426,7 @@ const floatToSmoothPercents = () => {
3521
3426
  }
3522
3427
  return smoothTransition(previous, current, 300);
3523
3428
  }), // 300ms duration for smooth transition
3524
- map((value) => Math.floor(value * 100)));
3429
+ map$1((value) => Math.floor(value * 100)));
3525
3430
  };
3526
3431
 
3527
3432
  const getImageFromVideoStream = (takeSizeFrom = 'video', imageOutput, sizes) => {
@@ -3912,7 +3817,7 @@ class VideoRecorder {
3912
3817
 
3913
3818
  class ClickableOverlayComponent {
3914
3819
  constructor() {
3915
- this.state = toSignal(fromSignal(UnrealInternalSignalEvents.ClickableOverlay).pipe(map((data) => (typeof data === 'object' ? data : null))));
3820
+ this.state = toSignal(fromSignal(UnrealInternalSignalEvents.ClickableOverlay).pipe(map$1((data) => (typeof data === 'object' ? data : null))));
3916
3821
  }
3917
3822
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: ClickableOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3918
3823
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.0", type: ClickableOverlayComponent, isStandalone: true, selector: "app-clickable-overlay", ngImport: i0, template: "@if (state()) {\r\n <div\r\n (click)=\"state()?.onOverlayClick()\"\r\n [ngClass]=\"state()?.className\"\r\n id=\"videoPlayOverlay\"\r\n >\r\n @if (state()?.isActivityDetected) {\r\n <div class=\"resume-box\">\r\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\r\n <div [innerHTML]=\"state()?.message\" class=\"text-number\"></div>\r\n </div>\r\n <div class=\"resume-box__text\">\r\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\r\n <p>\r\n No activity detected. Press 'Continue' if you wish to keep your\r\n session active\r\n </p>\r\n </div>\r\n <src-button\r\n [colorScheme]=\"'primary'\"\r\n [isFullWidth]=\"true\"\r\n [size]=\"'large'\"\r\n [data-testid]=\"'continue-session'\"\r\n >\r\n Continue\r\n </src-button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: ["#videoPlayOverlay{position:absolute;z-index:30;top:0;width:100%;height:100%;font-size:1.8em;font-family:var(--src-font-family-sans);background-color:#646464b3}.clickableState{display:flex;justify-content:center;align-items:center;cursor:pointer}.textDisplayState{display:flex}.hiddenState{display:none}.resume-box{width:340px;padding:32px 20px 20px;flex-direction:column;align-items:center;border-radius:var(--src-br-medium, 8px);background:var(--src-color-bg-default, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .resume-box__pic{width:72px;height:72px;margin:0 auto 22px;border-radius:48px;background:#ecf0f2;padding:12px;display:flex;align-items:center;justify-content:center}.resume-box .resume-box__pic .text-number{color:var(--src-colors-text-default, #1f2937);text-align:center;font-family:var(--src-font-family-sans);font-size:30px;font-style:normal;font-weight:400;line-height:24px}.resume-box__text{margin-bottom:18px}.resume-box__text p{text-align:center;font-family:var(--src-font-family-sans);font-size:var(--src-fs-base);font-style:normal;font-weight:400;line-height:24px;color:var(--src-color-text-default-subdued, #6b7280)}.resume-box__text .resume-box__heading{color:var(--src-color-bg-default, #1f2937);text-align:center;font-family:var(--src-font-family-sans);font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "colorScheme", "size", "customClass", "hasDisclosure", "isFullWidth", "isPressed", "isDisabled", "isLoading", "iconButton", "srcButtonConfig", "formID", "data-testid"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -3924,11 +3829,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImpor
3924
3829
 
3925
3830
  class FreezeFrameComponent {
3926
3831
  constructor() {
3927
- this.#store = inject(Store);
3928
- this.freezeFrameProgressMessageFromVideo = toSignal(this.#store.select(selectFreezeFrameProgressMessageFromVideo));
3929
- this.combinedFreeze = toSignal(this.#store.select(selectFreezeFrameCombinedDataUrl));
3832
+ this.store = inject(Store);
3833
+ this.freezeFrameProgressMessageFromVideo = toSignal(this.store.select(selectFreezeFrameProgressMessageFromVideo));
3834
+ this.combinedFreeze = toSignal(this.store.select(selectFreezeFrameCombinedDataUrl));
3930
3835
  }
3931
- #store;
3932
3836
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3933
3837
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.0", type: FreezeFrameComponent, isStandalone: true, selector: "app-freeze-frame", ngImport: i0, template: "<div class=\"freeze-images\">\r\n @if (combinedFreeze()) {\r\n <img\r\n [src]=\"combinedFreeze()\"\r\n class=\"videoStillImage\"\r\n alt=\"freezeFrameImage\"\r\n />\r\n }\r\n\r\n @if (freezeFrameProgressMessageFromVideo()) {\r\n <div class=\"progress-status\">\r\n {{ freezeFrameProgressMessageFromVideo() }}\r\n </div>\r\n }\r\n</div>\r\n", styles: [".freeze-images{pointer-events:none;position:absolute;left:0;top:0;width:100%;height:100%;z-index:1}.freeze-images .freezeFrameOverlay{top:0;left:0;width:100%;height:100%;position:absolute;object-fit:cover;object-position:center}.freeze-images .videoStillImage{position:absolute;z-index:1;top:0;left:0;width:100%;height:100%;object-fit:cover}.freeze-images .progress-status{position:absolute;top:50%;left:50%;z-index:1;display:flex;flex-direction:column;justify-content:center;align-items:center;width:auto;height:auto;margin:0;padding:10px;background-color:#ffffffb3;border:1px solid grey;transform:translate(-50%,-50%)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3934
3838
  }
@@ -4111,21 +4015,21 @@ class VideoStatsComponent {
4111
4015
  constructor() {
4112
4016
  this.collapsed = false;
4113
4017
  this.store = inject(Store);
4114
- this.videoStatGenerator = inject(VideoStreamStatusService);
4018
+ this.videoService = inject(VideoService);
4115
4019
  this.isDevMode = inject(DevModeService).isDevMode;
4116
- this.obs$ = this.videoStatGenerator.videoStats$;
4020
+ this.obs$ = this.videoService.videoStats$;
4117
4021
  this.viewportReady = toSignal(this.store.select(unrealFeature.selectViewportReady));
4118
4022
  this.ssInfo = toSignal(this.store.select(selectSignalingParameters));
4119
- this.fpsTick = toSignal(this.obs$.pipe(map((stat, index) => ({ value: stat.aggregatedStats.framesPerSecond, hash: index }))));
4120
- this.fpsAvgTick = toSignal(this.obs$.pipe(map((stat, index) => ({ value: stat.aggregatedStats.avgFrameRate, hash: index }))));
4121
- this.bitrateTick = toSignal(this.obs$.pipe(map((stat, index) => ({ value: stat.aggregatedStats.bitrate, hash: index }))));
4122
- this.videoQP = toSignal(this.obs$.pipe(map((stat, index) => ({ value: stat.aggregatedStats.VideoEncoderQP, hash: index }))));
4123
- this.bitrateAverageTick = toSignal(this.obs$.pipe(map((stat, index) => ({ value: stat.aggregatedStats.avgBitrate, hash: index }))));
4124
- this.videoStatus = toSignal(this.obs$.pipe(map((event) => this.isDevMode &&
4023
+ this.fpsTick = toSignal(this.obs$.pipe(map$1((stat, index) => ({ value: stat.aggregatedStats.framesPerSecond, hash: index }))));
4024
+ this.fpsAvgTick = toSignal(this.obs$.pipe(map$1((stat, index) => ({ value: stat.aggregatedStats.avgFrameRate, hash: index }))));
4025
+ this.bitrateTick = toSignal(this.obs$.pipe(map$1((stat, index) => ({ value: stat.aggregatedStats.bitrate, hash: index }))));
4026
+ this.videoQP = toSignal(this.obs$.pipe(map$1((stat, index) => ({ value: stat.aggregatedStats.VideoEncoderQP, hash: index }))));
4027
+ this.bitrateAverageTick = toSignal(this.obs$.pipe(map$1((stat, index) => ({ value: stat.aggregatedStats.avgBitrate, hash: index }))));
4028
+ this.videoStatus = toSignal(this.obs$.pipe(map$1((event) => this.isDevMode &&
4125
4029
  {
4126
4030
  ...event.aggregatedStats,
4127
4031
  quality: event.quality,
4128
- }), filter(Boolean), map((data) => {
4032
+ }), filter(Boolean), map$1((data) => {
4129
4033
  return this.fields
4130
4034
  .map((key) => {
4131
4035
  return {
@@ -4173,7 +4077,7 @@ class VideoStatsComponent {
4173
4077
  }
4174
4078
  updateGraph() {
4175
4079
  this.graphList = this.fields.map((key) => {
4176
- const stat = this.obs$.pipe(map((val, index) => {
4080
+ const stat = this.obs$.pipe(map$1((val, index) => {
4177
4081
  const value = val.aggregatedStats[key];
4178
4082
  return { value, hash: index };
4179
4083
  }));
@@ -4183,7 +4087,7 @@ class VideoStatsComponent {
4183
4087
  stat,
4184
4088
  };
4185
4089
  });
4186
- this.graphList$ = of(this.graphList).pipe(combineLatestWith(this.trigger$), map(([graphList]) => graphList.filter((r) => this.elementsToShow.includes(r.key))));
4090
+ this.graphList$ = of(this.graphList).pipe(combineLatestWith(this.trigger$), map$1(([graphList]) => graphList.filter((r) => this.elementsToShow.includes(r.key))));
4187
4091
  }
4188
4092
  toggle() {
4189
4093
  this.collapsed = !this.collapsed;
@@ -4220,20 +4124,20 @@ class VideoStatsComponent {
4220
4124
  }));
4221
4125
  }
4222
4126
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4223
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.0", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus()) {\r\n @if (viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"fpsTick()\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"videoQP()\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <!-- <li>\r\n <app-stat-graph\r\n label=\"AvgFPS (higher is better)\"\r\n [dataTick]=\"fpsAvgTick()\"\r\n color=\"#00aa00\"\r\n />\r\n </li>-->\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"bitrateTick()\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n <!-- <li>\r\n <app-stat-graph\r\n label=\"AvgBitrate\"\r\n [dataTick]=\"bitrateAverageTick()\"\r\n color=\"#D50007\"\r\n />\r\n </li>-->\r\n </div>\r\n <div>\r\n @for (graph of graphList$ | async; track graph) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [dataTick]=\"graph.stat | async\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed) {\r\n <div [innerHTML]=\"ssInfo() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n }\r\n}\r\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;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{cursor:pointer}#aggregatedStats .stat span{font-weight:700}#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: ["color", "tickStep", "label", "dataTick"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4127
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.0", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus()) {\r\n @if (viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"fpsTick()\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"videoQP()\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"bitrateTick()\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n </div>\r\n <div>\r\n @for (graph of graphList$ | async; track graph) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [dataTick]=\"graph.stat | async\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed) {\r\n <div [innerHTML]=\"ssInfo() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n }\r\n}\r\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;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{cursor:pointer}#aggregatedStats .stat span{font-weight:700}#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: ["color", "tickStep", "label", "dataTick"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4224
4128
  }
4225
4129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImport: i0, type: VideoStatsComponent, decorators: [{
4226
4130
  type: Component,
4227
- args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, AsyncPipe, JsonPipe, SafePipe], template: "@if (videoStatus()) {\r\n @if (viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"fpsTick()\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"videoQP()\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <!-- <li>\r\n <app-stat-graph\r\n label=\"AvgFPS (higher is better)\"\r\n [dataTick]=\"fpsAvgTick()\"\r\n color=\"#00aa00\"\r\n />\r\n </li>-->\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"bitrateTick()\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n <!-- <li>\r\n <app-stat-graph\r\n label=\"AvgBitrate\"\r\n [dataTick]=\"bitrateAverageTick()\"\r\n color=\"#D50007\"\r\n />\r\n </li>-->\r\n </div>\r\n <div>\r\n @for (graph of graphList$ | async; track graph) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [dataTick]=\"graph.stat | async\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed) {\r\n <div [innerHTML]=\"ssInfo() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n }\r\n}\r\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;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{cursor:pointer}#aggregatedStats .stat span{font-weight:700}#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"] }]
4131
+ args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, AsyncPipe, JsonPipe, SafePipe], template: "@if (videoStatus()) {\r\n @if (viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"fpsTick()\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"videoQP()\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [dataTick]=\"bitrateTick()\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n </div>\r\n <div>\r\n @for (graph of graphList$ | async; track graph) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [dataTick]=\"graph.stat | async\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed) {\r\n <div [innerHTML]=\"ssInfo() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n }\r\n}\r\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;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{cursor:pointer}#aggregatedStats .stat span{font-weight:700}#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"] }]
4228
4132
  }], ctorParameters: () => [] });
4229
4133
 
4230
4134
  class UnrealStatusComponent {
4231
4135
  constructor() {
4232
- this.#store = inject(Store);
4233
- this.#unrealInitialConfig = inject(UNREAL_CONFIG, {
4136
+ this.store = inject(Store);
4137
+ this.unrealInitialConfig = inject(UNREAL_CONFIG, {
4234
4138
  optional: true,
4235
4139
  });
4236
- this.#onDisconnect$ = this.#store
4140
+ this.onDisconnect$ = this.store
4237
4141
  .select(unrealFeature.selectCirrusConnected)
4238
4142
  .pipe(filter(Falsy));
4239
4143
  this.isDevMode = inject(DevModeService).isDevMode;
@@ -4241,11 +4145,8 @@ class UnrealStatusComponent {
4241
4145
  /**
4242
4146
  * An observable that emits smoothed percentage values from 0 to 100.
4243
4147
  */
4244
- this.messagePercents$ = toSignal(this.#onDisconnect$.pipe(switchMap(() => this.#store.select(selectTotalProgress).pipe(floatToSmoothPercents(), clampAndKeepMaxPercents(), combineLatestWith(this.#store.select(selectShowLoader)), map(([progress, showLoader]) => (showLoader ? progress || 0 : null))))));
4148
+ this.messagePercents$ = toSignal(this.onDisconnect$.pipe(switchMap$1(() => this.store.select(selectTotalProgress).pipe(floatToSmoothPercents(), clampAndKeepMaxPercents(), combineLatestWith(this.store.select(selectShowLoader)), map$1(([progress, showLoader]) => (showLoader ? progress || 0 : null))))));
4245
4149
  }
4246
- #store;
4247
- #unrealInitialConfig;
4248
- #onDisconnect$;
4249
4150
  ngOnInit() {
4250
4151
  /**
4251
4152
  * A subject that emits null values to trigger nullification.
@@ -4260,24 +4161,24 @@ class UnrealStatusComponent {
4260
4161
  /**
4261
4162
  * An observable that emits the status message from the Unreal feature store.
4262
4163
  */
4263
- const signalingStatus$ = this.#store.select(unrealFeature.selectStatusMessage);
4164
+ const signalingStatus$ = this.store.select(unrealFeature.selectStatusMessage);
4264
4165
  /**
4265
4166
  * An observable that emits connection status messages based on the Cirrus connection state.
4266
4167
  * Then call side effect to nullify the message.
4267
4168
  */
4268
- const cirrusConnected$ = this.#store
4169
+ const cirrusConnected$ = this.store
4269
4170
  .select(unrealFeature.selectCirrusConnected)
4270
- .pipe(skip(1), switchMap((connected) => connected
4171
+ .pipe(skip(1), switchMap$1((connected) => connected
4271
4172
  ? of('Creating Socket...')
4272
4173
  : of('Connection closed.').pipe(tap(() => nullify$.next(null)))));
4273
4174
  /**
4274
4175
  * An observable that emits the viewport visibility state.
4275
4176
  */
4276
- const viewportTrigger$ = this.#store.select(unrealFeature.selectViewportReady);
4177
+ const viewportTrigger$ = this.store.select(unrealFeature.selectViewportReady);
4277
4178
  /**
4278
4179
  * An observable that will nullify any message displayed after 500 mSec, when viewport changes its state.
4279
4180
  */
4280
- const streamToggle$ = merge(viewportTrigger$.pipe(filter(Falsy), skip(1)), viewportTrigger$.pipe(filter(Truthy))).pipe(delay(500), map(() => null));
4181
+ const streamToggle$ = merge(viewportTrigger$.pipe(filter(Falsy), skip(1)), viewportTrigger$.pipe(filter(Truthy))).pipe(delay(500), map$1(() => null));
4281
4182
  /**
4282
4183
  * A subscription that merges various status observables and emits debounced status via postMessage.
4283
4184
  */
@@ -4294,7 +4195,7 @@ class UnrealStatusComponent {
4294
4195
  * or any other relevant information that the iframe needs to process.
4295
4196
  */
4296
4197
  sendMessageToIndex(text) {
4297
- const objectElement = document.getElementById(this.#unrealInitialConfig?.screenLockerContainerId ||
4198
+ const objectElement = document.getElementById(this.unrealInitialConfig?.screenLockerContainerId ||
4298
4199
  SCREEN_LOCKER_CONTAINER_ID);
4299
4200
  try {
4300
4201
  const objectDocument = objectElement.contentWindow;
@@ -4334,7 +4235,6 @@ class UnrealSceneComponent {
4334
4235
  this.commandsSender = inject(UnrealCommunicatorService);
4335
4236
  this.inputService = inject(InputService);
4336
4237
  this.videoService = inject(VideoService);
4337
- this.stats = inject(VideoStreamStatusService);
4338
4238
  this.streamTelemetryService = inject(StreamStatusTelemetryService);
4339
4239
  this.element = inject(ElementRef);
4340
4240
  this.destroyRef = inject(DestroyRef);
@@ -4364,23 +4264,23 @@ class UnrealSceneComponent {
4364
4264
  fromEvent(this.element.nativeElement, 'contextmenu')
4365
4265
  .pipe(takeUntilDestroyed(this.destroyRef))
4366
4266
  .subscribe((event) => event.preventDefault());
4367
- this.videoService.container = this.container.nativeElement;
4368
- const customSizeInputChanged$ = this.studioResolutionValues.pipe(filter(Truthy), distinctUntilChanged(), filter(({ width, height }) => this.width !== width || this.height !== height), map(({ width, height }) => ({ width, height })), tap(({ width, height }) => {
4267
+ this.videoService.setContainer(this.container.nativeElement);
4268
+ 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 }) => {
4369
4269
  this.width = width;
4370
4270
  this.height = height;
4371
4271
  }));
4372
4272
  const resizeRequired$ = merge(fromSignal(UnrealInternalSignalEvents.OnVideoInitialized), fromEvent(window, 'resize'));
4373
- const videoStreamSizeChanged$ = this.stats?.videoStats$.pipe(filter(() => !this.isFreezeFrameLoading()), map((stats) => ({
4273
+ const videoStreamSizeChanged$ = this.videoService?.videoStats$.pipe(filter(() => !this.isFreezeFrameLoading()), map$1((stats) => ({
4374
4274
  w: stats.aggregatedStats.frameWidth || 0,
4375
4275
  h: stats.aggregatedStats.frameHeight || 0,
4376
4276
  })), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
4377
4277
  fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD)));
4378
4278
  merge(customSizeInputChanged$, resizeRequired$, videoStreamSizeChanged$)
4379
- .pipe(debounceTime$1(DEBOUNCE_TO_MANY_RESIZE_CALLS), switchMap(() => {
4279
+ .pipe(debounceTime$1(DEBOUNCE_TO_MANY_RESIZE_CALLS), switchMap$1(() => {
4380
4280
  const key = Math.random();
4381
4281
  // Monitoring the size of the video stream
4382
- return timer(0, 300).pipe(take(3), map(() => key));
4383
- }), map((key) => ({ key, ...this.resizeValues })), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
4282
+ return timer(0, 300).pipe(take(3), map$1(() => key));
4283
+ }), map$1((key) => ({ key, ...this.resizeValues })), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
4384
4284
  fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD) &&
4385
4285
  previous.key === current.key), tap((size) => this.adaptVideo(size)), distinctUntilChanged((previous, current) => fpIsASameAsB(previous.w, current.w, SAME_SIZE_THRESHOLD) &&
4386
4286
  fpIsASameAsB(previous.h, current.h, SAME_SIZE_THRESHOLD)), combineLatestWith(this.store
@@ -4523,7 +4423,7 @@ class VideoLockerComponent {
4523
4423
  this.videoIntroSrc$ = this.store.select(unrealFeature.selectVideoIntroSrc);
4524
4424
  this.imageIntroSrc$ = this.store
4525
4425
  .select(unrealFeature.selectImageIntroSrc)
4526
- .pipe(map$1((src) => {
4426
+ .pipe(map((src) => {
4527
4427
  // TODO need for demos site and need to delete this and move to demos
4528
4428
  if (!src) {
4529
4429
  return null;
@@ -4587,19 +4487,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImpor
4587
4487
  class WebrtcErrorModalComponent {
4588
4488
  constructor() {
4589
4489
  this.dialogRef = inject(DialogRef);
4590
- this.#store = inject(Store);
4490
+ this.store = inject(Store);
4591
4491
  }
4592
- #store;
4593
4492
  close() {
4594
4493
  this.dialogRef.close();
4595
4494
  }
4596
4495
  closeModalWithCirrusDisconnect() {
4597
- this.#store
4496
+ this.store
4598
4497
  .select(unrealFeature.selectCirrusConnected)
4599
4498
  .pipe(first())
4600
4499
  .subscribe((isConnected) => {
4601
4500
  if (!isConnected) {
4602
- this.#store.dispatch(destroyConnectionsAndResetState());
4501
+ this.store.dispatch(destroyConnectionsAndResetState());
4603
4502
  }
4604
4503
  this.close();
4605
4504
  });
@@ -4664,23 +4563,23 @@ class LowBandwidthDetectorComponent {
4664
4563
  this.store = inject(Store);
4665
4564
  this.dialog = inject(Dialog);
4666
4565
  this.destroyRef = inject(DestroyRef);
4667
- this.videoStatGenerator = inject(VideoStreamStatusService);
4566
+ this.videoService = inject(VideoService);
4668
4567
  this.devModeService = inject(DevModeService);
4669
4568
  this.viewportReady$ = this.store.select(unrealFeature.selectViewportReady);
4670
4569
  this.isLowBandwidth$ = this.store.select(unrealFeature.selectLowBandwidth);
4671
4570
  this.lowBandwidthTrigger$ = this.isLowBandwidth$.pipe(distinctUntilChanged$1(), filter(Truthy));
4672
4571
  this.highBandwidthTrigger$ = this.isLowBandwidth$.pipe(distinctUntilChanged$1(), filter(Falsy));
4673
4572
  this.viewPortDestroyedTrigger$ = this.viewportReady$.pipe(distinctUntilChanged$1(), filter(Falsy));
4674
- this.aggregatedStats$ = this.videoStatGenerator.videoStats$.pipe(map$1((data) => data.aggregatedStats), share());
4675
- this.nativeQuality$ = this.videoStatGenerator.videoStats$.pipe(map$1((data) => data.quality));
4573
+ this.aggregatedStats$ = this.videoService.videoStats$.pipe(map((data) => data.aggregatedStats), share());
4574
+ this.nativeQuality$ = this.videoService.videoStats$.pipe(map((data) => data.quality));
4676
4575
  this.monitoringTrigger$ = this.viewportReady$
4677
- .pipe(combineLatestWith(this.isLowBandwidth$), map$1(([viewPortReady, isLowBandwidth]) => viewPortReady && !isLowBandwidth))
4576
+ .pipe(combineLatestWith(this.isLowBandwidth$), map(([viewPortReady, isLowBandwidth]) => viewPortReady && !isLowBandwidth))
4678
4577
  .pipe(share());
4679
- this.reTrigger$ = this.monitoringTrigger$.pipe(map$1(() => ({ quality: 'lime' })));
4680
- this.canSwitchToLowBand$ = this.monitoringTrigger$.pipe(debounceTime$1(100), switchMap((canSwitch) => concat(of(false), timer(FilterModel.monitoringDelayTime).pipe(map$1(() => canSwitch))).pipe(take(2), takeUntil$1(this.monitoringTrigger$))), distinctUntilChanged$1(), tapLog('DROP: [LOWBANDWIDTH] Monitoring Active =>'), share());
4578
+ this.reTrigger$ = this.monitoringTrigger$.pipe(map(() => ({ quality: 'lime' })));
4579
+ this.canSwitchToLowBand$ = this.monitoringTrigger$.pipe(debounceTime$1(100), switchMap$1((canSwitch) => concat(of(false), timer(FilterModel.monitoringDelayTime).pipe(map(() => canSwitch))).pipe(take(2), takeUntil$1(this.monitoringTrigger$))), distinctUntilChanged$1(), tapLog('DROP: [LOWBANDWIDTH] Monitoring Active =>'), share());
4681
4580
  this.videoQuality$ = merge(this.nativeQuality$, this.reTrigger$).pipe(share(), takeUntil$1(this.viewPortDestroyedTrigger$));
4682
- this.fps$ = this.aggregatedStats$.pipe(map$1((data) => data.framesPerSecond || 0));
4683
- this.bitrateDrop$ = this.aggregatedStats$.pipe(map$1((data) => data.dataFlowCheckResult), filter((br) => br.isDropDetected), takeUntil$1(merge(this.viewPortDestroyedTrigger$, this.reTrigger$)));
4581
+ this.fps$ = this.aggregatedStats$.pipe(map((data) => data.framesPerSecond || 0));
4582
+ this.bitrateDrop$ = this.aggregatedStats$.pipe(map((data) => data.dataFlowCheckResult), filter((br) => br.isDropDetected), takeUntil$1(merge(this.viewPortDestroyedTrigger$, this.reTrigger$)));
4684
4583
  this.fps = toSignal(this.fps$);
4685
4584
  this.nativeQuality = toSignal(this.nativeQuality$);
4686
4585
  this.isLowBandwidth = toSignal(this.isLowBandwidth$);
@@ -4700,9 +4599,9 @@ class LowBandwidthDetectorComponent {
4700
4599
  .pipe(filter(Truthy), tap(() => {
4701
4600
  BITRATE_MONITOR.reset();
4702
4601
  this.isReducedQuality.set(false);
4703
- }), switchMap(() => this.bitrateDrop$), tap(this.trySetLowBandwidth.bind(this)), map$1((dataFlowResult) => dataFlowResult?.isDropDetected), distinctUntilChanged$1(), tap(() => {
4602
+ }), switchMap$1(() => this.bitrateDrop$), tap(this.trySetLowBandwidth.bind(this)), map((dataFlowResult) => dataFlowResult?.isDropDetected), distinctUntilChanged$1(), tap(() => {
4704
4603
  this.isReducedQuality.set(true);
4705
- }), switchMap(() => this.videoQuality$), filter((quality) => quality === 'lime'), tap(() => this.isReducedQuality.set(false)), takeUntilDestroyed(this.destroyRef))
4604
+ }), switchMap$1(() => this.videoQuality$), filter((quality) => quality === 'lime'), tap(() => this.isReducedQuality.set(false)), takeUntilDestroyed(this.destroyRef))
4706
4605
  .subscribe();
4707
4606
  }
4708
4607
  trySetLowBandwidth(dataFlowResult) {
@@ -4771,5 +4670,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.0", ngImpor
4771
4670
  * Generated bundle index. Do not edit.
4772
4671
  */
4773
4672
 
4774
- export { AFKService, AfkRestartScreenLockerComponent, AggregatorService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, ClickableOverlayComponent, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_TIMEOUT_PERIOD, DEFAULT_WARN_TIMEOUT, DataFlowMonitor, DevModeService, DisconnectReason, EControlSchemeType, EMessageType, EToClientMessageType, FULL_HD_HEIGHT, FULL_HD_WIDTH, FilterSettingsComponent, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, KalmanFilter1D, LatencyTimings, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, PingHandler, PlayerCountHandler, RegionsPingService, ResetTelemetry, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SSInfoHandler, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, SubService, TelemetryStart, TelemetryStop, UNREAL_CONFIG, UnrealCommunicatorService, UnrealEffects, UnrealInternalSignalEvents, UnrealSceneComponent, UnrealStatusMessage, VideoLockerComponent, VideoRecorder, VideoService, VideoStatsComponent, VideoStreamStatusService, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CIRRUS_CLOSED, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, alignProductsToPlaneCommand, changeLowBandwidth, changeStatusMainVideoOnScene, changeStreamResolutionAction, changeStreamResolutionSuccessAction, clampAndKeepMaxPercents, clampPanToProductsCommand, commandCompleted, commandStarted, decodeData, destroyConnectionsAndResetState, destroyRemoteConnections, dispatchResize, filteredLogs, floatToSmoothPercents, forceResizeUnrealVideo, fromResizeObserver, fromSignal, fromUnrealCallBackSignal, getActiveUrl, getApplyCameraPresetCommand, getApplyZoomCommand, getCameraBoxCommand, getCameraRecenterCommand, getCameraSettingsCommand, getChangeGizmoTypeCommand, getChangeResolutionCommand, getClickSnapCommand, getControlSensitivityCommand, getDebugModeCommand, getDragCommand, getDragSequenceCommand, getDropCommand, getEnableComposureCommand, getEnableControlsCommand, getEnableSceneStateCallbackCommand, getEnableSpinnerModeCommand, getEnableTeleportCommand, getExecuteConsoleCommand, getFitToObjectsCommand, getFreezeFrameCommand, getImageFromVideoStream, getInitSequenceByObjectNameCommand, getJumpToSequenceCommand, getLoadLevelCommand, getLoadProductCommand, getLoadSceneStateCommand, getLoopBackCommand, getMoveSelectedCommand, getPauseSequenceCommand, getPlaySequenceCommand, getRequestCameraPresetCommand, getResetControlClampsCommand, getRotateSelectedCommand, getRtcErrorMessage, getSelectProductByObjectNameCommand, getSetCameraControlClampsCommand, getSetControlCameraModeCommand, getSetFpsCommand, getSetMaterialCommand, getSetPawnMovementModeCommand, getSetSettingsSequenceCommand, getStopSequenceCommand, getTakeRenderCommand, getTakeSpinnerRenderCommand, getTakeSpinnerRenderPreviewCommand, getUnLoadAllProductsCommand, getUnLoadProductByObjectNameCommand, getUnselectAllProductsCommand, getWeatherCommand, initSignalling, initialState, mapQpToQuality, observeCommandResponse, removeExileCommands, resetAfk, resetAfkAction, resetConfig, resetIntroSrc, resetUnrealState, resetUnrealStateAction, resetWarnTimeout, selectCommandProgress, selectCommandsInProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsFreezeFrameLoading, selectIsVideoPlayingAndDataChannelConnected, selectLastCommandInProgress, selectLoaderCommands, selectMatchUrls, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectWarnTimeout, selectWsUrl, sendSignal, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setDataChannelConnected, setErrorMessage, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMatchUrls, setMaxFps, setProductsLocationCommand, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setViewportReady, showUnrealErrorMessage, trackMixpanelEvent, unLoadAllLevelsCommand, unrealFeature, unrealReducer, updateCirrusInfo };
4673
+ export { AFKService, AfkRestartScreenLockerComponent, AggregatorService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, ClickableOverlayComponent, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_TIMEOUT_PERIOD, DEFAULT_WARN_TIMEOUT, DataFlowMonitor, DevModeService, DisconnectReason, EControlSchemeType, EMessageType, EToClientMessageType, FULL_HD_HEIGHT, FULL_HD_WIDTH, FilterSettingsComponent, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, KalmanFilter1D, LatencyTimings, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, PingHandler, PlayerCountHandler, RegionsPingService, ResetTelemetry, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SSInfoHandler, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, SubService, TelemetryStart, TelemetryStop, UNREAL_CONFIG, UnrealCommunicatorService, UnrealEffects, UnrealInternalSignalEvents, UnrealSceneComponent, UnrealStatusMessage, VideoLockerComponent, VideoRecorder, VideoService, VideoStatsComponent, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CIRRUS_CLOSED, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, alignProductsToPlaneCommand, changeLowBandwidth, changeStatusMainVideoOnScene, changeStreamResolutionAction, changeStreamResolutionSuccessAction, clampAndKeepMaxPercents, clampPanToProductsCommand, commandCompleted, commandStarted, decodeData, destroyConnectionsAndResetState, destroyRemoteConnections, dispatchResize, floatToSmoothPercents, forceResizeUnrealVideo, fromResizeObserver, fromSignal, fromUnrealCallBackSignal, getActiveUrl, getApplyCameraPresetCommand, getApplyZoomCommand, getCameraBoxCommand, getCameraRecenterCommand, getCameraSettingsCommand, getChangeGizmoTypeCommand, getChangeResolutionCommand, getClickSnapCommand, getControlSensitivityCommand, getDebugModeCommand, getDragCommand, getDragSequenceCommand, getDropCommand, getEnableComposureCommand, getEnableControlsCommand, getEnableSceneStateCallbackCommand, getEnableSpinnerModeCommand, getEnableTeleportCommand, getExecuteConsoleCommand, getFitToObjectsCommand, getFreezeFrameCommand, getImageFromVideoStream, getInitSequenceByObjectNameCommand, getJumpToSequenceCommand, getLoadLevelCommand, getLoadProductCommand, getLoadSceneStateCommand, getLoopBackCommand, getMoveSelectedCommand, getPauseSequenceCommand, getPlaySequenceCommand, getRequestCameraPresetCommand, getResetControlClampsCommand, getRotateSelectedCommand, getRtcErrorMessage, getSelectProductByObjectNameCommand, getSetCameraControlClampsCommand, getSetControlCameraModeCommand, getSetFpsCommand, getSetMaterialCommand, getSetPawnMovementModeCommand, getSetSettingsSequenceCommand, getStopSequenceCommand, getTakeRenderCommand, getTakeSpinnerRenderCommand, getTakeSpinnerRenderPreviewCommand, getUnLoadAllProductsCommand, getUnLoadProductByObjectNameCommand, getUnselectAllProductsCommand, getWeatherCommand, initSignalling, initialState, mapQpToQuality, observeCommandResponse, removeExileCommands, resetAfk, resetAfkAction, resetConfig, resetIntroSrc, resetUnrealState, resetUnrealStateAction, resetWarnTimeout, selectCommandProgress, selectCommandsInProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsFreezeFrameLoading, selectIsVideoPlayingAndDataChannelConnected, selectLastCommandInProgress, selectLoaderCommands, selectMatchUrls, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectWarnTimeout, selectWsUrl, sendSignal, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setDataChannelConnected, setErrorMessage, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMatchUrls, setMaxFps, setProductsLocationCommand, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setViewportReady, showUnrealErrorMessage, trackMixpanelEvent, unLoadAllLevelsCommand, unrealFeature, unrealReducer, updateCirrusInfo };
4775
4674
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map