UNPKG

msc-node

Version:

mediasoup client side Node.js library

513 lines (512 loc) 23.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OfferMediaSection = exports.AnswerMediaSection = exports.MediaSection = void 0; const utils = __importStar(require("../../utils")); class MediaSection { constructor({ iceParameters, iceCandidates, dtlsParameters, planB = false }) { this._mediaObject = {}; this._planB = planB; if (iceParameters) { this.setIceParameters(iceParameters); } if (iceCandidates) { this._mediaObject.candidates = []; for (const candidate of iceCandidates) { const candidateObject = {}; // mediasoup does mandates rtcp-mux so candidates component is always // RTP (1). candidateObject.component = 1; candidateObject.foundation = candidate.foundation; candidateObject.ip = candidate.ip; candidateObject.port = candidate.port; candidateObject.priority = candidate.priority; candidateObject.transport = candidate.protocol; candidateObject.type = candidate.type; if (candidate.tcpType) { candidateObject.tcptype = candidate.tcpType; } this._mediaObject.candidates.push(candidateObject); } this._mediaObject.endOfCandidates = 'end-of-candidates'; this._mediaObject.iceOptions = 'renomination'; } if (dtlsParameters) { this.setDtlsRole(dtlsParameters.role); } } get mid() { return String(this._mediaObject.mid); } get closed() { return this._mediaObject.port === 0; } getObject() { return this._mediaObject; } setIceParameters(iceParameters) { this._mediaObject.iceUfrag = iceParameters.usernameFragment; this._mediaObject.icePwd = iceParameters.password; } disable() { this._mediaObject.direction = 'inactive'; delete this._mediaObject.ext; delete this._mediaObject.ssrcs; delete this._mediaObject.ssrcGroups; delete this._mediaObject.simulcast; delete this._mediaObject.simulcast_03; delete this._mediaObject.rids; } close() { this._mediaObject.direction = 'inactive'; this._mediaObject.port = 0; delete this._mediaObject.ext; delete this._mediaObject.ssrcs; delete this._mediaObject.ssrcGroups; delete this._mediaObject.simulcast; delete this._mediaObject.simulcast_03; delete this._mediaObject.rids; delete this._mediaObject.extmapAllowMixed; } } exports.MediaSection = MediaSection; class AnswerMediaSection extends MediaSection { constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, offerMediaObject, offerRtpParameters, answerRtpParameters, codecOptions, extmapAllowMixed = false }) { super({ iceParameters, iceCandidates, dtlsParameters, planB }); this._mediaObject.mid = String(offerMediaObject.mid); this._mediaObject.type = offerMediaObject.type; this._mediaObject.protocol = offerMediaObject.protocol; if (!plainRtpParameters) { this._mediaObject.connection = { ip: '127.0.0.1', version: 4 }; this._mediaObject.port = 7; } else { this._mediaObject.connection = { ip: plainRtpParameters.ip, version: plainRtpParameters.ipVersion }; this._mediaObject.port = plainRtpParameters.port; } switch (offerMediaObject.type) { case 'audio': case 'video': { this._mediaObject.direction = 'recvonly'; this._mediaObject.rtp = []; this._mediaObject.rtcpFb = []; this._mediaObject.fmtp = []; for (const codec of answerRtpParameters.codecs) { const rtp = { payload: codec.payloadType, codec: getCodecName(codec), rate: codec.clockRate }; if (codec.channels > 1) { rtp.encoding = codec.channels; } this._mediaObject.rtp.push(rtp); const codecParameters = utils.clone(codec.parameters, {}); if (codecOptions) { const { opusStereo, opusFec, opusDtx, opusMaxPlaybackRate, opusMaxAverageBitrate, opusPtime, videoGoogleStartBitrate, videoGoogleMaxBitrate, videoGoogleMinBitrate } = codecOptions; const offerCodec = offerRtpParameters.codecs .find((c) => (c.payloadType === codec.payloadType)); switch (codec.mimeType.toLowerCase()) { case 'audio/opus': { if (opusStereo !== undefined) { offerCodec.parameters['sprop-stereo'] = opusStereo ? 1 : 0; codecParameters.stereo = opusStereo ? 1 : 0; } if (opusFec !== undefined) { offerCodec.parameters.useinbandfec = opusFec ? 1 : 0; codecParameters.useinbandfec = opusFec ? 1 : 0; } if (opusDtx !== undefined) { offerCodec.parameters.usedtx = opusDtx ? 1 : 0; codecParameters.usedtx = opusDtx ? 1 : 0; } if (opusMaxPlaybackRate !== undefined) { codecParameters.maxplaybackrate = opusMaxPlaybackRate; } if (opusMaxAverageBitrate !== undefined) { codecParameters.maxaveragebitrate = opusMaxAverageBitrate; } if (opusPtime !== undefined) { offerCodec.parameters.ptime = opusPtime; codecParameters.ptime = opusPtime; } break; } case 'video/vp8': case 'video/vp9': case 'video/h264': case 'video/h265': { if (videoGoogleStartBitrate !== undefined) { codecParameters['x-google-start-bitrate'] = videoGoogleStartBitrate; } if (videoGoogleMaxBitrate !== undefined) { codecParameters['x-google-max-bitrate'] = videoGoogleMaxBitrate; } if (videoGoogleMinBitrate !== undefined) { codecParameters['x-google-min-bitrate'] = videoGoogleMinBitrate; } break; } } } const fmtp = { payload: codec.payloadType, config: '' }; for (const key of Object.keys(codecParameters)) { if (fmtp.config) { fmtp.config += ';'; } fmtp.config += `${key}=${codecParameters[key]}`; } if (fmtp.config) { this._mediaObject.fmtp.push(fmtp); } for (const fb of codec.rtcpFeedback) { this._mediaObject.rtcpFb.push({ payload: codec.payloadType, type: fb.type, subtype: fb.parameter }); } } this._mediaObject.payloads = answerRtpParameters.codecs .map((codec) => codec.payloadType) .join(' '); this._mediaObject.ext = []; for (const ext of answerRtpParameters.headerExtensions) { // Don't add a header extension if not present in the offer. const found = (offerMediaObject.ext || []) .some((localExt) => localExt.uri === ext.uri); if (!found) { continue; } this._mediaObject.ext.push({ uri: ext.uri, value: ext.id }); } // Allow both 1 byte and 2 bytes length header extensions. if (extmapAllowMixed && offerMediaObject.extmapAllowMixed === 'extmap-allow-mixed') { this._mediaObject.extmapAllowMixed = 'extmap-allow-mixed'; } // Simulcast. if (offerMediaObject.simulcast) { this._mediaObject.simulcast = { dir1: 'recv', list1: offerMediaObject.simulcast.list1 }; this._mediaObject.rids = []; for (const rid of offerMediaObject.rids || []) { if (rid.direction !== 'send') { continue; } this._mediaObject.rids.push({ id: rid.id, direction: 'recv' }); } } // Simulcast (draft version 03). else if (offerMediaObject.simulcast_03) { // eslint-disable-next-line camelcase this._mediaObject.simulcast_03 = { value: offerMediaObject.simulcast_03.value.replace(/send/g, 'recv') }; this._mediaObject.rids = []; for (const rid of offerMediaObject.rids || []) { if (rid.direction !== 'send') { continue; } this._mediaObject.rids.push({ id: rid.id, direction: 'recv' }); } } this._mediaObject.rtcpMux = 'rtcp-mux'; this._mediaObject.rtcpRsize = 'rtcp-rsize'; if (this._planB && this._mediaObject.type === 'video') { this._mediaObject.xGoogleFlag = 'conference'; } break; } case 'application': { // New spec. if (typeof offerMediaObject.sctpPort === 'number') { this._mediaObject.payloads = 'webrtc-datachannel'; this._mediaObject.sctpPort = sctpParameters.port; this._mediaObject.maxMessageSize = sctpParameters.maxMessageSize; } // Old spec. else if (offerMediaObject.sctpmap) { this._mediaObject.payloads = sctpParameters.port; this._mediaObject.sctpmap = { app: 'webrtc-datachannel', sctpmapNumber: sctpParameters.port, maxMessageSize: sctpParameters.maxMessageSize }; } break; } } } setDtlsRole(role) { switch (role) { case 'client': this._mediaObject.setup = 'active'; break; case 'server': this._mediaObject.setup = 'passive'; break; case 'auto': this._mediaObject.setup = 'actpass'; break; } } } exports.AnswerMediaSection = AnswerMediaSection; class OfferMediaSection extends MediaSection { constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, mid, kind, offerRtpParameters, streamId, trackId, oldDataChannelSpec = false }) { super({ iceParameters, iceCandidates, dtlsParameters, planB }); this._mediaObject.mid = String(mid); this._mediaObject.type = kind; if (!plainRtpParameters) { this._mediaObject.connection = { ip: '127.0.0.1', version: 4 }; if (!sctpParameters) { this._mediaObject.protocol = 'UDP/TLS/RTP/SAVPF'; } else { this._mediaObject.protocol = 'UDP/DTLS/SCTP'; } this._mediaObject.port = 7; } else { this._mediaObject.connection = { ip: plainRtpParameters.ip, version: plainRtpParameters.ipVersion }; this._mediaObject.protocol = 'RTP/AVP'; this._mediaObject.port = plainRtpParameters.port; } switch (kind) { case 'audio': case 'video': { this._mediaObject.direction = 'sendonly'; this._mediaObject.rtp = []; this._mediaObject.rtcpFb = []; this._mediaObject.fmtp = []; if (!this._planB) { this._mediaObject.msid = `${streamId || '-'} ${trackId}`; } for (const codec of offerRtpParameters.codecs) { const rtp = { payload: codec.payloadType, codec: getCodecName(codec), rate: codec.clockRate }; if (codec.channels > 1) { rtp.encoding = codec.channels; } this._mediaObject.rtp.push(rtp); const fmtp = { payload: codec.payloadType, config: '' }; for (const key of Object.keys(codec.parameters)) { if (fmtp.config) { fmtp.config += ';'; } fmtp.config += `${key}=${codec.parameters[key]}`; } if (fmtp.config) { this._mediaObject.fmtp.push(fmtp); } for (const fb of codec.rtcpFeedback) { this._mediaObject.rtcpFb.push({ payload: codec.payloadType, type: fb.type, subtype: fb.parameter }); } } this._mediaObject.payloads = offerRtpParameters.codecs .map((codec) => codec.payloadType) .join(' '); this._mediaObject.ext = []; for (const ext of offerRtpParameters.headerExtensions) { this._mediaObject.ext.push({ uri: ext.uri, value: ext.id }); } this._mediaObject.rtcpMux = 'rtcp-mux'; this._mediaObject.rtcpRsize = 'rtcp-rsize'; const encoding = offerRtpParameters.encodings[0]; const ssrc = encoding.ssrc; const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) ? encoding.rtx.ssrc : undefined; this._mediaObject.ssrcs = []; this._mediaObject.ssrcGroups = []; if (offerRtpParameters.rtcp.cname) { this._mediaObject.ssrcs.push({ id: ssrc, attribute: 'cname', value: offerRtpParameters.rtcp.cname }); } if (this._planB) { this._mediaObject.ssrcs.push({ id: ssrc, attribute: 'msid', value: `${streamId || '-'} ${trackId}` }); } if (rtxSsrc) { if (offerRtpParameters.rtcp.cname) { this._mediaObject.ssrcs.push({ id: rtxSsrc, attribute: 'cname', value: offerRtpParameters.rtcp.cname }); } if (this._planB) { this._mediaObject.ssrcs.push({ id: rtxSsrc, attribute: 'msid', value: `${streamId || '-'} ${trackId}` }); } // Associate original and retransmission SSRCs. this._mediaObject.ssrcGroups.push({ semantics: 'FID', ssrcs: `${ssrc} ${rtxSsrc}` }); } break; } case 'application': { // New spec. if (!oldDataChannelSpec) { this._mediaObject.payloads = 'webrtc-datachannel'; this._mediaObject.sctpPort = sctpParameters.port; this._mediaObject.maxMessageSize = sctpParameters.maxMessageSize; } // Old spec. else { this._mediaObject.payloads = sctpParameters.port; this._mediaObject.sctpmap = { app: 'webrtc-datachannel', sctpmapNumber: sctpParameters.port, maxMessageSize: sctpParameters.maxMessageSize }; } break; } } } // eslint-disable-next-line @typescript-eslint/no-unused-vars setDtlsRole(role) { // Always 'actpass'. this._mediaObject.setup = 'actpass'; } planBReceive({ offerRtpParameters, streamId, trackId }) { const encoding = offerRtpParameters.encodings[0]; const ssrc = encoding.ssrc; const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) ? encoding.rtx.ssrc : undefined; if (offerRtpParameters.rtcp.cname) { this._mediaObject.ssrcs.push({ id: ssrc, attribute: 'cname', value: offerRtpParameters.rtcp.cname }); } this._mediaObject.ssrcs.push({ id: ssrc, attribute: 'msid', value: `${streamId || '-'} ${trackId}` }); if (rtxSsrc) { if (offerRtpParameters.rtcp.cname) { this._mediaObject.ssrcs.push({ id: rtxSsrc, attribute: 'cname', value: offerRtpParameters.rtcp.cname }); } this._mediaObject.ssrcs.push({ id: rtxSsrc, attribute: 'msid', value: `${streamId || '-'} ${trackId}` }); // Associate original and retransmission SSRCs. this._mediaObject.ssrcGroups.push({ semantics: 'FID', ssrcs: `${ssrc} ${rtxSsrc}` }); } } planBStopReceiving({ offerRtpParameters }) { const encoding = offerRtpParameters.encodings[0]; const ssrc = encoding.ssrc; const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) ? encoding.rtx.ssrc : undefined; this._mediaObject.ssrcs = this._mediaObject.ssrcs .filter((s) => s.id !== ssrc && s.id !== rtxSsrc); if (rtxSsrc) { this._mediaObject.ssrcGroups = this._mediaObject.ssrcGroups .filter((group) => group.ssrcs !== `${ssrc} ${rtxSsrc}`); } } } exports.OfferMediaSection = OfferMediaSection; function getCodecName(codec) { const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); if (!mimeTypeMatch) { throw new TypeError('invalid codec.mimeType'); } return mimeTypeMatch[2]; }