Skip to content

Instantly share code, notes, and snippets.

@AKosmachyov
Last active April 29, 2021 10:58
Show Gist options
  • Save AKosmachyov/074808efbe7247706420d494ad91e417 to your computer and use it in GitHub Desktop.
Save AKosmachyov/074808efbe7247706420d494ad91e417 to your computer and use it in GitHub Desktop.
WebRTC stats for iOS(swift) & Web(JS)
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
* https://github.com/webrtc/apprtc/blob/78600dbe205774c115cf481a091387d928c99d6a/src/web_app/js/stats.js#L67
*/
'use strict';
// Enumerates the new standard compliant stats.
function enumerateStats(stats) {
// Create an object structure with all the needed stats and types that we care
// about. This allows to map the getStats stats to other stats names.
var statsObject = {
audio: {
local: {
audioLevel: 0.0,
bytesSent: 0,
clockRate: 0,
codecId: '',
mimeType: '',
packetsSent: 0,
payloadType: 0,
timestamp: 0.0,
trackId: '',
transportId: '',
},
remote: {
audioLevel: 0.0,
bytesReceived: 0,
clockRate: 0,
codecId: '',
fractionLost: 0,
jitter: 0,
mimeType: '',
packetsLost: 0,
packetsReceived: 0,
payloadType: 0,
timestamp: 0.0,
trackId: '',
transportId: '',
}
},
video: {
local: {
bytesSent: 0,
clockRate: 0,
codecId: '',
firCount: 0,
framesEncoded: 0,
frameHeight: 0,
framesSent: 0,
frameWidth: 0,
nackCount: 0,
packetsSent: 0,
payloadType: 0,
pliCount: 0,
qpSum: 0,
timestamp: 0.0,
trackId: '',
transportId: '',
},
remote: {
bytesReceived: 0,
clockRate: 0,
codecId: '',
firCount: 0,
fractionLost: 0,
frameHeight: 0,
framesDecoded: 0,
framesDropped: 0,
framesReceived: 0,
frameWidth: 0,
nackCount: 0,
packetsLost: 0,
packetsReceived: 0,
payloadType: 0,
pliCount: 0,
qpSum: 0,
timestamp: 0.0,
trackId: '',
transportId: '',
}
},
connection: {
availableOutgoingBitrate: 0,
bytesReceived: 0,
bytesSent: 0,
consentRequestsSent: 0,
currentRoundTripTime: 0.0,
localCandidateId: '',
localCandidateType: '',
localIp: '',
localPort: 0,
localPriority: 0,
localProtocol: '',
localRelayProtocol: undefined,
remoteCandidateId: '',
remoteCandidateType: '',
remoteIp: '',
remotePort: 0,
remotePriority: 0,
remoteProtocol: '',
requestsReceived: 0,
requestsSent: 0,
responsesReceived: 0,
responsesSent: 0,
timestamp: 0.0,
totalRoundTripTime: 0.0,
}
};
const localTrackIds = {
audio: '',
video: ''
};
const remoteTrackIds = {
audio: '',
video: ''
};
// Need to find the codec, local and remote ID's first.
if (stats) {
stats.forEach(function (report, stat) {
switch (report.type) {
case 'outbound-rtp':
if (report.hasOwnProperty('trackId')) {
if (report.kind === "audio") {
localTrackIds.audio = report.trackId
statsObject.audio.local.bytesSent = report.bytesSent;
statsObject.audio.local.codecId = report.codecId;
statsObject.audio.local.packetsSent = report.packetsSent;
statsObject.audio.local.timestamp = report.timestamp;
statsObject.audio.local.trackId = report.trackId;
statsObject.audio.local.transportId = report.transportId;
}
if (report.kind === "video") {
localTrackIds.video = report.trackId
statsObject.video.local.bytesSent = report.bytesSent;
statsObject.video.local.codecId = report.codecId;
statsObject.video.local.firCount = report.firCount;
statsObject.video.local.framesEncoded = report.frameEncoded;
statsObject.video.local.framesSent = report.framesSent;
statsObject.video.local.packetsSent = report.packetsSent;
statsObject.video.local.pliCount = report.pliCount;
statsObject.video.local.qpSum = report.qpSum;
statsObject.video.local.timestamp = report.timestamp;
statsObject.video.local.trackId = report.trackId;
statsObject.video.local.transportId = report.transportId;
}
}
break;
case 'inbound-rtp':
if (report.hasOwnProperty('trackId')) {
if (report.kind === "audio") {
localTrackIds.audio = report.trackId
statsObject.audio.remote.bytesReceived = report.bytesReceived;
statsObject.audio.remote.codecId = report.codecId;
statsObject.audio.remote.fractionLost = report.fractionLost;
statsObject.audio.remote.jitter = report.jitter;
statsObject.audio.remote.packetsLost = report.packetsLost;
statsObject.audio.remote.packetsReceived = report.packetsReceived;
statsObject.audio.remote.timestamp = report.timestamp;
statsObject.audio.remote.trackId = report.trackId;
statsObject.audio.remote.transportId = report.transportId;
}
if (report.kind === "video") {
localTrackIds.video = report.trackId
statsObject.video.remote.bytesReceived = report.bytesReceived;
statsObject.video.remote.codecId = report.codecId;
statsObject.video.remote.firCount = report.firCount;
statsObject.video.remote.fractionLost = report.fractionLost;
statsObject.video.remote.nackCount = report.nackCount;
statsObject.video.remote.packetsLost = report.patsLost;
statsObject.video.remote.packetsReceived = report.packetsReceived;
statsObject.video.remote.pliCount = report.pliCount;
statsObject.video.remote.qpSum = report.qpSum;
statsObject.video.remote.timestamp = report.timestamp;
statsObject.video.remote.trackId = report.trackId;
statsObject.video.remote.transportId = report.transportId;
}
}
break;
case 'candidate-pair':
if (report.hasOwnProperty('availableOutgoingBitrate')) {
statsObject.connection.availableOutgoingBitrate =
report.availableOutgoingBitrate;
statsObject.connection.bytesReceived = report.bytesReceived;
statsObject.connection.bytesSent = report.bytesSent;
statsObject.connection.consentRequestsSent =
report.consentRequestsSent;
statsObject.connection.currentRoundTripTime =
report.currentRoundTripTime;
statsObject.connection.localCandidateId = report.localCandidateId;
statsObject.connection.remoteCandidateId = report.remoteCandidateId;
statsObject.connection.requestsReceived = report.requestsReceived;
statsObject.connection.requestsSent = report.requestsSent;
statsObject.connection.responsesReceived = report.responsesReceived;
statsObject.connection.responsesSent = report.responsesSent;
statsObject.connection.timestamp = report.timestamp;
statsObject.connection.totalRoundTripTime =
report.totalRoundTripTime;
}
break;
default:
return;
}
}.bind());
// Using the codec, local and remote candidate ID's to find the rest of the
// relevant stats.
stats.forEach(function (report) {
switch (report.type) {
case 'track':
if (report.hasOwnProperty('trackIdentifier')) {
if (report.id.indexOf(localTrackIds.video) !== -1) {
statsObject.video.local.frameHeight = report.frameHeight;
statsObject.video.local.framesSent = report.framesSent;
statsObject.video.local.frameWidth = report.frameWidth;
}
if (report.id.indexOf(remoteTrackIds.video) !== -1) {
statsObject.video.remote.frameHeight = report.frameHeight;
statsObject.video.remote.framesDecoded = report.framesDecoded;
statsObject.video.remote.framesDropped = report.framesDropped;
statsObject.video.remote.framesReceived = report.framesReceived;
statsObject.video.remote.frameWidth = report.frameWidth;
}
if (report.id.indexOf(localTrackIds.audio) !== -1) {
statsObject.audio.local.audioLevel = report.audioLevel;
}
if (report.id.indexOf(remoteTrackIds.audio) !== -1) {
statsObject.audio.remote.audioLevel = report.audioLevel;
}
}
break;
case 'codec':
if (report.hasOwnProperty('id')) {
if (report.id.indexOf(statsObject.audio.local.codecId) !== -1) {
statsObject.audio.local.clockRate = report.clockRate;
statsObject.audio.local.mimeType = report.mimeType;
statsObject.audio.local.payloadType = report.payloadType;
}
if (report.id.indexOf(statsObject.audio.remote.codecId) !== -1) {
statsObject.audio.remote.clockRate = report.clockRate;
statsObject.audio.remote.mimeType = report.mimeType;
statsObject.audio.remote.payloadType = report.payloadType;
}
if (report.id.indexOf(statsObject.video.local.codecId) !== -1) {
statsObject.video.local.clockRate = report.clockRate;
statsObject.video.local.mimeType = report.mimeType;
statsObject.video.local.payloadType = report.payloadType;
}
if (report.id.indexOf(statsObject.video.remote.codecId) !== -1) {
statsObject.video.remote.clockRate = report.clockRate;
statsObject.video.remote.mimeType = report.mimeType;
statsObject.video.remote.payloadType = report.payloadType;
}
}
break;
case 'local-candidate':
if (report.hasOwnProperty('id')) {
if (report.id.indexOf(
statsObject.connection.localCandidateId) !== -1) {
statsObject.connection.localIp = report.ip;
statsObject.connection.localPort = report.port;
statsObject.connection.localPriority = report.priority;
statsObject.connection.localProtocol = report.protocol;
statsObject.connection.localType = report.candidateType;
statsObject.connection.localRelayProtocol = report.relayProtocol;
}
}
break;
case 'remote-candidate':
if (report.hasOwnProperty('id')) {
if (report.id.indexOf(
statsObject.connection.remoteCandidateId) !== -1) {
statsObject.connection.remoteIp = report.ip;
statsObject.connection.remotePort = report.port;
statsObject.connection.remotePriority = report.priority;
statsObject.connection.remoteProtocol = report.protocol;
statsObject.connection.remoteType = report.candidateType;
}
}
break;
default:
return;
}
}.bind());
}
return statsObject;
}
import WebRTC
var peerConnection: RTCPeerConnection?
func showStats() {
peerConnection?.statistics(completionHandler: { stats in
self.processStats(report: stats)
})
}
fileprivate func processStats(report: RTCStatisticsReport) {
var localVideoReport: VideoReport?
var remoteVideoReport: VideoReport?
report.statistics.forEach { (key: String, value: RTCStatistics) in
if value.type == "outbound-rtp" {
localVideoReport = createVideoReport(report: report, for: value)
}
if value.type == "inbound-rtp" {
remoteVideoReport = createVideoReport(report: report, for: value)
}
}
if let local = localVideoReport {
print("WebRTC local: sent packet (\(local.framesSent)), mime-type (\(local.mimeType)")
}
if let remote = remoteVideoReport {
print("WebRTC remote: mime-type (\(remote.mimeType)")
}
}
fileprivate func createVideoReport(report: RTCStatisticsReport, for category: RTCStatistics) -> VideoReport? {
if category.type != "outbound-rtp" && category.type != "inbound-rtp" {
return nil
}
let stats = category.values
if stats["kind"] as? String != "video" {
return nil
}
var videoReport = VideoReport()
videoReport.bytesSent = stats["bytesSent"] as? Int ?? 0
videoReport.codecId = stats["codecId"] as? String ?? ""
videoReport.firCount = stats["firCount"] as? Int ?? 0
videoReport.framesEncoded = stats["framesEncoded"] as? Int ?? 0
videoReport.framesSent = stats["framesSent"] as? Int ?? 0
videoReport.packetsSent = stats["packetsSent"] as? Int ?? 0
videoReport.pliCount = stats["pliCount"] as? Int ?? 0
videoReport.qpSum = stats["qpSum"] as? Int ?? 0
videoReport.trackId = stats["trackId"] as? String ?? ""
videoReport.transportId = stats["transportId"] as? String ?? ""
videoReport.timestamp = stats["timestamp"] as? Int ?? 0
if videoReport.trackId.count > 0,
let stats = report.statistics[videoReport.trackId]?.values
{
videoReport.frameHeight = stats["frameHeight"] as? Int ?? 0
videoReport.frameWidth = stats["frameWidth"] as? Int ?? 0
}
if videoReport.codecId.count > 0,
let stats = report.statistics[videoReport.codecId]?.values
{
videoReport.mimeType = stats["mimeType"] as? String ?? ""
}
return videoReport
}
public struct VideoReport {
var bytesSent = 0
var codecId = ""
var mimeType = ""
var firCount = 0
var framesEncoded = 0
var frameHeight = 0
var framesSent = 0
var frameWidth = 0
var packetsSent = 0
var payloadType = 0
var pliCount = 0
var qpSum = 0
var trackId = ""
var transportId = ""
var timestamp = 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment