553 lines
19 KiB
JavaScript
553 lines
19 KiB
JavaScript
(function (context, $) {
|
|
|
|
"use strict";
|
|
|
|
context.JK = context.JK || {};
|
|
context.JK.GearTest = function (app) {
|
|
|
|
var logger = context.JK.logger;
|
|
|
|
var isAutomated = false;
|
|
var drawUI = false;
|
|
var scoring = false;
|
|
var validLatencyScore = false;
|
|
var validIOScore = false;
|
|
var latencyScore = null;
|
|
var ioScore = null;
|
|
var lastSavedTime = new Date();
|
|
// this should be marked TRUE when the backend sends an invalid_audio_device alert
|
|
var asynchronousInvalidDevice = false;
|
|
var selectedDeviceInfo = null;
|
|
|
|
|
|
var $scoreReport = null;
|
|
var $ioHeader = null;
|
|
var $latencyHeader = null;
|
|
var $ioRate = null;
|
|
var $ioRateScore = null;
|
|
var $ioVar = null;
|
|
var $ioVarScore = null;
|
|
var $ioCountdown = null;
|
|
var $ioCountdownSecs = null;
|
|
var $latencyScore = null;
|
|
var $resultsText = null;
|
|
var $unknownText = null;
|
|
var $loopbackCompleted = null;
|
|
var $adjustGearSpeedCompleted = null;
|
|
var $adjustGearForIoFail = null;
|
|
var $ioScoreSection = null;
|
|
var $latencyScoreSection = null;
|
|
|
|
|
|
var $self = $(this);
|
|
|
|
var GEAR_TEST_START = "gear_test.start";
|
|
var GEAR_TEST_IO_START = "gear_test.io_start";
|
|
var GEAR_TEST_IO_DONE = "gear_test.io_done";
|
|
var GEAR_TEST_LATENCY_START = "gear_test.latency_start";
|
|
var GEAR_TEST_LATENCY_DONE = "gear_test.latency_done";
|
|
var GEAR_TEST_DONE = "gear_test.done";
|
|
var GEAR_TEST_FAIL = "gear_test.fail";
|
|
var GEAR_TEST_IO_PROGRESS = "gear_test.io_progress";
|
|
var GEAR_TEST_INVALIDATED_ASYNC = "gear_test.async_invalidated"; // happens when backend alerts us device is invalid
|
|
|
|
|
|
function isWdm() {
|
|
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
|
|
}
|
|
|
|
function isGoodFtue() {
|
|
return (isWdm() || validIOScore) && !asynchronousInvalidDevice;
|
|
}
|
|
|
|
function processIOScore(io) {
|
|
// take the higher variance, which is apparently actually std dev
|
|
var std = io.in_var > io.out_var ? io.in_var : io.out_var;
|
|
std = Math.round(std * 100) / 100;
|
|
// take the furthest-off-from-target io rate
|
|
var median = Math.abs(io.in_median - io.in_target) > Math.abs(io.out_median - io.out_target) ? [io.in_median, io.in_target] : [io.out_median, io.out_target];
|
|
var medianTarget = median[1];
|
|
median = Math.round(median[0]);
|
|
|
|
var stdIOClass = 'bad';
|
|
if (std <= 0.50) {
|
|
stdIOClass = 'good';
|
|
}
|
|
else if (std <= 1.00) {
|
|
stdIOClass = 'acceptable';
|
|
}
|
|
|
|
var medianIOClass = 'bad';
|
|
if (Math.abs(median - medianTarget) <= 1) {
|
|
medianIOClass = 'good';
|
|
}
|
|
else if (Math.abs(median - medianTarget) <= 2) {
|
|
medianIOClass = 'acceptable';
|
|
}
|
|
|
|
// uncomment one to force a particular type of I/O failure
|
|
// medianIOClass = "bad";
|
|
// stdIOClass = "bad"
|
|
|
|
// take worst between median or std
|
|
var ioClassToNumber = {bad: 2, acceptable: 1, good: 0}
|
|
var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass;
|
|
|
|
// now base the overall IO score based on both values.
|
|
|
|
$self.triggerHandler(GEAR_TEST_IO_DONE, {std:std, median:median, io:io, aggregrateIOClass: aggregrateIOClass, medianIOClass : medianIOClass, stdIOClass: stdIOClass, validLatencyScore: validLatencyScore})
|
|
//renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass);
|
|
|
|
if(aggregrateIOClass == "bad") {
|
|
validIOScore = false;
|
|
}
|
|
else {
|
|
validIOScore = true;
|
|
}
|
|
|
|
scoring = false;
|
|
|
|
if(isGoodFtue()) {
|
|
$self.triggerHandler(GEAR_TEST_DONE, {validLatencyScore: validLatencyScore})
|
|
}
|
|
else {
|
|
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std, validLatencyScore: validLatencyScore});
|
|
}
|
|
}
|
|
|
|
function automaticScore() {
|
|
logger.debug("automaticScore: calling FTUESave(false)");
|
|
lastSavedTime = new Date(); // save before and after FTUESave, because the event happens in a multithreaded way
|
|
var result = context.jamClient.FTUESave(false);
|
|
lastSavedTime = new Date();
|
|
if(result && result.error) {
|
|
logger.debug("unable to FTUESave(false). reason=" + result.detail);
|
|
context.JK.GearTest.testDeferred.reject(result);
|
|
return false;
|
|
}
|
|
else {
|
|
var latency = context.jamClient.FTUEGetExpectedLatency();
|
|
context.JK.GearTest.testDeferred.resolve(latency);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function loopbackScore() {
|
|
var cbFunc = 'JK.loopbackLatencyCallback';
|
|
logger.debug("Registering loopback latency callback: " + cbFunc);
|
|
context.jamClient.FTUERegisterLatencyCallback('JK.loopbackLatencyCallback');
|
|
var now = new Date();
|
|
logger.debug("Starting Latency Test..." + now);
|
|
context.jamClient.FTUEStartLatency();
|
|
}
|
|
|
|
// refocused affects how IO testing occurs.
|
|
// on refocus=true:
|
|
// * reuse IO score if it was good/acceptable
|
|
// * rescore IO if it was bad or skipped from previous try
|
|
function attemptScore(_selectedDeviceInfo, refocused) {
|
|
if(scoring) {
|
|
logger.debug("gear-test: already scoring");
|
|
return;
|
|
}
|
|
selectedDeviceInfo = _selectedDeviceInfo;
|
|
scoring = true;
|
|
asynchronousInvalidDevice = false;
|
|
$self.triggerHandler(GEAR_TEST_START);
|
|
$self.triggerHandler(GEAR_TEST_LATENCY_START);
|
|
validLatencyScore = false;
|
|
latencyScore = null;
|
|
if(!refocused) {
|
|
// don't reset a valid IO score on refocus
|
|
validIOScore = false;
|
|
ioScore = null;
|
|
}
|
|
|
|
// this timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in
|
|
setTimeout(function () {
|
|
|
|
context.JK.GearTest.testDeferred = new $.Deferred();
|
|
if(isAutomated) {
|
|
// use automated latency score mechanism
|
|
automaticScore()
|
|
}
|
|
else {
|
|
// use loopback score mechanism
|
|
loopbackScore();
|
|
}
|
|
|
|
context.JK.GearTest.testDeferred.done(function(latency) {
|
|
latencyScore = latency;
|
|
|
|
if(isAutomated) {
|
|
// uncomment to do a manual loopback test
|
|
//latency.latencyknown = false;
|
|
}
|
|
|
|
updateScoreReport(latency, refocused);
|
|
|
|
if (true || validLatencyScore) {
|
|
$self.triggerHandler(GEAR_TEST_IO_START);
|
|
// reuse valid IO score if this is on refocus
|
|
if(false && (refocused && validIOScore)) {
|
|
processIOScore(ioScore);
|
|
}
|
|
else {
|
|
var testTimeSeconds = gon.ftue_io_wait_time; // allow time for IO to establish itself
|
|
var startTime = testTimeSeconds / 2; // start measuring half way through the test, to get past IO oddities
|
|
$self.trigger(GEAR_TEST_IO_PROGRESS, {countdown:testTimeSeconds, first:true})
|
|
|
|
var interval = setInterval(function () {
|
|
testTimeSeconds -= 1;
|
|
$self.trigger(GEAR_TEST_IO_PROGRESS, {countdown:testTimeSeconds, first:false})
|
|
|
|
if(testTimeSeconds == startTime) {
|
|
logger.debug("Starting IO Perf Test starting at " + startTime + "s in")
|
|
context.jamClient.FTUEStartIoPerfTest();
|
|
}
|
|
|
|
if (testTimeSeconds == 0) {
|
|
clearInterval(interval);
|
|
logger.debug("Ending IO Perf Test at " + testTimeSeconds + "s in")
|
|
var io = context.jamClient.FTUEGetIoPerfData();
|
|
ioScore = io;
|
|
processIOScore(io);
|
|
}
|
|
}, 1000);
|
|
}
|
|
}
|
|
else {
|
|
scoring = false;
|
|
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', validLatencyScore: validLatencyScore, latencyScore: latencyScore.latency})
|
|
}
|
|
})
|
|
.fail(function(ftueSaveResult) {
|
|
scoring = false;
|
|
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'invalid_configuration', validLatencyScore: validLatencyScore, data: ftueSaveResult})
|
|
})
|
|
}, 250);
|
|
}
|
|
|
|
function updateScoreReport(latencyResult, refocused) {
|
|
var latencyClass = "neutral";
|
|
var latencyValue = null;
|
|
var validLatency = false;
|
|
console.log("FTUEGetExpectedLatency", latencyResult)
|
|
if (latencyResult && latencyResult.latencyknown) {
|
|
var latencyValue = latencyResult.latency;
|
|
latencyValue = Math.round(latencyValue * 100) / 100;
|
|
if (latencyValue <= 10) {
|
|
latencyClass = "good";
|
|
validLatency = true;
|
|
} else if (latencyValue <= gon.ftue_maximum_gear_latency) {
|
|
latencyClass = "acceptable";
|
|
validLatency = true;
|
|
} else {
|
|
latencyClass = "bad";
|
|
}
|
|
}
|
|
else {
|
|
latencyClass = 'unknown';
|
|
}
|
|
|
|
// uncomment these two lines to fail test due to latency
|
|
// latencyClass = "bad";
|
|
// validLatency = false;
|
|
|
|
validLatencyScore = validLatency;
|
|
|
|
if(refocused) {
|
|
context.JK.prodBubble($scoreReport, 'refocus-rescore', {validIOScore: validIOScore}, {positions:['top', 'left']});
|
|
}
|
|
|
|
$self.triggerHandler(GEAR_TEST_LATENCY_DONE, {latencyValue: latencyValue, latencyClass: latencyClass, refocused: refocused});
|
|
}
|
|
|
|
// std deviation is the worst value between in/out
|
|
// media is the worst value between in/out
|
|
// io is the value returned by the backend, which has more info
|
|
// ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad'
|
|
function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) {
|
|
$ioRateScore.text(median !== null ? median : '');
|
|
$ioVarScore.text(std !== null ? std : '');
|
|
if (ioClass && ioClass != "starting" && ioClass != "skip") {
|
|
$ioRate.show();
|
|
$ioVar.show();
|
|
}
|
|
if(ioClass == 'starting' || ioClass == 'skip') {
|
|
$ioHeader.show();
|
|
}
|
|
$resultsText.attr('io-rate-score', ioRateClass);
|
|
$resultsText.attr('io-var-score', ioVarClass);
|
|
$ioScoreSection.removeClass('good acceptable bad unknown starting skip')
|
|
if (ioClass) {
|
|
$ioScoreSection.addClass(ioClass);
|
|
}
|
|
// TODO: show help bubble of all data in IO data
|
|
}
|
|
|
|
function isScoring() {
|
|
return scoring;
|
|
}
|
|
|
|
function isValidLatencyScore() {
|
|
return validLatencyScore;
|
|
}
|
|
|
|
function isValidIOScore() {
|
|
return validIOScore;
|
|
}
|
|
|
|
function getLatencyScore() {
|
|
return latencyScore;
|
|
}
|
|
|
|
function getIOScore() {
|
|
return ioScore;
|
|
}
|
|
|
|
function getLastSavedTime() {
|
|
return lastSavedTime;
|
|
}
|
|
|
|
function onInvalidAudioDevice() {
|
|
logger.debug("gear_test: onInvalidAudioDevice")
|
|
asynchronousInvalidDevice = true;
|
|
$self.triggerHandler(GEAR_TEST_INVALIDATED_ASYNC);
|
|
context.JK.Banner.showAlert('Invalid Audio Device', 'It appears this audio device is not currently connected. Attach the device to your computer and restart the application, or select a different device.<br/><br/>If you think your gear is connected and working:<br/><br/>Try a different sample rate<br/><br/>Your device is in use by another application, in which case this <a rel="external" href="https://jamkazam.desk.com/customer/portal/articles/1723489-jamkazam-thinks-your-gear-is-disconnected---windows-only">support article</a> can help.')
|
|
|
|
}
|
|
|
|
function showLoopbackDone() {
|
|
$loopbackCompleted.show();
|
|
}
|
|
|
|
function showGearAdjustmentDone() {
|
|
$adjustGearSpeedCompleted.show();
|
|
}
|
|
|
|
function resetScoreReport() {
|
|
$ioHeader.hide();
|
|
$latencyHeader.hide();
|
|
$ioRate.hide();
|
|
$ioRateScore.empty();
|
|
$ioVar.hide();
|
|
$ioVarScore.empty();
|
|
$latencyScore.empty();
|
|
$resultsText.removeAttr('latency-score');
|
|
$resultsText.removeAttr('io-var-score');
|
|
$resultsText.removeAttr('io-rate-score');
|
|
$resultsText.removeAttr('scored');
|
|
$unknownText.hide();
|
|
$loopbackCompleted.hide();
|
|
$adjustGearSpeedCompleted.hide();
|
|
$ioScoreSection.removeClass('good acceptable bad unknown starting skip');
|
|
$latencyScoreSection.removeClass('good acceptable bad unknown starting')
|
|
}
|
|
|
|
function invalidateScore() {
|
|
validLatencyScore = false;
|
|
validIOScore = false;
|
|
asynchronousInvalidDevice = false;
|
|
resetScoreReport();
|
|
}
|
|
|
|
function renderLatencyScore(latencyValue, latencyClass) {
|
|
// latencyValue == null implies starting condition
|
|
if (latencyValue) {
|
|
$latencyScore.text(latencyValue + ' ms');
|
|
}
|
|
else {
|
|
$latencyScore.text('');
|
|
}
|
|
|
|
|
|
if(latencyClass == 'unknown') {
|
|
$latencyScore.text('Unknown');
|
|
$unknownText.show();
|
|
}
|
|
|
|
$latencyHeader.show();
|
|
$resultsText.attr('latency-score', latencyClass);
|
|
$latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass);
|
|
}
|
|
|
|
function renderIOScoringStarted(secondsLeft) {
|
|
$ioCountdownSecs.text(secondsLeft);
|
|
$ioCountdown.show();
|
|
}
|
|
|
|
function renderIOScoringStopped() {
|
|
$ioCountdown.hide();
|
|
}
|
|
|
|
function renderIOCountdown(secondsLeft) {
|
|
$ioCountdownSecs.text(secondsLeft);
|
|
}
|
|
|
|
function uniqueDeviceName() {
|
|
try {
|
|
return selectedDeviceInfo.input.info.displayName + '(' + selectedDeviceInfo.input.behavior.shortName + ')' + '-' +
|
|
selectedDeviceInfo.output.info.displayName + '(' + selectedDeviceInfo.output.behavior.shortName + ')' + '-' +
|
|
context.JK.GetOSAsString();
|
|
}
|
|
catch(e){
|
|
logger.error("unable to devise unique device name for stats: " + e.toString());
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
function handleUI($testResults) {
|
|
|
|
if(!$testResults.is('.ftue-box.results')) {
|
|
throw "GearTest != .ftue-box.results"
|
|
}
|
|
|
|
$scoreReport = $testResults;
|
|
$ioHeader = $scoreReport.find('.io');;
|
|
$latencyHeader = $scoreReport.find('.latency');
|
|
$ioRate = $scoreReport.find('.io-rate');
|
|
$ioRateScore = $scoreReport.find('.io-rate-score');
|
|
$ioVar = $scoreReport.find('.io-var');
|
|
$ioVarScore = $scoreReport.find('.io-var-score');
|
|
$ioScoreSection = $scoreReport.find('.io-score-section');
|
|
$latencyScore = $scoreReport.find('.latency-score');
|
|
$ioCountdown = $scoreReport.find('.io-countdown');
|
|
$ioCountdownSecs = $scoreReport.find('.io-countdown .secs');
|
|
$resultsText = $scoreReport.find('.results-text');
|
|
$unknownText = $scoreReport.find('.unknown-text');
|
|
$loopbackCompleted = $scoreReport.find('.loopback-completed')
|
|
$adjustGearSpeedCompleted = $scoreReport.find('.adjust-gear-speed-completed');
|
|
$adjustGearForIoFail = $scoreReport.find(".adjust-gear-for-io-fail")
|
|
$latencyScoreSection = $scoreReport.find('.latency-score-section');
|
|
|
|
function onGearTestStart(e, data) {
|
|
renderIOScore(null, null, null, null, null, null);
|
|
}
|
|
|
|
function onGearTestIOStart(e, data) {
|
|
renderIOScore(null, null, null, 'starting', 'starting', 'starting');
|
|
}
|
|
|
|
function onGearTestLatencyStart(e, data) {
|
|
resetScoreReport();
|
|
renderLatencyScore(null, 'starting');
|
|
}
|
|
|
|
function onGearTestLatencyDone(e, data) {
|
|
renderLatencyScore(data.latencyValue, data.latencyClass);
|
|
}
|
|
|
|
function onGearTestIOProgress(e, data) {
|
|
if(data.first) {
|
|
renderIOScoringStarted(data.countdown);
|
|
}
|
|
|
|
renderIOCountdown(data.countdown);
|
|
|
|
if(data.countdown == 0) {
|
|
renderIOScoringStopped();
|
|
}
|
|
}
|
|
|
|
function onGearTestIODone(e, data) {
|
|
renderIOScore(data.std, data.median, data.io, data.aggregrateIOClass, data.medianIOClass, data.stdIOClass);
|
|
}
|
|
|
|
function onGearTestDone(e, data) {
|
|
$resultsText.attr('scored', 'complete');
|
|
|
|
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.pass, latencyScore);
|
|
|
|
rest.userCertifiedGear({success: true, client_id: app.clientId, audio_latency: getLatencyScore().latency});
|
|
}
|
|
|
|
function onGearTestFail(e, data) {
|
|
$resultsText.attr('scored', 'complete');
|
|
|
|
if(data.reason == "latency") {
|
|
renderIOScore(null, null, null, 'skip', 'skip', 'skip');
|
|
}
|
|
|
|
rest.userCertifiedGear({success: false, client_id: app.clientId});
|
|
|
|
if(data.reason == "latency") {
|
|
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.latencyFail, data.latencyScore);
|
|
}
|
|
else if(data.reason = "io") {
|
|
if(data.ioTarget == 'bad') {
|
|
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.ioTargetFail, data.ioTargetScore);
|
|
}
|
|
else {
|
|
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.ioVarianceFail, data.ioVarianceScore);
|
|
}
|
|
}
|
|
else if(data.reason == 'invalid_configuration') {
|
|
logger.error("invalid configuration returned by gearTest." + data.data.detail)
|
|
}
|
|
else {
|
|
logger.error("unknown reason in onGearTestFail: " + data.reason)
|
|
}
|
|
}
|
|
|
|
$self
|
|
.on(GEAR_TEST_START, onGearTestStart)
|
|
.on(GEAR_TEST_IO_START, onGearTestIOStart)
|
|
.on(GEAR_TEST_LATENCY_START, onGearTestLatencyStart)
|
|
.on(GEAR_TEST_LATENCY_DONE, onGearTestLatencyDone)
|
|
.on(GEAR_TEST_IO_PROGRESS, onGearTestIOProgress)
|
|
.on(GEAR_TEST_IO_DONE, onGearTestIODone)
|
|
.on(GEAR_TEST_DONE, onGearTestDone)
|
|
.on(GEAR_TEST_FAIL, onGearTestFail);
|
|
}
|
|
|
|
function initialize($testResults, automated, noUI) {
|
|
|
|
isAutomated = automated;
|
|
drawUI = !noUI;
|
|
|
|
if(drawUI) {
|
|
handleUI($testResults);
|
|
}
|
|
}
|
|
|
|
// Latency Test Callback
|
|
context.JK.loopbackLatencyCallback = function (latencyMS) {
|
|
// Unregister callback:
|
|
context.jamClient.FTUERegisterLatencyCallback('');
|
|
|
|
logger.debug("loopback test done: " + latencyMS);
|
|
context.JK.GearTest.testDeferred.resolve({latency: latencyMS, latencyknown:true})
|
|
};
|
|
|
|
this.GEAR_TEST_START = GEAR_TEST_START;
|
|
this.GEAR_TEST_IO_START = GEAR_TEST_IO_START;
|
|
this.GEAR_TEST_IO_DONE = GEAR_TEST_IO_DONE;
|
|
this.GEAR_TEST_LATENCY_START = GEAR_TEST_LATENCY_START;
|
|
this.GEAR_TEST_LATENCY_DONE = GEAR_TEST_LATENCY_DONE;
|
|
this.GEAR_TEST_DONE = GEAR_TEST_DONE;
|
|
this.GEAR_TEST_FAIL = GEAR_TEST_FAIL;
|
|
this.GEAR_TEST_IO_PROGRESS = GEAR_TEST_IO_PROGRESS;
|
|
this.GEAR_TEST_INVALIDATED_ASYNC = GEAR_TEST_INVALIDATED_ASYNC;
|
|
|
|
this.initialize = initialize;
|
|
this.isScoring = isScoring;
|
|
this.attemptScore = attemptScore;
|
|
this.resetScoreReport = resetScoreReport;
|
|
this.showLoopbackDone = showLoopbackDone;
|
|
this.showGearAdjustmentDone = showGearAdjustmentDone;
|
|
this.invalidateScore = invalidateScore;
|
|
this.isValidLatencyScore = isValidLatencyScore;
|
|
this.isValidIOScore = isValidIOScore;
|
|
this.isGoodFtue = isGoodFtue;
|
|
this.getLatencyScore = getLatencyScore;
|
|
this.getIOScore = getIOScore;
|
|
this.getLastSavedTime = getLastSavedTime;
|
|
this.onInvalidAudioDevice = onInvalidAudioDevice;
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
})(window, jQuery); |