diff --git a/web/app/assets/javascripts/accounts_audio_profile.js b/web/app/assets/javascripts/accounts_audio_profile.js index f76e1f08b..9cc73ac57 100644 --- a/web/app/assets/javascripts/accounts_audio_profile.js +++ b/web/app/assets/javascripts/accounts_audio_profile.js @@ -67,7 +67,7 @@ function handleStartAudioQualification() { - if(true) { + if(false) { app.layout.startNewFtue(); } else { diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index cd7ff04cb..891b4d180 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -14,6 +14,7 @@ //= require jquery.monkeypatch //= require jquery_ujs //= require jquery.ui.draggable +//= require jquery.ui.droppable //= require jquery.bt //= require jquery.icheck //= require jquery.color diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 1175d9b28..bdede9b5f 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -124,6 +124,83 @@ "Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2" }; } + function FTUEGetChannels() { + return { + "inputs": [ + { + "assignment": 1, + "chat": false, + "device_id": "Built-in Microph", + "device_type": 5, + "id": "i~5~Built-in Microph~0~0~Built-in", + "name": "Built-in Microph - Left", + "number": 0 + }, + { + "assignment": 0, + "chat": false, + "device_id": "Built-in Microph", + "device_type": 5, + "id": "i~5~Built-in Microph~1~0~Built-in", + "name": "Built-in Microph - Right", + "number": 1 + } + ], + "outputs": [ + { + "assignment": -1, + "chat": false, + "device_id": "Built-in Output", + "device_type": 5, + "id": "o~5~Built-in Output~0~0~Built-in", + "name": "Built-in Output - Left", + "number": 0 + }, + { + "assignment": -1, + "chat": false, + "device_id": "Built-in Output", + "device_type": 5, + "id": "o~5~Built-in Output~1~0~Built-in", + "name": "Built-in Output - Right", + "number": 1 + } + ] + }; + } + function FTUEGetAudioDevices() { + return { + "devices": [ + { + "display_name": "Built-in", + "guid": "Built-in", + "input_count": 1, + "name": "Built-in", + "output_count": 1, + "port_audio_name": "Built-in" + }, + { + "display_name": "JamKazam Virtual Monitor", + "guid": "JamKazam Virtual Monitor", + "input_count": 0, + "name": "JamKazam Virtual Monitor", + "output_count": 1, + "port_audio_name": "JamKazam Virtual Monitor" + } + ] + }; + } + function FTUEStartIoPerfTest() {} + function FTUEGetIoPerfData() { + return { + "in_var" : 0.15, + "out_var" : 0.25, + "in_median" : 399.9, + "out_median" : 400.3, + "in_target" : 400, + "out_target" : 400 + }; + } function FTUESetInputMusicDevice() { } function FTUESetOutputMusicDevice() { } function FTUEGetInputMusicDevice() { return null; } @@ -678,6 +755,10 @@ this.FTUEGetOutputMusicDevice = FTUEGetOutputMusicDevice; this.FTUEGetChatInputVolume = FTUEGetChatInputVolume; this.FTUEGetChatInputs = FTUEGetChatInputs; + this.FTUEGetChannels = FTUEGetChannels; + this.FTUEGetAudioDevices = FTUEGetAudioDevices; + this.FTUEStartIoPerfTest = FTUEStartIoPerfTest; + this.FTUEGetIoPerfData = FTUEGetIoPerfData; this.FTUEGetDevices = FTUEGetDevices; this.FTUEGetFrameSize = FTUEGetFrameSize; this.FTUECancel = FTUECancel; diff --git a/web/app/assets/javascripts/gear/gear_wizard.js b/web/app/assets/javascripts/gear/gear_wizard.js index bdffef0dc..a1c4b382b 100644 --- a/web/app/assets/javascripts/gear/gear_wizard.js +++ b/web/app/assets/javascripts/gear/gear_wizard.js @@ -14,8 +14,8 @@ var $templateButtons = null; var $templateAudioPort = null; var $ftueButtons = null; - var $btnBack = null; var $btnNext = null; + var $btnBack = null; var $btnClose = null; var $btnCancel = null; @@ -48,6 +48,18 @@ 6: stepSuccess } + function beforeHideStep($step) { + var stepInfo = STEPS[step]; + + if (!stepInfo) { + throw "unknown step: " + step; + } + + if(stepInfo.beforeHide) { + stepInfo.beforeHide.call(stepInfo); + } + } + function beforeShowStep($step) { var stepInfo = STEPS[step]; @@ -61,6 +73,8 @@ function moveToStep() { var $nextWizardStep = $wizardSteps.filter($('[layout-wizard-step=' + step + ']')); + beforeHideStep($currentWizardStep); + $wizardSteps.hide(); $currentWizardStep = $nextWizardStep; @@ -165,6 +179,12 @@ function next() { if ($(this).is('.button-grey')) return false; + var stepInfo = STEPS[step]; + if(stepInfo.handleNext) { + var result = stepInfo.handleNext.call(stepInfo); + if(!result) {return false;} + } + step = step + 1; moveToStep(); @@ -172,6 +192,7 @@ } function closeDialog() { + beforeHideStep($currentWizardStep); app.layout.closeDialog('gear-wizard'); return false; } @@ -198,6 +219,21 @@ } } + function setBackState(enabled) { + + if(!$btnBack) return; + + $btnBack.removeClass('button-orange button-grey'); + + if (enabled) { + $btnBack.addClass('button-orange'); + } + else { + $btnBack.addClass('button-grey'); + } + } + + function initialize() { // on initial page load, we are not in the FTUE. so cancel the FTUE and call FTUESetStatus(true) if needed @@ -228,6 +264,7 @@ } this.setNextState = setNextState; + this.setBackState = setBackState; this.initialize = initialize; self = this; diff --git a/web/app/assets/javascripts/gear/step_configure_tracks.js b/web/app/assets/javascripts/gear/step_configure_tracks.js index ab0be8b4b..0351496a1 100644 --- a/web/app/assets/javascripts/gear/step_configure_tracks.js +++ b/web/app/assets/javascripts/gear/step_configure_tracks.js @@ -5,12 +5,111 @@ context.JK = context.JK || {}; context.JK.StepConfigureTracks = function (app) { - var $step = null; + var ASSIGNMENT = context.JK.ASSIGNMENT; + var VOICE_CHAT = context.JK.VOICE_CHAT; + var MAX_TRACKS = context.JK.MAX_TRACKS; - function initialize(_$step) { - $step = _$step; + + var $step = null; + var $templateAssignablePort = null; + var $templateTrackTarget = null; + var $unassignedChannelsHolder = null; + var $tracksHolder = null; + + + + function loadChannels() { + var musicPorts = jamClient.FTUEGetChannels(); + + $unassignedChannelsHolder.empty(); + $tracksHolder.find('.ftue-inputport').remove(); + + var inputChannels = musicPorts.inputs; + + context._.each(inputChannels, function (inputChannel) { + if(inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { + var $unassigned = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); + $unassignedChannelsHolder.append($unassigned); + $unassigned.draggable(); + + } + else { + var $assigned = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' })); + + // find the track this belongs in + + var trackNumber = inputChannel.assignment - 1; + + var $track = $tracksHolder.find('.track[data-num="' + trackNumber + '"]') + + if($track.length == 0) { + context.JK.alertSupportedNeeded('Unable to find a track for channel with assignment ' + inputChannel.assignment); + return false; + } + addChannelToTrack($assigned, $track); + $assigned.draggable(); + } + }) } + + function beforeShow() { + + loadChannels(); + } + + function removeChannelFromTrack() { + + } + function addChannelToTrack($channel, $track) { + $track.find('.track-target').append($channel); + } + + function initializeUnassignedDroppable() { + $unassignedChannelsHolder.droppable( + { + activeClass: 'drag-in-progress', + hoverClass: 'drag-hovering', + drop: function( event, ui ) { + console.log("event, ui", event, ui) + + } + }); + } + + function initializeTrackDroppables() { + var i; + for(i = 0; i < MAX_TRACKS; i++) { + var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' })); + $tracksHolder.append($target); + $target.droppable( + { + activeClass: 'drag-in-progress', + hoverClass: 'drag-hovering', + drop: function( event, ui ) { + console.log("event, ui", event, ui) + + var $target = $(this); + var $channel = ui.draggable; + addChannelToTrack($channel, $target); + } + }); + } + } + function initialize(_$step) { + $step = _$step; + + $templateAssignablePort = $('#template-assignable-port'); + $templateTrackTarget = $('#template-track-target'); + $unassignedChannelsHolder = $step.find('.unassigned-channels'); + $tracksHolder = $step.find('.tracks'); + + + initializeUnassignedDroppable(); + initializeTrackDroppables(); + } + + this.beforeShow = beforeShow; this.initialize = initialize; return this; diff --git a/web/app/assets/javascripts/gear/step_configure_voice_chat.js b/web/app/assets/javascripts/gear/step_configure_voice_chat.js index 46d9c9d6a..1e9be3ad4 100644 --- a/web/app/assets/javascripts/gear/step_configure_voice_chat.js +++ b/web/app/assets/javascripts/gear/step_configure_voice_chat.js @@ -7,10 +7,15 @@ var $step = null; + function beforeShow() { + + } + function initialize(_$step) { $step = _$step; } + this.beforeShow = beforeShow; this.initialize = initialize; return this; diff --git a/web/app/assets/javascripts/gear/step_direct_monitoring.js b/web/app/assets/javascripts/gear/step_direct_monitoring.js index a8e6aa1ac..80d8a83cb 100644 --- a/web/app/assets/javascripts/gear/step_direct_monitoring.js +++ b/web/app/assets/javascripts/gear/step_direct_monitoring.js @@ -7,10 +7,15 @@ var $step = null; + function beforeShow() { + + } + function initialize(_$step) { $step = _$step; } + this.beforeShow = beforeShow; this.initialize = initialize; return this; diff --git a/web/app/assets/javascripts/gear/step_network_test.js b/web/app/assets/javascripts/gear/step_network_test.js index d04e73343..91d05ba34 100644 --- a/web/app/assets/javascripts/gear/step_network_test.js +++ b/web/app/assets/javascripts/gear/step_network_test.js @@ -7,10 +7,15 @@ var $step = null; + function beforeShow() { + + } + function initialize(_$step) { $step = _$step; } + this.beforeShow = beforeShow; this.initialize = initialize; return this; diff --git a/web/app/assets/javascripts/gear/step_select_gear.js b/web/app/assets/javascripts/gear/step_select_gear.js index ec4db1c6d..4b97529ee 100644 --- a/web/app/assets/javascripts/gear/step_select_gear.js +++ b/web/app/assets/javascripts/gear/step_select_gear.js @@ -9,6 +9,7 @@ var VOICE_CHAT = context.JK.VOICE_CHAT; var self = null; var $step = null; + var rest = context.JK.Rest(); var $watchVideoInput = null; var $watchVideoOutput = null; var $audioInput = null; @@ -32,20 +33,31 @@ var $ioCountdown = null; var $ioCountdownSecs = null; var $resultsText = null; + var $unknownText = null; var $asioInputControlBtn = null; var $asioOutputControlBtn = null; var $resyncBtn = null; var $templateAudioPort = null; + var $launchLoopbackBtn = null; + var $instructions = null; var operatingSystem = null; var iCheckIgnore = false; + var scoring = false; // are we currently scoring + var validDevice = false; // do we currently have a device selected that we can score against? // cached values between var deviceInformation = null; + var lastSelectedDeviceInfo = null; + var shownOutputProdOnce = false; + var shownInputProdOnce = false; + var selectedDeviceInfo = null; var musicPorts = null; var validLatencyScore = false; var validIOScore = false; + var lastLatencyScore = null; + var lastIOScore = null; var audioDeviceBehavior = { MacOSX_builtin: { @@ -86,10 +98,6 @@ } } - var ASIO_SETTINGS_DEFAULT_TEXT = 'ASIO SETTINGS...'; - var ASIO_SETTINGS_INPUT_TEXT = 'ASIO INPUT SETTINGS...'; - var ASIO_SETTINGS_OUTPUT_TEXT = 'ASIO OUTPUT SETTINGS...'; - // should return one of: // * MacOSX_builtin // * MACOSX_interface @@ -126,8 +134,7 @@ var oldDevices = context.jamClient.FTUEGetDevices(false); var devices = context.jamClient.FTUEGetAudioDevices(); - console.log("oldDevices: " + JSON.stringify(oldDevices)); - console.log("devices: " + JSON.stringify(devices)); + logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices)); var loadedDevices = {}; @@ -185,16 +192,19 @@ $dialog.setNextState(validLatencyScore && validIOScore); } + function initializeBackButtonState() { + $dialog.setBackState(!scoring); + } + function initializeAudioInput() { var optionsHtml = ''; optionsHtml = ''; context._.each(deviceInformation, function (deviceInfo, deviceId) { - - console.log(arguments) optionsHtml += ''; }); $audioInput.html(optionsHtml); context.JK.dropdown($audioInput); + $audioInput.easyDropDown('enable') initializeAudioInputChanged(); } @@ -207,6 +217,7 @@ }); $audioOutput.html(optionsHtml); context.JK.dropdown($audioOutput); + $audioOutput.easyDropDown('disable'); // enable once they pick something in input initializeAudioOutputChanged(); } @@ -225,7 +236,6 @@ // and update's the UI accordingly function initializeChannels() { musicPorts = jamClient.FTUEGetChannels(); - console.log("musicPorts: %o", JSON.stringify(musicPorts)); initializeInputPorts(musicPorts); initializeOutputPorts(musicPorts); @@ -255,12 +265,14 @@ var $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)'); if ($assignedInputs.length == 0) { if ($allInputs.length >= 2) { + logger.debug("selecting 2 inputs") $unassignedInputs.eq(0).iCheck('check').attr('checked', 'checked'); // this is required because iCheck change handler re-writes the inputs. So we have to refetch unassigned outputs $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)'); $unassignedInputs.eq(0).iCheck('check').attr('checked', 'checked'); } else { + logger.debug("selecting 1 inputs") $unassignedInputs.eq(0).iCheck('check').attr('checked', 'checked'); } } @@ -269,15 +281,15 @@ var $assignedOutputs = $outputChannels.find('input[type="checkbox"]:checked'); var $unassignedOutputs = $outputChannels.find('input[type="checkbox"]:not(:checked)'); - console.log("outputs:", $assignedOutputs, $unassignedOutputs); if ($assignedOutputs.length == 0) { - console.log("selecting both outputs") + logger.debug("selecting both outputs") $unassignedOutputs.eq(0).iCheck('check').attr('checked', 'checked'); // this is required because iCheck change handler re-writes the inputs. So we have to refetch unassigned outputs $unassignedOutputs = $outputChannels.find('input[type="checkbox"]:not(:checked)'); $unassignedOutputs.eq(0).iCheck('check').attr('checked', 'checked'); } else if ($assignedOutputs.length == 1) { + logger.debug("selecting 1 output to round out 2 total") $unassignedOutputs.eq(0).iCheck('check').attr('checked', 'checked'); } @@ -404,6 +416,14 @@ }); } + function initializeLoopback() { + $launchLoopbackBtn.unbind('click').click(function() { + app.setWizardStep(1); + app.layout.showDialog('ftue'); + return false; + }) + } + function initializeFormElements() { if (!deviceInformation) throw "devices are not initialized"; @@ -411,6 +431,7 @@ initializeAudioOutput(); initializeFramesize(); initializeBuffers(); + initializeLoopback(); } function resetFrameBuffers() { @@ -438,6 +459,8 @@ $resultsText.removeAttr('latency-score'); $resultsText.removeAttr('io-var-score'); $resultsText.removeAttr('io-rate-score'); + $resultsText.removeAttr('scored'); + $unknownText.hide(); } function renderLatencyScore(latencyValue, latencyClass) { @@ -448,6 +471,13 @@ 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); @@ -460,11 +490,11 @@ function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) { $ioRateScore.text(median !== null ? median : ''); $ioVarScore.text(std !== null ? std : ''); - if (ioClass && ioClass != "starting") { + if (ioClass && ioClass != "starting" && ioClass != "skip") { $ioRate.show(); $ioVar.show(); } - if(ioClass == 'starting') { + if(ioClass == 'starting' || ioClass == 'skip') { $ioHeader.show(); } $resultsText.attr('io-rate-score', ioRateClass); @@ -478,7 +508,7 @@ function updateScoreReport(latencyResult) { var latencyClass = "neutral"; - var latencyValue = 'N/A'; + var latencyValue = null; var validLatency = false; if (latencyResult && latencyResult.latencyknown) { var latencyValue = latencyResult.latency; @@ -503,6 +533,7 @@ } function audioInputDeviceUnselected() { + validDevice = false; validLatencyScore = false; validIOScore = false; initializeNextButtonState(); @@ -511,10 +542,8 @@ } function renderScoringStarted() { - validLatencyScore = false; - validIOScore = false; - initializeNextButtonState(); resetScoreReport(); + initializeNextButtonState(); freezeAudioInteraction(); renderLatencyScore(null, 'starting'); renderIOScore(null, null, null, null, null, null); @@ -523,10 +552,13 @@ function renderScoringStopped() { initializeNextButtonState(); unfreezeAudioInteraction(); + $resultsText.attr('scored', 'complete'); + scoring = false; + initializeBackButtonState(); } - function freezeAudioInteraction() { + logger.debug("freezing audio interaction"); $audioInput.attr("disabled", "disabled").easyDropDown('disable'); $audioOutput.attr("disabled", "disabled").easyDropDown('disable'); $frameSize.attr("disabled", "disabled").easyDropDown('disable'); @@ -541,6 +573,7 @@ } function unfreezeAudioInteraction() { + logger.debug("unfreezing audio interaction"); $audioInput.removeAttr("disabled").easyDropDown('enable'); $audioOutput.removeAttr("disabled").easyDropDown('enable'); $frameSize.removeAttr("disabled").easyDropDown('enable'); @@ -554,50 +587,6 @@ iCheckIgnore = false; } - // Given a latency structure, update the view. - function newFtueUpdateLatencyView(latency) { - var $report = $('.ftue-new .latency .report'); - var $instructions = $('.ftue-new .latency .instructions'); - var latencyClass = "neutral"; - var latencyValue = "N/A"; - var $saveButton = $('#btn-ftue-2-save'); - if (latency && latency.latencyknown) { - latencyValue = latency.latency; - // Round latency to two decimal places. - latencyValue = Math.round(latencyValue * 100) / 100; - if (latency.latency <= 10) { - latencyClass = "good"; - setSaveButtonState($saveButton, true); - } else if (latency.latency <= 20) { - latencyClass = "acceptable"; - setSaveButtonState($saveButton, true); - } else { - latencyClass = "bad"; - setSaveButtonState($saveButton, false); - } - } else { - latencyClass = "unknown"; - setSaveButtonState($saveButton, false); - } - - $('.ms-label', $report).html(latencyValue); - $('p', $report).html('milliseconds'); - - $report.removeClass('good acceptable bad unknown'); - $report.addClass(latencyClass); - - var instructionClasses = ['neutral', 'good', 'acceptable', 'unknown', 'bad', 'start', 'loading']; - $.each(instructionClasses, function (idx, val) { - $('p.' + val, $instructions).hide(); - }); - if (latency === 'loading') { - $('p.loading', $instructions).show(); - } else { - $('p.' + latencyClass, $instructions).show(); - renderStopNewFtueLatencyTesting(); - } - } - function initializeWatchVideo() { $watchVideoInput.unbind('click').click(function () { @@ -665,6 +654,10 @@ }); } + function postDiagnostic() { + // TODO + } + function initializeResync() { $resyncBtn.unbind('click').click(function () { attemptScore(); @@ -716,6 +709,9 @@ var inputBehavior = audioDeviceBehavior[input.type]; var outputBehavior = audioDeviceBehavior[output.type]; + lastSelectedDeviceInfo = selectedDeviceInfo; + shownOutputProdOnce = false; + shownInputProdOnce = false; selectedDeviceInfo = { input: { id: audioInputDeviceId, @@ -728,6 +724,9 @@ behavior: outputBehavior } } + + // prod the user to watch the video if the input or output changes type + console.log("selectedDeviceInfo", selectedDeviceInfo); } @@ -736,6 +735,10 @@ var audioInputDeviceId = selectedDeviceInfo.input.id; var audioOutputDeviceId = selectedDeviceInfo.output.id; + if(audioInputDeviceId) { + $audioOutput.easyDropDown('enable'); + } + // don't re-assign input/output audio devices because it disturbs input/output track association if (jamClient.FTUEGetInputMusicDevice() != audioInputDeviceId) { jamClient.FTUESetInputMusicDevice(audioInputDeviceId); @@ -746,7 +749,7 @@ initializeChannels(); - var validDevice = autoSelectMinimumValidChannels(); + validDevice = autoSelectMinimumValidChannels(); if (!validDevice) { return false; @@ -767,10 +770,33 @@ } } + function isInputAudioTypeDifferentFromLastTime() { + return lastSelectedDeviceInfo && (lastSelectedDeviceInfo.input.type != selectedDeviceInfo.input.type); + } + + function isOutputAudioTypeDifferentFromLastTime() { + return lastSelectedDeviceInfo && isInputOutputDifferentTypes() && (lastSelectedDeviceInfo.output.type != selectedDeviceInfo.output.type) + } + + function isInputOutputDifferentTypes() { + return selectedDeviceInfo.input.type != selectedDeviceInfo.output.type; + } + function updateDialogForCurrentDevices() { var inputBehavior = selectedDeviceInfo.input.behavior; var outputBehavior = selectedDeviceInfo.output.behavior; + // deal with watch video + if(isInputOutputDifferentTypes()) { + // if we have two types of devices, you need two different videos + $watchVideoOutput.show().find('.video-type').show(); + $watchVideoInput.addClass('audio-output-showing').find('.video-type').show(); + } + else { + $watchVideoOutput.hide(); + $watchVideoInput.removeClass('audio-output-showing').find('.video-type').hide(); + } + // handle framesize/buffers if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs)) { $knobs.css('visibility', 'visible') @@ -783,18 +809,18 @@ if (inputBehavior) { if (inputBehavior.showASIO && !outputBehavior.showASIO) { // show single ASIO button - $asioInputControlBtn.text(ASIO_SETTINGS_DEFAULT_TEXT).show(); + $asioInputControlBtn.show(); $asioOutputControlBtn.hide(); } else if (!inputBehavior.showASIO && outputBehavior.showASIO) { // show single ASIO button - $asioInputControlBtn.text(ASIO_SETTINGS_DEFAULT_TEXT).show(); + $asioInputControlBtn.show(); $asioOutputControlBtn.hide(); } else if (inputBehavior.showASIO && outputBehavior.showASIO) { // show two ASIO buttons - $asioInputControlBtn.text(ASIO_SETTINGS_INPUT_TEXT).show(); - $asioOutputControlBtn.text(ASIO_SETTINGS_OUTPUT_TEXT).show(); + $asioInputControlBtn.show(); + $asioOutputControlBtn.show(); } else { // show no ASIO buttons @@ -817,71 +843,110 @@ } } - function attemptScore() { + 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'; + } + + // 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. + renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass); + + // lie for now until IO questions finalize + validIOScore = true; + + renderScoringStopped(); + } + + // 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(refocused) { + if(scoring) {return;} + scoring = true; + initializeBackButtonState(); + validLatencyScore = false; + if(!refocused) { + // don't reset a valid IO score on refocus + validIOScore = false; + } renderScoringStarted(); - // timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in + // this timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in setTimeout(function () { logger.debug("Calling FTUESave(false)"); jamClient.FTUESave(false); var latency = jamClient.FTUEGetExpectedLatency(); - console.log("FTUEGetExpectedLatency: %o", latency); + lastLatencyScore = latency; + + // prod user to watch video if the previous type and new type changed + if(!shownInputProdOnce && isInputAudioTypeDifferentFromLastTime()) { + context.JK.prodBubble($watchVideoInput, 'ftue-watch-video', {}, {positions:['top', 'right']}); + shownInputProdOnce = true; + } + + // prod user to watch video if the previous type and new type changed + if(!shownOutputProdOnce && isOutputAudioTypeDifferentFromLastTime()) { + context.JK.prodBubble($watchVideoOutput, 'ftue-watch-video', {}, {positions:['top', 'right']}); + shownOutputProdOnce = true; + } updateScoreReport(latency); + if(refocused) { + context.JK.prodBubble($scoreReport, 'refocus-rescore', {validIOScore: validIOScore}, {positions:['top', 'left']}); + } + // if there was a valid latency score, go on to the next step if (validLatencyScore) { - renderIOScore(null, null, null, 'starting', 'starting', 'starting'); - var testTimeSeconds = 10; // allow 10 seconds for IO to establish itself - context.jamClient.FTUEStartIoPerfTest(); - renderIOScoringStarted(testTimeSeconds); - renderIOCountdown(testTimeSeconds); - var interval = setInterval(function () { - testTimeSeconds -= 1; + // reuse valid IO score if this is on refocus + if(refocused && validIOScore) { + renderIOScore(null, null, null, 'starting', 'starting', 'starting'); + processIOScore(lastIOScore); + } + else { + renderIOScore(null, null, null, 'starting', 'starting', 'starting'); + var testTimeSeconds = gon.ftue_io_wait_time; // allow time for IO to establish itself + context.jamClient.FTUEStartIoPerfTest(); + renderIOScoringStarted(testTimeSeconds); renderIOCountdown(testTimeSeconds); - if (testTimeSeconds == 0) { - clearInterval(interval); - renderIOScoringStopped(); - var io = context.jamClient.FTUEGetIoPerfData(); - - // 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'; + var interval = setInterval(function () { + testTimeSeconds -= 1; + renderIOCountdown(testTimeSeconds); + if (testTimeSeconds == 0) { + clearInterval(interval); + renderIOScoringStopped(); + var io = context.jamClient.FTUEGetIoPerfData(); + lastIOScore = io; + processIOScore(io); } - 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'; - } - - // 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. - renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass); - - // lie for now until IO questions finalize - validIOScore = true; - - renderScoringStopped(); - } - }, 1000); + }, 1000); + } } else { renderIOScore(null, null, null, 'skip', 'skip', 'skip'); @@ -898,6 +963,45 @@ $audioOutput.unbind('change').change(audioDeviceChanged); } + function handleNext() { + + var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked'); + var $assignedOutputs = $outputChannels.find('input[type="checkbox"]:checked'); + + var errors = []; + if($assignedInputs.length == 0) { + errors.push("There must be at least one selected input ports."); + } + if($assignedInputs.length > 12) { + errors.push("There can only be up to 12 selected inputs ports."); + } + if($assignedOutputs.length != 2) { + errors.push("There must be exactly 2 selected output ports."); + } + var $errors = $('