sinch-rtc
Version:
RTC JavaScript/Web SDK
296 lines • 12.5 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.PeerConnectionClient = void 0;
const Event_1 = require("../utils/Event");
const jsep_1 = require("../session/jsep");
const PeerConnection_1 = require("./PeerConnection");
const PeerConnection_2 = require("./PeerConnection");
class PeerConnectionClient {
static create(media, trackEvent, useRelayIceCandidatesOnly, inactivate, clientEventsCollector, observer, iceServers) {
return new PeerConnectionClient(media, trackEvent, useRelayIceCandidatesOnly, inactivate, clientEventsCollector, observer, iceServers);
}
constructor(mediaController, trackEvent, useRelayIceCandidatesOnly, inactivate, clientEventsCollector, observer, iceServers) {
this.mediaController = mediaController;
this.trackEvent = trackEvent;
this.useRelayIceCandidatesOnly = useRelayIceCandidatesOnly;
this.inactivate = inactivate;
this.clientEventsCollector = clientEventsCollector;
this.observer = observer;
this.iceServers = iceServers;
this.DUMMY_INSTANCE_ID = "";
this.peerConnections = new Array();
this.onLocalJsepMessage = new Event_1.Event();
this.remoteStreams = new Map();
this.pendingTracks = new Map();
this.pendingRelayCandidates = new Array();
this.enabledTracks = new Map();
this.onSessionDescriptionFailed = (error) => {
console.error("Failed to create Session Description", error);
};
this.onTrack = (evt, instanceId) => {
var _a;
this.trackEvent.fire(evt.track);
if (this.instanceId) {
this.remoteMediaStream.addTrack(evt.track);
}
else {
if (!this.pendingTracks.has(instanceId))
this.pendingTracks.set(instanceId, new Array());
(_a = this.pendingTracks.get(instanceId)) === null || _a === void 0 ? void 0 : _a.push(evt.track);
}
};
this.cleanup = () => {
this.peerConnections.forEach((pc) => {
pc.clearAnyScheduledTimer();
pc.close();
});
};
this.onIceCandidate = (instanceId, candidate) => {
if (candidate === null || candidate === void 0 ? void 0 : candidate.candidate) {
this.onLocalJsepMessage.fire(new jsep_1.JsepMessage("candidate", JSON.stringify({
cand: candidate.candidate,
sdpMLI: candidate.sdpMLineIndex,
sdpMid: candidate.sdpMid,
}), instanceId));
}
};
this.onJsepMessageReceived = (messages) => {
messages.forEach((message) => {
if (message.isOffer || message.isAnswer)
this.onJsepDescription(message);
else if (message.isCandidate)
this.onJsepIceCandidate(message);
else if (message.isJoin && message.instanceId) {
this.setConnectedInstanceId(message.instanceId);
}
});
};
this.remoteMediaStream = new MediaStream();
/**
* Single PeerConnectionClient doesn't always handle single PeerConnection instance.
* Multiple PeerConnections are expected if the PeerConnectionClient is the caller and the callee has multiple instances registered.
*
* Thus, we start notifying about ICE state changes once the call is answered to know which PC instance we'll be dealing with.
* If we don't do this we could mix states of multiple PC instances while on observer's level the implementation is designed to handle single peer's ICE state.
*
* For simplicity we don't differentiate cases when we know since start that only 1 PC is expected (client is the callee) and just start emitting
* ICE state changes once the call is answered.
*/
this.shouldNotifyIceStateChanges = false;
}
setIceServers(iceServers) {
this.iceServers = iceServers;
}
createCertificate() {
return __awaiter(this, void 0, void 0, function* () {
if (RTCPeerConnection.generateCertificate)
return RTCPeerConnection.generateCertificate({
name: "ECDSA",
namedCurve: "P-256",
});
});
}
onCallAnswered(_) {
this.closeNonJoinedPCs();
this.startEmittingIceStateChanges();
}
onCallEstablished(_) {
// enable local tracks when call is established unless they have been paused.
if (this.enabledTracks.get("audio") != false)
this.enableSenderTracks(true, "audio");
if (this.enabledTracks.get("video") != false)
this.enableSenderTracks(true, "video");
}
enableSenderTracks(enabled, kind) {
this.enabledTracks.set(kind, enabled);
this.peerConnections.forEach((p) => {
p.enableSenderTracks(enabled, kind);
});
}
updateMediaController(media) {
this.mediaController = media;
this.peerConnections.forEach((p) => {
p.setLocalMedia(media);
});
}
start() {
return __awaiter(this, void 0, void 0, function* () {
this.certificate = yield this.createCertificate();
yield this.createPeerConnection(this.DUMMY_INSTANCE_ID);
});
}
onSessionDescriptionCreated(sd) {
if (sd.sdp && sd.type) {
this.originalOffer = this.originalOffer || sd;
this.onLocalJsepMessage.fire(new jsep_1.JsepMessage(sd.type, sd.sdp));
}
}
insertDtmf(tones) {
return __awaiter(this, void 0, void 0, function* () {
let inserted = false;
this.peerConnections.forEach((pc) => (inserted = inserted || pc.insertDtmf(tones)));
return inserted;
});
}
addPendingTracks() {
var _a;
if (this.instanceId)
(_a = this.pendingTracks
.get(this.instanceId)) === null || _a === void 0 ? void 0 : _a.splice(0).forEach((track) => {
this.remoteMediaStream.addTrack(track);
});
}
setConnectedInstanceId(instanceId) {
this.instanceId = instanceId;
this.addPendingTracks();
this.addPendingRelayCandidates();
}
get remoteStream() {
return this.remoteMediaStream;
}
mapIceCandidate(json) {
const candidate = JSON.parse(json);
return {
candidate: candidate.cand,
sdpMLineIndex: candidate.sdpMLI,
sdpMid: candidate.sdpMid,
};
}
addPendingRelayCandidates() {
var _a;
if (this.instanceId) {
const sdp = (_a = this.originalOffer) === null || _a === void 0 ? void 0 : _a.sdp;
if (sdp) {
this.getPeerConnection(this.instanceId).then((pc) => {
this.pendingRelayCandidates
.splice(0)
.forEach((c) => pc.addIceCandidate(this.mapIceCandidate(c)));
});
}
}
}
onJsepIceCandidate(message) {
return __awaiter(this, void 0, void 0, function* () {
if (message.instanceId)
(yield this.getPeerConnection(message.instanceId)).addIceCandidate(this.mapIceCandidate(message.data));
});
}
onJsepDescription(message) {
return __awaiter(this, void 0, void 0, function* () {
if (!message.instanceId)
return;
if (message.type == "offer")
this.instanceId = message.instanceId;
yield (yield this.getPeerConnection(message.instanceId)).setRemoteDescription({
sdp: message.data,
type: message.type,
});
});
}
findPeerConnection(instanceId) {
return this.peerConnections.find((p) => {
return p.instanceId == instanceId;
});
}
getDefaultPeerConnection(instanceId) {
const pc = this.findPeerConnection(this.DUMMY_INSTANCE_ID);
if (pc)
pc.instanceId = instanceId;
return pc;
}
getRTCConfig() {
return {
certificates: this.certificate ? [this.certificate] : undefined,
iceServers: this.iceServers,
iceTransportPolicy: this.useRelayIceCandidatesOnly
? PeerConnection_2.IceTransportPolicy.Relay
: PeerConnection_2.IceTransportPolicy.All,
};
}
closeNonJoinedPCs() {
this.peerConnections.forEach((pc) => {
if (pc.instanceId != this.instanceId) {
pc.clearAnyScheduledTimer();
pc.close();
}
});
}
startEmittingIceStateChanges() {
this.shouldNotifyIceStateChanges = true;
const instanceId = this.instanceId;
if (instanceId == undefined) {
console.error("Failed to find remote peer connection instance. Remote instance id not known");
return;
}
const pc = this.findPeerConnection(instanceId);
if (pc == undefined) {
console.error(`Failed to find remote peer connection instance with id ${instanceId}`);
return;
}
this.onIceConnectionStateChanged(pc.getIceConnectionState());
}
createPeerConnection(instanceId) {
return __awaiter(this, void 0, void 0, function* () {
const pc = new PeerConnection_1.PeerConnection(this, this.getRTCConfig(), this.inactivate, this.clientEventsCollector, instanceId, this.originalOffer);
pc.setLocalMedia(this.mediaController);
// Disable all local media until call is established or enabled
if (!this.enabledTracks.get("video"))
pc.enableSenderTracks(false, "video");
if (!this.enabledTracks.get("audio"))
pc.enableSenderTracks(false, "audio");
this.peerConnections.push(pc);
return pc;
});
}
getPeerConnection(instanceId) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
return ((_b = (_a = this.findPeerConnection(instanceId)) !== null && _a !== void 0 ? _a : this.getDefaultPeerConnection(instanceId)) !== null && _b !== void 0 ? _b : this.createPeerConnection(instanceId));
});
}
onIceConnectionStateChanged(state) {
if (!this.shouldNotifyIceStateChanges) {
return;
}
this.observer.onIceConnectionStateChanged(state);
}
setLegacyRelayCandidates(candidates) {
candidates.forEach((c) => this.pendingRelayCandidates.push(c));
}
getPeerConnectionStats() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.instanceId) {
console.error("Cannot get Webrtc stats. Instance id is not set");
return null;
}
const peerConnection = this.findPeerConnection(this.instanceId);
if (!peerConnection) {
console.error("Could not find peer connection for instance", {
instanceId: this.instanceId,
});
return null;
}
try {
return yield peerConnection.pc.getStats(null);
}
catch (error) {
console.error("Failed to get statistics for peer connection", {
instanceId: this.instanceId,
error,
});
return null;
}
});
}
}
exports.PeerConnectionClient = PeerConnectionClient;
//# sourceMappingURL=PeerConnectionClient.js.map