A tiny JavaScript library using WebRTC getStats API to return peer connection stats i.e. bandwidth usage, packets lost, local/remote ip addresses and ports, type of connection etc.
JavaScript HTML
Switch branches/tags
Clone or download

README.md

getStats.js | WebRTC getStats API

Single Page Demo or Multi User P2P Demo

npm downloads Build Status: Linux

A tiny JavaScript library using WebRTC getStats API to return peer connection stats i.e. bandwidth usage, packets lost, local/remote ip addresses and ports, type of connection etc.

It is MIT Licenced, which means that you can use it in any commercial/non-commercial product, free of cost.

getStats

npm install getstats

cd node_modules
cd getstats
node server.js

# and open:
# http://localhost:9999/

To use it:

<script src="./node_modules/getstats/getStats.js"></script>

Link the library

<script src="https://cdn.webrtc-experiment.com/getStats.js"></script>

<!-- or min.js -->
<script src="https://cdn.webrtc-experiment.com/getStats.min.js"></script>

<!-- or without CDN -->
<script src="https://www.webrtc-experiment.com/getStats.js"></script>

<!-- or rawgit -->
<script src="https://rawgit.com/muaz-khan/getStats/master/getStats.js"></script>

Or link specific build:

<script src="https://github.com/muaz-khan/getStats/releases/download/1.0.6/getStats.js"></script>

window.getStats

To invoke directly:

getStats(peer, callback, interval);

RTCPeerConnection.prototype.getPeerStats

Or, to setup an instance method:

// if your code is encapsulated under a method
(function() {
    RTCPeerConnection.prototype.getPeerStats = window.getStats;
    
    // or
    RTCPeerConnection.prototype.__getStats = window.getStats;
    
    // or
    RTCPeerConnection.prototype.getConnectionStats = window.getStats;
    
    // or
    RTCPeerConnection.prototype['your-choice'] = window.getStats;
})();

NEVER set/override RTCPeerConnection.prototype.getStats because it is a reserved method.

// following will fail
RTCPeerConnection.prototype.getStats = window.getStats;

// it should be
RTCPeerConnection.prototype.intanceMethodNamae = window.getStats;

Usage

var rtcPeerConnection = new RTCPeerConnection(rtcConfig);

var repeatInterval = 2000; // 2000 ms == 2 seconds
rtcPeerConnection.getPeerStats(function(result) {
    result.connectionType.remote.ipAddress
    result.connectionType.remote.candidateType
    result.connectionType.transport
    
    result.bandwidth.speed // bandwidth download speed (bytes per second)
    
    // to access native "results" array
    result.results.forEach(function(item) {
        if (item.type === 'ssrc' && item.transportId === 'Channel-audio-1') {
            var packetsLost = item.packetsLost;
            var packetsSent = item.packetsSent;
            var audioInputLevel = item.audioInputLevel;
            var trackId = item.googTrackId; // media stream track id
            var isAudio = item.mediaType === 'audio'; // audio or video
            var isSending = item.id.indexOf('_send') !== -1; // sender or receiver

            console.log('SendRecv type', item.id.split('_send').pop());
            console.log('MediaStream track type', item.mediaType);
        }
    });
}, repeatInterval);

Firefox?

peer.getStats(peer.getLocalStreams()[0].getAudioTracks()[0], function(results) {
    // rest goes here
}, 5 * 1000);

result.datachannel

// states => open or close
alert(result.datachannel.state === 'open');

result.isOfferer

Offerer is the person who invoked createOffer method.

result.encryption

To detect which tech is used to encrypt your connections.

alert(result.encryption === 'sha-256');

result.nomore()

This function can be used to ask to stop invoking getStats API.

btnStopGetStats.onclick  = function() {
    getStatsResult.nomore();
};

result.bandwidth

You can use result.bandwidth.speed to detect your system's available download speed.

{
    "speed": 25191,
    "systemBandwidth": 0,
    "sentPerSecond": 0,
    "encodedPerSecond": 0,
    "helper": {
        "audioBytesSent": 103053,
        "videoBytestSent": 0,
        "videoBytesSent": 4316619
    },
    "availableSendBandwidth": "5181906",
    "googActualEncBitrate": "294608",
    "googAvailableSendBandwidth": "5181906",
    "googAvailableReceiveBandwidth": "0",
    "googRetransmitBitrate": "0",
    "googTargetEncBitrate": "1700000",
    "googBucketDelay": "0",
    "googTransmitBitrate": "198296"
}

result.audio

{
    "send": {
        "tracks": ["ab5be64a-00b0-4c69-9ffa-b934a3cdaf92"],
        "codecs": ["opus"],
        "availableBandwidth": "0.4",
        "streams": 1
    },
    "recv": {
        "tracks": ["ab5be64a-00b0-4c69-9ffa-b934a3cdaf92"],
        "codecs": ["opus"],
        "availableBandwidth": "0.4",
        "streams": 1
    },
    "bytesSent": 103053,
    "bytesReceived": 103053
}

result.video

{
    "send": {
        "tracks": ["c07bbd24-4e99-4b94-b429-5174370f8d12"],
        "codecs": ["VP9"],
        "availableBandwidth": "24.2",
        "streams": 1
    },
    "recv": {
        "tracks": ["c07bbd24-4e99-4b94-b429-5174370f8d12"],
        "codecs": ["VP9"],
        "availableBandwidth": "25.5",
        "streams": 1
    },
    "bytesSent": 4316619,
    "bytesReceived": 4294692
}

result.connectionType

{
    "systemNetworkType": "wifi",
    "systemIpAddress": ["192.168.1.2:66666"],
    "local": {
        "candidateType": ["host"],
        "transport": ["udp"],
        "ipAddress": ["192.168.1.2:66666"],
        "networkType": ["wlan"]
    },
    "remote": {
        "candidateType": ["host"],
        "transport": ["udp"],
        "ipAddress": ["192.168.1.2:66666"],
        "networkType": []
    },
    "transport": "udp"
}

result.resolutions

{
    "send": {
        "width": "640",
        "height": "480"
    },
    "recv": {
        "width": "640",
        "height": "480"
    }
}

result.results

It is an array that is returned by browser's native PeerConnection API.

// "getStatsResult" is your "result" object
getStatsResult.results.forEach(function(item) {
    if (item.type === 'ssrc' && item.transportId === 'Channel-audio-1') {
        var packetsLost = item.packetsLost;
        var packetsSent = item.packetsSent;
        var audioInputLevel = item.audioInputLevel;
        var trackId = item.googTrackId; // media stream track id
        var isAudio = item.mediaType === 'audio'; // audio or video
        var isSending = item.id.indexOf('_send') !== -1; // sender or receiver

        console.log('SendRecv type', item.id.split('_send').pop());
        console.log('MediaStream track type', item.mediaType);
    }
});

Above array looks like this:

[{
    "googTrackId": "ab5be64a-00b0-4c69-9ffa-b934a3cdaf92",
    "id": "googTrack_ab5be64a-00b0-4c69-9ffa-b934a3cdaf92",
    "type": "googTrack",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googInitiator": "true",
    "id": "googLibjingleSession_2403774673032347671",
    "type": "googLibjingleSession",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googActualEncBitrate": "294608",
    "googAvailableSendBandwidth": "5181906",
    "googRetransmitBitrate": "0",
    "googAvailableReceiveBandwidth": "0",
    "googTargetEncBitrate": "1700000",
    "googBucketDelay": "0",
    "googTransmitBitrate": "198296",
    "id": "bweforvideo",
    "type": "VideoBwe",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googTrackId": "c07bbd24-4e99-4b94-b429-5174370f8d12",
    "id": "googTrack_c07bbd24-4e99-4b94-b429-5174370f8d12",
    "type": "googTrack",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googFingerprint": "8B:---:70",
    "googFingerprintAlgorithm": "sha-256",
    "googDerBase64": "MII----FdhT",
    "id": "googCertificate_8B:---:70",
    "type": "googCertificate",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googFingerprint": "EE:---:80",
    "googFingerprintAlgorithm": "sha-256",
    "googDerBase64": "MI----kr",
    "id": "googCertificate_EE:13:---:80",
    "type": "googCertificate",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googComponent": "1",
    "remoteCertificateId": "googCertificate_EE:---:80",
    "selectedCandidatePairId": "Conn-audio-1-0",
    "dtlsCipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "localCertificateId": "googCertificate_8B:---:70",
    "srtpCipher": "AES_CM_128_HMAC_SHA1_32",
    "id": "Channel-audio-1",
    "type": "googComponent",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "responsesSent": "12",
    "requestsReceived": "12",
    "googRemoteCandidateType": "local",
    "googReadable": "true",
    "googLocalAddress": "192.168.1.2:61885",
    "consentRequestsSent": "1",
    "googTransportType": "udp",
    "googChannelId": "Channel-audio-1",
    "googLocalCandidateType": "local",
    "googWritable": "true",
    "requestsSent": "12",
    "googRemoteAddress": "192.168.1.2:60256",
    "googRtt": "0",
    "googActiveConnection": "true",
    "packetsDiscardedOnSend": "0",
    "bytesReceived": "4455306",
    "responsesReceived": "12",
    "remoteCandidateId": "Cand-aws75dws",
    "localCandidateId": "Cand-4hzYq0su",
    "bytesSent": "4466782",
    "packetsSent": "5437",
    "id": "Conn-audio-1-0",
    "type": "googCandidatePair",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "portNumber": "61885",
    "networkType": "wlan",
    "ipAddress": "192.168.1.2",
    "transport": "udp",
    "candidateType": "host",
    "priority": "2122260223",
    "id": "Cand-4hzYq0su",
    "type": "localcandidate",
    "timestamp": "2017-11-21T04:09:48.850Z"
}, {
    "portNumber": "60256",
    "ipAddress": "192.168.1.2",
    "transport": "udp",
    "candidateType": "host",
    "priority": "2122260223",
    "id": "Cand-aws75dws",
    "type": "remotecandidate",
    "timestamp": "2017-11-21T04:09:48.850Z"
}, {
    "googDecodingCTN": "2206",
    "packetsLost": "0",
    "googSecondaryDecodedRate": "0",
    "googDecodingPLC": "21",
    "packetsReceived": "1056",
    "googExpandRate": "0.900574",
    "googJitterReceived": "1",
    "googDecodingCNG": "0",
    "ssrc": "3719978459",
    "googPreferredJitterBufferMs": "20",
    "googSpeechExpandRate": "0.178711",
    "totalSamplesDuration": "22.06",
    "totalAudioEnergy": "0",
    "transportId": "Channel-audio-1",
    "mediaType": "audio",
    "googDecodingPLCCNG": "72",
    "googCodecName": "opus",
    "googSecondaryDiscardedRate": "0",
    "googDecodingNormal": "2113",
    "googTrackId": "ab5be64a-00b0-4c69-9ffa-b934a3cdaf92",
    "audioOutputLevel": "0",
    "googAccelerateRate": "0",
    "bytesReceived": "103053",
    "googCurrentDelayMs": "61",
    "googDecodingCTSG": "0",
    "googCaptureStartNtpTimeMs": "3720226188883",
    "googPreemptiveExpandRate": "0",
    "googJitterBufferMs": "5",
    "googDecodingMuted": "71",
    "id": "ssrc_3719978459_recv",
    "type": "ssrc",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "audioInputLevel": "38",
    "packetsLost": "0",
    "googTrackId": "ab5be64a-00b0-4c69-9ffa-b934a3cdaf92",
    "googRtt": "1",
    "googResidualEchoLikelihoodRecentMax": "0",
    "googEchoCancellationReturnLossEnhancement": "-100",
    "totalSamplesDuration": "0",
    "googCodecName": "opus",
    "transportId": "Channel-audio-1",
    "mediaType": "audio",
    "aecDivergentFilterFraction": "0",
    "googEchoCancellationReturnLoss": "-100",
    "googResidualEchoLikelihood": "0",
    "googEchoCancellationQualityMin": "0",
    "totalAudioEnergy": "0",
    "ssrc": "4057371623",
    "googJitterReceived": "1",
    "googTypingNoiseState": "false",
    "packetsSent": "1056",
    "bytesSent": "103053",
    "id": "ssrc_4057371623_send",
    "type": "ssrc",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googContentType": "realtime",
    "googCaptureStartNtpTimeMs": "3720226188863",
    "googTargetDelayMs": "28",
    "packetsLost": "0",
    "googDecodeMs": "3",
    "googFrameHeightReceived": "480",
    "googFrameRateOutput": "2",
    "packetsReceived": "3843",
    "ssrc": "2926085704",
    "googRenderDelayMs": "10",
    "googMaxDecodeMs": "4",
    "googTrackId": "c07bbd24-4e99-4b94-b429-5174370f8d12",
    "googFrameWidthReceived": "640",
    "codecImplementationName": "libvpx",
    "transportId": "Channel-audio-1",
    "mediaType": "video",
    "googTimingFrameInfo": "2498355253,490771956,490771960,490771967,490771967,490771974,0,1",
    "googInterframeDelayMax": "67",
    "googCodecName": "VP9",
    "googFrameRateReceived": "2",
    "qpSum": "35281",
    "framesDecoded": "348",
    "googNacksSent": "0",
    "googFirsSent": "0",
    "bytesReceived": "4294692",
    "googCurrentDelayMs": "28",
    "googMinPlayoutDelayMs": "0",
    "googFrameRateDecoded": "2",
    "googJitterBufferMs": "14",
    "googPlisSent": "0",
    "id": "ssrc_2926085704_recv",
    "type": "ssrc",
    "timestamp": "2017-11-21T04:10:10.905Z"
}, {
    "googFrameWidthSent": "640",
    "packetsLost": "0",
    "googRtt": "1",
    "googEncodeUsagePercent": "19",
    "googCpuLimitedResolution": "false",
    "googNacksReceived": "0",
    "googBandwidthLimitedResolution": "false",
    "googPlisReceived": "0",
    "googAvgEncodeMs": "7",
    "googTrackId": "c07bbd24-4e99-4b94-b429-5174370f8d12",
    "googFrameRateInput": "2",
    "framesEncoded": "348",
    "codecImplementationName": "libvpx",
    "transportId": "Channel-audio-1",
    "mediaType": "video",
    "googFrameHeightSent": "480",
    "googFrameRateSent": "18",
    "googCodecName": "VP9",
    "qpSum": "35291",
    "googAdaptationChanges": "0",
    "ssrc": "3181351409",
    "googFirsReceived": "0",
    "packetsSent": "3884",
    "bytesSent": "4316619",
    "id": "ssrc_3181351409_send",
    "type": "ssrc",
    "timestamp": "2017-11-21T04:10:10.905Z"
}]

License

getStats.js is released under MIT licence . Copyright (c) Muaz Khan.