sinch-rtc
Version:
RTC JavaScript/Web SDK
283 lines • 12.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PeerConnection = exports.MAX_WAIT_DISCONNECTED_PC = exports.IceConnectionState = exports.IceTransportPolicy = void 0;
const WebRTCFactory_1 = require("./WebRTCFactory");
const Either_1 = require("fp-ts/Either");
const function_1 = require("fp-ts/function");
const ClientEvent_1 = require("../calling/reporting/ClientEvent");
var IceTransportPolicy;
(function (IceTransportPolicy) {
IceTransportPolicy["Relay"] = "relay";
IceTransportPolicy["All"] = "all";
})(IceTransportPolicy || (exports.IceTransportPolicy = IceTransportPolicy = {}));
var IceConnectionState;
(function (IceConnectionState) {
IceConnectionState["New"] = "new";
IceConnectionState["Checking"] = "checking";
IceConnectionState["Connected"] = "connected";
IceConnectionState["Completed"] = "completed";
IceConnectionState["Failed"] = "failed";
IceConnectionState["Disconnected"] = "disconnected";
IceConnectionState["Closed"] = "closed";
})(IceConnectionState || (exports.IceConnectionState = IceConnectionState = {}));
exports.MAX_WAIT_DISCONNECTED_PC = 1000 * 60 * 5; // Wait 5 minutes before disconnecting the call
/**
* Class that wraps original RTCPeerConnection member of browser WebRTC components
*/
class PeerConnection {
constructor(observer, rtcConfiguration, inactivate, clientEventsCollector, userInstanceId, originalOffer) {
this.observer = observer;
this.rtcConfiguration = rtcConfiguration;
this.inactivate = inactivate;
this.clientEventsCollector = clientEventsCollector;
this.userInstanceId = userInstanceId;
this.originalOffer = originalOffer;
this.pendingRemoteCandidates = Array();
this.localCandidates = Array();
this.logClientEvents = (iceConnectionState) => {
switch (iceConnectionState) {
case IceConnectionState.Connected: {
this.clientEventsCollector.addEvent(ClientEvent_1.ClientEvent.createClientEventType(ClientEvent_1.ClientEventName.WEBRTC_ICE_CONNECTION_CONNECTED));
break;
}
case IceConnectionState.Completed: {
this.clientEventsCollector.addEvent(ClientEvent_1.ClientEvent.createClientEventType(ClientEvent_1.ClientEventName.WEBRTC_ICE_CONNECTION_COMPLETED));
break;
}
case IceConnectionState.Disconnected: {
this.clientEventsCollector.addEvent(ClientEvent_1.ClientEvent.createClientEventType(ClientEvent_1.ClientEventName.WEBRTC_ICE_CONNECTION_DISCONNECTED));
break;
}
case IceConnectionState.Failed: {
this.clientEventsCollector.addEvent(ClientEvent_1.ClientEvent.createClientEventType(ClientEvent_1.ClientEventName.WEBRTC_ICE_CONNECTION_FAILED));
break;
}
default:
break;
}
return iceConnectionState;
};
this.getIceConnectionState = () => {
return this.pc.iceConnectionState;
};
this.notifyCurrentIceStateObserver = (iceConnectionState) => {
var _a;
(_a = this.observer) === null || _a === void 0 ? void 0 : _a.onIceConnectionStateChanged(iceConnectionState);
return iceConnectionState;
};
this.handleInactivateTimerState = (iceConnectionState) => {
// Ideally, we should check only for "failed" IceConnectionState but in chromium it stays in "Disconnected" state because of the following bug:
// See https://bugs.chromium.org/p/chromium/issues/detail?id=982793 and https://bugs.chromium.org/p/chromium/issues/detail?id=935898
return [
IceConnectionState.Disconnected,
IceConnectionState.Failed,
].includes(iceConnectionState)
? (0, Either_1.right)(iceConnectionState)
: (0, Either_1.left)(iceConnectionState);
};
this.whenRecovered = (iceConnectionState) => {
iceConnectionState === IceConnectionState.Connected &&
this.clearAnyScheduledTimer();
return iceConnectionState;
};
this.whenDisconnectedOrFailed = (iceConnectionState) => {
this.clearAnyScheduledTimer();
this.timeoutID = this.setTimerToInactivate(exports.MAX_WAIT_DISCONNECTED_PC, this.inactivate);
return iceConnectionState;
};
this.logIceState = (evt) => {
console.log(this.pc.iceConnectionState, this.pc.iceGatheringState, this.pc.signalingState, evt);
};
this.onLocalIceCandidate = (evt) => {
var _a;
if (evt.candidate) {
if (this.instanceId)
(_a = this.observer) === null || _a === void 0 ? void 0 : _a.onIceCandidate(this.instanceId, evt.candidate);
else
this.localCandidates.push(evt.candidate);
}
};
this.addIceCandidate = (candidate) => {
if (this.pc.remoteDescription) {
console.log("Adding ICE candidate to peer connection", { candidate });
this.pc
.addIceCandidate(candidate)
.catch((reason) => console.error("Failed to add candidate:", reason));
}
else {
console.log("Remote description is not available yet. Caching the ICE candidate", { candidate });
this.pendingRemoteCandidates.push(candidate);
}
};
this.pc = this.createPeerConnection();
this.pc.onicecandidate = this.onLocalIceCandidate;
this.pc.onnegotiationneeded = () => __awaiter(this, void 0, void 0, function* () {
yield this.createOffer();
});
this.pc.oniceconnectionstatechange = (0, function_1.flow)(this.logIceState, this.getIceConnectionState, this.notifyCurrentIceStateObserver, this.handleInactivateTimerState, (0, Either_1.match)(this.whenRecovered, this.whenDisconnectedOrFailed), this.logClientEvents);
this.pc.onsignalingstatechange = (evt) => this.logIceState(evt);
this.pc.ontrack = (evt) => {
var _a;
(_a = this.observer) === null || _a === void 0 ? void 0 : _a.onTrack(evt, this.instanceId);
};
}
clearAnyScheduledTimer() {
this.timeoutID && clearTimeout(this.timeoutID);
}
close() {
this.observer = null;
this.pc.close();
}
setTimerToInactivate(timeout, inactivate) {
return setTimeout(inactivate, timeout);
}
get transceivers() {
return this.pc.getTransceivers();
}
enableSenderTracks(enabled, kind) {
this.transceivers
.filter((t) => {
return t.sender.track && t.sender.track.kind == kind;
})
.forEach((t) => {
// this could/should be replaced with `t.direction = enable ? "sendrecv" : "recvonly"` ones perfect negotiation is in place RTC-7489
if (t.sender.track)
t.sender.track.enabled = enabled;
});
}
getSenders(kind) {
return this.transceivers
.filter((t) => {
return t.sender.track && t.sender.track.kind == kind;
})
.map((t) => t.sender);
}
insertDtmf(tones) {
let inserted = false;
this.getSenders("audio").forEach((s) => {
var _a;
if ((_a = s.dtmf) === null || _a === void 0 ? void 0 : _a.canInsertDTMF) {
s.dtmf.insertDTMF(tones);
inserted = true;
}
});
return inserted;
}
setLocalMedia(media) {
const senders = this.getSenders("audio").concat(this.getSenders("video"));
if (senders.length !== 0) {
return this.replaceLocalMedia(media);
}
for (const track of media.getTracks()) {
if (media.mediaStream instanceof MediaStream)
this.pc.addTrack(track, media.mediaStream);
else
this.pc.addTrack(track);
}
}
replaceLocalMedia(media) {
const newAudioTrack = media.getTracks().find((t) => t.kind === "audio");
const newVideoTrack = media.getTracks().find((t) => t.kind === "video");
const currentAudioSender = this.getSenders("audio");
const currentVideoSender = this.getSenders("video");
this.replaceSenderTrack(currentAudioSender, newAudioTrack);
this.replaceSenderTrack(currentVideoSender, newVideoTrack);
}
replaceSenderTrack(senders, newTrack) {
var _a;
if (!senders || senders.length === 0 || !newTrack)
return;
const sender = senders[0];
const oldTrack = sender.track;
const enabled = (_a = oldTrack === null || oldTrack === void 0 ? void 0 : oldTrack.enabled) !== null && _a !== void 0 ? _a : true;
newTrack.enabled = enabled;
sender.replaceTrack(newTrack).catch((reason) => {
console.error(`Failed to replace ${newTrack.kind} track:`, reason);
});
oldTrack === null || oldTrack === void 0 ? void 0 : oldTrack.stop();
}
get instanceId() {
return this.userInstanceId;
}
set instanceId(instanceId) {
this.userInstanceId = instanceId;
if (instanceId)
this.localCandidates
.splice(0)
.forEach((c) => { var _a; return (_a = this.observer) === null || _a === void 0 ? void 0 : _a.onIceCandidate(instanceId, c); });
}
applyPendingRemoteCandidates() {
if (!this.pc.remoteDescription) {
console.log("Skipping to apply pending remote candidates because remoteDescription is null");
return;
}
this.pendingRemoteCandidates.forEach(this.addIceCandidate);
this.pendingRemoteCandidates.length = 0;
}
createPeerConnection() {
return WebRTCFactory_1.WebRTCFactory.createPeerConnection(this.rtcConfiguration);
}
setPendingRemoteDesription() {
return __awaiter(this, void 0, void 0, function* () {
if (this.pendingRemoteDescription) {
return this.setRemoteDescription(this.pendingRemoteDescription);
}
});
}
setLocalDescription(sd) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
this.localDescription = sd;
yield this.pc.setLocalDescription(sd);
yield this.setPendingRemoteDesription();
if (this.originalOffer)
this.originalOffer = undefined;
else
(_a = this.observer) === null || _a === void 0 ? void 0 : _a.onSessionDescriptionCreated(sd);
});
}
createOffer() {
return __awaiter(this, void 0, void 0, function* () {
this.remoteDescription = undefined;
const sd = yield this.pc.createOffer();
yield this.setLocalDescription(this.originalOffer || sd);
});
}
createAnswer() {
return __awaiter(this, void 0, void 0, function* () {
const sd = yield this.pc.createAnswer();
yield this.setLocalDescription(sd);
return sd;
});
}
setRemoteDescription(sd) {
return __awaiter(this, void 0, void 0, function* () {
if (sd.type == "answer") {
if (this.remoteDescription) {
return;
}
if (!this.localDescription) {
this.pendingRemoteDescription = sd;
return;
}
}
this.remoteDescription = sd;
yield this.pc.setRemoteDescription(sd);
this.applyPendingRemoteCandidates();
// if we received an offer create an answer
if (sd.type == "offer")
yield this.createAnswer();
});
}
}
exports.PeerConnection = PeerConnection;
//# sourceMappingURL=PeerConnection.js.map