diff --git a/admin/Gemfile b/admin/Gemfile index 3ec3ed16b..750fb4e6d 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -71,6 +71,7 @@ gem 'rest-client' gem 'iso-639' gem 'rubyzip' gem 'sanitize' +gem 'slim' group :libv8 do gem 'libv8', "~> 3.11.8" diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index b6afa5e35..3e8c1dd6b 100644 --- a/admin/app/admin/jam_ruby_users.rb +++ b/admin/app/admin/jam_ruby_users.rb @@ -14,27 +14,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do filter :updated_at - form do |ff| - ff.inputs "Details" do - ff.input :email - ff.input :admin - ff.input :raw_password, :label => 'Password' - ff.input :first_name - ff.input :last_name - ff.input :city - ff.input :state - ff.input :country - ff.input :musician - ff.input :can_invite - ff.input :photo_url - ff.input :session_settings - end - ff.inputs "Signup" do - ff.input :email_template, :label => 'Welcome Email Template Name' - ff.input :confirm_url, :label => 'Signup Confirm URL' - end - ff.actions - end + form :partial => "form" show do |user| attributes_table do @@ -53,8 +33,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do row :gender row :email_confirmed row :image do user.photo_url ? image_tag(user.photo_url) : '' end - row :session_settings - row :can_invite end active_admin_comments end @@ -114,6 +92,30 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do # call `create!` to ensure that the rest of the action continues as normal create! end + + def update + + @user = resource + @user.email = params[:jam_ruby_user][:email] + @user.admin = params[:jam_ruby_user][:admin] + @user.musician = params[:jam_ruby_user][:musician] + @user.first_name = params[:jam_ruby_user][:first_name] + @user.last_name = params[:jam_ruby_user][:last_name] + @user.state = params[:jam_ruby_user][:state] + @user.city = params[:jam_ruby_user][:city] + + + if params[:jam_ruby_user][:show_frame_options].to_i == 1 + @user.mod_merge({User::MOD_GEAR => {User::MOD_GEAR_FRAME_OPTIONS => true}}) + else + @user.delete_mod(User::MOD_GEAR, User::MOD_GEAR_FRAME_OPTIONS) + end + + @user.save! + + redirect_to edit_admin_user_path(@user) + + end end end diff --git a/admin/app/views/admin/users/_form.html.slim b/admin/app/views/admin/users/_form.html.slim new file mode 100644 index 000000000..204d9a4da --- /dev/null +++ b/admin/app/views/admin/users/_form.html.slim @@ -0,0 +1,12 @@ += semantic_form_for([:admin_users, resource], :url => resource.new_record? ? admin_users_path : "#{ENV['RAILS_RELATIVE_URL_ROOT']}/admin/users/#{resource.id}") do |f| + = f.inputs "Details" do + = f.input :email, label: 'Email' + = f.input :admin + = f.input :first_name + = f.input :last_name + = f.input :city + = f.input :state + = f.input :musician + = f.inputs "Gear Mods" do + = f.input :show_frame_options, as: :boolean + = f.actions diff --git a/admin/config/initializers/jam_ruby_user.rb b/admin/config/initializers/jam_ruby_user.rb index 980c82b9b..6d7dca153 100644 --- a/admin/config/initializers/jam_ruby_user.rb +++ b/admin/config/initializers/jam_ruby_user.rb @@ -1,11 +1,5 @@ class JamRuby::User - EMAIL_TMPL_WELCOME = 'welcome_message' - EMAIL_TMPL_WELCOME_BETA = 'welcome_betauser' - EMAIL_TMPL_WELCOMES = [EMAIL_TMPL_WELCOME_BETA, EMAIL_TMPL_WELCOME] - - CONFIRM_URL = "http://www.jamkazam.com/confirm" # we can't get request.host_with_port, so hard-code confirm url (with user override) - attr_accessible :admin, :raw_password, :musician, :can_invite, :photo_url, :session_settings, :confirm_url, :email_template # :invite_email def raw_password @@ -25,50 +19,12 @@ end end - def confirm_url - @signup_confirm_url ||= CONFIRM_URL - end - - def confirm_url= url - @signup_confirm_url = url - end - - def email_template - @signup_email_template ||= EMAIL_TMPL_WELCOME_BETA - end - - def email_template= tmpl - @signup_email_template = tmpl - end def admin_user User.where(:email => self.email)[0] end - after_create do - self.update_attribute(:signup_token, SecureRandom.urlsafe_base64) - url = confirm_url - url.chomp!('/') - - # only supporting two welcome templates ATM - if EMAIL_TMPL_WELCOMES.index(self.email_template).nil? - self.email_template = EMAIL_TMPL_WELCOME - end - # UserMailer.send(self.email_template, self, "#{url}/#{self.signup_token}").deliver + def show_frame_options + self.get_gear_mod(MOD_GEAR_FRAME_OPTIONS) end - - after_save do - logger.debug("*** after_save: #{self.admin_changed?}") - if self.admin_changed? - if self.admin - if self.admin_user.nil? - au = User.create(:email => self.email) - au.update_attribute(:encrypted_password, self.password_digest) - end - else - self.admin_user.try(:destroy) - end - end - end - end diff --git a/ruby/lib/jam_ruby/constants/validation_messages.rb b/ruby/lib/jam_ruby/constants/validation_messages.rb index ea914a4c9..30544bc4a 100644 --- a/ruby/lib/jam_ruby/constants/validation_messages.rb +++ b/ruby/lib/jam_ruby/constants/validation_messages.rb @@ -80,7 +80,7 @@ module ValidationMessages DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender' # mods - MODS_NO_SHOW_MUST_BE_HASH = 'no_show must be a hash' + MODS_MUST_BE_HASH = 'must be a hash' MODS_UNKNOWN_KEY = 'unknown mod' # takes either a string/string hash, or a string/array-of-strings|symbols hash, diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index af931a0ac..6e9e6fad0 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -19,6 +19,12 @@ module JamRuby JAM_REASON_IMPORT = 'i' JAM_REASON_LOGIN = 'l' + # MOD KEYS + MOD_GEAR = "gear" + MOD_GEAR_FRAME_OPTIONS = "show_frame_options" + + MOD_NO_SHOW = "no_show" + devise :database_authenticatable, :recoverable, :rememberable acts_as_mappable @@ -189,8 +195,8 @@ module JamRuby # let's work to stop junk from getting into the mods array; this is essentially the schema def validate_mods mods_json.each do |key, value| - if key == "no_show" - errors.add(:mods, ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH) unless value.is_a?(Hash) + if key == MOD_NO_SHOW || key == MOD_GEAR + errors.add(:mods, ValidationMessages::MODS_MUST_BE_HASH) unless value.is_a?(Hash) else errors.add(:mods, ValidationMessages::MODS_UNKNOWN_KEY) end @@ -373,8 +379,8 @@ module JamRuby # new_modes should be a regular hash with non-symbolized keys (vs symbolized keys) def mod_merge(new_mods) self.mods = (mods_json.merge(new_mods) do |key, old_val, new_val| - if key == "no_show" - # we take the values from previous no_shows, and merge it with the new no_shows + if key == MOD_NO_SHOW || key == MOD_GEAR + # we take the values from previous hash, and merge it with the new hash old_val.merge(new_val) else raise "unknown in mode_merge key: #{key}" @@ -383,6 +389,36 @@ module JamRuby @mods_json = nil # invalidate this since we've updated self.mods end + # any mod with the value 'null' will be deleted + def delete_mod(root_key, sub_key) + mod = mods_json + root = mod[root_key] + if root + root.delete(sub_key) + # check if root key is completely empty + mod.delete(root_key) if root.length == 0 + # check if mod key is empty + mod = nil if mod.length == 0 + end + + self.mods = mod.nil? ? nil : mod.to_json + @mods_json = nil # invalidate this since we've updated self.mods + end + + def get_mod(root_key, sub_key) + mod = mods_json + root = mod[root_key] + root[sub_key] if root + end + + def get_gear_mod(sub_key) + get_mod(MOD_GEAR, sub_key) + end + + def get_no_show_mod(sub_key) + get_mod(MOD_NO_SHOW, sub_key) + end + def heartbeat_interval_client mods_json[:heartbeat_interval_client] end diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 36425e495..54797df8c 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -608,7 +608,7 @@ describe User do it "does not allow non-hash no_show" do user.mod_merge({no_show:true}) user.valid?.should be_false - user.errors[:mods].should == [ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH] + user.errors[:mods].should == [ValidationMessages::MODS_MUST_BE_HASH] end end diff --git a/web/app/assets/javascripts/dialog/adjustGearSpeedDialog.js b/web/app/assets/javascripts/dialog/adjustGearSpeedDialog.js new file mode 100644 index 000000000..1172ae500 --- /dev/null +++ b/web/app/assets/javascripts/dialog/adjustGearSpeedDialog.js @@ -0,0 +1,274 @@ +(function (context, $) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.AdjustGearSpeedDialog = function (app) { + var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; + var gearUtils = context.JK.GearUtils; + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var $dialog = null; + var modUtils = context.JK.ModUtils; + + var $runTestBtn = null; + var $scoreReport = null; + var $speedOptions = null; + var $saveBtn = null; + var $cancelBtn = null; + var $fairLabel = null; + var $slowLabel = null; + var $fastLabel = null; + + var startingFramesize = null; + var startingBufferIn = null; + var startingBufferOut = null; + + var frameBuffers = new context.JK.FrameBuffers(app); + var gearTest = new context.JK.GearTest(app); + + var selectedDeviceInfo; + var deviceInformation; + var operatingSystem; + var frameBuffers; + var $frameBuffers; + var gearTest; + + var $advanced; + + function attemptScore() { + gearTest.attemptScore(selectedDeviceInfo); + } + + function invalidateScore() { + gearTest.invalidateScore(); + } + + function getGearTest() { + return gearTest; + } + + function updateDefaultBuffers() { + gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers) + } + + function onFramesizeChanged() { + //context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + updateDefaultBuffers(); + context.jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + invalidateScore(); + } + + function onBufferInChanged() { + //context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + invalidateScore(); + } + + function onBufferOutChanged() { + //context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + invalidateScore(); + } + + function freezeAudioInteraction() { + logger.debug("adjustGearSpeed: freezing audio interaction"); + frameBuffers.disable(); + $speedOptions.iCheck('disable') + $cancelBtn.on('click', false).addClass('disabled'); + $runTestBtn.on('click', false).addClass('disabled'); + } + + function unfreezeAudioInteraction() { + logger.debug("adjustGearSpeed: unfreezing audio interaction"); + frameBuffers.enable(); + $speedOptions.iCheck('enable') + $cancelBtn.off('click', false).removeClass('disabled') + $runTestBtn.off('click', false).removeClass('disabled') + } + + + function updateDefaultLabel() { + if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { + + } + else { + } + } + + function translateFrameSizeToSpeed(framesize) { + if(framesize == 2.5) { + return 'fast' + } + else if(framesize == 5) { + return 'fair' + } + else if (framesize == 10) { + return "slow" + } + else { + throw "unknown framesize in translateFrameSizeToSpeed: " + framesize + } + } + + function beforeShow() { + selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice()); + deviceInformation = gearUtils.loadDeviceInfo(); + startingFramesize = context.jamClient.FTUEGetFrameSize(); + startingBufferIn = context.jamClient.FTUEGetInputLatency(); + startingBufferOut = context.jamClient.FTUEGetOutputLatency(); + var startingSpeed = translateFrameSizeToSpeed(startingFramesize) + logger.debug("speed upon entry: " + startingSpeed) + $speedOptions.filter('[value=' + startingSpeed + ']').iCheck('check') + setBuffers(startingSpeed); + updateDefaultLabel(); + invalidateScore(); + $saveBtn.on('click', false).addClass('disabled'); + app.user().done(function() { + if(modUtils.getGear('show_frame_options')) { + $advanced.show(); + } + }) + } + + function beforeHide() { + } + + function onCancel() { + var scoring = gearTest.isScoring(); + + if(!scoring) { + logger.debug("resetting framesize/buffers on cancel of adjust-gear-speed") + // make sure the frame/buffer values are the same as when entered + context.jamClient.FTUESetFrameSize(startingFramesize); + context.jamClient.FTUESetInputLatency(startingBufferIn); + context.jamClient.FTUESetOutputLatency(startingBufferOut); + } + + return !scoring; + } + + function onSave() { + + if($(this).is('.disabled')) { + logger.debug("cancelling save because not allowed yet") + return; + } + + context.jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + + app.layout.closeDialog('adjust-gear-speed-dialog') + + return false; + } + + function onGearTestStarted(e, data) { + renderScoringStarted(); + } + + function onGearTestDone(e, data) { + renderScoringStopped(); + $saveBtn.off('click', false).removeClass('disabled'); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function onGearTestFail(e, data) { + renderScoringStopped(); + $saveBtn.on('click', false).addClass('disabled'); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function renderScoringStarted() { + freezeAudioInteraction(); + } + + function renderScoringStopped() { + unfreezeAudioInteraction(); + } + + function setBuffers(speed) { + var newFrameSize = null; + if(speed == 'fast') { + newFrameSize = 2.5 + } + else if (speed == 'fair') { + newFrameSize = 5 + } + else if (speed == 'slow') { + newFrameSize = 10 + } + else { + throw "unknown speed setting: " + speed; + } + + frameBuffers.setFramesize(newFrameSize); + jamClient.FTUESetFrameSize(newFrameSize); + updateDefaultBuffers(); + } + + function onSpeedChanged() { + + setTimeout(function() { + var speed = $dialog.find('.speed-option .iradio_minimal.checked input').val() + + setBuffers(speed); + + attemptScore(); + }, 1) + + } + + function initialize() { + var dialogBindings = { + 'beforeShow': beforeShow, + 'beforeHide': beforeHide, + 'onCancel' : onCancel + }; + + app.bindDialog('adjust-gear-speed-dialog', dialogBindings); + + $dialog = $('#adjust-gear-speed-dialog'); + + $runTestBtn = $dialog.find('.run-test-btn'); + $scoreReport = $dialog.find('.results'); + $saveBtn = $dialog.find('.btnSave') + $cancelBtn = $dialog.find('.btnCancel') + $slowLabel = $dialog.find('label[for="adjust-gear-speed-slow"]') + $fairLabel = $dialog.find('label[for="adjust-gear-speed-fair"]') + $fastLabel = $dialog.find('label[for="adjust-gear-speed-fast"]') + + operatingSystem = context.JK.GetOSAsString(); + + $frameBuffers = $dialog.find('.frame-and-buffers'); + frameBuffers.initialize($frameBuffers); + $(frameBuffers) + .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged) + .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged) + .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged) + + + gearTest.initialize($scoreReport, true) + $(gearTest) + .on(gearTest.GEAR_TEST_START, onGearTestStarted) + .on(gearTest.GEAR_TEST_DONE, onGearTestDone) + .on(gearTest.GEAR_TEST_FAIL, onGearTestFail) + + $runTestBtn.click(attemptScore); + + $dialog.data('result', gearTest); // so that others can peek into gear test data after dialog is closed + + $advanced = $dialog.find('.advanced'); + $speedOptions = $dialog.find('.speed-option input'); + context.JK.checkbox($speedOptions).on('ifClicked', onSpeedChanged) + + $saveBtn.click(onSave) + }; + + + this.getGearTest = getGearTest; + this.initialize = initialize; + } + + return this; +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index d8fb6e4aa..ba3d4e25b 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -22,7 +22,8 @@ var rest = context.JK.Rest(); var inBadState = false; var userDeferred = null; - + var userData = null; + var self = this; var opts = { inClient: true, // specify false if you want the app object but none of the client-oriented features @@ -301,6 +302,7 @@ logger.debug("updating user info") userDeferred = update; // update the global user object if this succeeded }) + update.done(this.updateUserCache) return update; } @@ -309,6 +311,15 @@ return userDeferred; } + // gets the most recent user data. can be null when app is still initializing. + // user app.user() if initialize sequence is unknown/asynchronous + this.currentUser = function() { + if(userData == null) { + throw "currentUser has null user data" + } + return userData; + } + this.activeElementEvent = function(evtName, data) { return this.layout.activeElementEvent(evtName, data); } @@ -333,6 +344,10 @@ } }; + this.updateUserCache = function(_userData) { + userData = _userData + } + this.initialize = function (inOpts) { var url, hash; app = this; @@ -343,6 +358,7 @@ this.layout.handleDialogState(); userDeferred = rest.getUserDetail(); + userDeferred.done(this.updateUserCache) if (opts.inClient) { registerBadStateRecovered(); diff --git a/web/app/assets/javascripts/mods_utils.js.coffee b/web/app/assets/javascripts/mods_utils.js.coffee index b10250c32..bb06f4605 100644 --- a/web/app/assets/javascripts/mods_utils.js.coffee +++ b/web/app/assets/javascripts/mods_utils.js.coffee @@ -12,6 +12,7 @@ class ModUtils init: () => + # creates a new show structure suitable for applying to a user update noShow: (noShowName) => noShowValue = {} @@ -31,5 +32,11 @@ class ModUtils deferred.resolve(shouldShowForName) )) return deferred; + + # returns a gear mod by name + getGear: (name) => + gear = context.JK.app.currentUser().mods?.gear + if gear? then gear[name] else undefined + # global instance context.JK.ModUtils = new ModUtils() \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/frame_buffers.js b/web/app/assets/javascripts/wizard/frame_buffers.js index f96dde085..a19fdfdf7 100644 --- a/web/app/assets/javascripts/wizard/frame_buffers.js +++ b/web/app/assets/javascripts/wizard/frame_buffers.js @@ -9,11 +9,14 @@ var $bufferIn = null; var $bufferOut = null; var $frameSize = null; + var $adjustSettingsLink = null; var $self = $(this); + var logger = context.JK.logger; var FRAMESIZE_CHANGED = 'frame_buffers.framesize_changed'; var BUFFER_IN_CHANGED = 'frame_buffers.buffer_in_changed'; var BUFFER_OUT_CHANGED = 'frame_buffers.buffer_out_changed'; + var ADJUST_GEAR_LINK_CLICKED = 'frame_buffers.adjust_gear_settings_clicked'; function selectedFramesize() { return parseFloat($frameSize.val()); @@ -77,6 +80,12 @@ logger.debug("buffer-out changed: " + selectedBufferOut()); $self.triggerHandler(BUFFER_OUT_CHANGED, {value: selectedBufferOut()}); }); + + $adjustSettingsLink.click(function() { + logger.debug("adjust-gear-settings clicked"); + $self.triggerHandler(ADJUST_GEAR_LINK_CLICKED); + return false; + }); } function initialize(_$knobs) { @@ -89,6 +98,7 @@ $bufferIn = $knobs.find('.select-buffer-in'); $bufferOut = $knobs.find('.select-buffer-out'); $frameSize = $knobs.find('.select-frame-size'); + $adjustSettingsLink = $knobs.find('.adjust-gear-settings') events(); render(); @@ -97,6 +107,7 @@ this.FRAMESIZE_CHANGED = FRAMESIZE_CHANGED; this.BUFFER_IN_CHANGED = BUFFER_IN_CHANGED; this.BUFFER_OUT_CHANGED = BUFFER_OUT_CHANGED; + this.ADJUST_GEAR_LINK_CLICKED = ADJUST_GEAR_LINK_CLICKED; this.initialize = initialize; this.selectedFramesize = selectedFramesize; this.selectedBufferIn = selectedBufferIn; diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js index 349032d8c..cb0cd4317 100644 --- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js +++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js @@ -13,6 +13,7 @@ var $wizardSteps = null; var $templateSteps = null; var loopbackWizard = null; + var adjustGearSettings = null; var inputs = null; var self = this; @@ -183,9 +184,10 @@ return inputs; } - function initialize(_loopbackWizard) { + function initialize(_loopbackWizard, _adjustGearSettings) { loopbackWizard = _loopbackWizard; + adjustGearSettings = _adjustGearSettings; // on initial page load, we are not in the FTUE. so cancel the FTUE and call FTUESetStatus(true) if needed if(context.jamClient.FTUEGetStatus() == false) { @@ -224,6 +226,7 @@ this.createFTUEProfile = createFTUEProfile; this.getWizard = function() {return wizard; } this.getLoopbackWizard = function() { return loopbackWizard; }; + this.getAdjustGearSettings = function() { return adjustGearSettings; }; self = this; return this; diff --git a/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js b/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js index f76e0abaf..ac9a40c25 100644 --- a/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js +++ b/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js @@ -5,9 +5,11 @@ context.JK = context.JK || {}; context.JK.StepDirectMonitoring = function (app) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var $step = null; var $directMonitoringBtn = null; + var $adjustSettingsDirectMonitor = null; var isPlaying = false; var playCheckInterval = null; var trackDurationMs = null; @@ -83,12 +85,29 @@ } } + function onAdjustGearRequested() { + app.layout.showDialog('adjust-gear-speed-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + + var adjustGearTest = data.result; + + if(!data.canceled) { + if(adjustGearTest.isGoodFtue()) { + + } + } + else { + logger.debug("adjust-gear-speed was cancelled; ignoring") + } + }) + } + function initialize(_$step) { $step = _$step; $directMonitoringBtn = $step.find('.test-direct-monitoring'); - $directMonitoringBtn.on('click', togglePlay); + $adjustSettingsDirectMonitor = $step.find('.adjust-settings-direct-monitor'); + $adjustSettingsDirectMonitor.on('click', onAdjustGearRequested) } this.handleHelp = handleHelp; diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js index d1d53543f..9479f86c9 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -11,6 +11,7 @@ var VOICE_CHAT = context.JK.VOICE_CHAT; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; var gearUtils = context.JK.GearUtils; + var modUtils = context.JK.ModUtils; var self = null; var $step = null; @@ -19,6 +20,7 @@ var frameBuffers = new context.JK.FrameBuffers(app); var gearTest = new context.JK.GearTest(app); var loopbackShowing = false; + var adjustGearSettingsShowing = false; var wizard = null; // the goal of lastFailureAnalytics and trackedPass are to send only a single AudioTest 'Pass' or 'Failed' event, per FTUE wizard open/close @@ -32,6 +34,8 @@ var $inputChannels = null; var $outputChannels = null; var $knobs = null; + var $adjustSettingsLink = null; + var $adjustGearForIoFail = null; var $scoreReport = null; var $asioInputControlBtn = null; var $asioOutputControlBtn = null; @@ -86,7 +90,7 @@ } function initializeNextButtonState() { - dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue()); + dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue() || dialog.getAdjustGearSettings().getGearTest().isGoodFtue()); } function initializeBackButtonState() { @@ -365,8 +369,9 @@ if(dialog.getLoopbackWizard().getGearTest().isGoodFtue()) { gearTest.resetScoreReport(); gearTest.showLoopbackDone(); - context.JK.prodBubble(dialog.getWizard().getNextButton(), 'move-on-loopback-success', {}, {positions:['top']}); - + setTimeout(function() { + context.JK.prodBubble(dialog.getWizard().getNextButton(), 'can-move-on', {}, {positions:['top'], offsetParent: dialog.getWizard().getDialog()}); + }, 300); } initializeNextButtonState(); @@ -379,6 +384,42 @@ }) } + function onAdjustGearRequested() + { + if(gearTest.isScoring()) {logger.debug("ignoring adjust-gear request while scoring"); return false;} + + app.layout.showDialog('adjust-gear-speed-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + adjustGearSettingsShowing = false; + + var adjustGearTest = data.result; + + if(!data.canceled) { + if(adjustGearTest.isGoodFtue()) { + // update our own frame buffers to reflect any changes made by the adjust dialog + frameBuffers.setFramesize(context.jamClient.FTUEGetFrameSize()) + frameBuffers.setBufferIn(context.jamClient.FTUEGetInputLatency()) + frameBuffers.setBufferOut(context.jamClient.FTUEGetOutputLatency()) + + gearTest.resetScoreReport(); + gearTest.showGearAdjustmentDone(); + + setTimeout(function() { + context.JK.prodBubble(dialog.getWizard().getNextButton(), 'can-move-on', {}, {positions:['top'], offsetParent: dialog.getWizard().getDialog()}); + }, 300); + } + + initializeNextButtonState(); + initializeBackButtonState(); + } + else { + logger.debug("adjust-gear-speed was cancelled; ignoring") + } + }) + + adjustGearSettingsShowing = true; + return false; + } + function initializeFormElements() { if (!deviceInformation) throw "devices are not initialized"; @@ -486,6 +527,7 @@ function invalidateScore() { gearTest.invalidateScore(); dialog.getLoopbackWizard().getGearTest().invalidateScore(); + dialog.getAdjustGearSettings().getGearTest().invalidateScore(); initializeNextButtonState(); } @@ -755,6 +797,7 @@ validDevice = autoSelectMinimumValidChannels(); if (!validDevice) { + $adjustSettingsLink.hide(); return false; } @@ -774,6 +817,11 @@ shownOutputProdOnce = true; } + // further, check if we have both inputs and outputs defined; if so, show ? to allow launch of adjust gear settings dialog + if(modUtils.getGear('show_frame_options')) { + $adjustSettingsLink.show(); + } + return true; } @@ -821,11 +869,12 @@ } // handle framesize/buffers - if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs)) { + if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs || modUtils.getGear('show_frame_options'))) { $knobs.show(); } else { $knobs.hide(); + $adjustSettingsLink.hide(); } // handle ASIO visibility @@ -865,36 +914,9 @@ jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); } + function updateDefaultBuffers() { - - // handle specific framesize settings - if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { - var framesize = frameBuffers.selectedFramesize(); - - if(framesize == 2.5) { - logger.debug("setting default buffers to 1/1"); - frameBuffers.setBufferIn('1'); - frameBuffers.setBufferOut('1'); - } - else if(framesize == 5) { - logger.debug("setting default buffers to 3/2"); - frameBuffers.setBufferIn('3'); - frameBuffers.setBufferOut('2'); - } - else { - logger.debug("setting default buffers to 6/5"); - frameBuffers.setBufferIn('6'); - frameBuffers.setBufferOut('5'); - } - } - else { - logger.debug("setting default buffers to 0/0"); - frameBuffers.setBufferIn(0); - frameBuffers.setBufferOut(0); - } - - jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); - jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers) } // refocused affects how IO testing occurs. @@ -940,11 +962,26 @@ } } + function prodUserToTweakASIOSettings($btn) { + setTimeout(function() { + context.JK.prodBubble($btn, 'tweak-asio-settings', {}, {positions:['top']}); + }, 300) + } + function onGearTestFail(e, data) { renderScoringStopped(); gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); if(data.reason == "latency") { + + console.log("selectedDeviceInfo", selectedDeviceInfo) + if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) { + prodUserToTweakASIOSettings($asioInputControlBtn) + } + else if(selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) { + prodUserToTweakASIOSettings($asioOutputControlBtn) + } + storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore); } else if(data.reason = "io") { @@ -973,11 +1010,13 @@ var specificResolutions = []; - if(selectedDeviceInfo.input.behavior.type.indexOf('Win32_asio') > -1 || selectedDeviceInfo.output.behavior.type.indexOf('Win32_asio') > -1) { + if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1 || selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) { + // specificResolutions.push("Read over this article") specificResolutions.push("Select the ASIO SETTINGS... button and try different settings."); } - if(selectedDeviceInfo.input.behavior.type == 'Win32_wdm' || selectedDeviceInfo.output.behavior.type == 'Win32_wdm') { + if(selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm') { + // specificResolutions.push("Read over this article") specificResolutions.push("Change the Frame, Buffer In, or Buffer Out settings."); } @@ -1063,7 +1102,7 @@ } function onFocus() { - if(validDevice && !loopbackShowing && !gearTest.isScoring() && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) { + if(validDevice && !loopbackShowing && !adjustGearSettingsShowing && !gearTest.isScoring() && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) { scheduleRescanSystem(function() { attemptScore(true); }, 3000, false) } } @@ -1160,6 +1199,8 @@ $inputChannels = $step.find('.input-ports'); $outputChannels = $step.find('.output-ports'); $knobs = $step.find('.frame-and-buffers'); + $adjustSettingsLink = $knobs.find('.adjust-gear-settings') + $adjustGearForIoFail = $step.find(".adjust-gear-for-io-fail") $scoreReport = $step.find('.results'); $asioInputControlBtn = $step.find('.asio-settings-input-btn'); $asioOutputControlBtn = $step.find('.asio-settings-output-btn'); @@ -1175,6 +1216,7 @@ .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged) .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged) .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged) + .on(frameBuffers.ADJUST_GEAR_LINK_CLICKED, onAdjustGearRequested) gearTest.initialize($scoreReport, true) $(gearTest) @@ -1182,6 +1224,7 @@ .on(gearTest.GEAR_TEST_DONE, onGearTestDone) .on(gearTest.GEAR_TEST_FAIL, onGearTestFail) .on(gearTest.GEAR_TEST_INVALIDATED_ASYNC, onGearTestInvalidated) + $adjustGearForIoFail.click(onAdjustGearRequested); } this.getLastAudioTestFailAnalytics = getLastAudioTestFailAnalytics; diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js index 7b56534db..fc397c2f9 100644 --- a/web/app/assets/javascripts/wizard/gear_test.js +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -33,6 +33,8 @@ var $resultsText = null; var $unknownText = null; var $loopbackCompleted = null; + var $adjustGearSpeedCompleted = null; + var $adjustGearForIoFail = null; var $ioScoreSection = null; var $latencyScoreSection = null; @@ -78,6 +80,10 @@ 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; @@ -240,6 +246,10 @@ latencyClass = 'unknown'; } + // uncomment these two lines to fail test due to latency + // latencyClass = "bad"; + // validLatency = false; + validLatencyScore = validLatency; if(refocused) { @@ -300,7 +310,7 @@ 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.') + 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.

If you think your gear is connected and working, this support article can help.') } @@ -308,6 +318,10 @@ $loopbackCompleted.show(); } + function showGearAdjustmentDone() { + $adjustGearSpeedCompleted.show(); + } + function resetScoreReport() { $ioHeader.hide(); $latencyHeader.hide(); @@ -322,6 +336,7 @@ $resultsText.removeAttr('scored'); $unknownText.hide(); $loopbackCompleted.hide(); + $adjustGearSpeedCompleted.hide(); $ioScoreSection.removeClass('good acceptable bad unknown starting skip'); $latencyScoreSection.removeClass('good acceptable bad unknown starting') } @@ -398,6 +413,8 @@ $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) { @@ -514,6 +531,7 @@ this.attemptScore = attemptScore; this.resetScoreReport = resetScoreReport; this.showLoopbackDone = showLoopbackDone; + this.showGearAdjustmentDone = showGearAdjustmentDone; this.invalidateScore = invalidateScore; this.isValidLatencyScore = isValidLatencyScore; this.isValidIOScore = isValidIOScore; diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index ba5dd70e0..c9d67f533 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -145,6 +145,54 @@ return loadedDevices; } + gearUtils.updateDefaultBuffers = function(selectedDeviceInfo, frameBuffers) { + function hasWDMAssociated() { + return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm') + } + + function hasASIOAssociated() { + return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio') + } + + // handle specific framesize settings + if(hasWDMAssociated() || hasASIOAssociated()) { + var framesize = frameBuffers.selectedFramesize(); + + if(framesize == 2.5) { + // if there is a WDM device, start off at 1/1 due to empirically observed issues with 0/0 + if(hasWDMAssociated()) { + logger.debug("setting default buffers to 1/1"); + frameBuffers.setBufferIn('1'); + frameBuffers.setBufferOut('1'); + } + else { + // otherwise, it's ASIO, so go with 0/0 + logger.debug("setting default buffers to 0/0"); + frameBuffers.setBufferIn('0'); + frameBuffers.setBufferOut('0'); + } + } + else if(framesize == 5) { + logger.debug("setting default buffers to 3/2"); + frameBuffers.setBufferIn('3'); + frameBuffers.setBufferOut('2'); + } + else { + logger.debug("setting default buffers to 6/5"); + frameBuffers.setBufferIn('6'); + frameBuffers.setBufferOut('5'); + } + } + else { + logger.debug("setting default buffers to 0/0"); + frameBuffers.setBufferIn(0); + frameBuffers.setBufferOut(0); + } + + context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + } + gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) { return { os: operatingSystem, diff --git a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js index d22732519..ccba2ae88 100644 --- a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js +++ b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js @@ -58,35 +58,7 @@ } function updateDefaultBuffers() { - - // handle specific framesize settings - if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { - var framesize = frameBuffers.selectedFramesize(); - - if(framesize == 2.5) { - logger.debug("setting default buffers to 1/1"); - frameBuffers.setBufferIn('1'); - frameBuffers.setBufferOut('1'); - } - else if(framesize == 5) { - logger.debug("setting default buffers to 3/2"); - frameBuffers.setBufferIn('3'); - frameBuffers.setBufferOut('2'); - } - else { - logger.debug("setting default buffers to 6/5"); - frameBuffers.setBufferIn('6'); - frameBuffers.setBufferOut('5'); - } - } - else { - logger.debug("setting default buffers to 0/0"); - frameBuffers.setBufferIn(0); - frameBuffers.setBufferOut(0); - } - - jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); - jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers) } function onFramesizeChanged() { diff --git a/web/app/assets/javascripts/wizard/wizard.js b/web/app/assets/javascripts/wizard/wizard.js index e3a6eaed3..6d8600e3e 100644 --- a/web/app/assets/javascripts/wizard/wizard.js +++ b/web/app/assets/javascripts/wizard/wizard.js @@ -217,6 +217,10 @@ return $currentWizardStep; } + function getDialog() { + return $dialog; + } + function initialize(_$dialog, _$wizardSteps, _STEPS, _options) { $dialog = _$dialog; dialogName = $dialog.attr('layout-id'); @@ -239,6 +243,7 @@ this.onCloseDialog = onCloseDialog; this.onBeforeShow = onBeforeShow; this.onAfterHide = onAfterHide; + this.getDialog = getDialog; this.initialize = initialize; } diff --git a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss index b3f205d3f..abc010194 100644 --- a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss +++ b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss @@ -14,6 +14,10 @@ width:45%; } + .adjust-gear-settings { + display:none; + margin-left:4px; + } .buffers { float:left; diff --git a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss index 4710ba12c..a542212a7 100644 --- a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss @@ -20,6 +20,11 @@ display:inline; } } + &[data-type=adjust-gear-speed] { + span.conditional[data-type=adjust-gear-speed] { + display:inline; + } + } .io, .latency { display: none; } @@ -47,7 +52,7 @@ padding: 8px; } - .loopback-completed { + .loopback-completed, .adjust-gear-speed-completed { display:none; } @@ -115,6 +120,14 @@ display: none } + span.io-failure { + display:none; + } + + %span.no-io-failure { + display:inline; + } + &[latency-score="unknown"] { display: none; } @@ -159,6 +172,16 @@ } } + &[io-rate-score="bad"], &[io-var-score="bad"] { + span.io-failure { + display:inline; + } + + span.no-io-failure { + display:none; + } + } + &[latency-score="unknown"] { li.success { display: none; diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 3a7572ec4..29d498f69 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -274,7 +274,7 @@ } .watch-video { - margin-top: 26px; + margin-top: 12px; } .help-content { diff --git a/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss b/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss new file mode 100644 index 000000000..2bdcb6d7f --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss @@ -0,0 +1,160 @@ +@import "client/common.css.scss"; +@charset "UTF-8"; +#adjust-gear-speed-dialog { + min-height: 420px; + max-height: 420px; + width:800px; + + h2 { + color: #FFFFFF; + font-size: 15px; + font-weight: normal; + margin-bottom: 6px; + white-space:nowrap; + + } + + h2.settings { + display:inline; + position:absolute; + left:0; + margin-left:0; // override global .settings from sessions.css + } + + .ftue-box { + background-color: #222222; + font-size: 13px; + padding: 8px; + } + + .dialog-inner { + line-height: 1.3em; + width:800px; + padding:25px; + font-size:15px; + color:#aaa; + @include border_box_sizing; + + } + + .dialog-column { + + @include border_box_sizing; + + &:nth-of-type(1) { + width:75%; + margin-top:38px; + } + + &:nth-of-type(2) { + width:25%; + float:right; + } + } + + .speed-options { + display:inline-block; + width:60%; + margin:auto; + margin-left:25%; + } + + .speed-option { + @include border_box_sizing; + float:left; + width:33%; + text-align:center; + + .iradio_minimal { + margin:auto; + display:inline-block; + } + + label { + display:inline-block; + margin-left: 10px; + position: relative; + top: -3px; + } + } + + .speed-text { + margin:10px 0; + } + + .run-test-btn { + margin-top:5px; + margin-left:25px; + left:40%; + position:relative; + } + + .frame-and-buffers { + display:inline-block; + margin-left:30%; + + .framesize { + width:auto; + display:inline-block; + } + + .buffers { + width:auto; + display:inline-block; + margin-left:20px; + } + h2 { + display:inline-block; + line-height:18px; + vertical-align: top; + } + + .easydropdown-wrapper { + display:inline-block; + top:-3px; + margin-left:8px; + } + + + .select-frame-size { + margin-left:-2px; + } + } + + .help-text { + margin-bottom:20px; + } + + .basic { + margin:0 5px 20px 0; + } + .advanced { + display:none; + border-width:1px 0 0 0; + border-style:solid; + border-color:white; + padding-top:20px; + margin:0 5px 0 0; + .help-text { + text-align:left; + } + } + + .test-results-header { + position:static; + } + + + .ftue-box.results { + margin-top:20px; + height: 268px !important; + padding:0; + @include border_box_sizing; + } + + h2.results-text-header { + margin-top:5px; + } + + +} diff --git a/web/app/views/clients/_help.html.erb b/web/app/views/clients/_help.html.erb index 66ac73135..7623897dc 100644 --- a/web/app/views/clients/_help.html.erb +++ b/web/app/views/clients/_help.html.erb @@ -30,8 +30,14 @@ Push 'Resync' when done modifying Framesize, Buffer In, or Buffer Out. - + +