function score(stats) {
const scores = {};
const { audio, video } = normalize(stats);
if (audio) {
const delay = 20 + audio.bufferDelay + audio.roundTripTime / 2;
const pl = audio.packetLoss;
const R0 = 100;
const Ie = audio.dtx
? 8
: audio.bitrate
? clamp(55 - 4.6 * Math.log(audio.bitrate), 0, 30)
: 6;
const Bpl = audio.fec ? 20 : 10;
const Ipl = Ie + (100 - Ie) * (pl / (pl + Bpl));
const Id = delay * 0.03 + (delay > 150 ? 0.1 * delay - 150 : 0);
const R = clamp(R0 - Ipl - Id, 0, 100);
const MOS = 1 + 0.035 * R + (R * (R - 60) * (100 - R) * 7) / 1000000;
scores.audio = clamp(Math.round(MOS * 100) / 100, 1, 5);
}
if (video) {
const pixels = video.expectedWidth * video.expectedHeight;
const codecFactor = video.codec === 'vp9' ? 1.2 : 1.0;
const delay = video.bufferDelay + video.roundTripTime / 2;
if (video.frameRate !== 0) {
const bPPPF = (codecFactor * video.bitrate) / pixels / video.frameRate;
const base = clamp(0.56 * Math.log(bPPPF) + 5.36, 1, 5);
const MOS =
base -
1.9 * Math.log(video.expectedFrameRate / video.frameRate) -
delay * 0.002;
scores.video = clamp(Math.round(MOS * 100) / 100, 1, 5);
} else {
scores.video = 1;
}
}
return scores;
}