mediasoup-client: Chrome74 vs Chrome111
67 removals
Words removed | 168 |
Total words | 2156 |
Words removed (%) | 7.79 |
1012 lines
79 additions
Words added | 140 |
Total words | 2128 |
Words added (%) | 6.58 |
1024 lines
import * as sdpTransform from 'sdp-transform';
import * as sdpTransform from 'sdp-transform';
import { Logger } from '../Logger';
import { Logger } from '../Logger';
import * as utils from '../utils';
import * as utils from '../utils';
import * as ortc from '../ortc';
import * as ortc from '../ortc';
import * as sdpCommonUtils from './sdp/commonUtils';
import * as sdpCommonUtils from './sdp/commonUtils';
import * as sdpUnifiedPlanUtils from './sdp/unifiedPlanUtils';
import * as sdpUnifiedPlanUtils from './sdp/unifiedPlanUtils';
import {
import {
HandlerFactory,
HandlerFactory,
HandlerInterface,
HandlerInterface,
HandlerRunOptions,
HandlerRunOptions,
HandlerSendOptions,
HandlerSendOptions,
HandlerSendResult,
HandlerSendResult,
HandlerReceiveOptions,
HandlerReceiveOptions,
HandlerReceiveResult,
HandlerReceiveResult,
HandlerSendDataChannelOptions,
HandlerSendDataChannelOptions,
HandlerSendDataChannelResult,
HandlerSendDataChannelResult,
HandlerReceiveDataChannelOptions,
HandlerReceiveDataChannelOptions,
HandlerReceiveDataChannelResult
HandlerReceiveDataChannelResult
} from './HandlerInterface';
} from './HandlerInterface';
import { RemoteSdp } from './sdp/RemoteSdp';
import { RemoteSdp } from './sdp/RemoteSdp';
import { parse as parseScalabilityMode } from '../scalabilityModes';
import { IceParameters, DtlsRole } from '../Transport';
import { IceParameters, DtlsRole } from '../Transport';
import {
import {
RtpCapabilities,
RtpCapabilities,
RtpParameters,
RtpParameters,
RtpEncodingParameters
RtpEncodingParameters
} from '../RtpParameters';
} from '../RtpParameters';
import { SctpCapabilities, SctpStreamParameters } from '../SctpParameters';
import { SctpCapabilities, SctpStreamParameters } from '../SctpParameters';
const logger = new Logger('Chrome74');
const logger = new Logger('Chrome111');
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
export class Chrome74 extends HandlerInterface
export class Chrome111 extends HandlerInterface
{
{
// Handler direction.
// Handler direction.
private _direction?: 'send' | 'recv';
private _direction?: 'send' | 'recv';
// Remote SDP handler.
// Remote SDP handler.
private _remoteSdp?: RemoteSdp;
private _remoteSdp?: RemoteSdp;
// Generic sending RTP parameters for audio and video.
// Generic sending RTP parameters for audio and video.
private _sendingRtpParametersByKind?: { [key: string]: RtpParameters };
private _sendingRtpParametersByKind?: { [key: string]: RtpParameters };
// Generic sending RTP parameters for audio and video suitable for the SDP
// Generic sending RTP parameters for audio and video suitable for the SDP
// remote answer.
// remote answer.
private _sendingRemoteRtpParametersByKind?: { [key: string]: RtpParameters };
private _sendingRemoteRtpParametersByKind?: { [key: string]: RtpParameters };
// Initial server side DTLS role. If not 'auto', it will force the opposite
// Initial server side DTLS role. If not 'auto', it will force the opposite
// value in client side.
// value in client side.
private _forcedLocalDtlsRole?: DtlsRole;
private _forcedLocalDtlsRole?: DtlsRole;
// RTCPeerConnection instance.
// RTCPeerConnection instance.
private _pc: any;
private _pc: any;
// Map of RTCTransceivers indexed by MID.
// Map of RTCTransceivers indexed by MID.
private readonly _mapMidTransceiver: Map<string, RTCRtpTransceiver> =
private readonly _mapMidTransceiver: Map<string, RTCRtpTransceiver> =
new Map();
new Map();
// Local stream for sending.
// Local stream for sending.
private readonly _sendStream = new MediaStream();
private readonly _sendStream = new MediaStream();
// Whether a DataChannel m=application section has been created.
// Whether a DataChannel m=application section has been created.
private _hasDataChannelMediaSection = false;
private _hasDataChannelMediaSection = false;
// Sending DataChannel id value counter. Incremented for each new DataChannel.
// Sending DataChannel id value counter. Incremented for each new DataChannel.
private _nextSendSctpStreamId = 0;
private _nextSendSctpStreamId = 0;
// Got transport local and remote parameters.
// Got transport local and remote parameters.
private _transportReady = false;
private _transportReady = false;
/**
/**
* Creates a factory function.
* Creates a factory function.
*/
*/
static createFactory(): HandlerFactory
static createFactory(): HandlerFactory
{
{
return (): Chrome74 => new Chrome74();
return (): Chrome111 => new Chrome111();
}
}
constructor()
constructor()
{
{
super();
super();
}
}
get name(): string
get name(): string
{
{
return 'Chrome74';
return 'Chrome111';
}
}
close(): void
close(): void
{
{
logger.debug('close()');
logger.debug('close()');
// Close RTCPeerConnection.
// Close RTCPeerConnection.
if (this._pc)
if (this._pc)
{
{
try { this._pc.close(); }
try { this._pc.close(); }
catch (error) {}
catch (error) {}
}
}
this.emit('@close');
this.emit('@close');
}
}
async getNativeRtpCapabilities(): Promise<RtpCapabilities>
async getNativeRtpCapabilities(): Promise<RtpCapabilities>
{
{
logger.debug('getNativeRtpCapabilities()');
logger.debug('getNativeRtpCapabilities()');
const pc = new (RTCPeerConnection as any)(
const pc = new (RTCPeerConnection as any)(
{
{
iceServers : [],
iceServers : [],
iceTransportPolicy : 'all',
iceTransportPolicy : 'all',
bundlePolicy : 'max-bundle',
bundlePolicy : 'max-bundle',
rtcpMuxPolicy : 'require',
rtcpMuxPolicy : 'require',
sdpSemantics : 'unified-plan'
sdpSemantics : 'unified-plan'
});
});
try
try
{
{
pc.addTransceiver('audio');
pc.addTransceiver('audio');
pc.addTransceiver('video');
pc.addTransceiver('video');
const offer = await pc.createOffer();
const offer = await pc.createOffer();
try { pc.close(); }
try { pc.close(); }
catch (error) {}
catch (error) {}
const sdpObject = sdpTransform.parse(offer.sdp);
const sdpObject = sdpTransform.parse(offer.sdp);
const nativeRtpCapabilities =
const nativeRtpCapabilities =
sdpCommonUtils.extractRtpCapabilities({ sdpObject });
sdpCommonUtils.extractRtpCapabilities({ sdpObject });
return nativeRtpCapabilities;
return nativeRtpCapabilities;
}
}
catch (error)
catch (error)
{
{
try { pc.close(); }
try { pc.close(); }
catch (error2) {}
catch (error2) {}
throw error;
throw error;
}
}
}
}
async getNativeSctpCapabilities(): Promise<SctpCapabilities>
async getNativeSctpCapabilities(): Promise<SctpCapabilities>
{
{
logger.debug('getNativeSctpCapabilities()');
logger.debug('getNativeSctpCapabilities()');
return {
return {
numStreams : SCTP_NUM_STREAMS
numStreams : SCTP_NUM_STREAMS
};
};
}
}
run(
run(
{
{
direction,
direction,
iceParameters,
iceParameters,
iceCandidates,
iceCandidates,
dtlsParameters,
dtlsParameters,
sctpParameters,
sctpParameters,
iceServers,
iceServers,
iceTransportPolicy,
iceTransportPolicy,
additionalSettings,
additionalSettings,
proprietaryConstraints,
proprietaryConstraints,
extendedRtpCapabilities
extendedRtpCapabilities
}: HandlerRunOptions
}: HandlerRunOptions
): void
): void
{
{
logger.debug('run()');
logger.debug('run()');
this._direction = direction;
this._direction = direction;
this._remoteSdp = new RemoteSdp(
this._remoteSdp = new RemoteSdp(
{
{
iceParameters,
iceParameters,
iceCandidates,
iceCandidates,
dtlsParameters,
dtlsParameters,
sctpParameters
sctpParameters
});
});
this._sendingRtpParametersByKind =
this._sendingRtpParametersByKind =
{
{
audio : ortc.getSendingRtpParameters('audio', extendedRtpCapabilities),
audio : ortc.getSendingRtpParameters('audio', extendedRtpCapabilities),
video : ortc.getSendingRtpParameters('video', extendedRtpCapabilities)
video : ortc.getSendingRtpParameters('video', extendedRtpCapabilities)
};
};
this._sendingRemoteRtpParametersByKind =
this._sendingRemoteRtpParametersByKind =
{
{
audio : ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities),
audio : ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities),
video : ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities)
video : ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities)
};
};
if (dtlsParameters.role && dtlsParameters.role !== 'auto')
if (dtlsParameters.role && dtlsParameters.role !== 'auto')
{
{
this._forcedLocalDtlsRole = dtlsParameters.role === 'server'
this._forcedLocalDtlsRole = dtlsParameters.role === 'server'
? 'client'
? 'client'
: 'server';
: 'server';
}
}
this._pc = new (RTCPeerConnection as any)(
this._pc = new (RTCPeerConnection as any)(
{
{
iceServers : iceServers || [],
iceServers : iceServers || [],
iceTransportPolicy : iceTransportPolicy || 'all',
iceTransportPolicy : iceTransportPolicy || 'all',
bundlePolicy : 'max-bundle',
bundlePolicy : 'max-bundle',
rtcpMuxPolicy : 'require',
rtcpMuxPolicy : 'require',
sdpSemantics : 'unified-plan',
sdpSemantics : 'unified-plan',
...additionalSettings
...additionalSettings
},
},
proprietaryConstraints);
proprietaryConstraints);
if (this._pc.connectionState)
if (this._pc.connectionState)
{
{
this._pc.addEventListener('connectionstatechange', () =>
this._pc.addEventListener('connectionstatechange', () =>
{
{
this.emit('@connectionstatechange', this._pc.connectionState);
this.emit('@connectionstatechange', this._pc.connectionState);
});
});
}
}
else
else
{
{
logger.warn(
logger.warn(
'run() | pc.connectionState not supported, using pc.iceConnectionState');
'run() | pc.connectionState not supported, using pc.iceConnectionState');
this._pc.addEventListener('iceconnectionstatechange', () =>
this._pc.addEventListener('iceconnectionstatechange', () =>
{
{
switch (this._pc.iceConnectionState)
switch (this._pc.iceConnectionState)
{
{
case 'checking':
case 'checking':
this.emit('@connectionstatechange', 'connecting');
this.emit('@connectionstatechange', 'connecting');
break;
break;
case 'connected':
case 'connected':
case 'completed':
case 'completed':
this.emit('@connectionstatechange', 'connected');
this.emit('@connectionstatechange', 'connected');
break;
break;
case 'failed':
case 'failed':
this.emit('@connectionstatechange', 'failed');
this.emit('@connectionstatechange', 'failed');
break;
break;
case 'disconnected':
case 'disconnected':
this.emit('@connectionstatechange', 'disconnected');
this.emit('@connectionstatechange', 'disconnected');
break;
break;
case 'closed':
case 'closed':
this.emit('@connectionstatechange', 'closed');
this.emit('@connectionstatechange', 'closed');
break;
break;
}
}
});
});
}
}
}
}
async updateIceServers(iceServers: RTCIceServer[]): Promise<void>
async updateIceServers(iceServers: RTCIceServer[]): Promise<void>
{
{
logger.debug('updateIceServers()');
logger.debug('updateIceServers()');
const configuration = this._pc.getConfiguration();
const configuration = this._pc.getConfiguration();
configuration.iceServers = iceServers;
configuration.iceServers = iceServers;
this._pc.setConfiguration(configuration);
this._pc.setConfiguration(configuration);
}
}
async restartIce(iceParameters: IceParameters): Promise<void>
async restartIce(iceParameters: IceParameters): Promise<void>
{
{
logger.debug('restartIce()');
logger.debug('restartIce()');
// Provide the remote SDP handler with new remote ICE parameters.
// Provide the remote SDP handler with new remote ICE parameters.
this._remoteSdp!.updateIceParameters(iceParameters);
this._remoteSdp!.updateIceParameters(iceParameters);
if (!this._transportReady)
if (!this._transportReady)
return;
return;
if (this._direction === 'send')
if (this._direction === 'send')
{
{
const offer = await this._pc.createOffer({ iceRestart: true });
const offer = await this._pc.createOffer({ iceRestart: true });
logger.debug(
logger.debug(
'restartIce() | calling pc.setLocalDescription() [offer:%o]',
'restartIce() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'restartIce() | calling pc.setRemoteDescription() [answer:%o]',
'restartIce() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
}
}
else
else
{
{
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'restartIce() | calling pc.setRemoteDescription() [offer:%o]',
'restartIce() | calling pc.setRemoteDescription() [offer:%o]',
offer);
offer);
await this._pc.setRemoteDescription(offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
const answer = await this._pc.createAnswer();
logger.debug(
logger.debug(
'restartIce() | calling pc.setLocalDescription() [answer:%o]',
'restartIce() | calling pc.setLocalDescription() [answer:%o]',
answer);
answer);
await this._pc.setLocalDescription(answer);
await this._pc.setLocalDescription(answer);
}
}
}
}
async getTransportStats(): Promise<RTCStatsReport>
async getTransportStats(): Promise<RTCStatsReport>
{
{
return this._pc.getStats();
return this._pc.getStats();
}
}
async send(
async send(
{ track, encodings, codecOptions, codec }: HandlerSendOptions
{ track, encodings, codecOptions, codec }: HandlerSendOptions
): Promise<HandlerSendResult>
): Promise<HandlerSendResult>
{
{
this.assertSendDirection();
this.assertSendDirection();
logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id);
logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id);
if (encodings && encodings.length > 1)
if (encodings && encodings.length > 1)
{
{
encodings.forEach((encoding: RtpEncodingParameters, idx: number) =>
encodings.forEach((encoding: RtpEncodingParameters, idx: number) =>
{
{
encoding.rid = `r${idx}`;
encoding.rid = `r${idx}`;
});
});
}
}
const sendingRtpParameters =
const sendingRtpParameters =
utils.clone(this._sendingRtpParametersByKind![track.kind], {});
utils.clone(this._sendingRtpParametersByKind![track.kind], {});
// This may throw.
// This may throw.
sendingRtpParameters.codecs =
sendingRtpParameters.codecs =
ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
const sendingRemoteRtpParameters =
const sendingRemoteRtpParameters =
utils.clone(this._sendingRemoteRtpParametersByKind![track.kind], {});
utils.clone(this._sendingRemoteRtpParametersByKind![track.kind], {});
// This may throw.
// This may throw.
sendingRemoteRtpParameters.codecs =
sendingRemoteRtpParameters.codecs =
ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec);
ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec);
const mediaSectionIdx = this._remoteSdp!.getNextMediaSectionIdx();
const mediaSectionIdx = this._remoteSdp!.getNextMediaSectionIdx();
const transceiver = this._pc.addTransceiver(
const transceiver = this._pc.addTransceiver(
track,
track,
{
{
direction : 'sendonly',
direction : 'sendonly',
streams : [ this._sendStream ],
streams : [ this._sendStream ],
sendEncodings : encodings
sendEncodings : encodings
});
});
let offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
let localSdpObject = sdpTransform.parse(offer.sdp);
let localSdpObject = sdpTransform.parse(offer.sdp);
let offerMediaObject;
if (!this._transportReady)
if (!this._transportReady)
{
{
await this.setupTransport(
await this.setupTransport(
{
{
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localSdpObject
localSdpObject
});
});
}
}
// Special case for VP9 with SVC.
let hackVp9Svc = false;
const layers =
parseScalabilityMode((encodings || [ {} ])[0].scalabilityMode);
if (
encodings &&
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(
logger.debug(
'send() | calling pc.setLocalDescription() [offer:%o]',
'send() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
// We can now get the transceiver.mid.
// We can now get the transceiver.mid.
const localId = transceiver.mid;
const localId = transceiver.mid;
// Set MID.
// Set MID.
sendingRtpParameters.mid = localId;
sendingRtpParameters.mid = localId;
localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
const offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
// Set RTCP CNAME.
// Set RTCP CNAME.
sendingRtpParameters.rtcp.cname =
sendingRtpParameters.rtcp.cname =
sdpCommonUtils.getCname({ offerMediaObject });
sdpCommonUtils.getCname({ offerMediaObject });
// Set RTP encodings by parsing the SDP offer if no encodings are given.
// Set RTP encodings by parsing the SDP offer if no encodings are given.
if (!encodings)
if (!encodings)
{
{
sendingRtpParameters.encodings =
sendingRtpParameters.encodings =
sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject });
sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject });
}
}
// Set RTP encodings by parsing the SDP offer and complete them with given
// Set RTP encodings by parsing the SDP offer and complete them with given
// one if just a single encoding has been given.
// one if just a single encoding has been given.
else if (encodings.length === 1)
else if (encodings.length === 1)
{
{
let newEncodings =
const newEncodings =
sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject });
sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject });
Object.assign(newEncodings[0], encodings[0]);
Object.assign(newEncodings[0], encodings[0]);
// Hack for VP9 SVC.
if (hackVp9Svc)
newEncodings = [ newEncodings[0] ];
sendingRtpParameters.encodings = newEncodings;
sendingRtpParameters.encodings = newEncodings;
}
}
// Otherwise if more than 1 encoding are given use them verbatim.
// Otherwise if more than 1 encoding are given use them verbatim.
else
else
{
{
sendingRtpParameters.encodings = encodings;
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
{
// By default Chrome enables 2 temporal layers (not in all OS but
// anyway).
encoding.scalabilityMode = 'L1T2';
}
}
}
}
this._remoteSdp!.send(
this._remoteSdp!.send(
{
{
offerMediaObject,
offerMediaObject,
reuseMid : mediaSectionIdx.reuseMid,
reuseMid : mediaSectionIdx.reuseMid,
offerRtpParameters : sendingRtpParameters,
offerRtpParameters : sendingRtpParameters,
answerRtpParameters : sendingRemoteRtpParameters,
answerRtpParameters : sendingRemoteRtpParameters,
codecOptions,
codecOptions,
extmapAllowMixed : true
extmapAllowMixed : true
});
});
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'send() | calling pc.setRemoteDescription() [answer:%o]',
'send() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
// Store in the map.
// Store in the map.
this._mapMidTransceiver.set(localId, transceiver);
this._mapMidTransceiver.set(localId, transceiver);
return {
return {
localId,
localId,
rtpParameters : sendingRtpParameters,
rtpParameters : sendingRtpParameters,
rtpSender : transceiver.sender
rtpSender : transceiver.sender
};
};
}
}
async stopSending(localId: string): Promise<void>
async stopSending(localId: string): Promise<void>
{
{
this.assertSendDirection();
this.assertSendDirection();
logger.debug('stopSending() [localId:%s]', localId);
logger.debug('stopSending() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
transceiver.sender.replaceTrack(null);
transceiver.sender.replaceTrack(null);
this._pc.removeTrack(transceiver.sender);
this._pc.removeTrack(transceiver.sender);
const mediaSectionClosed =
const mediaSectionClosed =
this._remoteSdp!.closeMediaSection(transceiver.mid!);
this._remoteSdp!.closeMediaSection(transceiver.mid!);
if (mediaSectionClosed)
if (mediaSectionClosed)
{
{
try
try
{
{
transceiver.stop();
transceiver.stop();
}
}
catch (error)
catch (error)
{}
{}
}
}
const offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
logger.debug(
logger.debug(
'stopSending() | calling pc.setLocalDescription() [offer:%o]',
'stopSending() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'stopSending() | calling pc.setRemoteDescription() [answer:%o]',
'stopSending() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
this._mapMidTransceiver.delete(localId);
this._mapMidTransceiver.delete(localId);
}
}
async pauseSending(localId: string): Promise<void>
async pauseSending(localId: string): Promise<void>
{
{
this.assertSendDirection();
this.assertSendDirection();
logger.debug('pauseSending() [localId:%s]', localId);
logger.debug('pauseSending() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
transceiver.direction = 'inactive';
transceiver.direction = 'inactive';
this._remoteSdp!.pauseMediaSection(localId);
this._remoteSdp!.pauseMediaSection(localId);
const offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
logger.debug(
logger.debug(
'pauseSending() | calling pc.setLocalDescription() [offer:%o]',
'pauseSending() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'pauseSending() | calling pc.setRemoteDescription() [answer:%o]',
'pauseSending() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
}
}
async resumeSending(localId: string): Promise<void>
async resumeSending(localId: string): Promise<void>
{
{
this.assertSendDirection();
this.assertSendDirection();
logger.debug('resumeSending() [localId:%s]', localId);
logger.debug('resumeSending() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
this._remoteSdp!.resumeSendingMediaSection(localId);
this._remoteSdp!.resumeSendingMediaSection(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
transceiver.direction = 'sendonly';
transceiver.direction = 'sendonly';
const offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
logger.debug(
logger.debug(
'resumeSending() | calling pc.setLocalDescription() [offer:%o]',
'resumeSending() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'resumeSending() | calling pc.setRemoteDescription() [answer:%o]',
'resumeSending() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
}
}
async replaceTrack(
async replaceTrack(
localId: string, track: MediaStreamTrack | null
localId: string, track: MediaStreamTrack | null
): Promise<void>
): Promise<void>
{
{
this.assertSendDirection();
this.assertSendDirection();
if (track)
if (track)
{
{
logger.debug(
logger.debug(
'replaceTrack() [localId:%s, track.id:%s]', localId, track.id);
'replaceTrack() [localId:%s, track.id:%s]', localId, track.id);
}
}
else
else
{
{
logger.debug('replaceTrack() [localId:%s, no track]', localId);
logger.debug('replaceTrack() [localId:%s, no track]', localId);
}
}
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
await transceiver.sender.replaceTrack(track);
await transceiver.sender.replaceTrack(track);
}
}
async setMaxSpatialLayer(localId: string, spatialLayer: number): Promise<void>
async setMaxSpatialLayer(localId: string, spatialLayer: number): Promise<void>
{
{
this.assertSendDirection();
this.assertSendDirection();
logger.debug(
logger.debug(
'setMaxSpatialLayer() [localId:%s, spatialLayer:%s]',
'setMaxSpatialLayer() [localId:%s, spatialLayer:%s]',
localId, spatialLayer);
localId, spatialLayer);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
const parameters = transceiver.sender.getParameters();
const parameters = transceiver.sender.getParameters();
parameters.encodings.forEach((encoding: RTCRtpEncodingParameters, idx: number) =>
parameters.encodings.forEach((encoding: RTCRtpEncodingParameters, idx: number) =>
{
{
if (idx <= spatialLayer)
if (idx <= spatialLayer)
encoding.active = true;
encoding.active = true;
else
else
encoding.active = false;
encoding.active = false;
});
});
await transceiver.sender.setParameters(parameters);
await transceiver.sender.setParameters(parameters);
this._remoteSdp!.muxMediaSectionSimulcast(localId, parameters.encodings);
this._remoteSdp!.muxMediaSectionSimulcast(localId, parameters.encodings);
const offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
logger.debug(
logger.debug(
'setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]',
'setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]',
'setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
}
}
async setRtpEncodingParameters(localId: string, params: any): Promise<void>
async setRtpEncodingParameters(localId: string, params: any): Promise<void>
{
{
this.assertSendDirection();
this.assertSendDirection();
logger.debug(
logger.debug(
'setRtpEncodingParameters() [localId:%s, params:%o]',
'setRtpEncodingParameters() [localId:%s, params:%o]',
localId, params);
localId, params);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
const parameters = transceiver.sender.getParameters();
const parameters = transceiver.sender.getParameters();
parameters.encodings.forEach((encoding: RTCRtpEncodingParameters, idx: number) =>
parameters.encodings.forEach((encoding: RTCRtpEncodingParameters, idx: number) =>
{
{
parameters.encodings[idx] = { ...encoding, ...params };
parameters.encodings[idx] = { ...encoding, ...params };
});
});
await transceiver.sender.setParameters(parameters);
await transceiver.sender.setParameters(parameters);
this._remoteSdp!.muxMediaSectionSimulcast(localId, parameters.encodings);
this._remoteSdp!.muxMediaSectionSimulcast(localId, parameters.encodings);
const offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
logger.debug(
logger.debug(
'setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]',
'setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]',
'setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
}
}
async getSenderStats(localId: string): Promise<RTCStatsReport>
async getSenderStats(localId: string): Promise<RTCStatsReport>
{
{
this.assertSendDirection();
this.assertSendDirection();
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
return transceiver.sender.getStats();
return transceiver.sender.getStats();
}
}
async sendDataChannel(
async sendDataChannel(
{
{
ordered,
ordered,
maxPacketLifeTime,
maxPacketLifeTime,
maxRetransmits,
maxRetransmits,
label,
label,
protocol
protocol
}: HandlerSendDataChannelOptions
}: HandlerSendDataChannelOptions
): Promise<HandlerSendDataChannelResult>
): Promise<HandlerSendDataChannelResult>
{
{
this.assertSendDirection();
this.assertSendDirection();
const options =
const options =
{
{
negotiated : true,
negotiated : true,
id : this._nextSendSctpStreamId,
id : this._nextSendSctpStreamId,
ordered,
ordered,
maxPacketLifeTime,
maxPacketLifeTime,
maxRetransmits,
maxRetransmits,
protocol
protocol
};
};
logger.debug('sendDataChannel() [options:%o]', options);
logger.debug('sendDataChannel() [options:%o]', options);
const dataChannel = this._pc.createDataChannel(label, options);
const dataChannel = this._pc.createDataChannel(label, options);
// Increase next id.
// Increase next id.
this._nextSendSctpStreamId =
this._nextSendSctpStreamId =
++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS;
++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS;
// If this is the first DataChannel we need to create the SDP answer with
// If this is the first DataChannel we need to create the SDP answer with
// m=application section.
// m=application section.
if (!this._hasDataChannelMediaSection)
if (!this._hasDataChannelMediaSection)
{
{
const offer = await this._pc.createOffer();
const offer = await this._pc.createOffer();
const localSdpObject = sdpTransform.parse(offer.sdp);
const localSdpObject = sdpTransform.parse(offer.sdp);
const offerMediaObject = localSdpObject.media
const offerMediaObject = localSdpObject.media
.find((m: any) => m.type === 'application');
.find((m: any) => m.type === 'application');
if (!this._transportReady)
if (!this._transportReady)
{
{
await this.setupTransport(
await this.setupTransport(
{
{
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localSdpObject
localSdpObject
});
});
}
}
logger.debug(
logger.debug(
'sendDataChannel() | calling pc.setLocalDescription() [offer:%o]',
'sendDataChannel() | calling pc.setLocalDescription() [offer:%o]',
offer);
offer);
await this._pc.setLocalDescription(offer);
await this._pc.setLocalDescription(offer);
this._remoteSdp!.sendSctpAssociation({ offerMediaObject });
this._remoteSdp!.sendSctpAssociation({ offerMediaObject });
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]',
'sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]',
answer);
answer);
await this._pc.setRemoteDescription(answer);
await this._pc.setRemoteDescription(answer);
this._hasDataChannelMediaSection = true;
this._hasDataChannelMediaSection = true;
}
}
const sctpStreamParameters: SctpStreamParameters =
const sctpStreamParameters: SctpStreamParameters =
{
{
streamId : options.id,
streamId : options.id,
ordered : options.ordered,
ordered : options.ordered,
maxPacketLifeTime : options.maxPacketLifeTime,
maxPacketLifeTime : options.maxPacketLifeTime,
maxRetransmits : options.maxRetransmits
maxRetransmits : options.maxRetransmits
};
};
return { dataChannel, sctpStreamParameters };
return { dataChannel, sctpStreamParameters };
}
}
async receive(
async receive(
optionsList: HandlerReceiveOptions[]
optionsList: HandlerReceiveOptions[]
) : Promise<HandlerReceiveResult[]>
) : Promise<HandlerReceiveResult[]>
{
{
this.assertRecvDirection();
this.assertRecvDirection();
const results: HandlerReceiveResult[] = [];
const results: HandlerReceiveResult[] = [];
const mapLocalId: Map<string, string> = new Map();
const mapLocalId: Map<string, string> = new Map();
for (const options of optionsList)
for (const options of optionsList)
{
{
const { trackId, kind, rtpParameters, streamId } = options;
const { trackId, kind, rtpParameters, streamId } = options;
logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind);
logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind);
const localId = rtpParameters.mid || String(this._mapMidTransceiver.size);
const localId = rtpParameters.mid || String(this._mapMidTransceiver.size);
mapLocalId.set(trackId, localId);
mapLocalId.set(trackId, localId);
this._remoteSdp!.receive(
this._remoteSdp!.receive(
{
{
mid : localId,
mid : localId,
kind,
kind,
offerRtpParameters : rtpParameters,
offerRtpParameters : rtpParameters,
streamId : streamId || rtpParameters.rtcp!.cname!,
streamId : streamId || rtpParameters.rtcp!.cname!,
trackId
trackId
});
});
}
}
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'receive() | calling pc.setRemoteDescription() [offer:%o]',
'receive() | calling pc.setRemoteDescription() [offer:%o]',
offer);
offer);
await this._pc.setRemoteDescription(offer);
await this._pc.setRemoteDescription(offer);
let answer = await this._pc.createAnswer();
let answer = await this._pc.createAnswer();
const localSdpObject = sdpTransform.parse(answer.sdp);
const localSdpObject = sdpTransform.parse(answer.sdp);
for (const options of optionsList)
for (const options of optionsList)
{
{
const { trackId, rtpParameters } = options;
const { trackId, rtpParameters } = options;
const localId = mapLocalId.get(trackId);
const localId = mapLocalId.get(trackId);
const answerMediaObject = localSdpObject.media
const answerMediaObject = localSdpObject.media
.find((m: any) => String(m.mid) === localId);
.find((m: any) => String(m.mid) === localId);
// May need to modify codec parameters in the answer based on codec
// May need to modify codec parameters in the answer based on codec
// parameters in the offer.
// parameters in the offer.
sdpCommonUtils.applyCodecParameters(
sdpCommonUtils.applyCodecParameters(
{
{
offerRtpParameters : rtpParameters,
offerRtpParameters : rtpParameters,
answerMediaObject
answerMediaObject
});
});
}
}
answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) };
answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) };
if (!this._transportReady)
if (!this._transportReady)
{
{
await this.setupTransport(
await this.setupTransport(
{
{
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localSdpObject
localSdpObject
});
});
}
}
logger.debug(
logger.debug(
'receive() | calling pc.setLocalDescription() [answer:%o]',
'receive() | calling pc.setLocalDescription() [answer:%o]',
answer);
answer);
await this._pc.setLocalDescription(answer);
await this._pc.setLocalDescription(answer);
for (const options of optionsList)
for (const options of optionsList)
{
{
const { trackId } = options;
const { trackId } = options;
const localId = mapLocalId.get(trackId)!;
const localId = mapLocalId.get(trackId)!;
const transceiver = this._pc.getTransceivers()
const transceiver = this._pc.getTransceivers()
.find((t: RTCRtpTransceiver) => t.mid === localId);
.find((t: RTCRtpTransceiver) => t.mid === localId);
if (!transceiver)
if (!transceiver)
{
{
throw new Error('new RTCRtpTransceiver not found');
throw new Error('new RTCRtpTransceiver not found');
}
}
else
else
{
{
// Store in the map.
// Store in the map.
this._mapMidTransceiver.set(localId, transceiver);
this._mapMidTransceiver.set(localId, transceiver);
results.push({
results.push({
localId,
localId,
track : transceiver.receiver.track,
track : transceiver.receiver.track,
rtpReceiver : transceiver.receiver
rtpReceiver : transceiver.receiver
});
});
}
}
}
}
return results;
return results;
}
}
async stopReceiving(localIds: string[]): Promise<void>
async stopReceiving(localIds: string[]): Promise<void>
{
{
this.assertRecvDirection();
this.assertRecvDirection();
for (const localId of localIds)
for (const localId of localIds)
{
{
logger.debug('stopReceiving() [localId:%s]', localId);
logger.debug('stopReceiving() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
this._remoteSdp!.closeMediaSection(transceiver.mid!);
this._remoteSdp!.closeMediaSection(transceiver.mid!);
}
}
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'stopReceiving() | calling pc.setRemoteDescription() [offer:%o]',
'stopReceiving() | calling pc.setRemoteDescription() [offer:%o]',
offer);
offer);
await this._pc.setRemoteDescription(offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
const answer = await this._pc.createAnswer();
logger.debug(
logger.debug(
'stopReceiving() | calling pc.setLocalDescription() [answer:%o]',
'stopReceiving() | calling pc.setLocalDescription() [answer:%o]',
answer);
answer);
await this._pc.setLocalDescription(answer);
await this._pc.setLocalDescription(answer);
for (const localId of localIds)
for (const localId of localIds)
{
{
this._mapMidTransceiver.delete(localId);
this._mapMidTransceiver.delete(localId);
}
}
}
}
async pauseReceiving(localIds: string[]): Promise<void>
async pauseReceiving(localIds: string[]): Promise<void>
{
{
this.assertRecvDirection();
this.assertRecvDirection();
for (const localId of localIds)
for (const localId of localIds)
{
{
logger.debug('pauseReceiving() [localId:%s]', localId);
logger.debug('pauseReceiving() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
transceiver.direction = 'inactive';
transceiver.direction = 'inactive';
this._remoteSdp!.pauseMediaSection(localId);
this._remoteSdp!.pauseMediaSection(localId);
}
}
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]',
'pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]',
offer);
offer);
await this._pc.setRemoteDescription(offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
const answer = await this._pc.createAnswer();
logger.debug(
logger.debug(
'pauseReceiving() | calling pc.setLocalDescription() [answer:%o]',
'pauseReceiving() | calling pc.setLocalDescription() [answer:%o]',
answer);
answer);
await this._pc.setLocalDescription(answer);
await this._pc.setLocalDescription(answer);
}
}
async resumeReceiving(localIds: string[]): Promise<void>
async resumeReceiving(localIds: string[]): Promise<void>
{
{
this.assertRecvDirection();
this.assertRecvDirection();
for (const localId of localIds)
for (const localId of localIds)
{
{
logger.debug('resumeReceiving() [localId:%s]', localId);
logger.debug('resumeReceiving() [localId:%s]', localId);
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
transceiver.direction = 'recvonly';
transceiver.direction = 'recvonly';
this._remoteSdp!.resumeReceivingMediaSection(localId);
this._remoteSdp!.resumeReceivingMediaSection(localId);
}
}
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
logger.debug(
logger.debug(
'resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]',
'resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]',
offer);
offer);
await this._pc.setRemoteDescription(offer);
await this._pc.setRemoteDescription(offer);
const answer = await this._pc.createAnswer();
const answer = await this._pc.createAnswer();
logger.debug(
logger.debug(
'resumeReceiving() | calling pc.setLocalDescription() [answer:%o]',
'resumeReceiving() | calling pc.setLocalDescription() [answer:%o]',
answer);
answer);
await this._pc.setLocalDescription(answer);
await this._pc.setLocalDescription(answer);
}
}
async getReceiverStats(localId: string): Promise<RTCStatsReport>
async getReceiverStats(localId: string): Promise<RTCStatsReport>
{
{
this.assertRecvDirection();
this.assertRecvDirection();
const transceiver = this._mapMidTransceiver.get(localId);
const transceiver = this._mapMidTransceiver.get(localId);
if (!transceiver)
if (!transceiver)
throw new Error('associated RTCRtpTransceiver not found');
throw new Error('associated RTCRtpTransceiver not found');
return transceiver.receiver.getStats();
return transceiver.receiver.getStats();
}
}
async receiveDataChannel(
async receiveDataChannel(
{ sctpStreamParameters, label, protocol }: HandlerReceiveDataChannelOptions
{ sctpStreamParameters, label, protocol }: HandlerReceiveDataChannelOptions
): Promise<HandlerReceiveDataChannelResult>
): Promise<HandlerReceiveDataChannelResult>
{
{
this.assertRecvDirection();
this.assertRecvDirection();
const {
const {
streamId,
streamId,
ordered,
ordered,
maxPacketLifeTime,
maxRetransmits
}: SctpStreamParameters = 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);
const answer = await this._pc.createAnswer();
if (!this._transportReady)
{
const localSdpObject = sdpTransform.parse(answer.sdp);
await this.setupTransport(
{
localDtlsRole : this._forcedLocalDtlsRole ?? 'client',
localSdpObject
});
}
logger.debug(
'receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]',
answer);
await this._pc.setLocalDescription(answer);
this._hasDataChannelMediaSection = true;
}
return { dataChannel };
}
private async setupTransport(
{
localDtlsRole,
localSdpObject
}:
{
localDtlsRole: DtlsRole;
localSdpObject?: any;
}
): Promise<void>
{
if (!localSdpObject)
localSdpObject = sdpTransform.parse(this._pc.localD