* merging in some gateway fixes
This commit is contained in:
parent
00a6139cd1
commit
8262725aae
|
|
@ -17,7 +17,8 @@
|
|||
// heartbeat
|
||||
var heartbeatInterval = null;
|
||||
var heartbeatMS = null;
|
||||
var heartbeatMissedMS = 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset
|
||||
var heartbeatMissedMS = 10000; // if 10 seconds go by and we haven't seen a heartbeat ack, get upset
|
||||
var lastHeartbeatSentTime = null;
|
||||
var lastHeartbeatAckTime = null;
|
||||
var lastHeartbeatFound = false;
|
||||
var heartbeatAckCheckInterval = null;
|
||||
|
|
@ -140,6 +141,16 @@
|
|||
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
|
||||
notificationLastSeenAt = undefined;
|
||||
notificationLastSeen = undefined;
|
||||
// for debugging purposes, see if the last time we've sent a heartbeat is way off (500ms) of the target interval
|
||||
var now = new Date();
|
||||
|
||||
if(lastHeartbeatSentTime) {
|
||||
var drift = new Date().getTime() - lastHeartbeatSentTime.getTime() - heartbeatMS;
|
||||
if(drift > 500) {
|
||||
logger.error("significant drift between heartbeats: " + drift + 'ms beyond target interval')
|
||||
}
|
||||
}
|
||||
lastHeartbeatSentTime = now;
|
||||
context.JK.JamServer.send(message);
|
||||
lastHeartbeatFound = false;
|
||||
}
|
||||
|
|
@ -417,6 +428,7 @@
|
|||
connectTimeout = setTimeout(function() {
|
||||
connectTimeout = null;
|
||||
if(connectDeferred.state() === 'pending') {
|
||||
server.close(true);
|
||||
connectDeferred.reject();
|
||||
}
|
||||
}, 4000);
|
||||
|
|
|
|||
|
|
@ -7,18 +7,8 @@
|
|||
var logger = context.JK.logger;
|
||||
var myTrackCount;
|
||||
|
||||
var ASSIGNMENT = {
|
||||
CHAT: -2,
|
||||
OUTPUT: -1,
|
||||
UNASSIGNED: 0,
|
||||
TRACK1: 1,
|
||||
TRACK2: 2
|
||||
};
|
||||
|
||||
var VOICE_CHAT = {
|
||||
NO_CHAT: "0",
|
||||
CHAT: "1"
|
||||
};
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
|
||||
var instrument_array = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -6,18 +6,27 @@
|
|||
context.JK = context.JK || {};
|
||||
context.JK.GearWizard = function (app) {
|
||||
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
|
||||
var $dialog = null;
|
||||
var $wizardSteps = null;
|
||||
var $currentWizardStep = null;
|
||||
var step = 0;
|
||||
var $templateSteps = null;
|
||||
var $templateButtons = null;
|
||||
var $templateAudioPort = null;
|
||||
var $ftueButtons = null;
|
||||
var self = null;
|
||||
var operatingSystem = null;
|
||||
|
||||
// SELECT DEVICE STATE
|
||||
var validScore = false;
|
||||
// populated by loadDevices
|
||||
var deviceInformation = null;
|
||||
var musicPorts = null;
|
||||
|
||||
|
||||
var validLatencyScore = false;
|
||||
var validIOScore = false;
|
||||
|
||||
// SELECT TRACKS STATE
|
||||
|
||||
|
|
@ -30,20 +39,33 @@
|
|||
var STEP_ROUTER_NETWORK = 5;
|
||||
var STEP_SUCCESS = 6;
|
||||
|
||||
var PROFILE_DEV_SEP_TOKEN = '^';
|
||||
|
||||
var iCheckIgnore = false;
|
||||
|
||||
var audioDeviceBehavior = {
|
||||
MacOSX_builtin : {
|
||||
MacOSX_builtin: {
|
||||
display: 'MacOSX Built-In',
|
||||
videoURL: undefined
|
||||
},
|
||||
MACOSX_interface : {
|
||||
MacOSX_interface: {
|
||||
display: 'MacOSX external interface',
|
||||
videoURL: undefined
|
||||
},
|
||||
Win32_wdm : {
|
||||
Win32_wdm: {
|
||||
display: 'Windows WDM',
|
||||
videoURL: undefined
|
||||
},
|
||||
Win32_asio : {
|
||||
Win32_asio: {
|
||||
display: 'Windows ASIO',
|
||||
videoURL: undefined
|
||||
},
|
||||
Win32_asio4all : {
|
||||
Win32_asio4all: {
|
||||
display: 'Windows ASIO4ALL',
|
||||
videoURL: undefined
|
||||
},
|
||||
Linux: {
|
||||
display: 'Linux',
|
||||
videoURL: undefined
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +73,7 @@
|
|||
function beforeShowIntro() {
|
||||
var $watchVideo = $currentWizardStep.find('.watch-video');
|
||||
var videoUrl = 'https://www.youtube.com/watch?v=VexH4834o9I';
|
||||
if(operatingSystem == "Win32") {
|
||||
if (operatingSystem == "Win32") {
|
||||
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=VexH4834o9I');
|
||||
}
|
||||
$watchVideo.attr('href', videoUrl);
|
||||
|
|
@ -65,12 +87,90 @@
|
|||
var $audioOutput = $currentWizardStep.find('.select-audio-output-device');
|
||||
var $bufferIn = $currentWizardStep.find('.select-buffer-in');
|
||||
var $bufferOut = $currentWizardStep.find('.select-buffer-out');
|
||||
var $nextButton = $ftueButtons.find('.btn-next');
|
||||
var $frameSize = $currentWizardStep.find('.select-frame-size');
|
||||
var $inputChannels = $currentWizardStep.find('.input-ports');
|
||||
var $outputChannels = $currentWizardStep.find('.output-ports');
|
||||
var $scoreReport = $currentWizardStep.find('.results');
|
||||
var $latencyScoreSection = $scoreReport.find('.latency-score-section');
|
||||
var $latencyScore = $scoreReport.find('.latency-score');
|
||||
var $ioScoreSection = $scoreReport.find('.io-score-section');
|
||||
var $ioRateScore = $scoreReport.find('.io-rate-score');
|
||||
var $ioVarScore = $scoreReport.find('.io-var-score');
|
||||
var $ioCountdown = $scoreReport.find('.io-countdown');
|
||||
var $ioCountdownSecs = $scoreReport.find('.io-countdown .secs');
|
||||
var $nextButton = $ftueButtons.find('.btn-next');
|
||||
var $asioControlPanelBtn = $currentWizardStep.find('.asio-settings-btn');
|
||||
var $resyncBtn = $currentWizardStep.find('resync-btn')
|
||||
|
||||
// returns a deviceInfo hash for the device matching the deviceId, or null.
|
||||
// should return one of:
|
||||
// * MacOSX_builtin
|
||||
// * MACOSX_interface
|
||||
// * Win32_wdm
|
||||
// * Win32_asio
|
||||
// * Win32_asio4all
|
||||
// * Linux
|
||||
function determineDeviceType(deviceId, displayName) {
|
||||
if (operatingSystem == "MacOSX") {
|
||||
if (displayName.toLowerCase().trim() == "built-in") {
|
||||
return "MacOSX_builtin";
|
||||
}
|
||||
else {
|
||||
return "MacOSX_interface";
|
||||
}
|
||||
}
|
||||
else if (operatingSystem == "Win32") {
|
||||
if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) {
|
||||
return "Win32_wdm";
|
||||
}
|
||||
else if (displayName.toLowerCase().indexOf("asio4all") > -1) {
|
||||
return "Win32_asio4all"
|
||||
}
|
||||
else {
|
||||
return "Win32_asio";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Linux";
|
||||
}
|
||||
}
|
||||
|
||||
function loadDevices() {
|
||||
|
||||
var oldDevices = context.jamClient.FTUEGetDevices(false);
|
||||
var devices = context.jamClient.FTUEGetAudioDevices();
|
||||
console.log("oldDevices: " + JSON.stringify(oldDevices));
|
||||
console.log("devices: " + JSON.stringify(devices));
|
||||
|
||||
var loadedDevices = {};
|
||||
|
||||
// augment these devices by determining their type
|
||||
context._.each(devices.devices, function (device) {
|
||||
|
||||
if(device.name == "JamKazam Virtual Monitor") {
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceInfo = {};
|
||||
|
||||
deviceInfo.id = device.guid;
|
||||
deviceInfo.type = determineDeviceType(device.guid, device.display_name);
|
||||
console.log("deviceInfo.type: " + deviceInfo.type)
|
||||
deviceInfo.displayType = audioDeviceBehavior[deviceInfo.type].display;
|
||||
deviceInfo.displayName = device.display_name;
|
||||
|
||||
loadedDevices[device.guid] = deviceInfo;
|
||||
|
||||
logger.debug("loaded device: ", deviceInfo);
|
||||
})
|
||||
|
||||
deviceInformation = loadedDevices;
|
||||
|
||||
logger.debug(context.JK.dlen(deviceInformation) + " devices loaded.", deviceInformation);
|
||||
}
|
||||
|
||||
// returns a deviceInfo hash for the device matching the deviceId, or undefined.
|
||||
function findDevice(deviceId) {
|
||||
return {};
|
||||
return deviceInformation[deviceId];
|
||||
}
|
||||
|
||||
function selectedAudioInput() {
|
||||
|
|
@ -81,31 +181,369 @@
|
|||
return $audioOutput.val();
|
||||
}
|
||||
|
||||
function selectedFramesize() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferIn() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferOut() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
$nextButton.removeClass('button-orange button-grey');
|
||||
|
||||
if(validScore) $nextButton.addClass('button-orange');
|
||||
if (validLatencyScore) $nextButton.addClass('button-orange');
|
||||
else $nextButton.addClass('button-grey');
|
||||
}
|
||||
|
||||
function audioDeviceUnselected() {
|
||||
validScore = false;
|
||||
function initializeAudioInput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Choose...</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
|
||||
console.log(arguments)
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
});
|
||||
$audioInput.html(optionsHtml);
|
||||
context.JK.dropdown($audioInput);
|
||||
|
||||
initializeAudioInputChanged();
|
||||
}
|
||||
|
||||
function initializeAudioOutput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Same as Input...</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
});
|
||||
$audioOutput.html(optionsHtml);
|
||||
context.JK.dropdown($audioOutput);
|
||||
|
||||
initializeAudioOutputChanged();
|
||||
}
|
||||
|
||||
function initializeFramesize() {
|
||||
context.JK.dropdown($frameSize);
|
||||
}
|
||||
|
||||
function initializeBuffers() {
|
||||
context.JK.dropdown($bufferIn);
|
||||
context.JK.dropdown($bufferOut);
|
||||
}
|
||||
|
||||
|
||||
// reloads the backend's channel state for the currently selected audio devices,
|
||||
// and update's the UI accordingly
|
||||
function initializeChannels() {
|
||||
musicPorts = jamClient.FTUEGetChannels();
|
||||
console.log("musicPorts: %o", JSON.stringify(musicPorts));
|
||||
|
||||
initializeInputPorts(musicPorts);
|
||||
initializeOutputPorts(musicPorts);
|
||||
}
|
||||
|
||||
// during this phase of the FTUE, we have to assign selected input channels
|
||||
// to tracks. The user, however, does not have a way to indicate which channel
|
||||
// goes to which track (that's not until the next step of the wizard).
|
||||
// so, we just auto-generate a valid assignment
|
||||
function newInputAssignment() {
|
||||
var assigned = 0;
|
||||
context._.each(musicPorts.inputs, function(inputChannel) {
|
||||
if(isChannelAssigned(inputChannel)) {
|
||||
assigned += 1;
|
||||
}
|
||||
});
|
||||
|
||||
var newAssignment = Math.floor(assigned / 2) + 1;
|
||||
return newAssignment;
|
||||
}
|
||||
|
||||
function inputChannelChanged() {
|
||||
if(iCheckIgnore) return;
|
||||
|
||||
var $checkbox = $(this);
|
||||
var channelId = $checkbox.attr('data-id');
|
||||
var isChecked = $checkbox.is(':checked');
|
||||
|
||||
if(isChecked) {
|
||||
var newAssignment = newInputAssignment();
|
||||
logger.debug("assigning input channel %o to track: %o", channelId, newAssignment);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, newAssignment);
|
||||
}
|
||||
else {
|
||||
logger.debug("unassigning input channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
// unassigning creates a hole in our auto-assigned tracks. reassign them all to keep it consistent
|
||||
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked');
|
||||
var assigned = 0;
|
||||
context._.each($assignedInputs, function(assignedInput) {
|
||||
var $assignedInput = $(assignedInput);
|
||||
var assignedChannelId = $assignedInput.attr('data-id');
|
||||
var newAssignment = Math.floor(assigned / 2) + 1;
|
||||
logger.debug("re-assigning input channel %o to track: %o", assignedChannelId, newAssignment);
|
||||
context.jamClient.TrackSetAssignment(assignedChannelId, true, newAssignment);
|
||||
assigned += 1;
|
||||
});
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
// should be called in a ifChanged callback if you want to cancel.
|
||||
// you have to use this instead of 'return false' like a typical input 'change' event.
|
||||
function cancelICheckChange($checkbox) {
|
||||
iCheckIgnore = true;
|
||||
var checked = $checkbox.is(':checked');
|
||||
setTimeout(function() {
|
||||
if(checked) $checkbox.iCheck('uncheck').removeAttr('checked');
|
||||
else $checkbox.iCheck('check').attr('checked', 'checked');
|
||||
iCheckIgnore = false;
|
||||
}, 1);
|
||||
}
|
||||
|
||||
function outputChannelChanged() {
|
||||
if(iCheckIgnore) return;
|
||||
var $checkbox = $(this);
|
||||
var channelId = $checkbox.attr('data-id');
|
||||
var isChecked = $checkbox.is(':checked');
|
||||
|
||||
// don't allow more than 2 output channels selected at once
|
||||
if($outputChannels.find('input[type="checkbox"]:checked').length > 2) {
|
||||
context.JK.Banner.showAlert('You can only have a maximum of 2 output ports selected.');
|
||||
// can't allow uncheck of last output
|
||||
cancelICheckChange($checkbox);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isChecked) {
|
||||
logger.debug("assigning output channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.OUTPUT);
|
||||
}
|
||||
else {
|
||||
logger.debug("unassigning output channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
|
||||
function isChannelAssigned(channel) {
|
||||
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
|
||||
}
|
||||
|
||||
function initializeInputPorts(musicPorts) {
|
||||
$inputChannels.empty();
|
||||
var inputPorts = musicPorts.inputs;
|
||||
context._.each(inputPorts, function(inputChannel) {
|
||||
var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' }));
|
||||
var $checkbox = $inputChannel.find('input');
|
||||
if(isChannelAssigned(inputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
$checkbox.on('ifChanged', inputChannelChanged);
|
||||
$inputChannels.append($inputChannel);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeOutputPorts(musicPorts) {
|
||||
$outputChannels.empty();
|
||||
var outputChannels = musicPorts.outputs;
|
||||
context._.each(outputChannels, function(outputChannel) {
|
||||
var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' }));
|
||||
var $checkbox = $outputPort.find('input');
|
||||
if(isChannelAssigned(outputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
$checkbox.on('ifChanged', outputChannelChanged);
|
||||
$outputChannels.append($outputPort);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeFormElements() {
|
||||
if (!deviceInformation) throw "devices are not initialized";
|
||||
|
||||
initializeAudioInput();
|
||||
initializeAudioOutput();
|
||||
initializeFramesize();
|
||||
initializeBuffers();
|
||||
}
|
||||
|
||||
function resetFrameBuffers() {
|
||||
$frameSize.val('2.5');
|
||||
$bufferIn.val('0');
|
||||
$bufferOut.val('0');
|
||||
}
|
||||
|
||||
function clearInputPorts() {
|
||||
$inputChannels.empty();
|
||||
}
|
||||
|
||||
function clearOutputPorts() {
|
||||
$outputChannels.empty();
|
||||
}
|
||||
|
||||
function resetScoreReport() {
|
||||
$ioRateScore.empty();
|
||||
$ioVarScore.empty();
|
||||
$latencyScore.empty();
|
||||
}
|
||||
|
||||
function renderLatencyScore(latencyValue, latencyClass) {
|
||||
if(latencyValue) {
|
||||
$latencyScore.text(latencyValue + ' ms');
|
||||
}
|
||||
else {
|
||||
$latencyScore.text('');
|
||||
}
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
$ioRateScore.text(median ? median : '');
|
||||
$ioVarScore.text(std ? std : '');
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip').addClass(ioClass);
|
||||
// TODO: show help bubble of all data in IO data
|
||||
}
|
||||
|
||||
function updateScoreReport(latencyResult) {
|
||||
var latencyClass = "neutral";
|
||||
var latencyValue = 'N/A';
|
||||
var validLatency = false;
|
||||
if (latencyResult && latencyResult.latencyknown) {
|
||||
var latencyValue = latencyResult.latency;
|
||||
latencyValue = Math.round(latencyValue * 100) / 100;
|
||||
if (latencyValue <= 10) {
|
||||
latencyClass = "good";
|
||||
validLatency = true;
|
||||
} else if (latencyValue <= 20) {
|
||||
latencyClass = "acceptable";
|
||||
validLatency = true;
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
}
|
||||
}
|
||||
else {
|
||||
latencyClass = 'unknown';
|
||||
}
|
||||
|
||||
validLatencyScore = validLatency;
|
||||
|
||||
renderLatencyScore(latencyValue, latencyClass);
|
||||
}
|
||||
|
||||
function audioInputDeviceUnselected() {
|
||||
validLatencyScore = false;
|
||||
initializeNextButtonState();
|
||||
resetFrameBuffers();
|
||||
clearInputPorts();
|
||||
}
|
||||
|
||||
function renderScoringStarted() {
|
||||
validLatencyScore = false;
|
||||
initializeNextButtonState();
|
||||
resetScoreReport();
|
||||
freezeAudioInteraction();
|
||||
renderLatencyScore(null, 'starting');
|
||||
}
|
||||
|
||||
function renderScoringStopped() {
|
||||
initializeNextButtonState();
|
||||
unfreezeAudioInteraction();
|
||||
}
|
||||
|
||||
|
||||
function freezeAudioInteraction() {
|
||||
$audioInput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$audioOutput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$frameSize.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferIn.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferOut.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$asioControlPanelBtn.on("click", false);
|
||||
$resyncBtn.on('click', false);
|
||||
iCheckIgnore = true;
|
||||
$inputChannels.find('input[type="checkbox"]').iCheck('disable');
|
||||
$outputChannels.find('input[type="checkbox"]').iCheck('disable');
|
||||
}
|
||||
|
||||
function unfreezeAudioInteraction() {
|
||||
$audioInput.removeAttr("disabled").easyDropDown('enable');
|
||||
$audioOutput.removeAttr("disabled").easyDropDown('enable');
|
||||
$frameSize.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferIn.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferOut.removeAttr("disabled").easyDropDown('enable');
|
||||
$asioControlPanelBtn.off("click", false);
|
||||
$resyncBtn.off('click', false);
|
||||
$inputChannels.find('input[type="checkbox"]').iCheck('enable');
|
||||
$outputChannels.find('input[type="checkbox"]').iCheck('enable');
|
||||
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() {
|
||||
$watchVideoInput.unbind('click').click(function () {
|
||||
|
||||
var audioDevice = findDevice(selectedAudioInput());
|
||||
if(!audioDevice) {
|
||||
if (!audioDevice) {
|
||||
context.JK.Banner.showAlert('You must first choose an Audio Input Device so that we can determine which video to show you.');
|
||||
}
|
||||
else {
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type];
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type].videoURL;
|
||||
|
||||
if(videoURL) {
|
||||
if (videoURL) {
|
||||
$(this).attr('href', videoURL);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -117,16 +555,16 @@
|
|||
return false;
|
||||
});
|
||||
|
||||
$watchVideoOutput.unbind('click').click(function() {
|
||||
$watchVideoOutput.unbind('click').click(function () {
|
||||
|
||||
var audioDevice = findDevice(selectedAudioOutput());
|
||||
if(!audioDevice) {
|
||||
if (!audioDevice) {
|
||||
throw "this button should be hidden";
|
||||
}
|
||||
else {
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type];
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type].videoURL;
|
||||
|
||||
if(videoURL) {
|
||||
if (videoURL) {
|
||||
$(this).attr('href', videoURL);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -139,106 +577,131 @@
|
|||
});
|
||||
}
|
||||
|
||||
function initializeAudioInputChanged() {
|
||||
$audioInput.unbind('change').change(function(evt) {
|
||||
|
||||
var audioDeviceId = selectedAudioInput();
|
||||
|
||||
if(!audioDeviceId) {
|
||||
audioDeviceUnselected();
|
||||
return false;
|
||||
}
|
||||
|
||||
var audioDevice = findDevice(selectedAudioInput());
|
||||
|
||||
if(!audioDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find device information for: ' + audioDevice);
|
||||
}
|
||||
|
||||
releaseDropdown(function () {
|
||||
renderStartNewFtueLatencyTesting();
|
||||
var $select = $(evt.currentTarget);
|
||||
|
||||
var $audioSelect = $('.ftue-new .settings-2-device select');
|
||||
var audioDriverId = $audioSelect.val();
|
||||
|
||||
if (!audioDriverId) {
|
||||
context.JK.alertSupportNeeded();
|
||||
|
||||
renderStopNewFtueLatencyTesting();
|
||||
// reset back to 'Choose...'
|
||||
newFtueEnableControls(false);
|
||||
return;
|
||||
}
|
||||
jamClient.FTUESetMusicDevice(audioDriverId);
|
||||
|
||||
var musicInputs = jamClient.FTUEGetMusicInputs();
|
||||
var musicOutputs = jamClient.FTUEGetMusicOutputs();
|
||||
|
||||
// set the music input to the first available input,
|
||||
// and output to the first available output
|
||||
var kin = null, kout = null, k = null;
|
||||
// TODO FIXME - this jamClient call returns a dictionary.
|
||||
// It's difficult to know what to auto-choose.
|
||||
// For example, with my built-in audio, the keys I get back are
|
||||
// digital in, line in, mic in and stereo mix. Which should we pick for them?
|
||||
for (k in musicInputs) {
|
||||
kin = k;
|
||||
break;
|
||||
}
|
||||
for (k in musicOutputs) {
|
||||
kout = k;
|
||||
break;
|
||||
}
|
||||
var result;
|
||||
if (kin && kout) {
|
||||
jamClient.FTUESetMusicInput(kin);
|
||||
jamClient.FTUESetMusicOutput(kout);
|
||||
} else {
|
||||
// TODO FIXME - how to handle a driver selection where we are unable to
|
||||
// autoset both inputs and outputs? (I'd think this could happen if either
|
||||
// the input or output side returned no values)
|
||||
renderStopNewFtueLatencyTesting();
|
||||
console.log("invalid kin/kout %o/%o", kin, kout);
|
||||
return;
|
||||
}
|
||||
|
||||
newFtueUpdateLatencyView('loading');
|
||||
|
||||
newFtueEnableControls(true);
|
||||
newFtueOsSpecificSettings();
|
||||
// make sure whatever the user sees in the frontend is what the backend thinks
|
||||
// this is necesasry because if you do a FTUE pass, close the client, and pick the same device
|
||||
// the backend will *silently* use values from before, because the frontend does not query the backend
|
||||
// for these values anywhere.
|
||||
|
||||
// setting all 3 of these cause 3 FTUE's. Instead, we set batchModify to true so that they don't call FtueSave(false), and call later ourselves
|
||||
batchModify = true;
|
||||
newFtueAsioFrameSizeToBackend($('#ftue-2-asio-framesize'));
|
||||
newFtueAsioInputLatencyToBackend($('#ftue-2-asio-input-latency'));
|
||||
newFtueAsioOutputLatencyToBackend($('#ftue-2-asio-output-latency'));
|
||||
batchModify = false;
|
||||
|
||||
//setLevels(0);
|
||||
renderVolumes();
|
||||
|
||||
logger.debug("Calling FTUESave(" + false + ")");
|
||||
jamClient.FTUESave(false)
|
||||
pendingFtueSave = false; // this is not really used in any real fashion. just setting back to false due to batch modify above
|
||||
|
||||
setVuCallbacks();
|
||||
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
console.log("FTUEGetExpectedLatency: %o", latency);
|
||||
newFtueUpdateLatencyView(latency);
|
||||
});
|
||||
|
||||
})
|
||||
function renderIOScoringStarted(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
$ioCountdown.show();
|
||||
}
|
||||
|
||||
function renderIOScoringStopped() {
|
||||
$ioCountdown.hide();
|
||||
}
|
||||
|
||||
function renderIOCountdown(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
}
|
||||
|
||||
function attemptScore() {
|
||||
var audioInputDeviceId = selectedAudioInput();
|
||||
var audioOutputDeviceId = selectedAudioOutput();
|
||||
if (!audioInputDeviceId) {
|
||||
audioInputDeviceUnselected();
|
||||
return false;
|
||||
}
|
||||
|
||||
var audioInputDevice = findDevice(audioInputDeviceId);
|
||||
if (!audioInputDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find information for input device: ' + audioInputDeviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!audioOutputDeviceId) {
|
||||
audioOutputDeviceId = audioInputDeviceId;
|
||||
}
|
||||
var audioOutputDevice = findDevice(audioOutputDeviceId);
|
||||
if (!audioInputDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find information for output device: ' + audioOutputDeviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
jamClient.FTUESetInputMusicDevice(audioInputDeviceId);
|
||||
jamClient.FTUESetOutputMusicDevice(audioOutputDeviceId);
|
||||
|
||||
initializeChannels();
|
||||
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
|
||||
renderScoringStarted();
|
||||
logger.debug("Calling FTUESave(false)");
|
||||
jamClient.FTUESave(false);
|
||||
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
console.log("FTUEGetExpectedLatency: %o", latency);
|
||||
|
||||
updateScoreReport(latency);
|
||||
|
||||
// if there was a valid latency score, go on to the next step
|
||||
if(validLatencyScore) {
|
||||
renderIOScore(null, null, null, '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;
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
if(testTimeSeconds == 0) {
|
||||
clearInterval(interval);
|
||||
renderIOScoringStopped();
|
||||
var io = context.jamClient.FTUEGetIoPerfData();
|
||||
|
||||
console.log("io: ", 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';
|
||||
}
|
||||
|
||||
// now base the overall IO score based on both values.
|
||||
renderIOScore(std, median, io, ioClass);
|
||||
|
||||
// lie for now until IO questions finalize
|
||||
validIOScore = true;
|
||||
|
||||
renderScoringStopped();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
else {
|
||||
renderIOScore(null, null, null, 'skip');
|
||||
renderScoringStopped();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initializeAudioInputChanged() {
|
||||
$audioInput.unbind('change').change(attemptScore);
|
||||
}
|
||||
|
||||
function initializeAudioOutputChanged() {
|
||||
|
||||
}
|
||||
|
||||
function initializeStep() {
|
||||
loadDevices();
|
||||
initializeFormElements();
|
||||
initializeNextButtonState();
|
||||
initializeWatchVideo();
|
||||
initializeAudioInputChanged();
|
||||
}
|
||||
|
||||
initializeStep();
|
||||
|
|
@ -291,7 +754,9 @@
|
|||
function beforeShowStep($step) {
|
||||
var stepInfo = STEPS[step];
|
||||
|
||||
if(!stepInfo) {throw "unknown step: " + step;}
|
||||
if (!stepInfo) {
|
||||
throw "unknown step: " + step;
|
||||
}
|
||||
|
||||
stepInfo.beforeShow.call(self);
|
||||
}
|
||||
|
|
@ -304,7 +769,7 @@
|
|||
$currentWizardStep = $nextWizardStep;
|
||||
|
||||
var $ftueSteps = $(context._.template($templateSteps.html(), {}, { variable: 'data' }));
|
||||
var $activeStep = $ftueSteps.find('.ftue-stepnumber[data-step-number="'+ step +'"]');
|
||||
var $activeStep = $ftueSteps.find('.ftue-stepnumber[data-step-number="' + step + '"]');
|
||||
$activeStep.addClass('.active');
|
||||
$activeStep.next().show(); // show the .ftue-step-title
|
||||
$currentWizardStep.find('.ftuesteps').replaceWith($ftueSteps);
|
||||
|
|
@ -321,11 +786,11 @@
|
|||
var $btnCancel = $ftueButtonsContent.find('.btn-cancel');
|
||||
|
||||
// hide back button if 1st step or last step
|
||||
if(step == 0 && step == TOTAL_STEPS - 1) {
|
||||
if (step == 0 && step == TOTAL_STEPS - 1) {
|
||||
$btnBack.hide();
|
||||
}
|
||||
// hide next button if not on last step
|
||||
if (step == TOTAL_STEPS - 1 ) {
|
||||
if (step == TOTAL_STEPS - 1) {
|
||||
$btnNext.hide();
|
||||
}
|
||||
// hide close if on last step
|
||||
|
|
@ -350,9 +815,33 @@
|
|||
$currentWizardStep = null;
|
||||
}
|
||||
|
||||
// checks if we already have a profile called 'FTUE...'; if not, create one. if so, re-use it.
|
||||
function findOrCreateFTUEProfile() {
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
logger.debug("current profile name: " + profileName);
|
||||
|
||||
if(profileName && profileName.indexOf('FTUE') == 0) {
|
||||
|
||||
}
|
||||
else {
|
||||
var newProfileName = 'FTUEAttempt-' + new Date().getTime().toString();
|
||||
logger.debug("setting FTUE-prefixed profile name to: " + newProfileName);
|
||||
context.jamClient.FTUESetMusicProfileName(newProfileName);
|
||||
}
|
||||
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
logger.debug("name on exit: " + profileName);
|
||||
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
context.jamClient.FTUECancel();
|
||||
findOrCreateFTUEProfile();
|
||||
|
||||
step = args.d1;
|
||||
if(!step) step = 0;
|
||||
if (!step) step = 0;
|
||||
step = parseInt(step);
|
||||
moveToStep();
|
||||
}
|
||||
|
|
@ -362,18 +851,18 @@
|
|||
}
|
||||
|
||||
function afterHide() {
|
||||
|
||||
context.jamClient.FTUECancel();
|
||||
}
|
||||
|
||||
function back() {
|
||||
if($(this).is('.button-grey')) return;
|
||||
if ($(this).is('.button-grey')) return;
|
||||
step = step - 1;
|
||||
moveToStep();
|
||||
return false;
|
||||
}
|
||||
|
||||
function next() {
|
||||
if($(this).is('.button-grey')) return;
|
||||
if ($(this).is('.button-grey')) return;
|
||||
|
||||
step = step + 1;
|
||||
|
||||
|
|
@ -392,6 +881,7 @@
|
|||
function route() {
|
||||
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
var dialogBindings = { beforeShow: beforeShow, afterShow: afterShow, afterHide: afterHide };
|
||||
|
|
@ -402,6 +892,7 @@
|
|||
$wizardSteps = $dialog.find('.wizard-step');
|
||||
$templateSteps = $('#template-ftuesteps');
|
||||
$templateButtons = $('#template-ftue-buttons');
|
||||
$templateAudioPort = $('#template-audio-port');
|
||||
$ftueButtons = $dialog.find('.ftue-buttons');
|
||||
|
||||
operatingSystem = context.jamClient.GetOSAsString();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,20 @@
|
|||
UNIX: "Unix"
|
||||
};
|
||||
|
||||
// TODO: store these client_id values in instruments table, or store
|
||||
context.JK.ASSIGNMENT = {
|
||||
CHAT: -2,
|
||||
OUTPUT: -1,
|
||||
UNASSIGNED: 0,
|
||||
TRACK1: 1,
|
||||
TRACK2: 2
|
||||
};
|
||||
|
||||
context.JK.VOICE_CHAT = {
|
||||
NO_CHAT: "0",
|
||||
CHAT: "1"
|
||||
};
|
||||
|
||||
// TODO: store these client_id values in instruments table, or store
|
||||
// server_id as the client_id to prevent maintenance nightmares. As it's
|
||||
// set up now, we will have to deploy each time we add new instruments.
|
||||
context.JK.server_to_client_instrument_map = {
|
||||
|
|
|
|||
|
|
@ -184,8 +184,63 @@
|
|||
width:45%;
|
||||
}
|
||||
|
||||
|
||||
.buffers {
|
||||
.easydropdown-wrapper:nth-of-type(1) {
|
||||
left:5px;
|
||||
}
|
||||
.easydropdown-wrapper:nth-of-type(2) {
|
||||
left:35px;
|
||||
}
|
||||
.easydropdown, .easydropdown-wrapper {
|
||||
width:15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ftue-box.results {
|
||||
height: 230px !important;
|
||||
padding:0;
|
||||
|
||||
.scoring-section {
|
||||
font-size:15px;
|
||||
@include border_box_sizing;
|
||||
height:64px;
|
||||
|
||||
&.good {
|
||||
background-color:#72a43b;
|
||||
}
|
||||
&.acceptable {
|
||||
background-color:#cc9900;
|
||||
}
|
||||
&.bad, &.skip {
|
||||
background-color:#660000;
|
||||
}
|
||||
&.unknown {
|
||||
background-color:#999;
|
||||
}
|
||||
}
|
||||
|
||||
.io-countdown {
|
||||
display:none;
|
||||
padding-left:19px;
|
||||
position:relative;
|
||||
|
||||
.secs {
|
||||
position:absolute;
|
||||
width:19px;
|
||||
left:0;
|
||||
}
|
||||
}
|
||||
|
||||
.io-skip-msg {
|
||||
display:none;
|
||||
|
||||
.scoring-section.skip & {
|
||||
display:inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-output {
|
||||
|
|
@ -510,8 +565,8 @@
|
|||
.subcolumn.third {
|
||||
right:0px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.settings-controls {
|
||||
|
||||
clear:both;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
%select.w100.select-audio-input-device
|
||||
%option None
|
||||
%h2 Audio Input Ports
|
||||
.ftue-box.list.ports.output-ports
|
||||
.ftue-box.list.ports.input-ports
|
||||
%a.button-orange.asio-settings-btn ASIO SETTINGS...
|
||||
%a.button-orange.resync-btn RESYNC
|
||||
.wizard-step-column
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
%select.w100.select-audio-output-device
|
||||
%option Same as input
|
||||
%h2 Audio Output Ports
|
||||
.ftue-box.list.ports.input-ports
|
||||
.ftue-box.list.ports.output-ports
|
||||
.frame-and-buffers
|
||||
.framesize
|
||||
%h2 Frame
|
||||
|
|
@ -83,6 +83,20 @@
|
|||
.wizard-step-column
|
||||
%h2 Test Results
|
||||
.ftue-box.results
|
||||
.left.w50.center.white.scoring-section.latency-score-section
|
||||
.p5
|
||||
.latency LATENCY
|
||||
%span.latency-score
|
||||
.left.w50.center.white.scoring-section.io-score-section
|
||||
.p5
|
||||
.io I/O
|
||||
%span.io-skip-msg
|
||||
Skipped
|
||||
%span.io-countdown
|
||||
%span.secs
|
||||
seconds left
|
||||
%span.io-rate-score
|
||||
%span.io-var-score
|
||||
|
||||
.clearall
|
||||
|
||||
|
|
@ -195,5 +209,11 @@
|
|||
%a.button-orange.btn-next{href:'#'} NEXT
|
||||
%a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE
|
||||
|
||||
%script{type: 'text/template', id: 'template-audio-port'}
|
||||
.audio-port
|
||||
%input{ type: 'checkbox', 'data-id' => '{{data.id}}' }
|
||||
%span
|
||||
= '{{data.name}}'
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -285,10 +285,12 @@
|
|||
|
||||
// Check, disable or indeterminate
|
||||
if (/^(ch|di|in)/.test(method) && !active) {
|
||||
console.log("TAKING ROUTE: ", state);
|
||||
on(input, state);
|
||||
|
||||
// Uncheck, enable or determinate
|
||||
} else if (/^(un|en|de)/.test(method) && active) {
|
||||
console.log("TAKING ROUTE2: ", state);
|
||||
off(input, state);
|
||||
|
||||
// Update
|
||||
|
|
@ -322,7 +324,7 @@
|
|||
};
|
||||
|
||||
// Add checked, disabled or indeterminate state
|
||||
function on(input, state, keep) {
|
||||
function on(input, state, keep) {
|
||||
var node = input[0],
|
||||
parent = input.parent(),
|
||||
checked = state == _checked,
|
||||
|
|
|
|||
|
|
@ -65,20 +65,12 @@ module JamWebsockets
|
|||
@client_lookup[client_id] = client_context
|
||||
end
|
||||
|
||||
def remove_client(client_id, client)
|
||||
def remove_client(client_id)
|
||||
deleted = @client_lookup.delete(client_id)
|
||||
|
||||
if deleted.nil?
|
||||
@log.warn "unable to delete #{client_id} from client_lookup"
|
||||
elsif deleted.client != client
|
||||
# put it back--this is only possible if add_client hit the 'old connection' path
|
||||
# so in other words if this happens:
|
||||
# add_client(1, clientX)
|
||||
# add_client(1, clientY) # but clientX is essentially defunct - this could happen due to a bug in client, or EM doesn't notify always of connection close in time
|
||||
# remove_client(1, clientX) -- this check maintains that clientY stays as the current client in the hash
|
||||
@client_lookup[client_id] = deleted
|
||||
@log.debug "putting back client into @client_lookup for #{client_id} #{client.inspect}"
|
||||
else
|
||||
@log.warn "unable to delete #{client_id} from client_lookup because it's already gone"
|
||||
else
|
||||
@log.debug "cleaned up @client_lookup for #{client_id}"
|
||||
end
|
||||
|
||||
|
|
@ -377,7 +369,7 @@ module JamWebsockets
|
|||
|
||||
@log.debug "cleanup up logged-in client #{client}"
|
||||
|
||||
remove_client(client.client_id, client)
|
||||
remove_client(client.client_id)
|
||||
|
||||
context = @clients.delete(client)
|
||||
|
||||
|
|
@ -462,6 +454,13 @@ module JamWebsockets
|
|||
|
||||
user = valid_login(username, password, token, client_id)
|
||||
|
||||
# kill any websocket connections that have this same client_id, which can happen in race conditions
|
||||
existing_client = @client_lookup[client_id]
|
||||
if existing_client
|
||||
remove_client(client_id)
|
||||
existing_client.client.close_websocket
|
||||
end
|
||||
|
||||
connection = JamRuby::Connection.find_by_client_id(client_id)
|
||||
# if this connection is reused by a different user, then whack the connection
|
||||
# because it will recreate a new connection lower down
|
||||
|
|
|
|||
Loading…
Reference in New Issue