mediasoup-client
Version:
mediasoup client side TypeScript library
808 lines (807 loc) • 34.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Chrome74 = void 0;
const sdpTransform = require("sdp-transform");
const Logger_1 = require("../Logger");
const enhancedEvents_1 = require("../enhancedEvents");
const ortc = require("../ortc");
const errors_1 = require("../errors");
const scalabilityModes_1 = require("../scalabilityModes");
const RemoteSdp_1 = require("./sdp/RemoteSdp");
const sdpCommonUtils = require("./sdp/commonUtils");
const sdpUnifiedPlanUtils = require("./sdp/unifiedPlanUtils");
const ortcUtils = require("./ortc/utils");
const logger = new Logger_1.Logger('Chrome74');
const NAME = 'Chrome74';
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
class Chrome74 extends enhancedEvents_1.EnhancedEventEmitter {
// Closed flag.
_closed = false;
// Handler direction.
_direction;
// Remote SDP handler.
_remoteSdp;
// Callback to request sending extended RTP capabilities on demand.
_getSendExtendedRtpCapabilities;
// Initial server side DTLS role. If not 'auto', it will force the opposite
// value in client side.
_forcedLocalDtlsRole;
// RTCPeerConnection instance.
_pc;
// Map of RTCTransceivers indexed by MID.
_mapMidTransceiver = new Map();
// Default local stream for sending if no `streamId` is given in send().
_sendStream = new MediaStream();
// Whether a DataChannel m=application section has been created.
_hasDataChannelMediaSection = false;
// Sending DataChannel id value counter. Incremented for each new DataChannel.
_nextSendSctpStreamId = 0;
// Got transport local and remote parameters.
_transportReady = false;
/**
* Creates a factory function.
*/
static createFactory() {
return {
name: NAME,
factory: (options) => new Chrome74(options),
getNativeRtpCapabilities: async ({ direction, }) => {
logger.debug('getNativeRtpCapabilities() [direction:%o]', direction);
let pc = new RTCPeerConnection({
iceServers: [],
iceTransportPolicy: 'all',
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
});
try {
pc.addTransceiver('audio', { direction });
pc.addTransceiver('video', { direction });
const offer = await pc.createOffer();
try {
pc.close();
}
catch (error) { }
pc = undefined;
const sdpObject = sdpTransform.parse(offer.sdp);
const nativeRtpCapabilities = Chrome74.getLocalRtpCapabilities(sdpObject);
return nativeRtpCapabilities;
}
catch (error) {
try {
pc?.close();
}
catch (error2) { }
// eslint-disable-next-line no-useless-assignment
pc = undefined;
throw error;
}
},
getNativeSctpCapabilities: async () => {
logger.debug('getNativeSctpCapabilities()');
return {
numStreams: SCTP_NUM_STREAMS,
};
},
};
}
static getLocalRtpCapabilities(localSdpObject, extraHeaderExtensions = []) {
const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({
sdpObject: localSdpObject,
});
// Need to validate and normalize native RTP capabilities.
ortc.validateAndNormalizeRtpCapabilities(nativeRtpCapabilities);
// libwebrtc supports NACK for OPUS but doesn't announce it.
ortcUtils.addNackSupportForOpus(nativeRtpCapabilities);
for (const headerExtension of extraHeaderExtensions) {
ortcUtils.addHeaderExtensionSupport(nativeRtpCapabilities, headerExtension);
}
return nativeRtpCapabilities;
}
constructor({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, getSendExtendedRtpCapabilities, }) {
super();
logger.debug('constructor()');
this._direction = direction;
this._remoteSdp = new RemoteSdp_1.RemoteSdp({
iceParameters,
iceCandidates,
dtlsParameters,
sctpParameters,
});
this._getSendExtendedRtpCapabilities = getSendExtendedRtpCapabilities;
if (dtlsParameters.role && dtlsParameters.role !== 'auto') {
this._forcedLocalDtlsRole =
dtlsParameters.role === 'server' ? 'client' : 'server';
}
this._pc = new RTCPeerConnection({
iceServers: iceServers ?? [],
iceTransportPolicy: iceTransportPolicy ?? 'all',
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
...additionalSettings,
});
this._pc.addEventListener('icegatheringstatechange', this.onIceGatheringStateChange);
this._pc.addEventListener('icecandidateerror', this.onIceCandidateError);
if (this._pc.connectionState) {
this._pc.addEventListener('connectionstatechange', this.onConnectionStateChange);
}
else {
logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState');
this._pc.addEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
}
}
get name() {
return NAME;
}
close() {
logger.debug('close()');
if (this._closed) {
return;
}
this._closed = true;
// Close RTCPeerConnection.
try {
this._pc.close();
}
catch (error) { }
this._pc.removeEventListener('icegatheringstatechange', this.onIceGatheringStateChange);
this._pc.removeEventListener('icecandidateerror', this.onIceCandidateError);
this._pc.removeEventListener('connectionstatechange', this.onConnectionStateChange);
this._pc.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
this.emit('@close');
// Invoke close() in EnhancedEventEmitter classes.
super.close();
}
async updateIceServers(iceServers) {
this.assertNotClosed();
logger.debug('updateIceServers()');
const configuration = this._pc.getConfiguration();
configuration.iceServers = iceServers;
this._pc.setConfiguration(configuration);
}
async restartIce(iceParameters) {
this.assertNotClosed();
logger.debug('restartIce()');
// Provide the remote SDP handler with new remote ICE parameters.
this._remoteSdp.updateIceParameters(iceParameters);
if (!this._transportReady) {
return;
}
if (this._direction === 'send') {
const offer = await this._pc.createOffer({ iceRestart: true });
logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
}
else {
const offer = {
type: 'offer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
}
}
async getTransportStats() {
this.assertNotClosed();
return this._pc.getStats();
}
async send({ track, streamId, encodings, codecOptions, headerExtensionOptions, codec, }) {
this.assertNotClosed();
this.assertSendDirection();
logger.debug('send() [kind:%s, track.id:%s, streamId:%s]', track.kind, track.id, streamId);
if (encodings && encodings.length > 1) {
encodings.forEach((encoding, idx) => {
encoding.rid = `r${idx}`;
});
}
const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx();
const transceiver = this._pc.addTransceiver(track, {
direction: 'sendonly',
streams: [this._sendStream],
sendEncodings: encodings,
});
let offer = await this._pc.createOffer();
let localSdpObject = sdpTransform.parse(offer.sdp);
if (localSdpObject.extmapAllowMixed) {
this._remoteSdp.setSessionExtmapAllowMixed();
}
const extraHeaderExtensions = [];
extraHeaderExtensions.push({
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',
kind: track.kind,
direction: 'sendonly',
});
const nativeRtpCapabilities = Chrome74.getLocalRtpCapabilities(localSdpObject, extraHeaderExtensions);
const sendExtendedRtpCapabilities = this._getSendExtendedRtpCapabilities(nativeRtpCapabilities);
// Generic sending RTP parameters.
const sendingRtpParameters = ortc.getSendingRtpParameters(track.kind, sendExtendedRtpCapabilities);
// This may throw.
sendingRtpParameters.codecs = ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
// Generic sending RTP parameters suitable for the SDP remote answer.
const sendingRemoteRtpParameters = ortc.getSendingRemoteRtpParameters(track.kind, sendExtendedRtpCapabilities);
// This may throw.
sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec);
if (!this._transportReady) {
await this.setupTransport({
localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
localSdpObject,
});
}
// Special case for VP9 with SVC.
let hackVp9Svc = false;
const layers = (0, scalabilityModes_1.parse)((encodings ?? [{}])[0].scalabilityMode);
let offerMediaObject;
if (encodings?.length === 1 &&
layers.spatialLayers > 1 &&
sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') {
logger.debug('send() | enabling legacy simulcast for VP9 SVC');
hackVp9Svc = true;
localSdpObject = sdpTransform.parse(offer.sdp);
offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
sdpUnifiedPlanUtils.addLegacySimulcast({
offerMediaObject,
numStreams: layers.spatialLayers,
});
offer = {
type: 'offer',
sdp: sdpTransform.write(localSdpObject),
};
}
logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer);
// Optimize. Only generate new offer if needed.
if (headerExtensionOptions?.absCaptureTime) {
offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
sdpCommonUtils.addHeaderExtension({
offerMediaObject,
headerExtensionUri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',
headerExtensionId: sendingRemoteRtpParameters.headerExtensions.find(headerExtension => headerExtension.uri ===
'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time').id,
});
offer = {
type: 'offer',
sdp: sdpTransform.write(localSdpObject),
};
}
await this._pc.setLocalDescription(offer);
// We can now get the transceiver.mid.
const localId = transceiver.mid;
// Set MID.
sendingRtpParameters.mid = localId;
localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
// Set RTCP CNAME.
sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({
offerMediaObject,
});
// Set msid.
sendingRtpParameters.msid = `${streamId ?? this._sendStream.id} ${track.id}`;
// Set RTP encodings by parsing the SDP offer if no encodings are given.
if (!encodings) {
sendingRtpParameters.encodings = sdpUnifiedPlanUtils.getRtpEncodings({
offerMediaObject,
codecs: sendingRtpParameters.codecs,
});
}
// Set RTP encodings by parsing the SDP offer and complete them with given
// one if just a single encoding has been given.
else if (encodings.length === 1) {
let newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({
offerMediaObject,
codecs: sendingRtpParameters.codecs,
});
Object.assign(newEncodings[0], encodings[0]);
// Hack for VP9 SVC.
if (hackVp9Svc) {
newEncodings = [newEncodings[0]];
}
sendingRtpParameters.encodings = newEncodings;
}
// Otherwise if more than 1 encoding are given use them verbatim.
else {
sendingRtpParameters.encodings = encodings;
}
// If VP8 or H264 and there is effective simulcast, add scalabilityMode to
// each encoding.
if (sendingRtpParameters.encodings.length > 1 &&
(sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' ||
sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) {
for (const encoding of sendingRtpParameters.encodings) {
if (encoding.scalabilityMode) {
encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
}
else {
encoding.scalabilityMode = 'L1T3';
}
}
}
this._remoteSdp.send({
offerMediaObject,
reuseMid: mediaSectionIdx.reuseMid,
offerRtpParameters: sendingRtpParameters,
answerRtpParameters: sendingRemoteRtpParameters,
codecOptions,
});
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
// Store in the map.
this._mapMidTransceiver.set(localId, transceiver);
return {
localId,
rtpParameters: sendingRtpParameters,
rtpSender: transceiver.sender,
};
}
async stopSending(localId) {
this.assertSendDirection();
logger.debug('stopSending() [localId:%s]', localId);
if (this._closed) {
return;
}
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
void transceiver.sender.replaceTrack(null);
this._pc.removeTrack(transceiver.sender);
const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid);
if (mediaSectionClosed) {
try {
transceiver.stop();
}
catch (error) { }
}
const offer = await this._pc.createOffer();
logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
this._mapMidTransceiver.delete(localId);
}
async pauseSending(localId) {
this.assertNotClosed();
this.assertSendDirection();
logger.debug('pauseSending() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
transceiver.direction = 'inactive';
this._remoteSdp.pauseMediaSection(localId);
const offer = await this._pc.createOffer();
logger.debug('pauseSending() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('pauseSending() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
}
async resumeSending(localId) {
this.assertNotClosed();
this.assertSendDirection();
logger.debug('resumeSending() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
this._remoteSdp.resumeSendingMediaSection(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
transceiver.direction = 'sendonly';
const offer = await this._pc.createOffer();
logger.debug('resumeSending() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('resumeSending() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
}
async replaceTrack(localId, track) {
this.assertNotClosed();
this.assertSendDirection();
if (track) {
logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id);
}
else {
logger.debug('replaceTrack() [localId:%s, no track]', localId);
}
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
await transceiver.sender.replaceTrack(track);
}
async setMaxSpatialLayer(localId, spatialLayer) {
this.assertNotClosed();
this.assertSendDirection();
logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
const parameters = transceiver.sender.getParameters();
parameters.encodings.forEach((encoding, idx) => {
if (idx <= spatialLayer) {
encoding.active = true;
}
else {
encoding.active = false;
}
});
await transceiver.sender.setParameters(parameters);
this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings);
const offer = await this._pc.createOffer();
logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
}
async setRtpEncodingParameters(localId, params) {
this.assertNotClosed();
this.assertSendDirection();
logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
const parameters = transceiver.sender.getParameters();
parameters.encodings.forEach((encoding, idx) => {
parameters.encodings[idx] = { ...encoding, ...params };
});
await transceiver.sender.setParameters(parameters);
this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings);
const offer = await this._pc.createOffer();
logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
}
async getSenderStats(localId) {
this.assertNotClosed();
this.assertSendDirection();
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
return transceiver.sender.getStats();
}
async sendDataChannel({ sctpStreamParameters, }) {
this.assertNotClosed();
this.assertSendDirection();
const options = {
negotiated: true,
id: this._nextSendSctpStreamId,
ordered: sctpStreamParameters.ordered,
maxPacketLifeTime: sctpStreamParameters.maxPacketLifeTime,
maxRetransmits: sctpStreamParameters.maxRetransmits,
protocol: sctpStreamParameters.protocol,
};
logger.debug('sendDataChannel() [options:%o]', options);
const dataChannel = this._pc.createDataChannel(sctpStreamParameters.label, options);
// Increase next id.
this._nextSendSctpStreamId =
++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS;
// If this is the first DataChannel we need to create the SDP answer with
// m=application section.
if (!this._hasDataChannelMediaSection) {
const offer = await this._pc.createOffer();
const localSdpObject = sdpTransform.parse(offer.sdp);
const offerMediaObject = localSdpObject.media.find(m => m.type === 'application');
if (!this._transportReady) {
await this.setupTransport({
localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
localSdpObject,
});
}
logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer);
await this._pc.setLocalDescription(offer);
this._remoteSdp.sendSctpAssociation({ offerMediaObject });
const answer = {
type: 'answer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
await this._pc.setRemoteDescription(answer);
this._hasDataChannelMediaSection = true;
}
const newSctpStreamParameters = {
streamId: options.id,
ordered: options.ordered,
maxPacketLifeTime: options.maxPacketLifeTime,
maxRetransmits: options.maxRetransmits,
};
return { dataChannel, sctpStreamParameters: newSctpStreamParameters };
}
async receive(optionsList) {
this.assertNotClosed();
this.assertRecvDirection();
const results = [];
const mapLocalId = new Map();
for (const options of optionsList) {
const { trackId, kind, rtpParameters, streamId } = options;
logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind);
const localId = rtpParameters.mid ?? String(this._mapMidTransceiver.size);
mapLocalId.set(trackId, localId);
// We ignore MSID `trackId` when consuming and always use our computed
// `trackId` which matches the `consumer.id`.
const { msidStreamId } = ortcUtils.getMsidStreamIdAndTrackId(rtpParameters.msid);
this._remoteSdp.receive({
mid: localId,
kind,
offerRtpParameters: rtpParameters,
streamId: streamId ?? msidStreamId ?? rtpParameters.rtcp?.cname ?? '-',
trackId,
});
}
const offer = {
type: 'offer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer);
await this._pc.setRemoteDescription(offer);
let answer = await this._pc.createAnswer();
const localSdpObject = sdpTransform.parse(answer.sdp);
for (const options of optionsList) {
const { trackId, rtpParameters } = options;
const localId = mapLocalId.get(trackId);
const answerMediaObject = localSdpObject.media.find(m => String(m.mid) === localId);
// May need to modify codec parameters in the answer based on codec
// parameters in the offer.
sdpCommonUtils.applyCodecParameters({
offerRtpParameters: rtpParameters,
answerMediaObject,
});
}
answer = {
type: 'answer',
sdp: sdpTransform.write(localSdpObject),
};
if (!this._transportReady) {
await this.setupTransport({
localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
localSdpObject,
});
}
logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
for (const options of optionsList) {
const { trackId } = options;
const localId = mapLocalId.get(trackId);
const transceiver = this._pc
.getTransceivers()
.find((t) => t.mid === localId);
if (!transceiver) {
throw new Error('new RTCRtpTransceiver not found');
}
else {
// Store in the map.
this._mapMidTransceiver.set(localId, transceiver);
results.push({
localId,
track: transceiver.receiver.track,
rtpReceiver: transceiver.receiver,
});
}
}
return results;
}
async stopReceiving(localIds) {
this.assertRecvDirection();
if (this._closed) {
return;
}
for (const localId of localIds) {
logger.debug('stopReceiving() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
this._remoteSdp.closeMediaSection(transceiver.mid);
}
const offer = {
type: 'offer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
for (const localId of localIds) {
this._mapMidTransceiver.delete(localId);
}
}
async pauseReceiving(localIds) {
this.assertNotClosed();
this.assertRecvDirection();
for (const localId of localIds) {
logger.debug('pauseReceiving() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
transceiver.direction = 'inactive';
this._remoteSdp.pauseMediaSection(localId);
}
const offer = {
type: 'offer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
}
async resumeReceiving(localIds) {
this.assertNotClosed();
this.assertRecvDirection();
for (const localId of localIds) {
logger.debug('resumeReceiving() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
transceiver.direction = 'recvonly';
this._remoteSdp.resumeReceivingMediaSection(localId);
}
const offer = {
type: 'offer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
}
async getReceiverStats(localId) {
this.assertNotClosed();
this.assertRecvDirection();
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver) {
throw new Error('associated RTCRtpTransceiver not found');
}
return transceiver.receiver.getStats();
}
async receiveDataChannel({ maxMessageSize, sctpStreamParameters, label, protocol, }) {
this.assertNotClosed();
this.assertRecvDirection();
const { streamId, ordered, maxPacketLifeTime, maxRetransmits, } = sctpStreamParameters;
const options = {
negotiated: true,
id: streamId,
ordered,
maxPacketLifeTime,
maxRetransmits,
protocol,
};
logger.debug('receiveDataChannel() [options:%o]', options);
const dataChannel = this._pc.createDataChannel(label, options);
// If this is the first DataChannel we need to create the SDP offer with
// m=application section.
if (!this._hasDataChannelMediaSection) {
this._remoteSdp.receiveSctpAssociation();
const offer = {
type: 'offer',
sdp: this._remoteSdp.getSdp(),
};
logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer);
await this._pc.setRemoteDescription(offer);
let answer = await this._pc.createAnswer();
const localSdpObject = sdpTransform.parse(answer.sdp);
const answerMediaObject = localSdpObject.media.find(m => m.type === 'application');
answerMediaObject.maxMessageSize = maxMessageSize;
if (!this._transportReady) {
await this.setupTransport({
localDtlsRole: this._forcedLocalDtlsRole ?? 'client',
localSdpObject,
});
}
answer = {
type: 'answer',
sdp: sdpTransform.write(localSdpObject),
};
logger.debug('receiveDataChannel() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
this._hasDataChannelMediaSection = true;
}
return { dataChannel };
}
getDataChannelMaxMessageSize() {
return this._pc.sctp?.maxMessageSize;
}
async setupTransport({ localDtlsRole, localSdpObject, }) {
if (!localSdpObject) {
localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
}
// Get our local DTLS parameters.
const dtlsParameters = sdpCommonUtils.extractDtlsParameters({
sdpObject: localSdpObject,
});
// Set our DTLS role.
dtlsParameters.role = localDtlsRole;
// Update the remote DTLS role in the SDP.
this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client');
// Need to tell the remote transport about our parameters.
await new Promise((resolve, reject) => {
this.safeEmit('@connect', { dtlsParameters }, resolve, reject);
});
this._transportReady = true;
}
onIceGatheringStateChange = () => {
this.emit('@icegatheringstatechange', this._pc.iceGatheringState);
};
onIceCandidateError = (event) => {
this.emit('@icecandidateerror', event);
};
onConnectionStateChange = () => {
this.emit('@connectionstatechange', this._pc.connectionState);
};
onIceConnectionStateChange = () => {
switch (this._pc.iceConnectionState) {
case 'checking': {
this.emit('@connectionstatechange', 'connecting');
break;
}
case 'connected':
case 'completed': {
this.emit('@connectionstatechange', 'connected');
break;
}
case 'failed': {
this.emit('@connectionstatechange', 'failed');
break;
}
case 'disconnected': {
this.emit('@connectionstatechange', 'disconnected');
break;
}
case 'closed': {
this.emit('@connectionstatechange', 'closed');
break;
}
}
};
assertNotClosed() {
if (this._closed) {
throw new errors_1.InvalidStateError('method called in a closed handler');
}
}
assertSendDirection() {
if (this._direction !== 'send') {
throw new Error('method can just be called for handlers with "send" direction');
}
}
assertRecvDirection() {
if (this._direction !== 'recv') {
throw new Error('method can just be called for handlers with "recv" direction');
}
}
}
exports.Chrome74 = Chrome74;