1.0.28 • Published 4 months ago

@pixstream/companion v1.0.28

Weekly downloads
-
License
-
Repository
-
Last release
4 months ago

@pixstream/companion

Contains companion server logic.

https://github.com/MetahumanSDK/pixstream.js

Easy to use library for creating reallistic chat bots based on Epic Games® MetaHumans.

It is heavily based on the Epic Games® pixel streaming source code. But it's ported to Typescript and splited to different logic parts.

Naming

Streamer – Unreal Engine® based application that we want to stream.

Companion – NodeJs server that arrange connections to streamer.

Ballancer – server that equally distribute load between pairs: streamer and companion.

Getting started

Sample project: https://github.com/MetahumanSDK/metahuman-unreal-chat-streamer

install package npm i @pixstream/companion --save

then use is

const logger = new Logger('Companion-Server');
const app = express();

const config: Readonly<ICompanionServerConfig> = getConfigFromArgvOrDefault(DefaultCompanionServerConfig);
logger.log("Config: " + JSON.stringify(config, null, '\t'));

const server = config.hosting.useHttps
	? new HTTPS.Server(toServerOptions(config.hosting), app)
	: new HTTP.Server(app)

// `clientConfig` is send to Streamer and Players
// Example of STUN server setting
const clientConfigMessage: Readonly<ClientMessages.IClientConfigMessage> = {
	type: 'config',
	AppName: 'DefaultClientConfigAppName',
	AppDescription: 'DefaultClientConfigAppDescription',
	peerConnectionOptions: {}
};

//Setup http and https servers
server.listen(config.hosting.port, () => {
	console.log(`${config.hosting.useHttps ? 'Https' : 'Http'} listening on *: ${config.hosting}`);
});

// Communcation with Unreal Engine

const streamerCommunicator = new StreamerCommunicator({
	hosting: config.streamer.hosting
});

streamerCommunicator.connect();

streamerCommunicator.onStatus(status => {
	if (status === StreamerCommunicatorStatus.Connected) {
		streamerCommunicator.sendMessage(clientConfigMessage);
	}
});

// Communication with browser

const playerCommunicator = new PlayerCommunicator({
	server: server
});

playerCommunicator.onMessage
	(
		(playerId, message) => {
			// todo: make this more flexible
			// if (SharedContracts.ToStreamerMessageTypesArray.includes(message.type)) {
			if (message.type === 'iceCandidate' || message.type === 'offer') {
				const combinedMessage: StreamerMessages.IToStreamerMessage = {
					...message,
					playerId: playerId,
					type: message.type
				};
				// todo:streamer coudld be not connected at this moment
				streamerCommunicator.sendMessage(combinedMessage);
				return;
			}

			logger.warn(`ignored message from player id = ${playerId} with type ${message.type}. ${JSON.stringify(message)}`);
		}
	);

playerCommunicator.onPlayerConnected
	(
		playerId => {
			if (streamerCommunicator.status !== StreamerCommunicatorStatus.Connected) {
				logger.error(`Player ${playerId} connected but streamer not ready. Disconnecting player.`);

				// Reject connection if streamer is not connected
				playerCommunicator.disconnectPlayerById(playerId, 1013 /* Try again later */, 'Streamer is not connected');
				return;
			}

			// send config
			playerCommunicator.sendMessageToPlayer(playerId, clientConfigMessage);
		}
	);

playerCommunicator.onPlayerDisconnected
	(
		(playerId, reason) => {
			const msg: StreamerMessages.IPlayerDisconnectedMessage = {
				type: 'playerDisconnected',
				reason: reason,
				playerId: playerId
			};

			streamerCommunicator.sendMessage(msg);
		}
	);

streamerCommunicator.onMessage
	(
		(playerId, msg) => {
			// player messages
			if (msg.type === 'disconnectPlayer') {
				const disconnectMessage = msg as StreamerMessages.IDisconnectPlayerMessage;
				playerCommunicator.disconnectPlayerById(playerId, 1011 /* internal error */, disconnectMessage.reason);
				return;
			}

			// todo: could be done less specific but this breaks types
			// if (SharedContracts.ToClientMessageTypesArray.includes(msg.type)) {
			if (msg.type === 'answer' || msg.type === 'iceCandidate') {
				const clientMessage: ClientMessages.IToClientMessage = {
					...msg,
					type: msg.type
				};
				playerCommunicator.sendMessageToPlayer(playerId, clientMessage);
				return;
			}

			logger.warn(`ignored message from streamer with type ${msg.type}. ${JSON.stringify(msg)}`);
		}
	);

// Communication with ballancer

if (config.ballancer) {
	const ballancer = new BallancerCommunicator({
		ballancer: config.ballancer
	});

	ballancer.connect();
	ballancer.registerMMKeepAlive();

	// bind ballancer connect events
	ballancer.onStatus((newStatus) => {
		if (newStatus !== BallancerCommunicatorStatus.Connected)
			return;

		const message: BallancerMessages.IConnectMessage = {
			type: 'connect',
			connection: config.hosting,
			ready: streamerCommunicator.status === StreamerCommunicatorStatus.Connected,
			playerCount: playerCommunicator.playerCount
		};

		ballancer.sendMessage(message);
	});

	// bind streamer events to ballancer
	streamerCommunicator.onStatus(newStreamerStatus => {
		if (newStreamerStatus === StreamerCommunicatorStatus.Connected) {

			if (streamerCommunicator.websocketState !== 1)
				logger.error(`${StreamerCommunicator.name} is connected but websocketState is not in ready state.`);

			const message: BallancerMessages.IStreamerConnectedMessage = {
				type: 'streamerConnected'
			};

			ballancer.sendMessage(message);
			return;
		}

		if (newStreamerStatus === StreamerCommunicatorStatus.Disconnected) {
			const message: BallancerMessages.IStreamerDisconnectedMessage = {
				type: 'streamerDisconnected'
			};
			ballancer.sendMessage(message);
			playerCommunicator.disconnectAllPlayers(undefined, 'streamer is desconnected');
			return;
		}
	});

	// bind to player communicator
	playerCommunicator.onPlayerConnected
		(
			playerId => {
				// The ballancer will not re-direct clients to this Companion server if any client
				// is connected.
				const message: BallancerMessages.IClientConnectedMessage = {
					type: 'clientConnected',
					playerCount: playerCommunicator.playerCount,
					playerId
				};

				ballancer.sendMessage(message);
			}
		);

	playerCommunicator.onPlayerDisconnected
		(
			playerId => {
				const message: BallancerMessages.IClientDisconectedMessage = {
					type: 'clientDisconnected',
					playerCount: playerCommunicator.playerCount,
					playerId
				};

				ballancer.sendMessage(message);
			}
		);
}
1.0.28

4 months ago

1.0.27

5 months ago

1.0.22

11 months ago

1.0.21

11 months ago

1.0.25

7 months ago

1.0.24

7 months ago

1.0.23

8 months ago

1.0.20

11 months ago

1.0.19

11 months ago

1.0.18

11 months ago

1.0.17

11 months ago

1.0.16

11 months ago

1.0.9

11 months ago

1.0.8

11 months ago

1.0.7

12 months ago

1.0.6

12 months ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.11

11 months ago

1.0.10

11 months ago

1.0.15

11 months ago

1.0.14

11 months ago

1.0.13

11 months ago

1.0.12

11 months ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago