Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop

This commit is contained in:
Brian Smith 2014-11-15 11:33:43 -05:00
commit 12ca12f3cf
33 changed files with 867 additions and 186 deletions

View File

@ -71,6 +71,7 @@ gem 'rest-client'
gem 'iso-639'
gem 'rubyzip'
gem 'sanitize'
gem 'slim'
group :libv8 do
gem 'libv8', "~> 3.11.8"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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()

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 <a rel='external' href='https://jamkazam.desk.com/customer/portal/articles/1723489-jamkazam-thinks-your-gear-is-disconnected---windows-only'>article</a>")
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 <a rel='external' href='https://jamkazam.desk.com/customer/portal/articles/1723489-jamkazam-thinks-your-gear-is-disconnected---windows-only'>article</a>")
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;

View File

@ -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.<br/><br/>If you think your gear is connected and working, this <a rel="external" href="https://jamkazam.desk.com/customer/portal/articles/1723489-jamkazam-thinks-your-gear-is-disconnected---windows-only">support article</a> can help.')
}
@ -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;

View File

@ -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,

View File

@ -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() {

View File

@ -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;
}

View File

@ -14,6 +14,10 @@
width:45%;
}
.adjust-gear-settings {
display:none;
margin-left:4px;
}
.buffers {
float:left;

View File

@ -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;

View File

@ -274,7 +274,7 @@
}
.watch-video {
margin-top: 26px;
margin-top: 12px;
}
.help-content {

View File

@ -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;
}
}

View File

@ -30,8 +30,14 @@
Push 'Resync' when done modifying Framesize, Buffer In, or Buffer Out.
</script>
<script type="text/template" id="template-help-move-on-loopback-success">
You can move to the next step now.
<script type="text/template" id="template-help-can-move-on">
<div class="help-can-move-on">
You can move to the next step now.
</div>
</script>
<script type="text/template" id="template-help-tweak-asio-settings">
Click here to try faster ASIO settings.
</script>
<script type="text/template" id="template-help-session-plus-musicians">

View File

@ -239,11 +239,14 @@
var sessionSettingsDialog = new JK.SessionSettingsDialog(JK.app, sessionScreen);
sessionSettingsDialog.initialize();
var adjustGearSpeed = new JK.AdjustGearSpeedDialog(JK.app);
adjustGearSpeed.initialize();
var loopbackWizard = new JK.LoopbackWizard(JK.app);
loopbackWizard.initialize();
var gearWizard = new JK.GearWizard(JK.app);
gearWizard.initialize(loopbackWizard);
gearWizard.initialize(loopbackWizard, adjustGearSpeed);
var testBridgeScreen = new JK.TestBridgeScreen(JK.app);
testBridgeScreen.initialize();

View File

@ -1,33 +0,0 @@
.frame-and-buffers
.framesize
%h2 Frame
%select.select-frame-size
%option{val:'2.5'} 2.5
%option{val:'5'} 5
%option{val:'10'} 10
.buffers
%h2 Buffer In/Out
%select.select-buffer-in
%option{val:'0'} 0
%option{val:'1'} 1
%option{val:'2'} 2
%option{val:'3'} 3
%option{val:'4'} 4
%option{val:'5'} 5
%option{val:'6'} 6
%option{val:'7'} 7
%option{val:'8'} 8
%option{val:'9'} 9
%option{val:'10'} 10
%select.select-buffer-out
%option{val:'0'} 0
%option{val:'1'} 1
%option{val:'2'} 2
%option{val:'3'} 3
%option{val:'4'} 4
%option{val:'5'} 5
%option{val:'6'} 6
%option{val:'7'} 7
%option{val:'8'} 8
%option{val:'9'} 9
%option{val:'10'} 10

View File

@ -0,0 +1,35 @@
.frame-and-buffers
.framesize
h2 Frame
select.select-frame-size
option val='2.5' 2.5
option val='5' 5
option val='10' 10
.buffers
h2
| Buffer In/Out
a.adjust-gear-settings href="#" ?
select.select-buffer-in
option val='0' 0
option val='1' 1
option val='2' 2
option val='3' 3
option val='4' 4
option val='5' 5
option val='6' 6
option val='7' 7
option val='8' 8
option val='9' 9
option val='10' 10
select.select-buffer-out
option val='0' 0
option val='1' 1
option val='2' 2
option val='3' 3
option val='4' 4
option val='5' 5
option val='6' 6
option val='7' 7
option val='8' 8
option val='9' 9
option val='10' 10

View File

@ -32,11 +32,20 @@
%li.io-var-bad Your I/O variance is poor.
%li.success You may proceed to the next step.
%li.failure
%span.conditional{'data-type' => 'automated'} We're sorry, but your audio gear has failed. Please watch video or click HELP button below.
%span.conditional{'data-type' => 'automated'}
= "We're sorry, but your audio gear has failed. "
%span.io-failure
%a.adjust-gear-for-io-fail{href: "#"} Tweak your settings,
= "watch the video, or click HELP button below."
%span.no-io-failure
= "Please watch video or click HELP button below."
%span.conditional{'data-type' => 'loopback'} We're sorry, but your audio gear has failed. Please watch videos on the previous screen.
%span.conditional{'data-type' => 'adjust-gear-speed'} We're sorry, but your audio gear has failed. Adjust your settings and try again.
.unknown-text
%div We cannot accurately predict the latency of your audio gear. To proceed, you must run an audio loopback test. Click button below to do this.
%div.loopback-button-holder
%a.button-orange.loopback-test{href:'#'} RUN LOOPBACK TEST
.loopback-completed
You have completed the loopback test successfully. Click Next to continue.
You have completed the loopback test successfully. Click Next to continue.
.adjust-gear-speed-completed
You have adjusted your gear speed settings successfully. Click Next to continue.

View File

@ -136,6 +136,9 @@
%ul
%li If a button, push it into its off position.
%li If a knob, turn it so that 100% of audio is from your computer, and 0% is from the direct monitor.
%li
= "If audio is poor try "
%a.adjust-settings-direct-monitor{'href'=>'#'}tweaking your settings
.center
%a.button-orange.watch-video{href:'https://www.youtube.com/watch?v=-nC-D3JBHnk', rel:'external'} WATCH VIDEO
.wizard-step-column

View File

@ -0,0 +1,37 @@
.dialog.dialog-overlay-sm layout='dialog' layout-id='adjust-gear-speed-dialog' id='adjust-gear-speed-dialog'
.content-head
h1 Adjust Gear Speed
.dialog-inner
.dialog-content
.dialog-column.content
.help-text If you are failing the I/O test on your audio interface, or if your audio sounds bad, try selecting a slower audio processing speed. Your goal is to pass the I/O test and hear good audio, while sacrificing as little speed as possible.
.basic
h2.settings Settings:
.speed-options
.speed-option.setting-slow
input type="radio" name="gear-speed" id="adjust-gear-speed-slow" value='slow'
label for="adjust-gear-speed-slow" Slow
.speed-option.setting-fair
input type="radio" name="gear-speed" id="adjust-gear-speed-fair" value='fair'
label for="adjust-gear-speed-fair" Moderate
.speed-option.setting-fast
input type="radio" name="gear-speed" id="adjust-gear-speed-fast" value='fast'
label for="adjust-gear-speed-fast" Fast
.speed-text
//a.button-orange.run-test-btn RUN TEST
.advanced
.help-text If adjusting the settings above still doesn't work, you may try experimenting with the Frame and Buffer settings below to see if you can achieve better results using these controls.
h2.settings Advanced Settings:
= render :partial => "/clients/wizard/framebuffers"
a.button-orange.run-test-btn RUN TEST
.dialog-column
h2.test-results-header Test & Results
= render :partial => "/clients/wizard/gear_test", locals: {test_type: 'adjust-gear-speed'}
.buttons
.right
a.button-grey.btnCancel layout-action="cancel" CANCEL
a.button-orange.btnSave SAVE

View File

@ -29,3 +29,4 @@
= render 'dialogs/joinTestSessionDialog'
= render 'dialogs/changeSearchLocationDialog'
= render 'dialogs/allSyncsDialog'
= render 'dialogs/adjustGearSpeedDialog'

View File

@ -97,7 +97,6 @@ describe ApiClaimedRecordingsController do
it "quick mix, not completed" do
quick_mix = FactoryGirl.create(:quick_mix)
controller.current_user = @user
puts quick_mix.recording.candidate_claimed_recording
get :download, id: quick_mix.recording.candidate_claimed_recording.id
response.status.should == 404
end

View File

@ -152,6 +152,16 @@ def walk_gear_wizard
# step 5 - configure direct monitoring
find('.ftue-step-title', text: 'Turn Off Direct Monitoring')
# make a diversion into the 'adjust gear speed' dialog
find('.adjust-settings-direct-monitor').trigger(:click)
# should see dialog header
find('h1', text: 'Adjust Gear Speed')
# change to 'moderate' speed
find('.speed-option.setting-fair ins').trigger(:click)
# should cause a spinner/io test, and then save button comes up as clickable
find('.btnSave.button-orange:not(.disabled)').trigger(:click)
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
# step 6 - Test Router & Network

View File

@ -985,7 +985,8 @@ module JamWebsockets
# remove this connection from the database
ConnectionManager.active_record_transaction do |mgr|
mgr.delete_connection(cid) { |conn, count, music_session_id, user_id|
user = User.find(user_id)
user = User.find_by_id(user_id)
return if user.nil? # this can happen if you delete a user while their connection is up
@log.info "expiring stale connection client_id:#{cid}, user_id:#{user}"
Notification.send_friend_update(user_id, false, conn) if count == 0
music_session = ActiveMusicSession.find_by_id(music_session_id) unless music_session_id.nil?