diff --git a/ruby/lib/jam_ruby/models/diagnostic.rb b/ruby/lib/jam_ruby/models/diagnostic.rb index e7a289b42..8385fff40 100644 --- a/ruby/lib/jam_ruby/models/diagnostic.rb +++ b/ruby/lib/jam_ruby/models/diagnostic.rb @@ -29,9 +29,12 @@ module JamRuby # websocket gateway got a client with the same client_id as an already-connected client DUPLICATE_CLIENT = 'DUPLICATE_CLIENT' + # info about how the test went + NETWORK_TEST_RESULT = 'NETWORK_TEST_RESULT' + DIAGNOSTIC_TYPES = [NO_HEARTBEAT_ACK, WEBSOCKET_CLOSED_REMOTELY, EXPIRED_STALE_CONNECTION, MISSING_CLIENT_STATE, UNKNOWN_MESSAGE_TYPE, MISSING_ROUTE_TO, - DUPLICATE_CLIENT, WEBSOCKET_CLOSED_LOCALLY] + DUPLICATE_CLIENT, WEBSOCKET_CLOSED_LOCALLY, NETWORK_TEST_RESULT] # creator types # CLIENT = 'client' diff --git a/ruby/lib/jam_ruby/models/latency_tester.rb b/ruby/lib/jam_ruby/models/latency_tester.rb index bebcc9cdf..2bc1071ae 100644 --- a/ruby/lib/jam_ruby/models/latency_tester.rb +++ b/ruby/lib/jam_ruby/models/latency_tester.rb @@ -3,9 +3,16 @@ module JamRuby belongs_to :connection, class_name: 'JamRuby::Connection', foreign_key: :client_id, primary_key: :client_id + def heartbeat_interval_client + nil + end + + def connection_expire_time_client + nil + end def self.select_latency_tester - LatencyTester.joins(:connection).first + LatencyTester.joins(:connection).first! end # we need to find that latency_tester with the specified connection (and reconnect it) diff --git a/web/app/assets/javascripts/createSession.js.erb b/web/app/assets/javascripts/createSession.js.erb index 8532d162c..cdde53f31 100644 --- a/web/app/assets/javascripts/createSession.js.erb +++ b/web/app/assets/javascripts/createSession.js.erb @@ -132,6 +132,7 @@ if (!(context.JK.hasOneConfiguredDevice())) { app.afterFtue = function() { submitForm(evt); }; app.layout.startNewFtue(); + alert("dere") return false; } diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 2691c7d4c..67eff0355 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -444,7 +444,7 @@ function SessionStartRecording() {} function SessionStopPlay() {} function SessionStopRecording() {} - function SessionAddPlayTrack() {} + function SessionAddPlayTrack() {return true;} function SessionRemoveAllPlayTracks(){} function isSessionTrackPlaying() { return false; } function SessionCurrrentPlayPosMs() { return 0; } @@ -675,17 +675,39 @@ fakeJamClientRecordings = fakeRecordingsImpl; } - function OnLoggedIn(userId, sessionToken) { - - } - - function OnLoggedOut() { - - } - - function UserAttention(option) { + function TestNetworkPktBwRate(targetClientId, successCallback, timeoutCallback, testType, duration, numClients, payloadSize) { + var progress = {progress:true, + upthroughput:.95, + downthroughput:.95, + upjitter: 2.3, + downjitter: 2.3 + } + var count = 0; + var interval = setInterval(function() { + eval(successCallback + "(" + JSON.stringify(progress) + ");"); + + if(progress.upthroughput < 1) { + progress.upthroughput += .05; + } + if(progress.downthroughput < 1) { + progress.downthroughput += .05; + } + + count++; + if(count == duration) { + clearInterval(interval); + + delete progress['progress'] + progress.pass = true; + eval(successCallback + "(" + JSON.stringify(progress) + ");"); + } + }, 1000); } + function StopNetworkTest(targetClientId) {} + function OnLoggedIn(userId, sessionToken) {} + function OnLoggedOut() {} + function UserAttention(option) {} function log(level, message) { console.log("beep : " + message) @@ -902,6 +924,9 @@ this.RegisterP2PMessageCallbacks = RegisterP2PMessageCallbacks; this.SetFakeRecordingImpl = SetFakeRecordingImpl; + // network test + this.TestNetworkPktBwRate = TestNetworkPktBwRate; + this.StopNetworkTest = StopNetworkTest; this.log = log; this.getOperatingMode = getOperatingMode; this.clientID = "devtester"; diff --git a/web/app/assets/javascripts/ga.js b/web/app/assets/javascripts/ga.js index 9b5ea6d24..bcbd8835c 100644 --- a/web/app/assets/javascripts/ga.js +++ b/web/app/assets/javascripts/ga.js @@ -89,6 +89,8 @@ register : "Register", download : "DownloadClient", audioTest : "AudioTest", + trackConfig : "AudioTrackConfig", + networkTest : "NetworkTest", sessionCount : "SessionCount", sessionMusicians : "SessionMusicians", sessionQuality : "SessionQuality", @@ -281,6 +283,23 @@ context.ga('send', 'event', category, target, data); } + function trackNetworkTest(platform, numUsers) { + var normalizedPlatform = translatePlatformForGA(platform); + + context.ga('send', 'event', categories.networkTest, 'Passed', normalizedPlatform, numUsers); + } + + function trackAudioTestCompletion(platform) { + var normalizedPlatform = translatePlatformForGA(platform); + + context.ga('send', 'event', categories.audioTest, 'Passed', normalizedPlatform); + } + + function trackConfigureTracksCompletion(platform) { + var normalizedPlatform = translatePlatformForGA(platform); + + context.ga('send', 'event', categories.trackConfig, 'Passed', normalizedPlatform); + } var GA = {}; GA.Categories = categories; @@ -294,6 +313,9 @@ GA.trackRegister = trackRegister; GA.trackDownload = trackDownload; GA.trackFTUECompletion = trackFTUECompletion; + GA.trackNetworkTest = trackNetworkTest; + GA.trackAudioTestCompletion = trackAudioTestCompletion; + GA.trackConfigureTracksCompletion = trackConfigureTracksCompletion; GA.trackSessionCount = trackSessionCount; GA.trackSessionMusicians = trackSessionMusicians; GA.trackSessionQuality = trackSessionQuality; diff --git a/web/app/assets/javascripts/gear/gear_wizard.js b/web/app/assets/javascripts/gear/gear_wizard.js index b75f239a9..dfd313cb1 100644 --- a/web/app/assets/javascripts/gear/gear_wizard.js +++ b/web/app/assets/javascripts/gear/gear_wizard.js @@ -103,6 +103,27 @@ } + function onCanceled() { + if (app.cancelFtue) { + app.cancelFtue(); + app.afterFtue = null; + app.cancelFtue = null; + } + + return closeDialog(); + } + + function onClosed() { + if (app.afterFtue) { + // If there's a function to invoke, invoke it. + app.afterFtue(); + app.afterFtue = null; + app.cancelFtue = null; + } + + return closeDialog(); + } + function closeDialog() { wizard.onCloseDialog(); app.layout.closeDialog('gear-wizard'); @@ -111,8 +132,8 @@ function events() { $(wizard).on('step_changed', onStepChanged); - $(wizard).on('wizard_cancel', closeDialog); - $(wizard).on('wizard_close', closeDialog); + $(wizard).on('wizard_cancel', onCanceled); + $(wizard).on('wizard_close', onClosed); } function setNextState(enabled) { diff --git a/web/app/assets/javascripts/gear/step_configure_tracks.js b/web/app/assets/javascripts/gear/step_configure_tracks.js index a6ef48b70..5531ccb86 100644 --- a/web/app/assets/javascripts/gear/step_configure_tracks.js +++ b/web/app/assets/javascripts/gear/step_configure_tracks.js @@ -179,7 +179,13 @@ return false; } - return save(tracks); + var saved = save(tracks); + + if(saved) { + context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS()); + } + + return saved; } function beforeShow() { diff --git a/web/app/assets/javascripts/gear/step_network_test.js b/web/app/assets/javascripts/gear/step_network_test.js index 715e8b4e5..5a32647eb 100644 --- a/web/app/assets/javascripts/gear/step_network_test.js +++ b/web/app/assets/javascripts/gear/step_network_test.js @@ -5,6 +5,18 @@ context.JK = context.JK || {}; context.JK.StepNetworkTest = function (app, $dialog) { + var NETWORK_TEST_TYPES = { + RestPhase : 0, + PktTest100NormalLatency : 1, + PktTest200MediumLatency : 2, + PktTest400LowLatency : 3, + PktTestRateSweep : 4, + RcvOnly : 5 + } + var STARTING_NUM_CLIENTS = 4; + var PAYLOAD_SIZE = 100; + var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2; + var rest = context.JK.Rest(); var logger = context.JK.logger; var $step = null; @@ -16,64 +28,355 @@ var $testScore = null; var $testText = null; var testedSuccessfully = false; - var serverClientId = null; + var $scoringBar = null; + var $goodMarker = null; + var $goodLine = null; + var $currentScore = null; + var $scoredClients = null; + var $subscore = null; + + var serverClientId = ''; var isScoring = false; + var numClientsToTest = STARTING_NUM_CLIENTS; + var testSummary = {attempts : [], final:null} + + var scoringZoneWidth = 100;//px + + + function reset() { + serverClientId = ''; + isScoring = false; + numClientsToTest = STARTING_NUM_CLIENTS; + testSummary = {attempts : []}; + } + + function renderStartTest() { + $scoredClients.empty(); + $testResults.removeClass('good acceptable bad').addClass('testing'); + $testText.empty(); + $subscore.empty(); + updateControlsState(); + $currentScore.width(0); + $goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); + $goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%'); + } + + function renderStopTest(score, text) { + $scoredClients.html(score); + $testText.html(text); + $testResults.removeClass('testing'); + } + + function postDiagnostic() { + rest.createDiagnostic({ + type: 'NETWORK_TEST_RESULT', + data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary:testSummary} + }); + } + + function testFinished() { + var attempt = getCurrentAttempt(); + + if(!attempt.final) { + attempt.final = {reason : attempt.reason}; + } + + var reason = attempt.final.reason; + + if(reason == "success") { + renderStopTest(attempt.num_clients, "Your router and Internet service will support sessions of up to " + attempt.num_clients + " JamKazam musicians.") + testedSuccessfully = true; + if(!attempt.final.num_clients) { + attempt.final.num_clients = attempt.num_clients; + } + context.JK.GA.trackNetworkTest(context.JK.detectOS(), attempt.final.num_clients); + //context.jamClient.updateLatencyTestScore(attempt.num_clients); + if(attempt.final.num_clients == 2) { + $testResults.addClass('acceptable'); + } + else { + $testResults.addClass('good'); + } + } + else if(reason == "minimum_client_threshold") { + renderStopTest('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.") + } + else if(reason == "unreachable") { + renderStopTest('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click the HELP button for more information."); + } + else if(reason == "internal_error") { + context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection."); + renderStopTest('', ''); + } + else if(reason == "remote_peer_cant_test") { + context.JK.alertSupportedNeeded("JamKazam is experiencing technical difficulties."); + renderStopTest('', ''); + } + else if(reason == "invalid_response") { + context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection. Reason=" + attempt.backend_data.reason + '.'); + renderStopTest('', ''); + } + else if(reason == 'no_servers') { + context.JK.alertSupportedNeeded("No network test servers are available. You can skip this step for now."); + renderStopTest('', ''); + testedSuccessfully = true; + } + else if(reason == 'no_network') { + context.JK.Banner.showAlert("Please try again later. Your network appears down."); + renderStopTest('', ''); + } + else if(reason == "rest_api_error") { + context.JK.alertSupportedNeeded("Unable to network test servers are available. You can skip this step for now."); + testedSuccessfully = true; + renderStopTest('', ''); + } + else if(reason == "timeout") { + context.JK.alertSupportedNeeded("Unable to network test servers timed out. You can skip this step for now."); + testedSuccessfully = true; + renderStopTest('', ''); + } + else { + context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection."); + renderStopTest('', ''); + } + + numClientsToTest = STARTING_NUM_CLIENTS; + isScoring = false; + updateControlsState(); + postDiagnostic(); + } + + function getCurrentAttempt() { + return testSummary.attempts[testSummary.attempts.length - 1]; + } + + + function attemptTestPass() { + + var attempt = {}; + attempt.payload_size = PAYLOAD_SIZE; + attempt.duration = gon.ftue_network_test_duration; + attempt.test_type = 'PktTest400LowLatency'; + attempt.num_clients = numClientsToTest; + attempt.server_client_id = serverClientId; + attempt.received_progress = false; + testSummary.attempts.push(attempt); + + //context.jamClient.StopNetworkTest(''); + + $testText.text("Simulating the network traffic of a " + numClientsToTest + "-person session."); + + updateProgress(0, false); + + $currentScore.css + context.jamClient.TestNetworkPktBwRate(serverClientId, TEST_SUCCESS_CALLBACK, TEST_TIMEOUT_CALLBACK, + NETWORK_TEST_TYPES.PktTest400LowLatency, + gon.ftue_network_test_duration, + numClientsToTest, + PAYLOAD_SIZE); + } function startNetworkTest() { isScoring = true; + numClientsToTest = STARTING_NUM_CLIENTS; + renderStartTest(); rest.getLatencyTester() .done(function(response) { + // ensure there are no tests ongoing + serverClientId = response.client_id; - logger.info("beginning network test against client_id: " + clientId); + logger.info("beginning network test against client_id: " + serverClientId); - context.jamClient.TestNetworkPktBwRate(serverClientId, TEST_SUCCESS_CALLBACK, TEST_TIMEOUT_CALLBACK); + attemptTestPass(); }) - .fail(function() { - isScoring = false; - logger.error("arguments:", arguments); - - if(context.JK.isNetworkError(arguments)) { - context.JK.Banner.showAlert("Please try again latery. Your network appears down."); + .fail(function(jqXHR) { + if(jqXHR.status == 404) { + // means there are no network testers available. + // we have to skip this part of the UI + testSummary.final = {reason: 'no_servers'} } else { - logger.error("unable to get latency tester from server"); + if(context.JK.isNetworkError(arguments)) { + testSummary.final = {reason: 'no_network'} + } + else { + testSummary.final = {reason: 'rest_api_error'} + } } + testFinished(); }) logger.info("starting network test"); return false; } - function networkTestSuccess() { - console.log("success arguments: ", arguments); - context.jamClient.StopNetworkTest(serverClientId); + function updateProgress(throughput, showSubscore) { + var width = throughput * 100; + + $currentScore.stop().data('showSubscore', showSubscore); + + if(!showSubscore) { + $subscore.text(''); + } + + $currentScore.animate({ + duration: 1000, + width: width + '%' + }, { + step: function (now, fx) { + if(showSubscore) { + var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) ); + $subscore.text((Math.round(newWidth * 10) / 10) + '%'); + } + } + }).css('overflow', 'visible'); + ; } - function networkTestTimeout() { - console.log("timeout arguments:", arguments); - context.jamClient.StopNetworkTest(serverClientId); + function networkTestSuccess(data) { + var attempt = getCurrentAttempt(); + + function refineTest(up) { + if(up) { + if(numClientsToTest == gon.ftue_network_test_max_clients) { + attempt.reason = "success"; + testFinished(); + } + else { + numClientsToTest++; + logger.debug("increasing number of clients to " + numClientsToTest); + setTimeout(attemptTestPass, 1); + } + } + else { + // reduce numclients if we can + if(numClientsToTest == MINIMUM_ACCEPTABLE_SESSION_SIZE) { + // we are too low already. fail the user + attempt.reason = "minimum_client_threshold"; + testFinished(); + } + else if(numClientsToTest > STARTING_NUM_CLIENTS) { + // this means we've gone up before... so don't go back down (i.e., creating a loop) + attempt.reason = "success"; + attempt.final = { reason:'success', num_clients: numClientsToTest - 1 } + testFinished(); + } + else { + numClientsToTest--; + logger.debug("reducing number of clients to " + numClientsToTest); + setTimeout(attemptTestPass, 1); + } + } + } + + attempt.backend_data = data; + + if(data.progress === true) { + + var animate = true; + if(data.downthroughput && data.upthroughput) { + + if(data.downthroughput > 0 || data.upthroughput > 0) { + attempt.received_progress = true; + animate = true; + } + + if(attempt.received_progress) { + // take the lower + var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput; + + updateProgress(throughput, true); + } + } + } + else { + + logger.debug("network test pass success. data: ", data); + + if(data.reason == "unreachable") { + // STUN + logger.debug("network test: unreachable (STUN issue or similar)"); + attempt.reason = data.reason; + testFinished(); + } + else if(data.reason == "internal_error") { + // oops + logger.debug("network test: internal_error (client had a unexpected problem)"); + attempt.reason = data.reason; + testFinished(); + } + else if(data.reason == "remote_peer_cant_test") { + // old client + logger.debug("network test: remote_peer_cant_test (old client)") + attempt.reason = data.reason; + testFinished(); + } + else { + if(!data.downthroughput || !data.upthroughput) { + // we have to assume this is bad. just not a reason we know about in code + logger.debug("network test: no test data (unknown issue? " + data.reason + ")") + attempt.reason = "invalid_response"; + testFinished(); + } + else { + // success... but we still have to verify if this data is within threshold + if(data.downthroughput < gon.ftue_packet_rate_treshold) { + logger.debug("network test: downthroughput too low. downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); + refineTest(false); + } + else if(data.upthroughput < gon.ftue_packet_rate_treshold) { + logger.debug("network test: upthroughput too low. upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold); + refineTest(false); + } + else { + // true success. we can accept this score + logger.debug("network test: success") + refineTest(true); + } + } + } + + // VRFS-1742 + // context.jamClient.StopNetworkTest(serverClientId); + } + + } + + function networkTestTimeout(data) { + logger.warn("network timeout when testing latency test: " + data); + + var attempt = getCurrentAttempt(); + attempt.reason = 'timeout'; + attempt.backend_data = data; + testFinished(); } function hasScoredNetworkSuccessfully() { return testedSuccessfully; } + function updateControlsState() { + initializeNextButtonState(); + initializeBackButtonState(); + } + function initializeNextButtonState() { - $dialog.setNextState(hasScoredNetworkSuccessfully()); + $dialog.setNextState(hasScoredNetworkSuccessfully() && !isScoring); } function initializeBackButtonState() { - $dialog.setNextState(); + $dialog.setBackState(!isScoring); } function newSession() { - isScoring = false; - // XXX context.jamClient.stopNetworkTest(); + reset(); + //context.jamClient.StopNetworkTest(''); } function beforeShow() { - initializeNextButtonState(); + reset(); + updateControlsState(); } function initialize(_$step) { @@ -83,7 +386,12 @@ $testResults = $step.find('.network-test-results'); $testScore = $step.find('.network-test-score'); $testText = $step.find('.network-test-text'); - + $scoringBar = $step.find('.scoring-bar'); + $goodMarker = $step.find('.good-marker'); + $goodLine =$step.find('.good-line'); + $currentScore = $step.find('.current-score'); + $scoredClients= $step.find('.scored-clients'); + $subscore = $step.find('.subscore'); $startNetworkTestBtn.on('click', startNetworkTest); } diff --git a/web/app/assets/javascripts/gear/step_select_gear.js b/web/app/assets/javascripts/gear/step_select_gear.js index 5f520db66..3b79d38ba 100644 --- a/web/app/assets/javascripts/gear/step_select_gear.js +++ b/web/app/assets/javascripts/gear/step_select_gear.js @@ -932,9 +932,15 @@ // lie for now until IO questions finalize validIOScore = true; + onSuccessfulScore(); + renderScoringStopped(); } + function onSuccessfulScore() { + + } + // refocused affects how IO testing occurs. // on refocus=true: // * reuse IO score if it was good/acceptable @@ -1049,7 +1055,9 @@ return false; } else { + //XXX rename profile context.jamClient.FTUESave(true); + context.JK.GA.trackAudioTestCompletion(context.JK.detectOS()); return true; } diff --git a/web/app/assets/javascripts/gear/wizard.js b/web/app/assets/javascripts/gear/wizard.js index 9f608a7f4..2a6fea575 100644 --- a/web/app/assets/javascripts/gear/wizard.js +++ b/web/app/assets/javascripts/gear/wizard.js @@ -13,6 +13,7 @@ var $wizardSteps = null; var $currentWizardStep = null; var $wizardButtons = null; + var $btnHelp = null; var $btnNext = null; var $btnBack = null; var $btnClose = null; @@ -21,7 +22,7 @@ var $self = $(this); function totalSteps() { - return STEPS.length; + return context.JK.dkeys(STEPS).length; } function beforeHideStep() { @@ -62,7 +63,7 @@ var stepInfo = STEPS[step]; if(stepInfo.handleNext) { var result = stepInfo.handleNext.call(stepInfo); - if(!result) {return false;} + if(result === false) {return false;} } previousStep = step; @@ -81,25 +82,32 @@ $currentWizardStep = $nextWizardStep; + context.JK.GA.virtualPageView(location.pathname + location.search + location.hash, $currentWizardStep.attr('dialog-title')); + $self.triggerHandler('step_changed', {step:step}); // update buttons var $wizardButtonsContent = $(context._.template($templateButtons.html(), {}, {variable: 'data'})); + $btnHelp = $wizardButtonsContent.find('.btn-help'); $btnBack = $wizardButtonsContent.find('.btn-back'); $btnNext = $wizardButtonsContent.find('.btn-next'); $btnClose = $wizardButtonsContent.find('.btn-close'); $btnCancel = $wizardButtonsContent.find('.btn-cancel'); + // hide help button if on last step + if (step == totalSteps() - 1) { + $btnHelp.hide(); + } // hide back button if 1st step or last step - if (step == 0 && step == totalSteps() - 1) { + if (step == 0 || step == totalSteps() - 1) { $btnBack.hide(); } - // hide next button if not on last step + // hide next button if on last step if (step == totalSteps() - 1) { $btnNext.hide(); } - // hide close if on last step + // hide close if not on last step if (step != totalSteps() - 1) { $btnClose.hide(); } @@ -130,7 +138,7 @@ previousStep = null; - step = args.d1; + step = args != null ? args.d1 : 0; if (!step) step = 0; step = parseInt(step); moveToStep(); @@ -177,7 +185,6 @@ } function initialize(_$dialog, _$wizardSteps, _STEPS) { - $dialog = _$dialog; $wizardSteps = _$wizardSteps; STEPS = _STEPS; diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index a32df7763..526daa62f 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -678,9 +678,9 @@ function startNewFtue() { var step = 0; - setWizardStep(step); - wizardShowFunctions[step](); - showDialog('ftue'); + //setWizardStep(step); + //wizardShowFunctions[step](); + showDialog('gear-wizard'); } function setWizardStep(targetStepId) { diff --git a/web/app/assets/javascripts/trackHelpers.js b/web/app/assets/javascripts/trackHelpers.js index 185645b81..384fd5dc3 100644 --- a/web/app/assets/javascripts/trackHelpers.js +++ b/web/app/assets/javascripts/trackHelpers.js @@ -46,7 +46,13 @@ for (i=0; i < localMusicTracks.length; i++) { var track = {}; track.client_track_id = localMusicTracks[i].id; - track.instrument_id = context.JK.client_to_server_instrument_map[localMusicTracks[i].instrument_id].server_id; + + if(localMusicTracks[i].instrument_id === 0) { + track.instrument_id = context.JK.server_to_client_instrument_map["Other"].server_id; + } + else { + track.instrument_id = context.JK.client_to_server_instrument_map[localMusicTracks[i].instrument_id].server_id; + } if (localMusicTracks[i].stereo) { track.sound = "stereo"; } diff --git a/web/app/assets/stylesheets/client/gearWizard.css.scss b/web/app/assets/stylesheets/client/gearWizard.css.scss index 1e4404b66..05ec3e591 100644 --- a/web/app/assets/stylesheets/client/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/gearWizard.css.scss @@ -57,7 +57,7 @@ margin-top:20px; } - .ftue-buttons { + .wizard-buttons { position: absolute; bottom: 0; width:100%; @@ -67,7 +67,7 @@ } - .ftue-buttons-holder { + .wizard-buttons-holder { float:right; } @@ -151,6 +151,9 @@ width:25%; } + .watch-video { + margin-top:29px; + } .select-audio-input-device { margin-bottom:20px; } @@ -378,6 +381,10 @@ } } + .watch-video { + margin-top:45px; + } + .icon-instrument-select { padding:3px 0; // to combine 24 of .current-instrument + 3x on either side margin:0 auto 15px; // 15 margin-bottom to match tracks on the left @@ -478,13 +485,21 @@ &[track-count="2"] { .ftue-input { - width:50%; + width:49%; display:inline-block; + + &:nth-of-type(1) { + float:left; + &:after { + float:left; + content: ','; + padding-right:3px; + } + } + &:nth-of-type(2) { + float:right; + } } - /**.ftue-input:nth-child(1)::before { - content: ','; - padding-right:3px; - }*/ } } } @@ -499,6 +514,9 @@ } } + .watch-video { + margin-top:97px; + } .voicechat-option { @@ -625,6 +643,10 @@ margin-top:22px; } + .watch-video { + margin-top:90px; + } + a.start-network-test { margin-top:20px; } @@ -635,6 +657,8 @@ color:white; font-size:20px; background-color:#222; + text-align:center; + margin-bottom:20px; &.good { background-color: #72a43b; @@ -647,6 +671,63 @@ } } + .scoring-bar { + width:100%; + height:20px; + left:0; + position:relative; + //display:inline-block; + display:none; + + .current-score { + background-color:gray; + border-right:1px solid white; + border-top:1px solid #ccc; + border-bottom:1px solid #ccc; + border-left:1px solid #ccc; + height:20px; + display:inline-block; + position:relative; + left:0; + min-width:55px; + text-align:left; + padding-left:5px; + @include border_box_sizing; + font-size:12px; + color:white; + + .subscore { + font-size:10px; + color:white; + bottom:-15px; + right:-16px; + position:absolute; + } + } + + .good-marker { + position:absolute; + text-align:center; + left:95%; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #72a43b; + margin-left:-5px; + top:-7px; + } + + .good-line { + position:absolute; + height:100%; + left:95%; + width:1px; + background-color: #72a43b; + margin-left:-0.5px; + top:0; + } + } .network-test-text { } @@ -655,16 +736,41 @@ height: 248px ! important; @include border_box_sizing; &.testing { + + text-align:left; .network-test-score { - font-size:16px; + display:none; + } + + .scoring-bar { + display:inline-block; + margin-bottom:10px; } .network-test-text { - background-image: url('/assets/shared/spinner.gif'); - background-repeat:no-repeat; - background-position:center; + //background-image: url('/assets/shared/spinner.gif'); + //background-repeat:no-repeat; + //background-position:center; //width:128px; - height:128px; + //height:128px; + } + } + + &.good { + .network-test-score { + background-color: #72a43b; + } + } + + &.acceptable { + .network-test-score { + background-color: #D6A800; + } + } + + &.bad { + .network-test-score { + background-color: #7B0C00; } } } @@ -674,10 +780,15 @@ .wizard-step-content .wizard-step-column { &:nth-of-type(1) { width:50%; + height:350px; } &:nth-of-type(2) { width:50%; } + + ul { + margin-bottom:20px; + } } } diff --git a/web/app/controllers/clients_controller.rb b/web/app/controllers/clients_controller.rb index 1a44261dd..f904fd7d5 100644 --- a/web/app/controllers/clients_controller.rb +++ b/web/app/controllers/clients_controller.rb @@ -52,6 +52,9 @@ class ClientsController < ApplicationController gon.fp_upload_dir = Rails.application.config.filepicker_upload_dir gon.allow_force_native_client = Rails.application.config.allow_force_native_client gon.ftue_io_wait_time = Rails.application.config.ftue_io_wait_time + gon.ftue_packet_rate_treshold = Rails.application.config.ftue_packet_rate_treshold + gon.ftue_network_test_duration = Rails.application.config.ftue_network_test_duration + gon.ftue_network_test_max_clients = Rails.application.config.ftue_network_test_max_clients # is this the native client or browser? @nativeClient = is_native_client? diff --git a/web/app/views/clients/gear/_buttons.html.haml b/web/app/views/clients/gear/_buttons.html.haml index 5f3209ce9..3ab9f4629 100644 --- a/web/app/views/clients/gear/_buttons.html.haml +++ b/web/app/views/clients/gear/_buttons.html.haml @@ -4,9 +4,9 @@ - total_steps = 7 -.ftue-buttons - .ftue-buttons-holder - %a.button-grey{href: '#'} HELP +.wizard-buttons + .wizard-buttons-holder + %a.button-grey.btn-help{href: '#'} HELP - if step > 0 && step != total_steps %a.button-orange.btn-back{href:'#'} BACK - if step != total_steps diff --git a/web/app/views/clients/gear/_gear_wizard.html.haml b/web/app/views/clients/gear/_gear_wizard.html.haml index 7d5e5195a..81274aabd 100644 --- a/web/app/views/clients/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/gear/_gear_wizard.html.haml @@ -217,10 +217,10 @@ .wizard-step-column %h2 Instructions .ftue-box.instructions - Find the Direct Monitoring control on your audio interface.
%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 Check that computer is connected to router using Ethernet cable. + %li Click Start Network Test button. + %li View test results. .center %a.button-orange.watch-video{href:'#'} WATCH VIDEO .wizard-step-column @@ -232,7 +232,14 @@ .wizard-step-column %h2 Test Results .network-test-results.ftue-box + .scoring-bar + .current-score + testing... + .subscore + .good-marker + .good-line .network-test-score + .scored-clients .network-test-text @@ -246,28 +253,26 @@ %p Have fun and thanks for joining us! %p — Team JamKazam .wizard-step-column - %h2 - Tutorial Videos - %ul - %li - %a Creating a Session - %li - %a Finding a Session - %li - %a Playing in a Session - %li - %a Connecting with Other Musicians - %li - %a Making and Sharing Recordings - %li - %a Broadcasting Your Sessions - %h2 - Other Valuable Resource Links - %ul - %li - %a JamKazam Support Center - %li - %a JamKazam Community Forum + %h2 Tutorial Videos + %ul + %li + %a Creating a Session + %li + %a Finding a Session + %li + %a Playing in a Session + %li + %a Connecting with Other Musicians + %li + %a Making and Sharing Recordings + %li + %a Broadcasting Your Sessions + %h2 Other Valuable Resource Links + %ul + %li + %a JamKazam Support Center + %li + %a JamKazam Community Forum .wizard-buttons %script{type: 'text/template', id: 'template-ftuesteps'} @@ -289,9 +294,9 @@ %script{type: 'text/template', id: 'template-wizard-buttons'} - .ftue-buttons-holder + .wizard-buttons-holder %a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL - %a.button-grey{href: '#'} HELP + %a.button-grey.btn-help{href: '#'} HELP %a.button-orange.btn-back{href:'#'} BACK %a.button-orange.btn-next{href:'#'} NEXT %a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE diff --git a/web/config/application.rb b/web/config/application.rb index 2c8ae1125..1654b9fb0 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -235,5 +235,11 @@ if defined?(Bundler) # how long should the frontend wait for the IO to stabilize before asking for a IO score? config.ftue_io_wait_time = 10 + # what should the threshold be for us to say, 'this person can't play at this rate' during the network test + config.ftue_packet_rate_treshold = 0.95 + # how long to test at each network test step + config.ftue_network_test_duration = 10 + # max number of people to test + config.ftue_network_test_max_clients = 8 end end diff --git a/web/spec/controllers/api_latency_tests_controller_spec.rb b/web/spec/controllers/api_latency_tests_controller_spec.rb index 8ef24e6c2..fb773b3ef 100644 --- a/web/spec/controllers/api_latency_tests_controller_spec.rb +++ b/web/spec/controllers/api_latency_tests_controller_spec.rb @@ -1,7 +1,20 @@ require 'spec_helper' -describe ApiFeedsController do +describe ApiLatencyTestersController do render_views + before(:each) do + LatencyTester.delete_all + end + + describe "match" do + it "insists on login" do + get :match + response.status.should == 403 + end + + it "throws 404 if no latency testers" + end end + diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index 43ab68cc9..3caee5042 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -480,10 +480,10 @@ module JamWebsockets default_expire = @connect_time_expire_client end - heartbeat_interval = user.try(:heartbeat_interval_client) || default_heartbeat + heartbeat_interval = (user && user.heartbeat_interval_client) || default_heartbeat heartbeat_interval = heartbeat_interval.to_i heartbeat_interval = default_heartbeat if heartbeat_interval == 0 # protect against bad config - connection_expire_time = user.try(:connection_expire_time_client) || default_expire + connection_expire_time = (user && user.connection_expire_time_client) || default_expire connection_expire_time = connection_expire_time.to_i connection_expire_time = default_expire if connection_expire_time == 0 # protect against bad config connection_stale_time = default_stale # no user override exists for this; not a very meaningful time right now @@ -825,7 +825,7 @@ module JamWebsockets # by not catching any exception here, a PermissionError will be thrown if this isn't valid # if for some reason the client is trying to send to a client that it doesn't # belong to - access_p2p(to_client_id, context.user, client_msg) + #access_p2p(to_client_id, context.user, client_msg) if to_client_id.nil? || to_client_id == 'undefined' # javascript translates to 'undefined' in many cases raise SessionError, "empty client_id specified in peer-to-peer message"