UNPKG

@pureweb/platform-streaming-agent

Version:

The PureWeb platform streaming agent enables your game to communicate and stream through the PureWeb Platform

268 lines (267 loc) 13.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PresenceMonitorExtension = void 0; const Log_1 = __importDefault(require("../../Log")); const IExtension_1 = require("../../IExtension"); const ConnectionStates_1 = require("../../ConnectionStates"); const platform_sdk_1 = require("@pureweb/platform-sdk"); class PresenceMonitorExtension extends IExtension_1.AbstractExtension { constructor() { super(...arguments); this._disconnectedParticipants = new Set(); this._participants = new Set(); this.participantJoined = false; this.claimed = false; this.checkingParticipants = false; this._bOfferSent = false; this.start = async () => { this.agent.subscribeToMessages(platform_sdk_1.MessageTypes.BROADCAST, { broadcastMessage: (data) => { const msg = data?.message; let action; let agentId; Log_1.default.info('Received broadcast message: ' + msg); if (msg === undefined) { return; } try { const message = JSON.parse(msg.replace(/\\"/g, '"')); action = message.action; agentId = message.agentId; } catch (e) { return; } if (action === 'TERMINATE') { this.checkForTermination(action); } else if (action === 'CLAIMED') { this._claimingAgent = agentId; this.claimed = true; this.participantJoined = true; this.checkForTermination('START_CLAIMED_TIMEOUT'); } } }); return this.monitorPresence() .then(() => { return true; }) .catch(() => { return false; }); }; this.stop = () => { this.participantJoined = false; this.agent.unsubscribeFromPresence(platform_sdk_1.PresenceTypes.ALL); clearInterval(this.presenceCheckInterval); return Promise.resolve(true); }; this.monitorPresence = async () => { //We going to check presence of the agents on 30 seconds intervals in case //we do not receive event on the agent departure this.presenceCheckInterval = setInterval(async () => { try { if (!this.agent || this.checkingParticipants) { return; } this.checkingParticipants = true; const agents = await this.agent.getRemoteAgents(); this._participants.forEach((participant) => { if (agents.indexOf(participant) < 0) { //We show error here because we should have receive departure event before Log_1.default.error(`Failed to receive participant ${participant} departure on presence subscription`); Log_1.default.info(`Removing ${participant} from presence monitor participant list`); this.removeParticipant(participant); } }); //Check if we need to terminate if there are no active streaming agents this.checkForTermination(); this.checkingParticipants = false; } catch (e) { this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.PRESENCE_FAILURE); } }, 30000); // subscribe to departures only. Client of this extension will add agents that // we want to monitor. this.agent.subscribeToPresence(platform_sdk_1.PresenceTypes.ALL, { departures: (data) => { if (this.agent.id != data.agentId) { if (this._participants.has(data.agentId)) { Log_1.default.info(`Removing ${data.agentId} from presence monitor participant list`); this.removeParticipant(data.agentId); } if (this._claimingAgent === data.agentId) { Log_1.default.info(`Claiming agent ${data.agentId} has departed.`); this._claimingAgent = undefined; } Log_1.default.info(`${Array.from(this._participants).join(', ')} remote agents.`); this.checkForTermination(); } }, arrivals: (data) => { //This can be any agent joining the environment. //Log just for debugging/tracking purposes // logger.info(`Agent arrived: ${data.agentId} EnvironmentId: ${this.agent.agentEnvironment.id}`); //Check if the agent that previously was streaming is reconnecting. //Put the agent back as a participant if it is reconected agent. //All other agents have to send webRTC offer in order to become a participant that we monitor if (this._disconnectedParticipants.has(data.agentId)) { Log_1.default.info(`Reconnected agent ${data.agentId} is back in the participant list.`); this._participants.add(data.agentId); if (this._claimingAgent === data.agentId) { this._claimingAgent = undefined; } } } }); try { if (this.config.rendezvousTimeoutSeconds >= 0) { setTimeout(async () => { if (!this.participantJoined) { if (this.config.standbyMode === true) { Log_1.default.info(`Standby timeout reached. Streaming agent shutting down`); await this.platform.launchRequestAcknowledgements(platform_sdk_1.LaunchRequestAcknowledgementType.StandbyAgentTimeout, 'Standby agent timed out'); this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.STANDBY_TIMEOUT); } else { if (this.config.streamReady) { Log_1.default.info('Streaming Agent resetting'); this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.TERMINATED); } else { Log_1.default.info(`Agent Rendezvous missed.`); this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.AGENT_RENDEZVOUS_MISSED); } } } else { Log_1.default.info(`Agent Rendezvous successful.`); } }, this.config.rendezvousTimeoutSeconds * 1000); } } catch (err) { Log_1.default.error(`Could not obtain participant list from Presence service: ${err}`); this.stop(); } }; } async monitor(agentId, bOffer) { const agents = await this.agent.getRemoteAgents(); //Make sure that the agent the we want to monitor for departure //is still in the environment if (agents.indexOf(agentId) < 0) { Log_1.default.info('Requested agent ' + agentId + ' is not present in the environment'); return; } if (this._participants.has(agentId)) { Log_1.default.error(`Multiple requests to monitor the same agent id: ${agentId}. Participants ${Array.from(this._participants).join(', ')}, EnvironmentId: ${this.agent.agentEnvironment.id}`); return; } this._participants.add(agentId); this.participantJoined = true; this._bOfferSent = bOffer; } /** * Removes participant and saves it as a disconnected participant * so if the agent reconnects it will not require to send the offer * in order to keep the SA going. * @param agentId agent id */ removeParticipant(agentId) { this._disconnectedParticipants.add(agentId); this._participants.delete(agentId); } get participants() { return this._participants.size; } onStateChanged(handler) { this.connectionStateChange = handler; } /** * Checks if we need to terminate if there are no active streaming agents */ checkForTermination(action) { if (action === 'START_CLAIMED_TIMEOUT') { setTimeout(async () => { if (this.participantJoined && this._participants.size > 0 && this._bOfferSent === true) { Log_1.default.info('Claimed timeout aborted. Agent joined the session before claimed timeout.'); return; } Log_1.default.info('Claimed timeout expired. Setting connection state to claimed timeout.'); this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.CLAIMED_TIMOUT); }, this.config.claimedTimeoutSeconds * 1000); return; } if (action === 'TERMINATE' && this.config.streamReady) { if (this.participantJoined && this._participants.size > 0) { Log_1.default.info('Termination delayed. Session in progress.'); return; } setTimeout(async () => { if (this.participantJoined && this._participants.size > 0) { Log_1.default.info('Termination delayed. Session in progress.'); return; } Log_1.default.info('Terminating stream ready Streaming Agent'); try { await this.platform.launchRequestAcknowledgements(platform_sdk_1.LaunchRequestAcknowledgementType.StreamingAgentFinished, 'Stream ready Streaming Agent terminated'); } catch (err) { Log_1.default.error('Unable to acknowledge streaming agent finish state: ' + err); } this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.TERMINATED); }, 2000); } // In Stream Ready we do not terminate if participant has not joined if (this.config.streamReady === true && this.participantJoined === false && this._bOfferSent === false) { Log_1.default.info('Waiting for participant to join before terminating.'); return; } if (this.participantJoined && this._participants.size == 0) { Log_1.default.info('No active remote agents. Checking if we need to terminate.'); if (this.claimed && this._claimingAgent && this._bOfferSent === false) { Log_1.default.info('The agent claimed the session but has not sent the offer. Keep waiting...'); return; } //If claimed we do not linger. We terminate immediately. if (this.claimed) { this.config.lingerTimeoutSeconds = 0; } // Negative values disables this completely. if (this.config.lingerTimeoutSeconds >= 0) { Log_1.default.info(`No active remote agents. Waiting ${this.config.lingerTimeoutSeconds} seconds to shutdown.`); setTimeout(async () => { //Abort termination if another agent join before lingering timeout if (this._participants.size > 0) { Log_1.default.info('Termination aborted. Another agent joined the session before lingering timeout.'); Log_1.default.info('Streaming agent will stay alive'); return; } Log_1.default.info('Lingering timeout expired. Setting connection state to all agents departed.'); try { if (this.platform?.credentials?.agent?.launchRequestId?.length > 0) { await this.platform.launchRequestAcknowledgements(platform_sdk_1.LaunchRequestAcknowledgementType.StreamingAgentFinished, 'All peers have finished consuming stream'); } else { Log_1.default.info('No associated launch request. Assuming local development workflow'); } } catch (err) { Log_1.default.error('Unable to acknowledge streaming agent finish state: ' + err); } this.connectionStateChange?.(ConnectionStates_1.ConnectionStates.PEERS_EXITED); }, this.config.lingerTimeoutSeconds * 1000); } else { Log_1.default.info('Linger timeout disabled. Streaming agent will stay active.'); } } } } exports.PresenceMonitorExtension = PresenceMonitorExtension;