UNPKG

sinch-rtc

Version:

RTC JavaScript/Web SDK

283 lines 12.8 kB
"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