in src/transceivercontroller/DefaultTransceiverController.ts [469:575]
protected setupAudioRedWorker(): void {
// @ts-ignore
const supportsRTCScriptTransform = !!window.RTCRtpScriptTransform;
// @ts-ignore
const supportsInsertableStreams = !!RTCRtpSender.prototype.createEncodedStreams;
if (supportsRTCScriptTransform) {
// This is the prefered approach according to
// https://github.com/w3c/webrtc-encoded-transform/blob/main/explainer.md.
this.logger.info(
'[AudioRed] Supports encoded insertable streams using RTCRtpScriptTransform'
);
} else if (supportsInsertableStreams) {
this.logger.info('[AudioRed] Supports encoded insertable streams using TransformStream');
} else {
this.disableAudioRedundancy();
// We need to recreate the peer connection without encodedInsertableStreams in the
// peer connection config otherwise we would need to create pass through transforms
// for all media streams. Throwing the error here and having AttackMediaInputTask throw the
// error again will result in a full reconnect.
throw new Error(
'[AudioRed] Encoded insertable streams not supported. Recreating peer connection with audio redundancy disabled.'
);
}
// Run the entire redundant audio worker setup in a `try` block to allow any errors to trigger a reconnect with
// audio redundancy disabled.
try {
this.audioRedWorkerURL = URL.createObjectURL(
new Blob([RedundantAudioEncoderWorkerCode], {
type: 'application/javascript',
})
);
this.logger.info(`[AudioRed] Redundant audio worker URL ${this.audioRedWorkerURL}`);
this.audioRedWorker = new Worker(this.audioRedWorkerURL);
} catch (error) {
this.logger.error(`[AudioRed] Unable to create audio red worker due to ${error}`);
URL.revokeObjectURL(this.audioRedWorkerURL);
this.audioRedWorkerURL = null;
this.audioRedWorker = null;
this.disableAudioRedundancy();
this.logger.info(`[AudioRed] Recreating peer connection with audio redundancy disabled`);
// We need to recreate the peer connection without encodedInsertableStreams in the
// peer connection config otherwise we would need to create pass through transforms
// for all media streams. Throwing the error here and having AttackMediaInputTask throw the
// error again will result in a full reconnect.
throw error;
}
this.audioRedEnabled = true;
// We cannot use console.log in production code and we cannot
// transfer the logger object so we need the worker to post messages
// to the main thread for logging
this.audioRedWorker.onmessage = (event: MessageEvent) => {
/* istanbul ignore else */
if (event.data.type === 'REDWorkerLog') {
this.logger.info(event.data.log);
} /* istanbul ignore next */ else if (event.data.type === 'RedundantAudioEncoderStats') {
const redMetricReport = new RedundantAudioRecoveryMetricReport();
redMetricReport.currentTimestampMs = Date.now();
redMetricReport.ssrc = event.data.ssrc;
redMetricReport.totalAudioPacketsLost = event.data.totalAudioPacketsLost;
redMetricReport.totalAudioPacketsExpected = event.data.totalAudioPacketsExpected;
redMetricReport.totalAudioPacketsRecoveredRed = event.data.totalAudioPacketsRecoveredRed;
redMetricReport.totalAudioPacketsRecoveredFec = event.data.totalAudioPacketsRecoveredFec;
this.forEachRedMetricsObserver(redMetricReport);
}
};
if (supportsRTCScriptTransform) {
// @ts-ignore
this._localAudioTransceiver.sender.transform = new RTCRtpScriptTransform(
this.audioRedWorker,
{ type: 'SenderTransform' }
);
// @ts-ignore
this._localAudioTransceiver.receiver.transform = new RTCRtpScriptTransform(
this.audioRedWorker,
{ type: 'ReceiverTransform' }
);
// eslint-disable-next-line
} else /* istanbul ignore else */ if (supportsInsertableStreams) {
// @ts-ignore
const sendStreams = this._localAudioTransceiver.sender.createEncodedStreams();
// @ts-ignore
const receiveStreams = this._localAudioTransceiver.receiver.createEncodedStreams();
this.audioRedWorker.postMessage(
{
msgType: 'StartRedWorker',
send: sendStreams,
receive: receiveStreams,
},
[
sendStreams.readable,
sendStreams.writable,
receiveStreams.readable,
receiveStreams.writable,
]
);
}
/* istanbul ignore next */
this.meetingSessionContext?.audioVideoController.addObserver(this);
/* istanbul ignore next */
this.addRedundantAudioRecoveryMetricsObserver(this.meetingSessionContext?.statsCollector);
}