@3dsource/angular-unreal-module
Version:
Angular Unreal module for connect with unreal engine stream
1 lines • 429 kB
Source Map (JSON)
{"version":3,"file":"3dsource-angular-unreal-module.mjs","sources":["../../../../projects/3dsource/angular-unreal-module/src/lib/interfaces/CustomCloseCodes.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/interfaces/disconnect-reasons.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/interfaces/SignalingMessage.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/interfaces/unreal-internal-signal-events.interface.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/AnswerHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/ConfigHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/IceCandidateHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/InstanceReadyHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/store/unreal.actions.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/ButtonsAndCodes.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/EControlSchemeType.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/EMessageType.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/EToClientMessageType.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/initial-config.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/InputOptions.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/UnrealStatusMessage.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/constants/unreal.constant.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/store/unreal.reducer.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/store/unreal.feature.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/sub.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/afk.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/freeze-frame.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/DataFlowMonitor.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-indicator/DataFlowConstants.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/video.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/command-telemetry.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/StreamerListHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/OfferHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/signalling.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/web-rtc-player.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/deepEqual.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/aggregator.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/console-extensions.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/dev-mode.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/TouchEmulator.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/unreal-communicator.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/input.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/regions-ping.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/services/stream-status-telemetry.service.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/unreal-error-modal/unreal-error-modal.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/unreal-error-modal/unreal-error-modal.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/store/unreal.effects.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/store/unreal.selectors.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/InstanceReservedHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/PingHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/PlayerCountHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/FromStreamerHandlers/SSInfoHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/WsHandlers/OnCloseHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/WsHandlers/OnErrorHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/WsHandlers/OnMessageHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/WsHandlers/OpenHandler.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/clamp-and-keep-max-percents.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/CommandObserver.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/DataDecoder.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/EventWrapper.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/dispatchResize.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/error-codes.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/float-to-smooth-percents.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/get-image-from-video-stream.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/GetNextUrl.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/KalmanFilter1D.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/LatencyTimings.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/mapQpToQuality.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/prepare-commands.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/remove-exile-commands.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/resetAfk.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/resize-observer.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/helpers/VideoRecorder.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/clickable-overlay/clickable-overlay.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/clickable-overlay/clickable-overlay.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/freeze-frame/freeze-frame.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/freeze-frame/freeze-frame.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/pipes/safe-html.pipe.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-modal/low-bandwidth-modal.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-modal/low-bandwidth-modal.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/screen-locker/afk-restart-screen-locker.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/screen-locker/afk-restart-screen-locker.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/stat-graph/stat-graph.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/stat-graph/stat-graph.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/video-stats/video-stats.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/video-stats/video-stats.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/unreal-status/unreal-status.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/unreal-status/unreal-status.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/unreal-scene/unreal-scene.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/unreal-scene/unreal-scene.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/video-locker/video-locker.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/video-locker/video-locker.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/webrtc-error-modal/webrtc-error-modal.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/webrtc-error-modal/webrtc-error-modal.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-indicator/filter-settings/filter-settings.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-indicator/filter-settings/filter-settings.component.html","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-indicator/low-bandwidth-detector.component.ts","../../../../projects/3dsource/angular-unreal-module/src/lib/components/low-bandwidth-indicator/low-bandwidth-detector.component.html","../../../../projects/3dsource/angular-unreal-module/src/3dsource-angular-unreal-module.ts"],"sourcesContent":["export const WSCloseCode_NORMAL_CLOSURE = 3000;\nexport const WSCloseCode_NORMAL_AFK_TIMEOUT = 3001;\nexport const WSCloseCode_NORMAL_MANUAL_DISCONNECT = 3002;\nexport const WSCloseCode_NORMAL_CIRRUS_CLOSED = 3003;\n\nexport const WSCloseCodes = [\n WSCloseCode_NORMAL_CLOSURE,\n WSCloseCode_NORMAL_AFK_TIMEOUT,\n WSCloseCode_NORMAL_MANUAL_DISCONNECT,\n WSCloseCode_NORMAL_CIRRUS_CLOSED,\n] as const;\n\nexport type WSCloseCodesValues = (typeof WSCloseCodes)[number];\n","export const DisconnectReason = {\n afk: 'afk',\n none: 'none',\n reset: 'reset',\n wsOnError: 'wsOnError',\n dataChannelClosed: 'dataChannelClosed',\n dataChannelTimeout: 'dataChannelTimeout',\n} as const;\n\nexport type DisconnectType =\n (typeof DisconnectReason)[keyof typeof DisconnectReason];\n","/*\nexport interface RequestReservation extends InstanceMessageBase {\n type: 'requestReservation';\n}\n*/\n\nimport type { SignalingData } from './SignalingData';\n\nexport interface InstanceReady extends InstanceMessageBase {\n type: 'instanceReady';\n}\n\nexport interface InstanceReserved extends InstanceMessageBase {\n type: 'instanceReserved';\n}\n\ninterface InstanceMessageBase extends MessageBase {\n correlationId: string;\n payload: {\n instanceId: string;\n };\n}\n\nexport interface MessageBase {\n type: OrchestrationMessageType;\n}\n\nexport interface SSInfo extends MessageBase {\n type: 'ssInfo';\n message: string;\n data: SignalingData;\n}\n\nexport interface PlayerCountMessage extends MessageBase {\n type: 'playerCount';\n playerCount: number;\n}\nexport interface StreamerListMessage extends MessageBase {\n type: 'streamerList';\n ids: string[];\n}\n\nexport interface ConfigMessage extends MessageBase, UnknownFields {\n type: 'config';\n peerConnectionOptions: RTCConfiguration;\n}\n\nexport interface CandidateMessage extends MessageBase {\n type: 'iceCandidate';\n candidate: RTCIceCandidate;\n}\n\nexport interface PingMessage extends MessageBase {\n type: 'ping';\n playerId: string;\n appConnectionCounter: number;\n}\n\nexport type UnknownFields = Record<string, unknown>;\n\nexport const OrchestrationMessageTypes = {\n streamerList: 'streamerList',\n listStreamers: 'listStreamers',\n requestReservation: 'requestReservation',\n instanceReady: 'instanceReady',\n instanceReserved: 'instanceReserved',\n ssInfo: 'ssInfo',\n playerCount: 'playerCount',\n answer: 'answer',\n iceCandidate: 'iceCandidate',\n ping: 'ping',\n config: 'config',\n offer: 'offer',\n} as const;\n\nexport interface SignalingMessageMap {\n [OrchestrationMessageTypes.streamerList]: StreamerListMessage;\n [OrchestrationMessageTypes.ssInfo]: SSInfo;\n [OrchestrationMessageTypes.playerCount]: PlayerCountMessage;\n [OrchestrationMessageTypes.answer]: RTCSessionDescriptionInit;\n [OrchestrationMessageTypes.config]: ConfigMessage;\n [OrchestrationMessageTypes.iceCandidate]: CandidateMessage;\n [OrchestrationMessageTypes.instanceReady]: InstanceReady;\n [OrchestrationMessageTypes.instanceReserved]: InstanceReserved;\n [OrchestrationMessageTypes.ping]: PingMessage;\n [OrchestrationMessageTypes.offer]: RTCSessionDescriptionInit;\n}\n\nexport type OrchestrationMessageType = keyof SignalingMessageMap;\n\nexport type SignalingMessageHandler<T extends keyof SignalingMessageMap> = (\n msg: SignalingMessageMap[T],\n) => void;\n\nexport interface PollingOrchestrationMessage {\n mm_message?: string;\n signallingServer?: string;\n error?: string;\n info?: {\n age: number | null;\n left: number | null;\n percent: number | null;\n total: number | null;\n };\n}\n","import type { MetaBoxCommandPacket } from '@3dsource/types-unreal';\nimport type { Quality } from './quality.interface';\nimport type { DataFlowCheckResult } from '../helpers';\n\nexport const UnrealInternalSignalEvents = {\n OnVideoInitialized: 'OnVideoInitialized',\n RestAfkTimer: 'RestAfkTimer',\n FreezeFrameStart: 'FreezeFrameStart',\n UnrealCallback: 'UnrealCallback',\n ClickableOverlay: 'ClickableOverlay',\n VideoPlayClick: 'VideoPlayClick',\n ShowPlayButton: 'ShowPlayButton',\n ForceResizeViewport: 'ForceResizeViewport',\n VideoAdaptedToContainer: 'VideoAdaptedToContainer',\n TelemetryStart: 'TelemetryStart',\n TelemetryStop: 'TelemetryStop',\n TelemetryReset: 'TelemetryReset',\n} as const;\n\nexport interface SignalDescriptor {\n [UnrealInternalSignalEvents.UnrealCallback]: {\n json: UnrealCallBackJson;\n };\n [UnrealInternalSignalEvents.ClickableOverlay]: {\n message: string;\n className: string;\n isActivityDetected: boolean;\n onOverlayClick: () => void;\n } | void;\n [UnrealInternalSignalEvents.VideoPlayClick]: {\n text: string;\n };\n [UnrealInternalSignalEvents.ForceResizeViewport]: void;\n [UnrealInternalSignalEvents.OnVideoInitialized]: HTMLVideoElement;\n [UnrealInternalSignalEvents.VideoAdaptedToContainer]: void;\n [UnrealInternalSignalEvents.ShowPlayButton]: {\n text: string;\n callback: () => boolean;\n };\n [UnrealInternalSignalEvents.RestAfkTimer]: void;\n [UnrealInternalSignalEvents.FreezeFrameStart]: void;\n [UnrealInternalSignalEvents.TelemetryReset]: void;\n [UnrealInternalSignalEvents.TelemetryStart]: { externalId: string };\n [UnrealInternalSignalEvents.TelemetryStop]: {\n externalId: string;\n payload?: { multi?: boolean };\n };\n\n [key: string]: any;\n}\n\nexport type UnrealCallBackJson =\n | MetaBoxCommandPacket\n | WrappedMetaBoxCommandPacket;\n\nexport interface WrappedMetaBoxCommandPacket {\n commandCallback: MetaBoxCommandPacket;\n}\n\nexport interface IAggregatedStat {\n quality: Quality;\n aggregatedStats: InboundVideoStats;\n}\n\nexport interface InboundVideoStats extends RTCInboundRtpStreamStats {\n VideoEncoderQP: number;\n runTime: number;\n pixelRatio: number;\n bytesReceivedStart: number;\n framesDecodedStart: number;\n timestampStart: number;\n videoCodec: string;\n avgBitrate: number;\n avgFrameRate: number;\n currentRoundTripTime: number;\n codecs: Record<string, string>;\n receiveToCompositeMs: number;\n bitrate: number;\n kalmanBitrate: number;\n bitrateDrop: number;\n dataFlowCheckResult: DataFlowCheckResult;\n dataFlowBitrate: DataFlowCheckResult;\n}\n\nexport interface AwsInstance {\n wsUrl: string | null;\n pollingUrl: string | null;\n instanceName: string | null;\n}\n","import type { SignallingService } from '../../services';\n\nexport function AnswerHandler(\n this: SignallingService,\n msg: RTCSessionDescriptionInit,\n) {\n this.onWebRtcAnswer$.next(msg);\n}\n","import type { SignallingService } from '../../services';\nimport type { ConfigMessage } from '../../interfaces';\n\nexport function ConfigHandler(this: SignallingService, msg: ConfigMessage) {\n this.onConfig$.next(msg);\n}\n","import type { SignallingService } from '../../services';\n\nexport function IceCandidateHandler(\n this: SignallingService,\n msg: { candidate: RTCIceCandidate },\n) {\n this.onWebRtcIce$.next(msg.candidate);\n}\n","import type { InstanceReady } from '../../interfaces';\nimport type { SignallingService } from '../../services';\n\nexport function InstanceReadyHandler(\n this: SignallingService,\n _: InstanceReady,\n) {}\n","import { createAction, props } from '@ngrx/store';\nimport {\n DisconnectType,\n LBMStats,\n SignalingData,\n StreamConfig,\n UnrealErrorType,\n} from '../interfaces';\n\nconst scoped = (templateString: TemplateStringsArray) =>\n `[UNREAL] ${templateString[0]}`;\nexport const trackMixpanelEvent = createAction(\n scoped`track mixpanel event`,\n props<{ event: string; data?: unknown }>(),\n);\nexport const changeLowBandwidth = createAction(\n scoped`change low bandwidth`,\n props<{\n lowBandwidth: boolean;\n stats?: LBMStats;\n }>(),\n);\nexport const setMaxFps = createAction(\n scoped`change fps`,\n props<{ maxFps: number }>(),\n);\nexport const destroyRemoteConnections = createAction(\n scoped`destroyRemoteConnections`,\n props<{ disconnectReason: DisconnectType }>(),\n);\nexport const destroyConnectionsAndResetState = createAction(\n scoped`destroyConnections and reset`,\n);\nexport const setCirrusConnected = createAction(scoped`cirrusConnected`);\nexport const setCirrusDisconnected = createAction(scoped`cirrusDisconnected`);\nexport const changeStatusMainVideoOnScene = createAction(\n scoped`change status main video on scene`,\n props<{ isVideoPlaying: boolean }>(),\n);\nexport const setAwsInstance = createAction(\n scoped`set aws instance`,\n props<{\n wsUrl: string | null;\n pollingUrl: string | null;\n instanceName: string | null;\n }>(),\n);\nexport const setStatusMessage = createAction(\n scoped`set status message`,\n props<{ message: string | null }>(),\n);\nexport const setStatusPercentSignallingServer = createAction(\n scoped`change status percent signalling server`,\n props<{ percent: number | null }>(),\n);\nexport const setFreezeFrame = createAction(\n scoped`set freeze frame`,\n props<{\n dataUrl: string | null;\n progress: number | null;\n }>(),\n);\nexport const setMatchUrls = createAction(\n scoped`set match urls`,\n props<{ urls: string[] }>(),\n);\nexport const setStreamClientCompanyId = createAction(\n scoped`set stream client company id`,\n props<{ id: string }>(),\n);\nexport const setStreamViewId = createAction(\n scoped`set stream view id`,\n props<{ id: string }>(),\n);\nexport const setIntroImageSrc = createAction(\n scoped`set intro image src`,\n props<{ src: string }>(),\n);\nexport const setLoadingImageSrc = createAction(\n scoped`set loading image src`,\n props<{ src: string }>(),\n);\nexport const setIntroVideoSrc = createAction(\n scoped`set intro video src`,\n props<{ src: string }>(),\n);\nexport const resetIntroSrc = createAction(scoped`reset intro src`);\nexport const setFreezeFrameFromVideo = createAction(\n scoped`set freeze frame from video`,\n props<{\n dataUrl: string | null;\n progress: number | null;\n }>(),\n);\nexport const setEstablishingConnection = createAction(\n scoped`set establishing connection`,\n props<{ value: boolean }>(),\n);\nexport const setDataChannelConnected = createAction(\n scoped`set data channel connected`,\n props<{ value: boolean }>(),\n);\nexport const setConfig = createAction(\n scoped`set config`,\n props<{ config: Partial<StreamConfig> }>(),\n);\nexport const setErrorMessage = createAction(\n scoped`set error message`,\n props<{\n errorType: UnrealErrorType;\n message: string;\n }>(),\n);\nexport const setViewportReady = createAction(\n scoped`set viewport ready`,\n props<{ value: boolean }>(),\n);\nexport const changeStreamResolutionAction = createAction(\n scoped`change stream resolution`,\n props<{\n width: number | null;\n height: number | null;\n }>(),\n);\nexport const changeStreamResolutionSuccessAction = createAction(\n scoped`change stream resolution success action`,\n props<{\n width: number | null;\n height: number | null;\n }>(),\n);\nexport const setSignalingName = createAction(\n scoped`set aws instanceName`,\n props<{ instanceName: string }>(),\n);\nexport const updateCirrusInfo = createAction(\n scoped`set unrealInfo`,\n props<{\n ssInfo: string;\n ssData: SignalingData;\n }>(),\n);\nexport const commandStarted = createAction(\n scoped`command started`,\n props<{ id: string; command: string }>(),\n);\nexport const commandCompleted = createAction(\n scoped`command completed`,\n props<{ id: string }>(),\n);\nexport const setLoopBackCommandIsCompleted = createAction(\n scoped`set loopBack command is completed`,\n);\nexport const showUnrealErrorMessage = createAction(\n scoped`show unreal error message`,\n props<{ code: number | null; error?: string }>(),\n);\nexport const initSignalling = createAction(scoped`init signalling`);\nexport const resetConfig = createAction(scoped`reset config`);\nexport const resetAfkAction = createAction(scoped`reset afk action`);\nexport const resetWarnTimeout = createAction(scoped`reset config warn timeout`);\nexport const resetUnrealStateAction = createAction(scoped`reset state`);\nexport const resetUnrealState = createAction(scoped`reset unreal state`);\n","export const SpecialKeyCodes = {\n BackSpace: 8,\n Shift: 16,\n Control: 17,\n Alt: 18,\n RightShift: 253,\n RightControl: 254,\n RightAlt: 255,\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button\nexport const MouseButton = {\n MainButton: 0, // Left button.\n AuxiliaryButton: 1, // Wheel button.\n SecondaryButton: 2, // Right button.\n FourthButton: 3, // Browser Back button.\n FifthButton: 4, // Browser Forward button.\n};\n\n// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons\nexport const MouseButtonsMask = {\n PrimaryButton: 1, // Left button.\n SecondaryButton: 2, // Right button.\n AuxiliaryButton: 4, // Wheel button.\n FourthButton: 8, // Browser Back button.\n FifthButton: 16, // Browser Forward button.\n};\n","export const EControlSchemeType = {\n // A mouse can lock inside the WebRTC player so the user can simply move the\n // mouse to control the orientation of the camera. The user presses the\n // Escape key to unlock the mouse.\n LockedMouse: 0,\n\n // A mouse can hover over the WebRTC player so the user needs to click and\n // drag to control the orientation of the camera.\n HoveringMouse: 1,\n Default: 2,\n} as const;\n\nexport type EControlSchemeTypeValues =\n (typeof EControlSchemeType)[keyof typeof EControlSchemeType];\n","// Must be kept in sync with PixelStreamingProtocol::EToUE4Msg C++ descriptors.\n\nexport const EMessageType = {\n /*\n * Control Messages. Range = 0..49.\n */\n IFrameRequest: 0,\n RequestQualityControl: 1,\n MaxFpsRequest: 2,\n AverageBitrateRequest: 3,\n StartStreaming: 4,\n StopStreaming: 5,\n LatencyTest: 6,\n RequestInitialSettings: 7,\n\n /*\n * Input Messages. Range = 50..89.\n */\n\n // Generic Input Messages. Range = 50..59.\n UIInteraction: 50,\n Command: 51,\n\n // Keyboard Input Message. Range = 60..69.\n KeyDown: 60,\n KeyUp: 61,\n KeyPress: 62,\n\n // Mouse Input Messages. Range = 70..79.\n MouseEnter: 70,\n MouseLeave: 71,\n MouseDown: 72,\n MouseUp: 73,\n MouseMove: 74,\n MouseWheel: 75,\n\n // Touch Input Messages. Range = 80..89.\n TouchStart: 80,\n TouchEnd: 81,\n TouchMove: 82,\n} as const;\n\nexport type EMessageTypeValues =\n (typeof EMessageType)[keyof typeof EMessageType];\n","export const EToClientMessageType = {\n QualityControlOwnership: 0,\n Response: 1,\n Command: 2,\n FreezeFrame: 3,\n UnfreezeFrame: 4,\n VideoEncoderAvgQP: 5,\n LatencyTest: 6,\n InitialSettings: 7,\n InputControlOwnership: 12,\n Protocol: 255,\n} as const;\n","import { InjectionToken } from '@angular/core';\nimport type { UnrealInitialConfig } from '../interfaces';\n\nexport const UNREAL_CONFIG = new InjectionToken<UnrealInitialConfig>(\n 'Unreal Initial Configuration',\n);\n","import { EControlSchemeType } from './EControlSchemeType';\nimport type { InputProps } from '../interfaces';\n\nexport const InputOptions: InputProps = {\n controlScheme: EControlSchemeType.Default,\n suppressBrowserKeys: true,\n fakeMouseWithTouches: false,\n};\n","export const UnrealStatusMessage = {\n CONNECTING_TO_SESSION: 'Connecting to session.',\n STARTING_YOUR_SESSION: 'Starting your session',\n} as const;\n","export const DEBOUNCE_TO_MANY_RESIZE_CALLS = 100;\nexport const SAME_SIZE_THRESHOLD = 1.01;\nexport const MINIMAL_FPS = 6;\nexport const STREAMING_VIDEO_ID = 'streamingVideo';\nexport const CONSOLE_COMMAND_ENABLE_MESSAGES = 'EnableAllScreenMessages';\nexport const CONSOLE_COMMAND_DISABLE_MESSAGES = 'DisableAllScreenMessages';\nexport const CONSOLE_COMMAND_PIXEL_QUALITY =\n 'PixelStreaming.FreezeFrameQuality 95';\nexport const FULL_HD_WIDTH = 1920;\nexport const FULL_HD_HEIGHT = 1080;\nexport const WS_TIMEOUT = 2000;\nexport const WS_OPEN_STATE = 1;\nexport const DEFAULT_TIMEOUT_PERIOD = 15;\nexport const DEFAULT_WARN_TIMEOUT = 120;\nexport const DATA_CHANNEL_CONNECTION_TIMEOUT = 8000; // 8000 ms;\nexport const SIGNALLING_PERCENT_VALUE = 56;\nexport const SCREEN_LOCKER_CONTAINER_ID = '3dsource_start_screen';\n","import {\n changeLowBandwidth,\n changeStatusMainVideoOnScene,\n changeStreamResolutionSuccessAction,\n commandCompleted,\n commandStarted,\n initSignalling,\n resetConfig,\n resetIntroSrc,\n resetUnrealState,\n resetUnrealStateAction,\n resetWarnTimeout,\n setAwsInstance,\n setCirrusConnected,\n setCirrusDisconnected,\n setConfig,\n setDataChannelConnected,\n setErrorMessage,\n setEstablishingConnection,\n setFreezeFrame,\n setFreezeFrameFromVideo,\n setIntroImageSrc,\n setIntroVideoSrc,\n setLoadingImageSrc,\n setLoopBackCommandIsCompleted,\n setMatchUrls,\n setSignalingName,\n setStatusMessage,\n setStatusPercentSignallingServer,\n setStreamClientCompanyId,\n setStreamViewId,\n setViewportReady,\n updateCirrusInfo,\n} from './unreal.actions';\nimport { createReducer, on } from '@ngrx/store';\nimport {\n AwsInstance,\n DisconnectReason,\n DisconnectType,\n FreezeFrameMessage,\n LBMStats,\n SignalingData,\n StreamConfig,\n StreamResolutionProps,\n UnrealError,\n} from '../interfaces';\nimport { DEFAULT_WARN_TIMEOUT } from '../constants';\nimport { removeExileCommands } from '../helpers';\n\nexport interface CommandsLoaderState {\n commandsInProgress: {\n timeStamp: number;\n command: string;\n id: string;\n }[];\n totalCommandsStarted: number;\n totalCommandsCompleted: number;\n}\n\nexport interface UnrealState {\n lowBandwidthStats: LBMStats | undefined;\n streamConfig: StreamConfig;\n awsInstance: AwsInstance;\n lowBandwidth: boolean;\n wasInitialized: boolean;\n isFirstSuccessLoad: boolean;\n cirrusConnected: boolean;\n viewportReady: boolean;\n freezeFrameFromVideo: FreezeFrameMessage;\n freezeFrame: FreezeFrameMessage;\n statusMessage: string | null;\n /**\n * @deprecated, use ssData in future\n */\n ssInfo: string | null;\n ssData: SignalingData | null;\n statusPercentSignallingServer: number | null;\n errorMessage: UnrealError | null;\n dataChannelConnected: boolean;\n streamResolution: StreamResolutionProps;\n isVideoPlaying: boolean;\n establishingConnection: boolean;\n disconnectReason: DisconnectType;\n loaderCommands: CommandsLoaderState;\n matchUrls: string[];\n streamClientCompanyId: string | null;\n streamViewId: string | null;\n videoIntroSrc: string | null;\n imageIntroSrc: string | null;\n imageLoadingSrc: string;\n}\n\nexport const initialState: UnrealState = {\n lowBandwidthStats: undefined,\n wasInitialized: false,\n isFirstSuccessLoad: false,\n lowBandwidth: false,\n cirrusConnected: false,\n establishingConnection: false,\n viewportReady: false,\n dataChannelConnected: false,\n isVideoPlaying: false,\n statusPercentSignallingServer: null,\n statusMessage: null,\n errorMessage: null,\n ssInfo: null,\n ssData: null,\n streamResolution: { width: null, height: null },\n freezeFrameFromVideo: { dataUrl: null, progress: null },\n freezeFrame: { dataUrl: null, progress: null },\n disconnectReason: DisconnectReason.none,\n awsInstance: {\n wsUrl: null,\n instanceName: null,\n pollingUrl: null,\n },\n streamConfig: {\n autoStart: true,\n warnTimeout: DEFAULT_WARN_TIMEOUT,\n matchMakerUrls: [],\n },\n loaderCommands: {\n commandsInProgress: [],\n totalCommandsStarted: 0,\n totalCommandsCompleted: 0,\n },\n matchUrls: [],\n streamClientCompanyId: '',\n streamViewId: 'default',\n videoIntroSrc: null,\n imageIntroSrc: null,\n imageLoadingSrc: '',\n};\nexport const unrealReducer = createReducer(\n initialState,\n on(changeLowBandwidth, (state: UnrealState, { lowBandwidth, stats }) => {\n return {\n ...state,\n lowBandwidth: lowBandwidth,\n lowBandwidthStats: lowBandwidth ? stats : undefined,\n };\n }),\n on(changeStatusMainVideoOnScene, (state: UnrealState, { isVideoPlaying }) => {\n return {\n ...state,\n isVideoPlaying: isVideoPlaying,\n };\n }),\n on(\n setAwsInstance,\n (state: UnrealState, { instanceName, wsUrl, pollingUrl }) => {\n return {\n ...state,\n awsInstance: {\n instanceName,\n wsUrl,\n pollingUrl,\n },\n };\n },\n ),\n on(setViewportReady, (state: UnrealState, { value }) => {\n return {\n ...state,\n viewportReady: value,\n statusMessage: value ? null : state.statusMessage,\n errorMessage: value ? null : state.errorMessage,\n };\n }),\n on(updateCirrusInfo, (state: UnrealState, { ssInfo, ssData }) => {\n return {\n ...state,\n ssInfo: ssInfo, // For back compatibility\n ssData: ssData, // Contains all the data from the ssInfo as object\n };\n }),\n on(\n changeStreamResolutionSuccessAction,\n (state: UnrealState, { width, height }) => {\n return {\n ...state,\n streamResolution: {\n width: width,\n height: height,\n },\n };\n },\n ),\n on(setFreezeFrame, (state: UnrealState, freezeFrame) => {\n return {\n ...state,\n freezeFrame: {\n dataUrl:\n freezeFrame.progress === 0 ||\n freezeFrame.progress === 1 ||\n freezeFrame.progress === null\n ? freezeFrame.dataUrl\n : state.freezeFrame.dataUrl,\n progress: freezeFrame.progress || null,\n },\n };\n }),\n on(setErrorMessage, (state: UnrealState, errorMessage) => {\n if (state.dataChannelConnected) {\n return state;\n }\n\n return {\n ...state,\n errorMessage: errorMessage,\n statusMessage: null,\n };\n }),\n on(setFreezeFrameFromVideo, (state: UnrealState, freezeFrameFromVideo) => {\n return {\n ...state,\n freezeFrameFromVideo: {\n dataUrl: freezeFrameFromVideo.dataUrl,\n progress: freezeFrameFromVideo.progress || null,\n },\n };\n }),\n on(setStatusMessage, (state: UnrealState, { message }) => {\n return {\n ...state,\n statusMessage: message,\n };\n }),\n on(setEstablishingConnection, (state: UnrealState, { value }) => {\n return {\n ...state,\n establishingConnection: value,\n };\n }),\n on(setStatusPercentSignallingServer, (state: UnrealState, { percent }) => {\n return {\n ...state,\n statusPercentSignallingServer: percent,\n };\n }),\n on(setDataChannelConnected, (state: UnrealState, { value }) => {\n return {\n ...state,\n dataChannelConnected: value,\n wasInitialized: value ? true : state.wasInitialized,\n };\n }),\n on(setConfig, (state: UnrealState, { config }) => {\n return {\n ...state,\n streamConfig: { ...state.streamConfig, ...config },\n };\n }),\n on(resetConfig, (state: UnrealState) => {\n return {\n ...state,\n streamConfig: initialState.streamConfig,\n };\n }),\n on(resetWarnTimeout, (state: UnrealState) => {\n return {\n ...state,\n streamConfig: {\n ...state.streamConfig,\n warnTimeout: DEFAULT_WARN_TIMEOUT,\n },\n };\n }),\n on(setCirrusConnected, (state: UnrealState) => {\n return {\n ...state,\n cirrusConnected: true,\n };\n }),\n on(setCirrusDisconnected, (state: UnrealState) => {\n return {\n ...state,\n cirrusConnected: false,\n };\n }),\n on(initSignalling, (state: UnrealState) => {\n return {\n ...state,\n disconnectReason: DisconnectReason.none,\n };\n }),\n on(setSignalingName, (state: UnrealState, { instanceName }) => {\n return {\n ...state,\n awsInstance: { ...state.awsInstance, instanceName },\n };\n }),\n on(setLoopBackCommandIsCompleted, (state) => {\n return {\n ...state,\n isFirstSuccessLoad: true,\n };\n }),\n on(setMatchUrls, (state: UnrealState, { urls }) => {\n return {\n ...state,\n matchUrls: urls,\n };\n }),\n on(setStreamClientCompanyId, (state: UnrealState, { id }) => {\n return {\n ...state,\n streamClientCompanyId: id,\n };\n }),\n on(setStreamViewId, (state: UnrealState, { id }) => {\n return {\n ...state,\n streamViewId: id,\n };\n }),\n on(setIntroImageSrc, (state: UnrealState, { src }) => {\n return {\n ...state,\n imageIntroSrc: src,\n };\n }),\n on(setLoadingImageSrc, (state: UnrealState, { src }) => {\n return {\n ...state,\n imageLoadingSrc: src,\n };\n }),\n on(setIntroVideoSrc, (state: UnrealState, { src }) => {\n return {\n ...state,\n videoIntroSrc: src,\n };\n }),\n on(resetIntroSrc, (state: UnrealState) => {\n return {\n ...state,\n imageIntroSrc: '',\n videoIntroSrc: '',\n };\n }),\n on(commandStarted, (state, { id, command }) => {\n return {\n ...state,\n loaderCommands: {\n ...state.loaderCommands,\n totalCommandsStarted: state.loaderCommands.totalCommandsStarted + 1,\n commandsInProgress: [\n ...state.loaderCommands.commandsInProgress,\n { id, command, timeStamp: new Date().getTime() },\n ],\n },\n };\n }),\n on(commandCompleted, (state, { id }) => {\n return {\n ...state,\n loaderCommands: removeExileCommands(state.loaderCommands, id),\n };\n }),\n on(resetUnrealState, (state: UnrealState) => {\n return {\n ...initialState,\n wasInitialized: state.wasInitialized,\n isFirstSuccessLoad: state.isFirstSuccessLoad,\n matchUrls: state.matchUrls,\n streamClientCompanyId: state.streamClientCompanyId,\n streamViewId: state.streamViewId,\n imageIntroSrc: state.imageIntroSrc,\n videoIntroSrc: state.videoIntroSrc,\n imageLoadingSrc: state.imageLoadingSrc,\n };\n }),\n on(resetUnrealStateAction, () => {\n return initialState;\n }),\n);\n","import { createFeature } from '@ngrx/store';\nimport { unrealReducer } from './unreal.reducer';\n\nexport const unrealFeature = createFeature({\n name: 'unrealFeature',\n reducer: unrealReducer,\n});\n","import { share, skip } from 'rxjs';\nimport { Store } from '@ngrx/store';\nimport { inject } from '@angular/core';\nimport { unrealFeature } from '../store';\nimport { filter } from 'rxjs/operators';\nimport { Falsy } from '@3dsource/utils';\n\nexport class SubService {\n store = inject(Store);\n\n disconnect$ = this.store\n .select(unrealFeature.selectCirrusConnected)\n .pipe(skip(1), filter(Falsy), share());\n}\n","import { Injectable } from '@angular/core';\nimport { distinctUntilChanged, filter, withLatestFrom } from 'rxjs/operators';\nimport {\n DEFAULT_TIMEOUT_PERIOD,\n EControlSchemeType,\n InputOptions,\n} from '../constants';\nimport { Truthy } from '@3dsource/utils';\nimport { SubService } from './sub.service';\nimport { UnrealInternalSignalEvents } from '../interfaces';\nimport {\n destroyConnectionsAndResetState,\n selectWarnTimeout,\n unrealFeature,\n} from '../store';\nimport { fromSignal, sendSignal } from '../helpers';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { merge } from 'rxjs';\n\n@Injectable()\nexport class AFKService extends SubService {\n // Optionally detect if the user is not interacting (AFK) and disconnect them.\n private enabled = true; // Set to true to enable the AFK system.\n private closeTimeout = DEFAULT_TIMEOUT_PERIOD; // The time after the warning when we disconnect the user.\n private active = false; // Whether the AFK system is currently looking for inactivity.\n private warnTimer!: ReturnType<typeof setTimeout>; // The timer which waits to show the inactivity warning overlay.\n private countdown = 0; // The inactivity warning overlay has a countdown to show time until disconnect.\n private countdownTimer!: ReturnType<typeof setTimeout>; // The timer used to tick the seconds shown on the inactivity warning overlay.\n private selectWarnTimeout = toSignal(this.store.select(selectWarnTimeout));\n\n constructor() {\n super();\n this.initAfk();\n }\n\n initAfk() {\n merge(\n this.store.select(selectWarnTimeout),\n fromSignal(UnrealInternalSignalEvents.RestAfkTimer),\n )\n .pipe(\n withLatestFrom(this.store.select(unrealFeature.selectViewportReady)),\n filter(([, viewportReady]) => viewportReady),\n )\n .subscribe(() => this.resetAfkWarningTimer());\n this.store\n .select(unrealFeature.selectViewportReady)\n .pipe(distinctUntilChanged(), filter(Truthy))\n .subscribe(() => this.startAfkWarningTimer());\n this.disconnect$.subscribe(() => this.stop());\n }\n\n init() {\n // do nothing, just to not forget to initialize Service\n }\n\n private hideOverlay() {\n sendSignal(UnrealInternalSignalEvents.ClickableOverlay);\n }\n\n /**\n * Start a timer which when elapsed will warn the user they are inactive.\n */\n private startAfkWarningTimer() {\n this.active = this.enabled;\n this.resetAfkWarningTimer();\n }\n\n /**\n * If the user interacts, then reset the warning timer.\n */\n private resetAfkWarningTimer() {\n if (this.active) {\n this.stop();\n this.warnTimer = setTimeout(() => {\n this.showAfkOverlay();\n }, this.selectWarnTimeout()! * 1000);\n }\n }\n\n /**\n * Update the count-down spans number for the overlay\n * @param countdown the count down number to be inserted into the span for updating\n */\n private updateCountDown(countdown: number) {\n this.dispatchMessage(String(countdown));\n }\n\n /**\n * Update the text overlays inner text\n * @param message the update text to be inserted into the overlay\n */\n private dispatchMessage(message: string) {\n sendSignal(UnrealInternalSignalEvents.ClickableOverlay, {\n message,\n isActivityDetected: true,\n className: 'clickableState',\n onOverlayClick: () => this.reset(),\n });\n }\n\n private stop() {\n clearInterval(this.countdownTimer);\n clearTimeout(this.warnTimer);\n this.hideOverlay();\n }\n\n private reset() {\n this.hideOverlay();\n clearInterval(this.countdownTimer);\n this.startAfkWarningTimer();\n }\n\n private showAfkOverlay() {\n // Pause the timer while the user is looking at the inactivity warning overlay.\n this.active = false;\n this.countdown = this.closeTimeout;\n this.updateCountDown(this.countdown);\n\n if (InputOptions.controlScheme == EControlSchemeType.LockedMouse) {\n document.exitPointerLock();\n }\n\n this.countdownTimer = setInterval(() => {\n this.countdown--;\n if (this.countdown === 0) {\n // The user failed to click so disconnect them\n this.hideOverlay();\n // TODO possible way (Blinking), because postponed close event: destroyRemoteConnections({ disconnectReason: DisconnectReason.afk }),\n this.store.dispatch(destroyConnectionsAndResetState());\n clearInterval(this.countdownTimer);\n } else {\n this.updateCountDown(this.countdown);\n }\n }, 1000);\n }\n}\n","import { filter } from 'rxjs/operators';\nimport { Injectable } from '@angular/core';\nimport { SubService } from './sub.service';\nimport { Logger, Truthy } from '@3dsource/utils';\nimport { setFreezeFrame } from '../store';\nimport { UnrealInternalSignalEvents } from '../interfaces';\nimport { sendSignal } from '../helpers';\nimport { unrealFeature } from '../store';\n\n@Injectable()\nexport class FreezeFrameService extends SubService {\n receiving = false;\n\n private size = 0;\n private jpeg!: Uint8Array | undefined;\n private freezeFrameOverlay = new Image();\n\n init() {\n this.store\n .select(unrealFeature.selectViewportReady)\n .pipe(filter(Truthy))\n .subscribe(() => this.invalidate());\n }\n\n setData(view: Uint8Array) {\n view = view.slice(1 + 4);\n\n const jpeg = new Uint8Array(this.jpeg!.length + view.length);\n jpeg.set(this.jpeg!, 0);\n jpeg.set(view, this.jpeg!.length);\n this.jpeg = jpeg;\n if (this.jpeg.length === this.size) {\n this.receiving = false;\n Logger.info(`FRAME: Received complete freeze frame ${this.size}`);\n this.showFreezeFrame();\n } else if (this.jpeg.length > this.size) {\n Logger.error(\n `FRAME: Received bigger freeze frame than advertised: ${this.jpeg.length}/${this.size}`,\n );\n this.store.dispatch(setFreezeFrame({ dataUrl: null, progress: 0 }));\n\n this.jpeg = undefined;\n this.receiving = false;\n } else {\n this.dispatchInProgress();\n /* Logger.warn(\n `received next chunk (${view.length} bytes) of freeze frame: ${this.jpeg.length}/${this.size}`,\n );*/\n }\n }\n\n private dispatchInProgress() {\n this.store.dispatch(\n setFreezeFrame({\n dataUrl: null,\n progress: (this.jpeg?.length || 0) / this.size,\n }),\n );\n }\n\n start(view: Uint8Array) {\n this.size = new DataView(view.slice(1, 5).buffer).getInt32(0, true);\n this.jpeg = view.slice(1 + 4);\n if (this.jpeg.length < this.size) {\n this.dispatchInProgress();\n sendSignal(UnrealInternalSignalEvents.FreezeFrameStart);\n Logger.info(\n `FRAME: Received first chunk of freeze frame: ${this.jpeg.length}/${this.size}`,\n );\n this.receiving = true;\n } else {\n Logger.info(\n `FRAME: Received complete freeze frame: ${this.jpeg.length}/${this.size}`,\n );\n this.receiving = false;\n this.showFreezeFrame();\n }\n }\n\n invalidate() {\n this.store.dispatch(setFreezeFrame({ dataUrl: null, progress: null }));\n this.jpeg = undefined;\n this.receiving = false;\n }\n\n private showFreezeFrame() {\n if (!this.jpeg) {\n return;\n }\n\n const base64 = btoa(\n this.jpeg?.reduce((data, byte) => data + String.fromCharCode(byte), ''),\n );\n this.freezeFrameOverlay.src = 'data:image/jpeg;base64,' + base64;\n this.store.dispatch(\n setFreezeFrame({\n dataUrl: this.freezeFrameOverlay.src,\n progress: 1,\n }),\n );\n }\n}\n","import type { Quality } from '../interfaces';\nimport { calculateMedian, clampf } from '@3dsource/utils';\nimport type { FilterSettings } from '../components/low-bandwidth-indicator/DataFlowConstants';\n\nexport interface DataFlowCheckResult {\n isDropDetected: boolean;\n dropPercentage: number;\n activeMedian: number;\n quality: Quality;\n message: string;\n dataHistory: number[];\n config: {\n yellowFlagThresholdPercentage: number;\n redFlagThresholdPercentage: number;\n historyBufferLength: number;\n splitPoint: number;\n };\n}\n\nexport class DataFlowMonitor {\n protected dataHistory: number[] = [];\n\n /**\n * Initializes the DataFlowMonitor monitor.\n * @param yellowFlagThresholdPercentage - The percentage drop to trigger a YELLOW warning (default: 15%).\n * @param redFlagThresholdPercentage - The percentage drop to trigger a RED warning (default: 30%).\n * @param historyBufferLength - buffer length (default: 100).\n * @param splitPoint - The point at which to split the history buffer into two halves (default: 0.5).\n */\n constructor(\n protected yellowFlagThresholdPercentage = 15,\n protected redFlagThresholdPercentage = 30,\n protected historyBufferLength = 100,\n protected splitPoint = 0.5,\n ) {}\n\n reset() {\n this.dataHistory.length = 0;\n }\n\n config(data: FilterSettings) {\n this.reset();\n this.yellowFlagThresholdPercentage = data.yellowFlag;\n this.redFlagThresholdPercentage = data.redFlag;\n }\n\n /**\n * Adds a new bitrate measurement and checks for significant drops.\n * @param currentValue - The current bitrate in kbps.\n * @returns BitrateCheckResult indicating if a drop was detected.\n */\n addValue(currentValue: number): DataFlowCheckResult {\n if (isNaN(currentValue) || currentValue < 1) {\n return {} as DataFlowCheckResult;\n }\n const historyLength = this.dataHistory.length;\n\n if (historyLength > this.historyBufferLength) {\n this.dataHistory.shift();\n }\n this.dataHistory.push(currentValue);\n\n if (historyLength < this.historyBufferLength) {\n this.dataHistory.length = this.historyBufferLength;\n this.dataHistory.fill(currentValue, 0, this.historyBufferLength);\n }\n\n const splitIndex = Math.floor(historyLength * this.splitPoint);\n const firstHalf = this.dataHistory.slice(0, splitIndex);\n const secondHalf = this.dataHistory.slice(splitIndex);\n const firstHalfMedian = calculateMedian(firstHalf);\n const secondHalfMedian = calculateMedian(secondHalf);\n\n const dropPercentage = clampf(\n 0,\n 100,\n ((firstHalfMedian - secondHalfMedian) / firstHalfMedian) * 100,\n );\n\n const isRedFlag = dropPercentage >= this.redFlagThresholdPercentage;\n if (dropPercentage >= this.yellowFlagThresholdPercentage) {\n return {\n config: {\n yellowFlagThresholdPercentage: this.yellowFlagThresholdPercentage,\n redFlagThresholdPercentage: this.redFlagThresholdPercentage,\n historyBufferLength: this.historyBufferLength,\n splitPoint: this.splitPoint,\n },\n isDropDetected: true,\n dropPercentage,\n dataHistory: [...this.dataHistory],\n activeMedian: secondHalfMedian,\n quality: isRedFlag ? 'red' : 'orange',\n message: `Significant flow drop detected: ${dropPercentage.toFixed(2)}% (from ${firstHalfMedian} to ${secondHalfMedian})`,\n };\n }\n\n return {\n config: {\n yellowFlagThresholdPercentage: this.yellowFlagThresholdPercentage,\n redFlagThresholdPercentage: this.redFlagThresholdPercentage,\n historyBufferLength: this.historyBufferLength,\n splitPoint: this.splitPoint,\n },\n isDropDetected: false,\n dropPercentage,\n dataHistory: [...this.dataHistory],\n activeMedian: secondHalfMedian,\n quality: 'lime',\n message: 'Stable flow',\n };\n }\n}\n","import { DataFlowMonitor } from '../../helpers/DataFlowMonitor';\n\nexport interface FilterSettings {\n /**\n * Minimum number of kilobits per second to trigger low bandwidth\n */\n minimumBitrate: number;\n /**\n * Amount of a percentage drop to trigger a yellow warning\n */\n\n yellowFlag: number;\n\n /**\n * Amount of a percentage drop to trigger a red warning\n */\n redFlag: number;\n\n /**\n * Minimum number of frames per second to trigger low bandwidth\n */\n minimumFps: number;\n\n /**\n * Time to wait before checking if we can switch to low bandwidth\n */\n monitoringDelayTime: number;\n\n // initial guess in kbps\n initialBitrateEstimate: number;\n\n // initial error covariance\n initialErrorCovariance: number;\n\n // Q: how much we expect the bitrate to change between measurements\n processNoise: number;\n\n // R: variance of measurement noise\n measurementNoise: number;\n\n panelOpen: boolean;\n}\n\n/**\n * Default LBM Filter Parameters\n */\nexport const DefaultFilterModel: FilterSettings = {\n minimumBitrate: 1600,\n yellowFlag: 30,\n redFlag: 50,\n minimumFps: 5,\n monitoringDelayTime: 5000,\n initialBitrateEstimate: 1600,\n initialErrorCovariance: 1,\n processNoise: 3,\n measurementNoise: 500,\n panelOpen: false,\n} as const;\n\n/**\n * Global LBM Filter Parameters\n */\nexport const FilterModel = { ...DefaultFilterModel };\n\n/**\n * Bitrate Monitor Static Class\n */\nexport const BITRATE_MONITOR = new DataFlowMonitor(\n FilterModel.yellowFlag,\n FilterModel.redFlag,\n 20,\n);\n","import { from, fromEvent, interval, map, share, Subject, take } from 'rxjs';\nimport { SubService } from './sub.service';\nimport { Injectable } from '@angular/core';\nimport {\n type IAggregatedStat,\n type InboundVideoStats,\n UnrealInternalSignalEvents,\n} from '../interfaces';\nimport { catchError, filter, first, switchMap } from 'rxjs/operators';\nimport {\n dispatchResize,\n KalmanFilter1D,\n LatencyTimings,\n mapQpToQuality,\n sendSignal,\n} f