@epicgames-ps/lib-pixelstreamingfrontend-ue5.4
Version:
Frontend library for Unreal Engine 5.4 Pixel Streaming
999 lines (917 loc) • 35.5 kB
text/typescript
// Copyright Epic Games, Inc. All Rights Reserved.
import { Logger } from '../Logger/Logger';
import { SettingFlag } from './SettingFlag';
import { SettingNumber } from './SettingNumber';
import { SettingText } from './SettingText';
import { SettingOption } from './SettingOption';
import { EventEmitter, SettingsChangedEvent } from '../Util/EventEmitter';
import { SettingBase } from './SettingBase';
/**
* A collection of flags that can be toggled and are core to all Pixel Streaming experiences.
* These are used in the `Config.Flags` map.
*/
export class Flags {
static AutoConnect = 'AutoConnect' as const;
static AutoPlayVideo = 'AutoPlayVideo' as const;
static AFKDetection = 'TimeoutIfIdle' as const;
static BrowserSendOffer = 'OfferToReceive' as const;
static HoveringMouseMode = 'HoveringMouse' as const;
static ForceMonoAudio = 'ForceMonoAudio' as const;
static ForceTURN = 'ForceTURN' as const;
static FakeMouseWithTouches = 'FakeMouseWithTouches' as const;
static IsQualityController = 'ControlsQuality' as const;
static MatchViewportResolution = 'MatchViewportRes' as const;
static StartVideoMuted = 'StartVideoMuted' as const;
static SuppressBrowserKeys = 'SuppressBrowserKeys' as const;
static UseMic = 'UseMic' as const;
static KeyboardInput = 'KeyboardInput' as const;
static MouseInput = 'MouseInput' as const;
static TouchInput = 'TouchInput' as const;
static GamepadInput = 'GamepadInput' as const;
static XRControllerInput = 'XRControllerInput' as const;
static WaitForStreamer = 'WaitForStreamer' as const;
static HideUI = 'HideUI' as const;
}
export type FlagsKeys = Exclude<keyof typeof Flags, 'prototype'>;
export type FlagsIds = typeof Flags[FlagsKeys];
const isFlagId = (id: string): id is FlagsIds =>
Object.getOwnPropertyNames(Flags).some(
(name: FlagsKeys) => Flags[name] === id
);
/**
* A collection of numeric parameters that are core to all Pixel Streaming experiences.
*
*/
export class NumericParameters {
static AFKTimeoutSecs = 'AFKTimeout' as const;
static AFKCountdownSecs = 'AFKCountdown' as const;
static MinQP = 'MinQP' as const;
static MaxQP = 'MaxQP' as const;
static WebRTCFPS = 'WebRTCFPS' as const;
static WebRTCMinBitrate = 'WebRTCMinBitrate' as const;
static WebRTCMaxBitrate = 'WebRTCMaxBitrate' as const;
static MaxReconnectAttempts = 'MaxReconnectAttempts' as const;
static StreamerAutoJoinInterval = 'StreamerAutoJoinInterval' as const;
}
export type NumericParametersKeys = Exclude<
keyof typeof NumericParameters,
'prototype'
>;
export type NumericParametersIds =
typeof NumericParameters[NumericParametersKeys];
const isNumericId = (id: string): id is NumericParametersIds =>
Object.getOwnPropertyNames(NumericParameters).some(
(name: NumericParametersKeys) => NumericParameters[name] === id
);
/**
* A collection of textual parameters that are core to all Pixel Streaming experiences.
*
*/
export class TextParameters {
static SignallingServerUrl = 'ss' as const;
}
export type TextParametersKeys = Exclude<
keyof typeof TextParameters,
'prototype'
>;
export type TextParametersIds = typeof TextParameters[TextParametersKeys];
const isTextId = (id: string): id is TextParametersIds =>
Object.getOwnPropertyNames(TextParameters).some(
(name: TextParametersKeys) => TextParameters[name] === id
);
/**
* A collection of enum based parameters that are core to all Pixel Streaming experiences.
*
*/
export class OptionParameters {
static PreferredCodec = 'PreferredCodec' as const;
static StreamerId = 'StreamerId' as const;
}
export type OptionParametersKeys = Exclude<
keyof typeof OptionParameters,
'prototype'
>;
export type OptionParametersIds = typeof OptionParameters[OptionParametersKeys];
const isOptionId = (id: string): id is OptionParametersIds =>
Object.getOwnPropertyNames(OptionParameters).some(
(name: OptionParametersKeys) => OptionParameters[name] === id
);
/**
* Utility types for inferring data type based on setting ID
*/
export type OptionIds =
| FlagsIds
| NumericParametersIds
| TextParametersIds
| OptionParametersIds;
export type OptionKeys<T> = T extends FlagsIds
? boolean
: T extends NumericParametersIds
? number
: T extends TextParametersIds
? string
: T extends OptionParametersIds
? string
: never;
export type AllSettings = {
[K in OptionIds]: OptionKeys<K>;
};
export interface ConfigParams {
/** Initial Pixel Streaming settings */
initialSettings?: Partial<AllSettings>;
/** If useUrlParams is set true, will read initial values from URL parameters and persist changed settings into URL */
useUrlParams?: boolean;
}
export class Config {
/* A map of flags that can be toggled - options that can be set in the application - e.g. Use Mic? */
private flags = new Map<FlagsIds, SettingFlag>();
/* A map of numerical settings - options that can be in the application - e.g. MinBitrate */
private numericParameters = new Map<NumericParametersIds, SettingNumber>();
/* A map of text settings - e.g. signalling server url */
private textParameters = new Map<TextParametersIds, SettingText>();
/* A map of enum based settings - e.g. preferred codec */
private optionParameters = new Map<OptionParametersIds, SettingOption>();
private _useUrlParams: boolean;
// ------------ Settings -----------------
constructor(config: ConfigParams = {}) {
const { initialSettings, useUrlParams } = config;
this._useUrlParams = !!useUrlParams;
this.populateDefaultSettings(this._useUrlParams, initialSettings);
}
/**
* True if reading configuration initial values from URL parameters, and
* persisting changes in URL when changed.
*/
public get useUrlParams() {
return this._useUrlParams;
}
/**
* Populate the default settings for a Pixel Streaming application
*/
private populateDefaultSettings(useUrlParams: boolean, settings: Partial<AllSettings>): void {
/**
* Text Parameters
*/
this.textParameters.set(
TextParameters.SignallingServerUrl,
new SettingText(
TextParameters.SignallingServerUrl,
'Signalling url',
'Url of the signalling server',
settings && settings.hasOwnProperty(TextParameters.SignallingServerUrl) ?
settings[TextParameters.SignallingServerUrl] :
(location.protocol === 'https:' ? 'wss://' : 'ws://') +
window.location.hostname +
// for readability, we omit the port if it's 80
(window.location.port === '80' ||
window.location.port === ''
? ''
: `:${window.location.port}`),
useUrlParams
)
);
this.optionParameters.set(
OptionParameters.StreamerId,
new SettingOption(
OptionParameters.StreamerId,
'Streamer ID',
'The ID of the streamer to stream.',
settings && settings.hasOwnProperty(OptionParameters.StreamerId) ?
settings[OptionParameters.StreamerId] :
'',
[],
useUrlParams
)
);
/**
* Enum Parameters
*/
this.optionParameters.set(
OptionParameters.PreferredCodec,
new SettingOption(
OptionParameters.PreferredCodec,
'Preferred Codec',
'The preferred codec to be used during codec negotiation',
'H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
settings && settings.hasOwnProperty(OptionParameters.PreferredCodec) ?
[settings[OptionParameters.PreferredCodec]] :
(function (): Array<string> {
const browserSupportedCodecs: Array<string> = [];
// Try get the info needed from the RTCRtpReceiver. This is only available on chrome
if (!RTCRtpReceiver.getCapabilities) {
browserSupportedCodecs.push('Only available on Chrome');
return browserSupportedCodecs;
}
const matcher = /(VP\d|H26\d|AV1).*/;
const codecs =
RTCRtpReceiver.getCapabilities('video').codecs;
codecs.forEach((codec) => {
const str =
codec.mimeType.split('/')[1] +
' ' +
(codec.sdpFmtpLine || '');
const match = matcher.exec(str);
if (match !== null) {
browserSupportedCodecs.push(str);
}
});
return browserSupportedCodecs;
})(),
useUrlParams
)
);
/**
* Boolean parameters
*/
this.flags.set(
Flags.AutoConnect,
new SettingFlag(
Flags.AutoConnect,
'Auto connect to stream',
'Whether we should attempt to auto connect to the signalling server or show a click to start prompt.',
settings && settings.hasOwnProperty(Flags.AutoConnect) ?
settings[Flags.AutoConnect] :
false,
useUrlParams
)
);
this.flags.set(
Flags.AutoPlayVideo,
new SettingFlag(
Flags.AutoPlayVideo,
'Auto play video',
'When video is ready automatically start playing it as opposed to showing a play button.',
settings && settings.hasOwnProperty(Flags.AutoPlayVideo) ?
settings[Flags.AutoPlayVideo] :
true,
useUrlParams
)
);
this.flags.set(
Flags.BrowserSendOffer,
new SettingFlag(
Flags.BrowserSendOffer,
'Browser send offer',
'Browser will initiate the WebRTC handshake by sending the offer to the streamer',
settings && settings.hasOwnProperty(Flags.BrowserSendOffer) ?
settings[Flags.BrowserSendOffer] :
false,
useUrlParams
)
);
this.flags.set(
Flags.UseMic,
new SettingFlag(
Flags.UseMic,
'Use microphone',
'Make browser request microphone access and open an input audio track.',
settings && settings.hasOwnProperty(Flags.UseMic) ?
settings[Flags.UseMic] :
false,
useUrlParams
)
);
this.flags.set(
Flags.StartVideoMuted,
new SettingFlag(
Flags.StartVideoMuted,
'Start video muted',
'Video will start muted if true.',
settings && settings.hasOwnProperty(Flags.StartVideoMuted) ?
settings[Flags.StartVideoMuted] :
false,
useUrlParams
)
);
this.flags.set(
Flags.SuppressBrowserKeys,
new SettingFlag(
Flags.SuppressBrowserKeys,
'Suppress browser keys',
'Suppress certain browser keys that we use in UE, for example F5 to show shader complexity instead of refresh the page.',
settings && settings.hasOwnProperty(Flags.SuppressBrowserKeys) ?
settings[Flags.SuppressBrowserKeys] :
true,
useUrlParams
)
);
this.flags.set(
Flags.IsQualityController,
new SettingFlag(
Flags.IsQualityController,
'Is quality controller?',
'True if this peer controls stream quality',
settings && settings.hasOwnProperty(Flags.IsQualityController) ?
settings[Flags.IsQualityController] :
true,
useUrlParams
)
);
this.flags.set(
Flags.ForceMonoAudio,
new SettingFlag(
Flags.ForceMonoAudio,
'Force mono audio',
'Force browser to request mono audio in the SDP',
settings && settings.hasOwnProperty(Flags.ForceMonoAudio) ?
settings[Flags.ForceMonoAudio] :
false,
useUrlParams
)
);
this.flags.set(
Flags.ForceTURN,
new SettingFlag(
Flags.ForceTURN,
'Force TURN',
'Only generate TURN/Relayed ICE candidates.',
settings && settings.hasOwnProperty(Flags.ForceTURN) ?
settings[Flags.ForceTURN] :
false,
useUrlParams
)
);
this.flags.set(
Flags.AFKDetection,
new SettingFlag(
Flags.AFKDetection,
'AFK if idle',
'Timeout the experience if user is AFK for a period.',
settings && settings.hasOwnProperty(Flags.AFKDetection) ?
settings[Flags.AFKDetection] :
false,
useUrlParams
)
);
this.flags.set(
Flags.MatchViewportResolution,
new SettingFlag(
Flags.MatchViewportResolution,
'Match viewport resolution',
'Pixel Streaming will be instructed to dynamically resize the video stream to match the size of the video element.',
settings && settings.hasOwnProperty(Flags.MatchViewportResolution) ?
settings[Flags.MatchViewportResolution] :
false,
useUrlParams
)
);
this.flags.set(
Flags.HoveringMouseMode,
new SettingFlag(
Flags.HoveringMouseMode,
'Control Scheme: Locked Mouse',
'Either locked mouse, where the pointer is consumed by the video and locked to it, or hovering mouse, where the mouse is not consumed.',
settings && settings.hasOwnProperty(Flags.HoveringMouseMode) ?
settings[Flags.HoveringMouseMode] :
false,
useUrlParams,
(isHoveringMouse: boolean, setting: SettingBase) => {
setting.label = `Control Scheme: ${isHoveringMouse ? 'Hovering' : 'Locked'} Mouse`;
}
)
);
this.flags.set(
Flags.FakeMouseWithTouches,
new SettingFlag(
Flags.FakeMouseWithTouches,
'Fake mouse with touches',
'A single finger touch is converted into a mouse event. This allows a non-touch application to be controlled partially via a touch device.',
settings && settings.hasOwnProperty(Flags.FakeMouseWithTouches) ?
settings[Flags.FakeMouseWithTouches] :
false,
useUrlParams
)
);
this.flags.set(
Flags.KeyboardInput,
new SettingFlag(
Flags.KeyboardInput,
'Keyboard input',
'If enabled, send keyboard events to streamer',
settings && settings.hasOwnProperty(Flags.KeyboardInput) ?
settings[Flags.KeyboardInput] :
true,
useUrlParams
)
);
this.flags.set(
Flags.MouseInput,
new SettingFlag(
Flags.MouseInput,
'Mouse input',
'If enabled, send mouse events to streamer',
settings && settings.hasOwnProperty(Flags.MouseInput) ?
settings[Flags.MouseInput] :
true,
useUrlParams
)
);
this.flags.set(
Flags.TouchInput,
new SettingFlag(
Flags.TouchInput,
'Touch input',
'If enabled, send touch events to streamer',
settings && settings.hasOwnProperty(Flags.TouchInput) ?
settings[Flags.TouchInput] :
true,
useUrlParams
)
);
this.flags.set(
Flags.GamepadInput,
new SettingFlag(
Flags.GamepadInput,
'Gamepad input',
'If enabled, send gamepad events to streamer',
settings && settings.hasOwnProperty(Flags.GamepadInput) ?
settings[Flags.GamepadInput] :
true,
useUrlParams
)
);
this.flags.set(
Flags.XRControllerInput,
new SettingFlag(
Flags.XRControllerInput,
'XR controller input',
'If enabled, send XR controller events to streamer',
settings && settings.hasOwnProperty(Flags.XRControllerInput) ?
settings[Flags.XRControllerInput] :
true,
useUrlParams
)
);
this.flags.set(
Flags.WaitForStreamer,
new SettingFlag(
Flags.WaitForStreamer,
'Wait for streamer',
'Will continue trying to connect to the first streamer available.',
settings && settings.hasOwnProperty(Flags.WaitForStreamer) ?
settings[Flags.WaitForStreamer] :
true,
useUrlParams
)
);
this.flags.set(
Flags.HideUI,
new SettingFlag(
Flags.HideUI,
'Hide the UI overlay',
'Will hide all UI overlay details',
settings && settings.hasOwnProperty(Flags.HideUI) ?
settings[Flags.HideUI] :
false,
useUrlParams
)
);
/**
* Numeric parameters
*/
this.numericParameters.set(
NumericParameters.AFKTimeoutSecs,
new SettingNumber(
NumericParameters.AFKTimeoutSecs,
'AFK timeout',
'The time (in seconds) it takes for the application to time out if AFK timeout is enabled.',
0 /*min*/,
null /*max*/,
settings && settings.hasOwnProperty(NumericParameters.AFKTimeoutSecs) ?
settings[NumericParameters.AFKTimeoutSecs] :
120, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.AFKCountdownSecs,
new SettingNumber(
NumericParameters.AFKCountdownSecs,
'AFK countdown',
'The time (in seconds) for a user to respond before the stream is ended after an AFK timeout.',
10 /*min*/,
null /*max*/,
10 /*value*/,
useUrlParams
)
)
this.numericParameters.set(
NumericParameters.MaxReconnectAttempts,
new SettingNumber(
NumericParameters.MaxReconnectAttempts,
'Max Reconnects',
'Maximum number of reconnects the application will attempt when a streamer disconnects.',
0 /*min*/,
999 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.MaxReconnectAttempts) ?
settings[NumericParameters.MaxReconnectAttempts] :
3, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.MinQP,
new SettingNumber(
NumericParameters.MinQP,
'Min QP',
'The lower bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.',
0 /*min*/,
51 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.MinQP) ?
settings[NumericParameters.MinQP] :
0, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.MaxQP,
new SettingNumber(
NumericParameters.MaxQP,
'Max QP',
'The upper bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.',
0 /*min*/,
51 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.MaxQP) ?
settings[NumericParameters.MaxQP] :
51, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.WebRTCFPS,
new SettingNumber(
NumericParameters.WebRTCFPS,
'Max FPS',
'The maximum FPS that WebRTC will try to transmit frames at.',
1 /*min*/,
999 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.WebRTCFPS) ?
settings[NumericParameters.WebRTCFPS] :
60, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.WebRTCMinBitrate,
new SettingNumber(
NumericParameters.WebRTCMinBitrate,
'Min Bitrate (kbps)',
'The minimum bitrate that WebRTC should use.',
0 /*min*/,
500000 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.WebRTCMinBitrate) ?
settings[NumericParameters.WebRTCMinBitrate] :
0, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.WebRTCMaxBitrate,
new SettingNumber(
NumericParameters.WebRTCMaxBitrate,
'Max Bitrate (kbps)',
'The maximum bitrate that WebRTC should use.',
0 /*min*/,
500000 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.WebRTCMaxBitrate) ?
settings[NumericParameters.WebRTCMaxBitrate] :
0, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.StreamerAutoJoinInterval,
new SettingNumber(
NumericParameters.StreamerAutoJoinInterval,
'Streamer Auto Join Interval (ms)',
'Delay between retries when waiting for an available streamer.',
500 /*min*/,
900000 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.StreamerAutoJoinInterval) ?
settings[NumericParameters.StreamerAutoJoinInterval] :
3000, /*value*/
useUrlParams
)
);
}
/**
* Add a callback to fire when the numeric setting is toggled.
* @param id The id of the flag.
* @param onChangedListener The callback to fire when the numeric value changes.
*/
_addOnNumericSettingChangedListener(
id: NumericParametersIds,
onChangedListener: (newValue: number) => void
): void {
if (this.numericParameters.has(id)) {
this.numericParameters
.get(id)
.addOnChangedListener(onChangedListener);
}
}
_addOnOptionSettingChangedListener(
id: OptionParametersIds,
onChangedListener: (newValue: string) => void
): void {
if (this.optionParameters.has(id)) {
this.optionParameters
.get(id)
.addOnChangedListener(onChangedListener);
}
}
/**
* @param id The id of the numeric setting we are interested in getting a value for.
* @returns The numeric value stored in the parameter with the passed id.
*/
getNumericSettingValue(id: NumericParametersIds): number {
if (this.numericParameters.has(id)) {
return this.numericParameters.get(id).number;
} else {
throw new Error(`There is no numeric setting with the id of ${id}`);
}
}
/**
* @param id The id of the text setting we are interested in getting a value for.
* @returns The text value stored in the parameter with the passed id.
*/
getTextSettingValue(id: TextParametersIds): string {
if (this.textParameters.has(id)) {
return this.textParameters.get(id).value as string;
} else {
throw new Error(`There is no numeric setting with the id of ${id}`);
}
}
/**
* Set number in the setting.
* @param id The id of the numeric setting we are interested in.
* @param value The numeric value to set.
*/
setNumericSetting(id: NumericParametersIds, value: number): void {
if (this.numericParameters.has(id)) {
this.numericParameters.get(id).number = value;
} else {
throw new Error(`There is no numeric setting with the id of ${id}`);
}
}
/**
* Add a callback to fire when the flag is toggled.
* @param id The id of the flag.
* @param onChangeListener The callback to fire when the value changes.
*/
_addOnSettingChangedListener(
id: FlagsIds,
onChangeListener: (newFlagValue: boolean) => void
): void {
if (this.flags.has(id)) {
this.flags.get(id).onChange = onChangeListener;
}
}
/**
* Add a callback to fire when the text is changed.
* @param id The id of the flag.
* @param onChangeListener The callback to fire when the value changes.
*/
_addOnTextSettingChangedListener(
id: TextParametersIds,
onChangeListener: (newTextValue: string) => void
): void {
if (this.textParameters.has(id)) {
this.textParameters.get(id).onChange = onChangeListener;
}
}
/**
* Get the option which has the given id.
* @param id The id of the option.
* @returns The SettingOption object matching id
*/
getSettingOption(id: OptionParametersIds): SettingOption {
return this.optionParameters.get(id);
}
/**
* Get the value of the configuration flag which has the given id.
* @param id The unique id for the flag.
* @returns True if the flag is enabled.
*/
isFlagEnabled(id: FlagsIds): boolean {
return this.flags.get(id).flag as boolean;
}
/**
* Set flag to be enabled/disabled.
* @param id The id of the flag to toggle.
* @param flagEnabled True if the flag should be enabled.
*/
setFlagEnabled(id: FlagsIds, flagEnabled: boolean) {
if (!this.flags.has(id)) {
Logger.Warning(
Logger.GetStackTrace(),
`Cannot toggle flag called ${id} - it does not exist in the Config.flags map.`
);
} else {
this.flags.get(id).flag = flagEnabled;
}
}
/**
* Set the text setting.
* @param id The id of the setting
* @param settingValue The value to set in the setting.
*/
setTextSetting(id: TextParametersIds, settingValue: string) {
if (!this.textParameters.has(id)) {
Logger.Warning(
Logger.GetStackTrace(),
`Cannot set text setting called ${id} - it does not exist in the Config.textParameters map.`
);
} else {
this.textParameters.get(id).text = settingValue;
}
}
/**
* Set the option setting list of options.
* @param id The id of the setting
* @param settingOptions The values the setting could take
*/
setOptionSettingOptions(
id: OptionParametersIds,
settingOptions: Array<string>
) {
if (!this.optionParameters.has(id)) {
Logger.Warning(
Logger.GetStackTrace(),
`Cannot set text setting called ${id} - it does not exist in the Config.optionParameters map.`
);
} else {
this.optionParameters.get(id).options = settingOptions;
}
}
/**
* Set option enum settings selected option.
* @param id The id of the setting
* @param settingOptions The value to select out of all the options
*/
setOptionSettingValue(id: OptionParametersIds, settingValue: string) {
if (!this.optionParameters.has(id)) {
Logger.Warning(
Logger.GetStackTrace(),
`Cannot set text setting called ${id} - it does not exist in the Config.enumParameters map.`
);
} else {
const optionSetting = this.optionParameters.get(id);
const existingOptions = optionSetting.options;
if (!existingOptions.includes(settingValue)) {
existingOptions.push(settingValue);
optionSetting.options = existingOptions;
}
optionSetting.selected = settingValue;
}
}
/**
* Set the label for the flag.
* @param id The id of the flag.
* @param label The new label to use for the flag.
*/
setFlagLabel(id: FlagsIds, label: string) {
if (!this.flags.has(id)) {
Logger.Warning(
Logger.GetStackTrace(),
`Cannot set label for flag called ${id} - it does not exist in the Config.flags map.`
);
} else {
this.flags.get(id).label = label;
}
}
/**
* Set a subset of all settings in one function call.
*
* @param settings A (partial) list of settings to set
*/
setSettings(settings: Partial<AllSettings>) {
for (const key of Object.keys(settings)) {
if (isFlagId(key)) {
this.setFlagEnabled(key, settings[key]);
} else if (isNumericId(key)) {
this.setNumericSetting(key, settings[key]);
} else if (isTextId(key)) {
this.setTextSetting(key, settings[key]);
} else if (isOptionId(key)) {
this.setOptionSettingValue(key, settings[key]);
}
}
}
/**
* Get all settings
* @returns All setting values as an object with setting ids as keys
*/
getSettings(): Partial<AllSettings> {
const settings: Partial<AllSettings> = {};
for (const [key, value] of this.flags.entries()) {
settings[key] = value.flag;
}
for (const [key, value] of this.numericParameters.entries()) {
settings[key] = value.number;
}
for (const [key, value] of this.textParameters.entries()) {
settings[key] = value.text;
}
for (const [key, value] of this.optionParameters.entries()) {
settings[key] = value.selected;
}
return settings;
}
/**
* Get all Flag settings as an array.
* @returns All SettingFlag objects
*/
getFlags(): Array<SettingFlag> {
return Array.from(this.flags.values());
}
/**
* Get all Text settings as an array.
* @returns All SettingText objects
*/
getTextSettings(): Array<SettingText> {
return Array.from(this.textParameters.values());
}
/**
* Get all Number settings as an array.
* @returns All SettingNumber objects
*/
getNumericSettings(): Array<SettingNumber> {
return Array.from(this.numericParameters.values());
}
/**
* Get all Option settings as an array.
* @returns All SettingOption objects
*/
getOptionSettings(): Array<SettingOption> {
return Array.from(this.optionParameters.values());
}
/**
* Emit events when settings change.
* @param eventEmitter
*/
_registerOnChangeEvents(eventEmitter: EventEmitter) {
for (const key of this.flags.keys()) {
const flag = this.flags.get(key);
if (flag) {
flag.onChangeEmit = (newValue: boolean) =>
eventEmitter.dispatchEvent(
new SettingsChangedEvent({
id: flag.id,
type: 'flag',
value: newValue,
target: flag
})
);
}
}
for (const key of this.numericParameters.keys()) {
const number = this.numericParameters.get(key);
if (number) {
number.onChangeEmit = (newValue: number) =>
eventEmitter.dispatchEvent(
new SettingsChangedEvent({
id: number.id,
type: 'number',
value: newValue,
target: number
})
);
}
}
for (const key of this.textParameters.keys()) {
const text = this.textParameters.get(key);
if (text) {
text.onChangeEmit = (newValue: string) =>
eventEmitter.dispatchEvent(
new SettingsChangedEvent({
id: text.id,
type: 'text',
value: newValue,
target: text
})
);
}
}
for (const key of this.optionParameters.keys()) {
const option = this.optionParameters.get(key);
if (option) {
option.onChangeEmit = (newValue: string) =>
eventEmitter.dispatchEvent(
new SettingsChangedEvent({
id: option.id,
type: 'option',
value: newValue,
target: option
})
);
}
}
}
}
/**
* The enum associated with the mouse being locked or hovering
*/
export enum ControlSchemeType {
LockedMouse = 0,
HoveringMouse = 1
}