Last active
April 29, 2021 10:58
-
-
Save AKosmachyov/074808efbe7247706420d494ad91e417 to your computer and use it in GitHub Desktop.
WebRTC stats for iOS(swift) & Web(JS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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