UNPKG

@wowzamediasystems/sdk-rts

Version:

SDK for building a realtime broadcaster using the Wowza platform.

157 lines (146 loc) 6.42 kB
import EventEmitter from 'events' import PeerConnection, { webRTCEvents } from '../PeerConnection' import { signalingEvents } from '../Signaling' let logger const maxReconnectionInterval = 32000 const baseInterval = 1000 /** * @typedef {Object} WowzaDirectorResponse * @property {Array<String>} urls - WebSocket available URLs. * @property {String} jwt - Access token for signaling initialization. * @property {Array<RTCIceServer>} iceServers - Object which represents a list of Ice servers. */ /** * Callback invoke when a new connection path is needed. * * @callback tokenGeneratorCallback * @returns {Promise<WowzaDirectorResponse>} Promise object which represents the result of getting the new connection path. * * You can use your own token generator or use the <a href='Director'>Director available methods</a>. */ /** * @class BaseWebRTC * @extends EventEmitter * @classdesc Base class for common actions about peer connection and reconnect mechanism for Publishers and Viewer instances. * * @constructor * @param {String} streamName - Wowza existing stream name. * @param {tokenGeneratorCallback} tokenGenerator - Callback function executed when a new token is needed. * @param {Object} loggerInstance - Logger instance from the extended classes. * @param {Boolean} autoReconnect - Enable auto reconnect. */ export default class BaseWebRTC extends EventEmitter { constructor (streamName, tokenGenerator, loggerInstance, autoReconnect) { super() logger = loggerInstance if (!streamName) { logger.error('Stream Name is required to construct this module.') throw new Error('Stream Name is required to construct this module.') } if (!tokenGenerator) { logger.error('Token generator is required to construct this module.') throw new Error('Token generator is required to construct this module.') } this.webRTCPeer = new PeerConnection() this.signaling = null this.streamName = streamName this.autoReconnect = autoReconnect this.reconnectionInterval = baseInterval this.alreadyDisconnected = false this.firstReconnection = true this.stopReconnection = false this.isReconnecting = false this.tokenGenerator = tokenGenerator this.options = null } /** * Get current RTC peer connection. * @returns {RTCPeerConnection} Object which represents the RTCPeerConnection. */ getRTCPeerConnection () { return this.webRTCPeer ? this.webRTCPeer.getRTCPeer() : null } /** * Stops connection. */ stop () { logger.info('Stopping') this.webRTCPeer.closeRTCPeer() this.signaling?.close() this.signaling = null this.stopReconnection = true this.webRTCPeer = new PeerConnection() } /** * Get if the current connection is active. * @returns {Boolean} - True if connected, false if not. */ isActive () { const rtcPeerState = this.webRTCPeer.getRTCPeerStatus() logger.info('Broadcast status: ', rtcPeerState || 'not_established') return rtcPeerState === 'connected' } /** * Sets reconnection if autoReconnect is enabled. */ setReconnect () { this.signaling.on('migrate', () => this.replaceConnection()) if (this.autoReconnect) { this.signaling.on(signalingEvents.connectionError, () => { if (this.firstReconnection || !this.alreadyDisconnected) { this.firstReconnection = false this.reconnect({ error: new Error('Signaling error: wsConnectionError') }) } }) this.webRTCPeer.on(webRTCEvents.connectionStateChange, (state) => { if ((state === 'failed' || (state === 'disconnected' && this.alreadyDisconnected)) && this.firstReconnection) { this.firstReconnection = false this.reconnect({ error: new Error('Connection state change: RTCPeerConnectionState disconnected') }) } else if (state === 'disconnected') { this.alreadyDisconnected = true setTimeout(() => this.reconnect({ error: new Error('Connection state change: RTCPeerConnectionState disconnected') }), 1500) } else { this.alreadyDisconnected = false } }) } } /** * Reconnects to last broadcast. * @fires BaseWebRTC#reconnect * @param {Object} [data] - This object contains the error property. It may be expanded to contain more information in the future. * @property {String} error - The value sent in the first [reconnect event]{@link BaseWebRTC#event:reconnect} within the error key of the payload */ async reconnect (data) { try { logger.info('Attempting to reconnect...') if (!this.isActive() && !this.stopReconnection && !this.isReconnecting) { this.stop() /** * Emits with every reconnection attempt made when an active stream * stopped unexpectedly. * * @event BaseWebRTC#reconnect * @type {Object} * @property {Number} timeout - Next retry interval in milliseconds. * @property {Error} error - Error object with cause of failure. Possible errors are: <ul> <li> <code>Signaling error: wsConnectionError</code> if there was an error in the Websocket connection. <li> <code>Connection state change: RTCPeerConnectionState disconnected</code> if there was an error in the RTCPeerConnection. <li> <code>Attempting to reconnect</code> if the reconnect was trigered externally. <li> Or any internal error thrown by either <a href="Publish#connect">Publish.connect</a> or <a href="View#connect">View.connect</a> methods</ul> */ this.emit('reconnect', { timeout: (nextReconnectInterval(this.reconnectionInterval)), error: data?.error ? data?.error : new Error('Attempting to reconnect') }) this.isReconnecting = true await this.connect(this.options) this.alreadyDisconnected = false this.reconnectionInterval = baseInterval this.firstReconnection = true this.isReconnecting = false } } catch (error) { this.isReconnecting = false this.reconnectionInterval = nextReconnectInterval(this.reconnectionInterval) logger.error(`Reconnection failed, retrying in ${this.reconnectionInterval}ms. `, error) setTimeout(() => this.reconnect({ error }), this.reconnectionInterval) } } } const nextReconnectInterval = (interval) => { return interval < maxReconnectionInterval ? interval * 2 : interval }