@epicgames-ps/lib-pixelstreamingfrontend-ue5.4
Version:
Frontend library for Unreal Engine 5.4 Pixel Streaming
261 lines (228 loc) • 9.35 kB
text/typescript
// Copyright Epic Games, Inc. All Rights Reserved.
import { Logger } from '../Logger/Logger';
import * as MessageReceive from './MessageReceive';
import * as MessageSend from './MessageSend';
import { SignallingProtocol } from './SignallingProtocol';
// declare the new method for the websocket interface
declare global {
interface WebSocket {
onmessagebinary?(event?: MessageEvent): void;
}
}
/**
* The controller for the WebSocket and all associated methods
*/
export class WebSocketController {
WS_OPEN_STATE = 1;
webSocket: WebSocket;
onOpen: EventTarget;
onClose: EventTarget;
signallingProtocol: SignallingProtocol;
constructor() {
this.onOpen = new EventTarget();
this.onClose = new EventTarget();
this.signallingProtocol = new SignallingProtocol();
SignallingProtocol.setupDefaultHandlers(this);
}
/**
* Connect to the signaling server
* @param connectionURL - The Address of the signaling server
* @returns - If there is a connection
*/
connect(connectionURL: string): boolean {
Logger.Log(Logger.GetStackTrace(), connectionURL, 6);
try {
this.webSocket = new WebSocket(connectionURL);
this.webSocket.onopen = (event) => this.handleOnOpen(event);
this.webSocket.onerror = () => this.handleOnError();
this.webSocket.onclose = (event) => this.handleOnClose(event);
this.webSocket.onmessage = (event) => this.handleOnMessage(event);
this.webSocket.onmessagebinary = (event) =>
this.handleOnMessageBinary(event);
return true;
} catch (error) {
Logger.Error(error, error);
return false;
}
}
/**
* Handles what happens when a message is received in binary form
* @param event - Message Received
*/
handleOnMessageBinary(event: MessageEvent) {
// if the event is empty return
if (!event || !event.data) {
return;
}
// handle the binary and then handle the message
event.data
.text()
.then((messageString: unknown) => {
// build a new message
const constructedMessage = new MessageEvent(
'messageFromBinary',
{
data: messageString
}
);
// send the new stringified event back into `onmessage`
this.handleOnMessage(constructedMessage);
})
.catch((error: Error) => {
Logger.Error(
Logger.GetStackTrace(),
`Failed to parse binary blob from websocket, reason: ${error}`
);
});
}
/**
* Handles what happens when a message is received
* @param event - Message Received
*/
handleOnMessage(event: MessageEvent) {
// Check if websocket message is binary, if so, stringify it.
if (event.data && event.data instanceof Blob) {
this.handleOnMessageBinary(event);
return;
}
const message: MessageReceive.MessageRecv = JSON.parse(event.data);
Logger.Log(
Logger.GetStackTrace(),
'received => \n' +
JSON.stringify(JSON.parse(event.data), undefined, 4),
6
);
// Send to our signalling protocol to handle the incoming message
this.signallingProtocol.handleMessage(message.type, event.data);
}
/**
* Handles when the Websocket is opened
* @param event - Not Used
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handleOnOpen(event: Event) {
Logger.Log(
Logger.GetStackTrace(),
'Connected to the signalling server via WebSocket',
6
);
this.onOpen.dispatchEvent(new Event('open'));
}
/**
* Handles when there is an error on the websocket
* @param event - Error Payload
*/
handleOnError() {
Logger.Error(Logger.GetStackTrace(), 'WebSocket error');
}
/**
* Handles when the Websocket is closed
* @param event - Close Event
*/
handleOnClose(event: CloseEvent) {
Logger.Log(
Logger.GetStackTrace(),
'Disconnected to the signalling server via WebSocket: ' +
JSON.stringify(event.code) +
' - ' +
event.reason
);
this.onClose.dispatchEvent(new CustomEvent('close', { 'detail': event }));
}
requestStreamerList() {
const payload = new MessageSend.MessageListStreamers();
this.webSocket.send(payload.payload());
}
sendSubscribe(streamerid: string) {
const payload = new MessageSend.MessageSubscribe(streamerid);
this.webSocket.send(payload.payload());
}
sendUnsubscribe() {
const payload = new MessageSend.MessageUnsubscribe();
this.webSocket.send(payload.payload());
}
sendWebRtcOffer(offer: RTCSessionDescriptionInit, extraParams: MessageSend.ExtraOfferParameters) {
const payload = new MessageSend.MessageWebRTCOffer(offer, extraParams);
this.webSocket.send(payload.payload());
}
sendWebRtcAnswer(answer: RTCSessionDescriptionInit, extraParams: MessageSend.ExtraAnswerParameters) {
const payload = new MessageSend.MessageWebRTCAnswer(answer, extraParams);
this.webSocket.send(payload.payload());
}
sendWebRtcDatachannelRequest() {
const payload = new MessageSend.MessageWebRTCDatachannelRequest();
this.webSocket.send(payload.payload());
}
sendSFURecvDataChannelReady() {
const payload = new MessageSend.MessageSFURecvDataChannelReady();
this.webSocket.send(payload.payload());
}
/**
* Sends an RTC Ice Candidate to the Server
* @param candidate - RTC Ice Candidate
*/
sendIceCandidate(candidate: RTCIceCandidate) {
Logger.Log(Logger.GetStackTrace(), 'Sending Ice Candidate');
if (
this.webSocket &&
this.webSocket.readyState === this.WS_OPEN_STATE
) {
//ws.send(JSON.stringify({ type: 'iceCandidate', candidate: candidate }));
const IceCandidate = new MessageSend.MessageIceCandidate(candidate);
this.webSocket.send(IceCandidate.payload());
}
}
/**
* Closes the Websocket connection
*/
close() {
this.webSocket?.close();
}
/**
* The Message Contains the payload of the peer connection options used for the RTC Peer hand shake
* @param messageConfig - Config Message received from he signaling server
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onConfig(messageConfig: MessageReceive.MessageConfig) {}
/**
* The Message contains all the ids of streamers available on the server.
* @param messageStreamerList - The message with the list of the available streamer ids.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onStreamerList(messageStreamerList: MessageReceive.MessageStreamerList) {}
/**
* The Message contains the new id of a subscribed to streamer.
* @param message - Message conaining the new id of the streamer.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onStreamerIDChanged(message: MessageReceive.MessageStreamerIDChanged) {}
/**
* @param iceCandidate - Ice Candidate sent from the Signaling server server's RTC hand shake
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onIceCandidate(iceCandidate: RTCIceCandidateInit) {}
/**
* Event is fired when the websocket receives the answer for the RTC peer Connection
* @param messageAnswer - The RTC Answer payload from the signaling server
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onWebRtcAnswer(messageAnswer: MessageReceive.MessageAnswer) {}
/**
* Event is fired when the websocket receives the offer for the RTC peer Connection
* @param messageOffer - The sdp offer
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onWebRtcOffer(messageOffer: MessageReceive.MessageOffer) {}
/**
* Event is fired when the websocket receives the data channels for the RTC peer Connection from the SFU
* @param messageDataChannels - The data channels details
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onWebRtcPeerDataChannels(messageDataChannels: MessageReceive.MessagePeerDataChannels) {}
/**
* Event is fired when the websocket receives the an updated player count from cirrus
* @param MessagePlayerCount - The new player count
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onPlayerCount(playerCount: MessageReceive.MessagePlayerCount) {}
}