Home Reference Source

src/Health.js

import 'platform';
import util from './Util.js'
import l from './Logger.js'

class Health{
  constructor(ctx){
    this.interval=5000;
    this.statsReportTimer = null;
    this.context = ctx;
    this.oldStats = null;
    this.result = null;
    this.fractionLost = {
			audio: [
				{rating: 1, fromAflost: 0, toAflost: 50},
				{rating: 2, fromAflost: 51, toAflost: 150},
				{rating: 3, fromAflost: 151, toAflost: 250},
				{rating: 4, fromAflost: 251, toAflost: 350},
				{rating: 5, fromAflost: 351, toAflost: 9999999},
			],
			video: [
				{rating: 1, fromAflost: 0, toAflost: 40},
				{rating: 2, fromAflost: 41, toAflost: 55},
				{rating: 3, fromAflost: 56, toAflost: 70},
				{rating: 4, fromAflost: 71, toAflost: 90},
				{rating: 5, fromAflost: 91, toAflost: 9999999},
			]
		};
  }
  stop(){
		if(this.statsReportTimer){
			window.clearInterval(this.statsReportTimer);
			this.statsReportTimer = null;
		}
	}

  start(){
    if (this.context.isCaller===true)return; // master만 health 던지자
    l.i("Health is start w/interval:"+ this.interval);
    if(this.statsReportTimer){
      window.clearInterval(this.statsReportTimer);
      this.statsReportTimer = null;
    }

    this.statsReportTimer = window.setInterval(util.bind(function(){
      this.getStats(util.bind(function(stats){
        var i = 0,
          len = 0,
          attr = null,
          result = {
            localCandidate: "",
            remoteCandidate: "",
            localFrameWidth : "",
            localFrameHeight : "",
            remoteFrameWidth: "",
            remoteFrameHeight: "",
            localFrameRate: "",
            remoteFrameRate: "",
            availableSendBandwidth: 0,
            availableReceiveBandwidth: 0,
            rtt: 0,
            rttRating: "",
            localAudioFractionLost: 0,
            localVideoFractionLost: 0,
            localAudioFractionRating: 0,
            localVideoFractionRating: 0,
            remoteAudioFractionLost: 0,
            remoteVideoFractionLost: 0,
            remoteAudioFractionRating: 0,
            remoteVideoFractionRating: 0,
            fractionRating: 0,
            localAudioPacketsLost: 0,
            remoteAudioPaketsLost: 0,
            localVideoPacketsLost: 0,
            remoteVideoPacketsLost: 0,
          },
          nowLocalAPLost = 0, nowLocalVPLost = 0,
          nowLocalAPSent = 0, nowLocalVPSent = 0,
          oldLocalAPLost = 0, oldLocalVPLost = 0,
          oldLocalAPSent = 0, oldLocalVPSent = 0,
          nowRemoteAPLost = 0, nowRemoteVPLost = 0,
          nowRemoteAPReceived = 0, nowRemoteVPReceived = 0,
          oldRemoteAPLost = 0, oldRemoteVPLost = 0,
          oldRemoteAPReceived = 0, oldRemoteVPReceived = 0,
          oldStats = this.oldStats,
          ffAudioRtt,
          ffVideoRtt,
          message;

        if(platform.name === "Firefox"){
          for(attr in stats){
            if(stats[attr].type === "candidatepair" && stats[attr].selected === true){
              result.localCandidate = stats[stats[attr].localCandidateId].candidateType;
              result.remoteCandidate = stats[stats[attr].remoteCandidateId].candidateType;
              continue;
            }

            if(stats[attr].type === "inboundrtp" && stats[attr].mediaType === "audio" && stats[attr].isRemote === true){
              if(stats[attr].hasOwnProperty("mozRtt")){
                ffAudioRtt = stats[attr].mozRtt;
              }

              if(stats[attr].hasOwnProperty("packetsLost")){
                nowRemoteAPLost = stats[attr].packetsLost;
              }
              if(stats[attr].hasOwnProperty("packetsReceived")){
                nowRemoteAPReceived = stats[attr].packetsReceived;
              }
              continue;
            }

            if(stats[attr].type === "inboundrtp" && stats[attr].mediaType === "video" && stats[attr].isRemote === true){
              if(stats[attr].hasOwnProperty("mozRtt")){
                ffVideoRtt = stats[attr].mozRtt;
              }

              if(stats[attr].hasOwnProperty("packetsLost")){
                nowRemoteVPLost = stats[attr].packetsLost;
              }
              if(stats[attr].hasOwnProperty("packetsReceived")){
                nowRemoteVPReceived = stats[attr].packetsReceived;
              }
              continue;
            }
          }

          for(attr in oldStats){
            if(stats[attr].type === "inboundrtp" && stats[attr].mediaType === "audio" && stats[attr].isRemote === true){
              if(stats[attr].hasOwnProperty("packetsLost")){
                oldRemoteAPLost = stats[attr].packetsLost;
              }
              if(stats[attr].hasOwnProperty("packetsReceived")){
                oldRemoteAPReceived = stats[attr].packetsReceived;
              }
              continue;
            }

            if(stats[attr].type === "inboundrtp" && stats[attr].mediaType === "video" && stats[attr].isRemote === true){
              if(stats[attr].hasOwnProperty("packetsLost")){
                oldRemoteVPLost = stats[attr].packetsLost;
              }
              if(stats[attr].hasOwnProperty("packetsReceived")){
                oldRemoteVPReceived = stats[attr].packetsReceived;
              }
              continue;
            }
          }

          if(this.context.useVideo===false){
            result.rtt = ffAudioRtt;
          }
          else{
            result.rtt = ffVideoRtt;
          }
        }else{// chrome
          len = stats.length;
          for(; i<len; i++){
            if(stats[i].googActiveConnection === "true"){
              result.localCandidate = stats[i].googLocalCandidateType;
              result.remoteCandidate = stats[i].googRemoteCandidateType;

              //result.rtt = parseInt(stats[i].googRtt);
              continue;
            }

            if(stats[i].hasOwnProperty("audioInputLevel")){
              nowLocalAPLost = parseInt(stats[i].packetsLost);
              nowLocalAPSent = parseInt(stats[i].packetsSent);
              if(this.context.useVideo ===false){
                result.rtt = parseInt(stats[i].googRtt);
              }
              continue;
            }

            if(stats[i].hasOwnProperty("googFrameWidthSent")){
              result.localFrameWidth = parseInt(stats[i].googFrameWidthSent);
              result.localFrameHeight = parseInt(stats[i].googFrameHeightSent);
              result.localFrameRate = parseInt(stats[i].googFrameRateSent);

              nowLocalVPLost = parseInt(stats[i].packetsLost);
              nowLocalVPSent = parseInt(stats[i].packetsSent);
              if(this.context.useVideo===true){
                result.rtt = parseInt(stats[i].googRtt);
              }
              continue;
            }

            if(stats[i].hasOwnProperty("audioOutputLevel")){
              nowRemoteAPLost = parseInt(stats[i].packetsLost);
              nowRemoteAPReceived = parseInt(stats[i].packetsReceived);
              continue;
            }

            if(stats[i].hasOwnProperty("googFrameWidthReceived")){
              result.remoteFrameWidth = parseInt(stats[i].googFrameWidthReceived);
              result.remoteFrameHeight = parseInt(stats[i].googFrameHeightReceived);
              result.remoteFrameRate = parseInt(stats[i].googFrameRateReceived);

              nowRemoteVPLost = parseInt(stats[i].packetsLost);
              nowRemoteVPReceived = parseInt(stats[i].packetsReceived);
              continue;
            }

            if(stats[i].hasOwnProperty("googAvailableSendBandwidth")){
              result.availableSendBandwidth = parseInt(stats[i].googAvailableSendBandwidth);
              result.availableReceiveBandwidth = parseInt(stats[i].googAvailableReceiveBandwidth);
              continue;
            }
          }

          for(i=0; i<oldStats.length; i++){
            if(oldStats[i].hasOwnProperty("audioInputLevel")){
              oldLocalAPLost = parseInt(oldStats[i].packetsLost);
              oldLocalAPSent = parseInt(oldStats[i].packetsSent);
              continue;
            }

            if(oldStats[i].hasOwnProperty("googFrameWidthSent")){
              oldLocalVPLost = parseInt(oldStats[i].packetsLost);
              oldLocalVPSent = parseInt(oldStats[i].packetsSent);
              continue;
            }

            if(oldStats[i].hasOwnProperty("audioOutputLevel")){
              oldRemoteAPLost = parseInt(oldStats[i].packetsLost);
              oldRemoteAPReceived = parseInt(oldStats[i].packetsReceived);
              continue;
            }

            if(oldStats[i].hasOwnProperty("googFrameWidthReceived")){
              oldRemoteVPLost = parseInt(oldStats[i].packetsLost);
              oldRemoteVPReceived = parseInt(oldStats[i].packetsReceived);
              continue;
            }
          }
        }
        //var localAFLost = parseFloat(parseFloat((nowLocalAPLost - oldLocalAPLost) / (nowLocalAPSent - oldLocalAPSent) * 255 || 0).toFixed(4));
        var localAFLost = parseInt((nowLocalAPLost - oldLocalAPLost) / (nowLocalAPSent - oldLocalAPSent) * 255 || 0);
        //var localVFLost = parseFloat(parseFloat((nowLocalVPLost - oldLocalVPLost) / (nowLocalVPSent - oldLocalVPSent) * 255 || 0).toFixed(4));
        var localVFLost = parseInt((nowLocalVPLost - oldLocalVPLost) / (nowLocalVPSent - oldLocalVPSent) * 255 || 0);

        result.localAudioPacketsLost = parseInt( (nowLocalAPLost - oldLocalAPLost)/this.interval  )
        result.localVideoPacketsLost = parseInt( (nowLocalVPLost - oldLocalVPLost)/this.interval)
        result.remoteAudioPacketsLost = parseInt( (nowRemoteAPLost - oldRemoteAPLost)/this.interval  )
        result.remoteVideoPacketsLost = parseInt( (nowRemoteVPLost - oldRemoteVPLost)/this.interval  )

        result.localAudioFractionLost = localAFLost;
        result.localVideoFractionLost = localVFLost;

        result.localAudioFractionRating = this.getFractionLostRating(localAFLost, this.fractionLost.audio);
        result.localVideoFractionRating = this.getFractionLostRating(localVFLost, this.fractionLost.video);


        // var remoteAFLost = parseFloat(parseFloat((nowRemoteAPLost - oldRemoteAPLost) / (nowRemoteAPReceived - oldRemoteAPReceived) * 255 || 0).toFixed(4));
        var remoteAFLost = parseInt((nowRemoteAPLost - oldRemoteAPLost) / (nowRemoteAPReceived - oldRemoteAPReceived) * 255 || 0);
        // var remoteVFLost = parseFloat(parseFloat((nowRemoteVPLost - oldRemoteVPLost) / (nowRemoteVPReceived - oldRemoteVPReceived) * 255 || 0).toFixed(4));
        var remoteVFLost = parseInt((nowRemoteVPLost - oldRemoteVPLost) / (nowRemoteVPReceived - oldRemoteVPReceived) * 255 || 0);

        result.remoteAudioFractionLost = remoteAFLost;
        result.remoteVideoFractionLost = remoteVFLost;

        result.remoteAudioFractionRating = this.getFractionLostRating(remoteAFLost, this.fractionLost.audio);
        result.remoteVideoFractionRating = this.getFractionLostRating(remoteVFLost, this.fractionLost.video);

        result.fractionRating = result.remoteVideoFractionRating;

        result.rttRating = this.getRttRating(result.rtt);

        this.oldStats = stats;
        //console.log(result);
        this.result = result;

        if (this.context.eventManager.hasEventListener('onStat')) {
          this.context.eventManager.dispatchEvent('onStat', result);
        }
        //l.i("this result:");

        message = this.context.signalingConnection.createMessage({ command: 'health', body: JSON.stringify(result) });
        //l.i(message);
        if (message)this.context.signalingConnection.send(JSON.stringify(message));
      },this));
    },this), this.interval);
  }

  getRttRating(rtt){
    var rttRating = 0;
    if (rtt >= 1000){	rttRating = 5;}
		else if (rtt >= 800){	rttRating = 4;}
		else if (rtt >= 600){	rttRating = 3;}
		else if (rtt >= 400){	rttRating = 2;}
		else if (rtt < 400){ rttRating = 1;}
		return rttRating;
  }

  getFractionLostRating(loss, fl){
    var f = fl;
    for(let i=0; i<f.length; i++){
			if(f[i].fromFractionLost <= loss && f[i].toFractionLost >= loss){
				return f[i].rating;
			}
		}
    return 1;
  }

  getStats(fn){
		if(platform.name === "Firefox"){
			this.context.peerConnection.getStats(null, util.bind(function(res){
				fn.call(this, res);
			}, this), function(){});
		}
		else{
			this.context.peerConnection.getStats(util.bind(function(res){
				var items = [ ];
				res.result().forEach(function (result) {
					var item = { };
					result.names().forEach(function (name) {
						item[name] = result.stat(name);
					});
					item.id = result.id;
					item.type = result.type;
					item.timestamp = result.timestamp;

					items.push(item);
				});

				fn.call(this, items);
			}, this));
		}
	}
}

export default Health;