Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions examples/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Config, PixelStreaming, SPSApplication, TextParameters, PixelStreamingApplicationStyle, MessageRecv, Flags } from "@tensorworks/libspsfrontend";
import {
Config,
Flags,
MessageRecv,
PixelStreaming,
PixelStreamingApplicationStyle,
SPSApplication
} from "@tensorworks/libspsfrontend";

// Apply default styling from Epic Games Pixel Streaming Frontend
export const PixelStreamingApplicationStyles = new PixelStreamingApplicationStyle();
Expand All @@ -20,6 +27,7 @@ class ScalablePixelStreaming extends PixelStreaming {
}
};

// Initialise the SPS frontend on load of the body element
document.body.onload = function () {

// Create a config object. We default to sending the WebRTC offer from the browser as true, TimeoutIfIdle to true, AutoConnect to false and MaxReconnectAttempts to 0
Expand All @@ -34,10 +42,12 @@ document.body.onload = function () {
stream.handleOnConfig(messageExtendedConfig);
}

// Create and append our application
// Create the SPS application
const spsApplication = new SPSApplication({
stream,
onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode) /* Light/Dark mode support. */
});

// Append the SPS application element to the document body element
document.body.appendChild(spsApplication.rootElement);
}
65 changes: 53 additions & 12 deletions library/src/LoadingOverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,91 @@ export class LoadingOverlay extends TextOverlay {

private static _rootElement: HTMLElement;
private static _textElement: HTMLElement;
private static _spinner: HTMLElement;
private static _spinnerElement: HTMLElement;

/**
* @returns The created root element of this overlay.
*/
public static rootElement(): HTMLElement {

// Check if the loading overlay element doesn't exists
if (!LoadingOverlay._rootElement) {

// Create a loading overlay div element
LoadingOverlay._rootElement = document.createElement('div');
LoadingOverlay._rootElement.id = "loadingOverlay";
LoadingOverlay._rootElement.className = "textDisplayState";
}

// Return the loading overlay root element
return LoadingOverlay._rootElement;
}

/**
* @returns The created content element of this overlay, which contain whatever content this element contains, like text or a button.
*/
public static textElement(): HTMLElement {

// Check if the text element doesn't exists
if (!LoadingOverlay._textElement) {

// Create a text div element
LoadingOverlay._textElement = document.createElement('div');
LoadingOverlay._textElement.id = 'loadingOverlayText';
}

// Return the text element
return LoadingOverlay._textElement;
}


/**
*
* @returns The created a loading spinner element
*/
public static spinner(): HTMLElement {
if (!LoadingOverlay._spinner) {
// build the spinner div
if (!LoadingOverlay._spinnerElement) {
// Get the size of loading spinners root element
const size = LoadingOverlay._rootElement.clientWidth * 0.03;
LoadingOverlay._spinner = document.createElement('div');
LoadingOverlay._spinner.id = "loading-spinner"
LoadingOverlay._spinner.className = "loading-spinner";
LoadingOverlay._spinner.setAttribute("style", "width: " + size + "px; height: " + size + "px;");

// Create the loading spinner element
LoadingOverlay._spinnerElement = document.createElement('div');
LoadingOverlay._spinnerElement.id = "loading-spinner"
LoadingOverlay._spinnerElement.className = "loading-spinner";
LoadingOverlay._spinnerElement.setAttribute("style", "width: " + size + "px; height: " + size + "px;");

// Create an spinner section element
const SpinnerSectionOne = document.createElement("div");
SpinnerSectionOne.setAttribute("style", "width: " + size * 0.8 + "px; height: " + size * 0.8 + "px; border-width: " + size * 0.125 + "px;");
LoadingOverlay._spinner.appendChild(SpinnerSectionOne);

// Add the spinner section to spinner element
LoadingOverlay._spinnerElement.appendChild(SpinnerSectionOne);

// Create an spinner section element
const SpinnerSectionTwo = document.createElement("div");
SpinnerSectionTwo.setAttribute("style", "width: " + size * 0.8 + "px; height: " + size * 0.8 + "px; border-width: " + size * 0.125 + "px;");
LoadingOverlay._spinner.appendChild(SpinnerSectionTwo);

// Add the spinner section to spinner element
LoadingOverlay._spinnerElement.appendChild(SpinnerSectionTwo);

// Create an spinner section element
const SpinnerSectionThree = document.createElement("div");
SpinnerSectionThree.setAttribute("style", "width: " + size * 0.8 + "px; height: " + size * 0.8 + "px; border-width: " + size * 0.125 + "px;");
LoadingOverlay._spinner.appendChild(SpinnerSectionThree);

// Add the spinner section to spinner element
LoadingOverlay._spinnerElement.appendChild(SpinnerSectionThree);
}
return LoadingOverlay._spinner;

// Return the spinner element
return LoadingOverlay._spinnerElement;
}

/**
* Construct a connect overlay with a connection button.
* @param parentElem the parent element this overlay will be inserted into.
*/
public constructor(parentElem: HTMLElement) {

// Call the parent constructor
super(parentElem, LoadingOverlay.rootElement(), LoadingOverlay.textElement());
}

Expand All @@ -66,13 +97,23 @@ export class LoadingOverlay extends TextOverlay {
* @param text the update text to be inserted into the overlay
*/
public update(text: string): void {

// Check if the text parameter is valid
if (text != null || text != undefined) {

// Set the text element to an empty string
this.textElement.innerHTML = "";

// Create a text container element
let textContainer = document.createElement("div");

// Set the element inner html to the text parmater
textContainer.innerHTML = text;

// Append the text container to the text element
this.textElement.append(textContainer);

// Append the loading spinner to the text element
this.textElement.append(LoadingOverlay.spinner());
}
}
Expand Down
6 changes: 5 additions & 1 deletion library/src/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export enum MessageSendTypes {
}

/**
* Aggregated Stats Message Wrapper
* Aggregated Stats Message Wrapper to send stats to the signalling server
*/
export class MessageStats extends MessageSend {
inboundVideoStats: InboundVideoStats;
Expand All @@ -30,7 +30,11 @@ export class MessageStats extends MessageSend {
*/
constructor(aggregatedStats: AggregatedStats) {
super();

// Set the message type as stats
this.type = MessageSendTypes.STATS

// Map the aggregated stats to the message stats properties
this.inboundVideoStats = aggregatedStats.inboundVideoStats;
this.inboundAudioStats = aggregatedStats.inboundAudioStats;
this.candidatePair = aggregatedStats.getActiveCandidatePair();
Expand Down
71 changes: 61 additions & 10 deletions library/src/SPSApplication.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
import { Application, SettingUIFlag, UIOptions } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4';
import { AggregatedStats, SettingFlag, TextParameters } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { LoadingOverlay } from './LoadingOverlay';
import { SPSSignalling } from './SignallingExtension';
import { MessageStats } from './Messages';
import {
AggregatedStats,
SettingFlag,
TextParameters
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import {
Application,
SettingUIFlag,
UIOptions
} from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4';

// For local testing. Declare a websocket URL that can be imported via a .env file that will override
// the signalling server URL builder.
declare var WEBSOCKET_URL: string;


export class SPSApplication extends Application {
private loadingOverlay: LoadingOverlay;
private signallingExtension: SPSSignalling;

// Create a flags class for the send stats to server
static Flags = class {
static sendToServer = "sendStatsToServer"
}

constructor(config: UIOptions) {
super(config);

// Initialise the signaling server extensions to support sps signalling messages
this.signallingExtension = new SPSSignalling(this.stream.webSocketController);
this.signallingExtension.onAuthenticationResponse = this.handleSignallingResponse.bind(this);
this.signallingExtension.onInstanceStateChanged = this.handleSignallingResponse.bind(this);

// Enforce the munging of the websocket address to support SPS
this.enforceSpecialSignallingServerUrl();

// Add 'Send Stats to Server' checkbox
// Add a SPS settings section to the settings panel
const spsSettingsSection = this.configUI.buildSectionWithHeading(this.settingsPanel.settingsContentElement, "Scalable Pixel Streaming");

// Add the 'Send Stats to server' check box to the list of settings
const sendStatsToServerSetting = new SettingFlag(
SPSApplication.Flags.sendToServer,
"Send stats to server",
Expand All @@ -35,9 +48,13 @@ export class SPSApplication extends Application {
this.stream.config.useUrlParams
);

// Add the 'Send Stats to server' check box to the sps settings section
spsSettingsSection.appendChild(new SettingUIFlag(sendStatsToServerSetting).rootElement);

// Initialise the loading overlay
this.loadingOverlay = new LoadingOverlay(this.stream.videoElementParent);

// Add an event handler for when the statReceive event is emitted
this.stream.addEventListener(
'statsReceived',
({ data: { aggregatedStats } }) => {
Expand All @@ -48,38 +65,68 @@ export class SPSApplication extends Application {
);
}

/**
* Handled the response when the sps messages are emitted
* @param signallingResp the informative response message
* @param isError if the message is an error
*/
handleSignallingResponse(signallingResp: string, isError: boolean) {

// Check if the message is an error
if (isError) {

// Show the error overlay
this.showErrorOverlay(signallingResp);
} else {

// Show the loading overlay
this.showLoadingOverlay(signallingResp);
}
}

/**
* Enforces changes the websocket path to conform to the SPS specification
* Can be overridden if the WEBSOCKET_URL var defined in the .env file has been defined
*/
enforceSpecialSignallingServerUrl() {

// SPS needs to build a specific signalling server url based on the application name so K8s can distinguish it
this.stream.setSignallingUrlBuilder(() => {

// if we have overriden the signalling server URL with a .env file use it here
if (WEBSOCKET_URL !== undefined ) {
// Check if the WEBSOCKET_URL var in the .env file has been defined
if (WEBSOCKET_URL !== undefined) {

// Return the value of the WEBSOCKET_URL var defined in the .env file
return WEBSOCKET_URL as string;
}

// get the current signalling url
// Get the current signalling server URL from the settings
let signallingUrl = this.stream.config.getTextSettingValue(TextParameters.SignallingServerUrl);

// build the signalling URL based on the existing window location, the result should be 'domain.com/signalling/app-name'
// Build the signalling URL based on the existing window location, the result should be 'domain.com/signalling/app-name'
signallingUrl = signallingUrl.endsWith("/") ? signallingUrl + "signalling" + window.location.pathname : signallingUrl + "/signalling" + window.location.pathname;

// Return the modified signalling server URL
return signallingUrl
});
}

showLoadingOverlay(signallingResp: string) {
/**
* Shows the loading overlay
* @param message The message to display
*/
showLoadingOverlay(message: string) {

// Hide the current overlay
this.hideCurrentOverlay();

// Show the loading overlay
this.loadingOverlay.show();
this.loadingOverlay.update(signallingResp);

// Update the loading overlay with the signalling response
this.loadingOverlay.update(message);

// Set the current overlay to the loading overlay
this.currentOverlay = this.loadingOverlay;
}

Expand All @@ -88,7 +135,11 @@ export class SPSApplication extends Application {
* @param stats - Aggregated Stats
*/
sendStatsToSignallingServer(stats: AggregatedStats) {

// Create a new stats signalling message
const data = new MessageStats(stats);

// Send the stats message to the signalling server
this.stream.webSocketController.webSocket.send(data.payload());
}
}
Loading