sinch-rtc
Version:
RTC JavaScript/Web SDK
339 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.CumulativeValues = exports.WebRTCStatsCollector = exports.OUTBOUND_RTP_VIDEO_STATS = exports.OUTBOUND_RTP_AUDIO_STATS = exports.INBOUND_RTP_VIDEO_STATS = exports.INBOUND_RTP_AUDIO_STATS = void 0;
const WebRTCStats_types_1 = require("./WebRTCStats.types");
const WebRTCStats_1 = require("./WebRTCStats");
const CallReportUtils_1 = require("./CallReportUtils");
const STATS_COLLECTION_INTERVAL_AT_CALL_START_MS = 2000;
const COUNT_FOR_INITIAL_INTERVAL = 10;
const STATS_COLLECTION_INTERVAL_MS = 10000;
exports.INBOUND_RTP_AUDIO_STATS = [
"actualBitrate",
"audioLevel",
"bytesReceived",
"codec",
"concealedSamples",
"concealmentEvents",
"currentRoundTripTime",
"jitter",
"packetsLost",
"packetsReceived",
"timestamp",
"totalSamplesDuration",
"totalSamplesReceived",
];
exports.INBOUND_RTP_VIDEO_STATS = [
"actualBitrate",
"bytesReceived",
"codec",
"currentRoundTripTime",
"frameHeight",
"frameWidth",
"framesDecoded",
"framesDropped",
"framesReceived",
"packetsLost",
"packetsReceived",
"qpSum",
"timestamp",
];
exports.OUTBOUND_RTP_AUDIO_STATS = [
"actualBitrate",
"audioLevel",
"bytesSent",
"codec",
"currentRoundTripTime",
"packetsSent",
"timestamp",
"totalSamplesDuration",
];
exports.OUTBOUND_RTP_VIDEO_STATS = [
"actualBitrate",
"bytesSent",
"codec",
"currentRoundTripTime",
"frameHeight",
"frameWidth",
"framesEncoded",
"framesSent",
"hugeFramesSent",
"packetsSent",
"qpSum",
"timestamp",
];
const RTC_MEDIA_TYPE_MAPPING = {
[WebRTCStats_types_1.RtcStatsTypes.InboundRtp]: {
[WebRTCStats_types_1.MediaType.Audio]: {
STATS: exports.INBOUND_RTP_AUDIO_STATS,
},
[WebRTCStats_types_1.MediaType.Video]: {
STATS: exports.INBOUND_RTP_VIDEO_STATS,
},
},
[WebRTCStats_types_1.RtcStatsTypes.OutboundRtp]: {
[WebRTCStats_types_1.MediaType.Audio]: {
STATS: exports.OUTBOUND_RTP_AUDIO_STATS,
},
[WebRTCStats_types_1.MediaType.Video]: {
STATS: exports.OUTBOUND_RTP_VIDEO_STATS,
},
},
};
class WebRTCStatsCollector {
constructor() {
this.getPropertyValuesFromNames = (names, objectWithValues) => names.map((name) => objectWithValues[name]);
this.candidatePair = {};
this.codec = new Map();
this.cumulativeValues = new Map();
this.mediaSource = new Map();
this.rawStatsReport = new Map();
this.remoteCandidates = new Map();
this.localCandidates = new Map();
this.tracks = new Map();
this.webRtcStats = new WebRTCStats_1.WebRTCStats();
}
startStatsCollection(call) {
let statsCounter = 0;
this.intervalId = setInterval(() => {
statsCounter++;
if (statsCounter > COUNT_FOR_INITIAL_INTERVAL) {
clearInterval(this.intervalId);
this.collectStats(call);
this.intervalId = setInterval(() => this.collectStats(call), STATS_COLLECTION_INTERVAL_MS);
}
else {
this.collectStats(call);
}
}, STATS_COLLECTION_INTERVAL_AT_CALL_START_MS);
}
stopStatsCollection() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
}
reset() {
this.candidatePair = {};
this.codec = new Map();
this.cumulativeValues = new Map();
this.mediaSource = new Map();
this.rawStatsReport = new Map();
this.remoteCandidates = new Map();
this.localCandidates = new Map();
this.tracks = new Map();
this.webRtcStats = new WebRTCStats_1.WebRTCStats();
}
getStats() {
return this.webRtcStats.getStats();
}
getConnectionInfos() {
return this.webRtcStats.connectionInfos;
}
addCodec(codecId, statsEntry) {
const codec = this.codec.get(codecId);
if (!codec) {
return;
}
statsEntry.codec = codec.mimeType;
}
addRTT(statsEntry) {
var _a;
statsEntry.currentRoundTripTime = (_a = this.candidatePair) === null || _a === void 0 ? void 0 : _a.currentRoundTripTime;
}
addAudioLevelAndTotalSamplesDuration(statsEntry) {
const trackId = statsEntry.trackId;
const track = this.tracks.get(trackId);
if (!track) {
return;
}
const mediaSourceId = track.mediaSourceId;
const mediaSource = this.mediaSource.get(mediaSourceId);
if (!mediaSource) {
return;
}
statsEntry.audioLevel = mediaSource.audioLevel;
statsEntry.totalSamplesDuration = mediaSource.totalSamplesDuration;
}
addActualBitrate(statsEntry) {
const statsKey = statsEntry.id;
const timestamp = statsEntry.timestamp;
const bytes = statsEntry.bytesReceived
? statsEntry.bytesReceived
: statsEntry.bytesSent;
const cumulativeValue = this.cumulativeValues.get(statsKey);
cumulativeValue.addValue(timestamp, bytes * 8);
statsEntry.actualBitrate = cumulativeValue.getCurrentAverageValue();
}
addRequiredProps() {
this.rawStatsReport.forEach((rawStat) => {
this.addCodec(rawStat.codecId, rawStat);
this.addRTT(rawStat);
this.addAudioLevelAndTotalSamplesDuration(rawStat);
this.addActualBitrate(rawStat);
});
}
updateStatsReport() {
const statsReport = this.webRtcStats.statsReport;
this.rawStatsReport.forEach((rawStat, key) => {
var _a;
const statsEntry = statsReport.get(key);
const mediaType = (_a = rawStat.mediaType) !== null && _a !== void 0 ? _a : rawStat.kind;
const type = rawStat.type;
const names = RTC_MEDIA_TYPE_MAPPING[type][mediaType]
.STATS;
const values = this.getPropertyValuesFromNames(names, rawStat);
if (statsEntry) {
statsEntry.values.push(values);
}
else {
statsReport.set(key, {
id: key,
mediaType,
type,
names,
values: [values],
});
}
});
}
updateConnectionInfo() {
const localCandidateId = this.candidatePair.localCandidateId;
const remoteCandidateId = this.candidatePair.remoteCandidateId;
const localCandidate = this.localCandidates.get(localCandidateId);
const remoteCandidate = this.remoteCandidates.get(remoteCandidateId);
if (localCandidate == undefined || remoteCandidate == undefined) {
return;
}
const connectionInfo = {
localIceCandidate: CallReportUtils_1.CallReportUtils.getIceCandidate(localCandidate),
remoteIceCandidate: CallReportUtils_1.CallReportUtils.getIceCandidate(remoteCandidate),
timestamp: new Date(),
};
this.webRtcStats.connectionInfos.push(connectionInfo);
}
collectStats(call) {
return __awaiter(this, void 0, void 0, function* () {
try {
const rawReport = yield call.getPeerConnectionStats();
if (!rawReport) {
return;
}
rawReport.forEach((rawStatsEntry) => {
const type = rawStatsEntry.type;
switch (type) {
case WebRTCStats_types_1.RtcStatsTypes.Codec: {
this.codec.set(rawStatsEntry.id, {
codecId: rawStatsEntry.id,
mimeType: rawStatsEntry.mimeType,
});
break;
}
case WebRTCStats_types_1.RtcStatsTypes.CandidatePair: {
if (rawStatsEntry.state === "succeeded") {
this.candidatePair = rawStatsEntry;
}
break;
}
case WebRTCStats_types_1.RtcStatsTypes.Track: {
this.tracks.set(rawStatsEntry.id, rawStatsEntry);
break;
}
case WebRTCStats_types_1.RtcStatsTypes.MediaSource: {
const kind = rawStatsEntry.kind;
if (kind === WebRTCStats_types_1.MediaType.Audio) {
this.mediaSource.set(rawStatsEntry.id, rawStatsEntry);
}
break;
}
case WebRTCStats_types_1.RtcStatsTypes.RemoteCandidate: {
this.remoteCandidates.set(rawStatsEntry.id, this.createCandidate(rawStatsEntry));
break;
}
case WebRTCStats_types_1.RtcStatsTypes.LocalCandidate: {
this.localCandidates.set(rawStatsEntry.id, this.createCandidate(rawStatsEntry));
break;
}
case WebRTCStats_types_1.RtcStatsTypes.InboundRtp:
case WebRTCStats_types_1.RtcStatsTypes.OutboundRtp: {
if (!this.cumulativeValues.has(rawStatsEntry.id)) {
this.cumulativeValues.set(rawStatsEntry.id, new CumulativeValues());
}
this.rawStatsReport.set(rawStatsEntry.id, rawStatsEntry);
break;
}
default:
break;
}
});
this.addRequiredProps();
this.updateStatsReport();
this.updateConnectionInfo();
}
catch (error) {
console.error(error);
}
});
}
createCandidate(rawStatsEntry) {
const id = rawStatsEntry.id;
const type = rawStatsEntry.type;
const candidateType = rawStatsEntry.candidateType;
const protocol = rawStatsEntry.protocol;
const address = rawStatsEntry.address;
const port = rawStatsEntry.port;
const timestamp = rawStatsEntry.timestamp;
const relayProtocol = rawStatsEntry.relayProtocol;
const url = rawStatsEntry.url;
return {
id,
timestamp,
type,
candidateType,
protocol,
address,
port,
relayProtocol,
url,
};
}
}
exports.WebRTCStatsCollector = WebRTCStatsCollector;
class CumulativeValues {
constructor() {
this.previousTimestamp = 0;
this.previousValue = 0;
this.currentAverageValue = 0;
}
reset(initialTimestamp, initialVal) {
this.previousTimestamp = initialTimestamp;
this.previousValue = initialVal;
this.currentAverageValue = initialVal;
}
addValue(timestamp, value) {
if (value > this.previousValue &&
timestamp > this.previousTimestamp &&
this.previousTimestamp !== 0) {
const deltaTimestamp = (timestamp - this.previousTimestamp) / 1000000;
this.currentAverageValue = (value - this.previousValue) / deltaTimestamp;
this.previousTimestamp = timestamp;
this.previousValue = value;
}
else {
this.reset(timestamp, value);
}
}
getCurrentAverageValue() {
return this.currentAverageValue;
}
}
exports.CumulativeValues = CumulativeValues;
//# sourceMappingURL=WebRTCStatsCollector.js.map