UNPKG

@wowzamediasystems/sdk-rts

Version:

SDK for building a realtime broadcaster using the Wowza platform.

244 lines (225 loc) 8.74 kB
import Logger from './Logger' import config from './config' import FetchError from './utils/FetchError' const logger = Logger.get('Director') const streamTypes = { WEBRTC: 'WebRtc', RTMP: 'Rtmp' } let liveWebsocketDomain = '' export const defaultApiEndpoint = config.WOWZA_DIRECTOR_ENDPOINT let apiEndpoint = defaultApiEndpoint /** * @module Director * @description Simplify API calls to find the best server and region to publish and subscribe to. * For security reasons all calls will return a [JWT](https://jwt.io) token for authentication including the required * socket path to connect with. * * You will need your own Publishing token and Stream name, please refer to [Managing Your Tokens](https://docs.dolby.io/streaming-apis/docs/managing-your-tokens). */ /** * @typedef {Object} WowzaDirectorResponse * @global * @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. */ /** * @typedef {Object} DirectorPublisherOptions * @global * @property {String} token - Wowza Publishing Token. * @property {String} streamName - Wowza Stream Name. * @property {("WebRtc" | "Rtmp")} [streamType] - Wowza Stream Type. */ /** * @typedef {Object} DirectorSubscriberOptions * @global * @property {String} streamName - Wowza publisher Stream Name. * @property {String} streamAccountId - Wowza Account ID. * @property {String} [subscriberToken] - Token to subscribe to secure streams. If you are subscribing to an unsecure stream, you can omit this param. */ const Director = { /** * @function * @name setEndpoint * @description Set Director API endpoint where requests will be sent. * @param {String} url - New Director API endpoint * @returns {void} */ setEndpoint: (url) => { apiEndpoint = url.replace(/\/$/, '') }, /** * @function * @name getEndpoint * @description Get current Director API endpoint where requests will be sent. Default endpoint is 'https://director.wowza.com'. * @returns {String} API base url */ getEndpoint: () => { return apiEndpoint }, /** * @function * @name setLiveDomain * @description Set Websocket Live domain from Director API response. * If it is set to empty, it will not parse the response. * @param {String} domain - New Websocket Live domain * @returns {void} */ setLiveDomain: (domain) => { liveWebsocketDomain = domain.replace(/\/$/, '') }, /** * @function * @name getLiveDomain * @description Get current Websocket Live domain. * By default is empty which corresponds to not parse the Director response. * @returns {String} Websocket Live domain */ getLiveDomain: () => { return liveWebsocketDomain }, /** * @function * @name getPublisher * @description Get publisher connection data. * @param {DirectorPublisherOptions} options - Wowza options. * @returns {Promise<WowzaDirectorResponse>} Promise object which represents the result of getting the publishing connection path. * @example const response = await Director.getPublisher(options) * @example * import { Publish, Director } from '@wowzamediasystems/sdk-rts' * * //Define getPublisher as callback for Publish * const streamName = "My Wowza Stream Name" * const token = "My Wowza publishing token" * const tokenGenerator = () => Director.getPublisher({token, streamName}) * * //Create a new instance * const wowzaPublish = new Publish(streamName, tokenGenerator) * * //Get MediaStream * const mediaStream = getYourMediaStreamImplementation() * * //Options * const broadcastOptions = { * mediaStream: mediaStream * } * * //Start broadcast * await wowzaPublish.connect(broadcastOptions) */ getPublisher: async (options, streamName = null, streamType = streamTypes.WEBRTC) => { const optionsParsed = getPublisherOptions(options, streamName, streamType) logger.info('Getting publisher connection path for stream name: ', optionsParsed.streamName) const payload = { streamName: optionsParsed.streamName, streamType: optionsParsed.streamType } const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${optionsParsed.token}` } const url = `${Director.getEndpoint()}/api/director/publish` try { const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(payload) }) let data = await response.json() if (data.status === 'fail') { const error = new FetchError(data.data.message, response.status) throw error } data = parseIncomingDirectorResponse(data) logger.debug('Getting publisher response: ', data) return data.data } catch (e) { logger.error('Error while getting publisher connection path. ', e) throw e } }, /** * @function * @name getSubscriber * @description Get subscriber connection data. * @param {DirectorSubscriberOptions} options - Wowza options. * @returns {Promise<WowzaDirectorResponse>} Promise object which represents the result of getting the subscribe connection data. * @example const response = await Director.getSubscriber(options) * @example * import { View, Director } from '@wowzamediasystems/sdk-rts' * * //Define getSubscriber as callback for Subscribe * const streamName = "My Wowza Stream Name" * const accountId = "Wowza Publisher account Id" * const tokenGenerator = () => Director.getSubscriber({streamName, accountId}) * //... or for an secure stream * const tokenGenerator = () => Director.getSubscriber({streamName, accountId, subscriberToken: '176949b9e57de248d37edcff1689a84a047370ddc3f0dd960939ad1021e0b744'}) * * //Create a new instance * const wowzaView = new View(streamName, tokenGenerator) * * //Set track event handler to receive streams from Publisher. * wowzaView.on('track', (event) => { * addStreamToYourVideoTag(event.streams[0]) * }) * * //View Options * const options = { * } * * //Start connection to broadcast * await wowzaView.connect(options) */ getSubscriber: async (options, streamAccountId = null, subscriberToken = null) => { const optionsParsed = getSubscriberOptions(options, streamAccountId, subscriberToken) logger.info(`Getting subscriber connection data for stream name: ${optionsParsed.streamName} and account id: ${optionsParsed.streamAccountId}`) const payload = { streamAccountId: optionsParsed.streamAccountId, streamName: optionsParsed.streamName } let headers = { 'Content-Type': 'application/json' } if (optionsParsed.subscriberToken) { headers = { ...headers, Authorization: `Bearer ${optionsParsed.subscriberToken}` } } const url = `${Director.getEndpoint()}/api/director/subscribe` try { const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(payload) }) let data = await response.json() if (data.status === 'fail') { const error = new FetchError(data.data.message, response.status) throw error } data = parseIncomingDirectorResponse(data) logger.debug('Getting subscriber response: ', data) return data.data } catch (e) { logger.error('Error while getting subscriber connection path. ', e) throw e } } } const getPublisherOptions = (options, legacyStreamName, legacyStreamType) => { let parsedOptions = (typeof options === 'object') ? options : {} if (Object.keys(parsedOptions).length === 0) { parsedOptions = { token: options, streamName: legacyStreamName, streamType: legacyStreamType } } return parsedOptions } const getSubscriberOptions = (options, legacyStreamAccountId, legacySubscriberToken) => { let parsedOptions = (typeof options === 'object') ? options : {} if (Object.keys(parsedOptions).length === 0) { parsedOptions = { streamName: options, streamAccountId: legacyStreamAccountId, subscriberToken: legacySubscriberToken } } if (config.WOWZA_FIXED_ACCOUNT_ID) { parsedOptions = { ...parsedOptions, streamAccountId: config.WOWZA_FIXED_ACCOUNT_ID } } return parsedOptions } const parseIncomingDirectorResponse = (directorResponse) => { if (Director.getLiveDomain()) { const domainRegex = /\/\/(.*?)\// const urlsParsed = directorResponse.data.urls.map(url => { const matched = domainRegex.exec(url) return url.replace(matched[1], Director.getLiveDomain()) }) directorResponse.data.urls = urlsParsed } return directorResponse } export default Director