merge develop

This commit is contained in:
Brian Smith 2014-06-23 01:09:03 -04:00
commit e211871168
70 changed files with 1515 additions and 597 deletions

View File

@ -116,8 +116,8 @@ ActiveAdmin.register_page 'Feed' do
mixes.each do |mix|
li do
text_node "Created At: #{mix.created_at.strftime('%b %d %Y, %H:%M')}, "
text_node "Started At: #{mix.started_at.strftime('%b %d %Y, %H:%M')}, "
text_node "Completed At: #{mix.completed_at.strftime('%b %d %Y, %H:%M')}, "
text_node "Started At: #{mix.started_at ? mix.started_at.strftime('%b %d %Y, %H:%M') : ''}, "
text_node "Completed At: #{mix.completed_at ? mix.completed_at.strftime('%b %d %Y, %H:%M') : ''}, "
text_node "Error Count: #{mix.error_count}, "
text_node "Error Reason: #{mix.error_reason}, "
text_node "Error Detail: #{mix.error_detail}, "

View File

@ -10,7 +10,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
filter :email
filter :first_name
filter :last_name
filter :internet_service_provider
filter :created_at
filter :updated_at
@ -28,7 +27,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
ff.input :musician
ff.input :can_invite
ff.input :photo_url
ff.input :internet_service_provider
ff.input :session_settings
end
ff.inputs "Signup" do
@ -53,7 +51,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
row :last_name
row :birth_date
row :gender
row :internet_service_provider
row :email_confirmed
row :image do user.photo_url ? image_tag(user.photo_url) : '' end
row :session_settings
@ -81,7 +78,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
column :last_name
column :birth_date
column :gender
column :internet_service_provider
column :email_confirmed
column :photo_url
column :session_settings

View File

@ -6,7 +6,7 @@
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, :internet_service_provider, :session_settings, :confirm_url, :email_template # :invite_email
attr_accessible :admin, :raw_password, :musician, :can_invite, :photo_url, :session_settings, :confirm_url, :email_template # :invite_email
def raw_password
''

View File

@ -3,6 +3,11 @@ description "jam-admin"
start on startup
start on runlevel [2345]
stop on runlevel [016]
limit nofile 20000 20000
limit core unlimited unlimited
respawn
respawn limit 10 5
pre-start script
set -e

View File

@ -36,6 +36,7 @@ FactoryGirl.define do
addr 0
locidispid 0
client_type 'client'
sequence(:channel_id) { |n| "Channel#{n}"}
association :user, factory: :user
end

View File

@ -179,4 +179,5 @@ sms_index.sql
music_sessions_description_search.sql
rsvp_slots_prof_level.sql
add_file_name_music_notation.sql
change_scheduled_start_music_session.sql
change_scheduled_start_music_session.sql
connection_channel_id.sql

View File

@ -0,0 +1 @@
ALTER TABLE connections ADD COLUMN channel_id VARCHAR(256) NOT NULL;

View File

@ -86,6 +86,7 @@ message ClientMessage {
SERVER_REJECTION_ERROR = 1005;
SERVER_PERMISSION_ERROR = 1010;
SERVER_BAD_STATE_ERROR = 1015;
SERVER_DUPLICATE_CLIENT_ERROR = 1016;
}
// Identifies which inner message is filled in
@ -179,6 +180,7 @@ message ClientMessage {
optional ServerRejectionError server_rejection_error = 1005;
optional ServerPermissionError server_permission_error = 1010;
optional ServerBadStateError server_bad_state_error = 1015;
optional ServerDuplicateClientError server_duplicate_client_error = 1016;
}
// route_to: server
@ -636,4 +638,9 @@ message ServerBadStateError {
optional string error_msg = 1;
}
// route_to: client
// this indicates that we detected another client with the same client ID
message ServerDuplicateClientError {
}

View File

@ -44,7 +44,7 @@ module JamRuby
end
# reclaim the existing connection, if ip_address is not nil then perhaps a new address as well
def reconnect(conn, reconnect_music_session_id, ip_address, connection_stale_time, connection_expire_time)
def reconnect(conn, channel_id, reconnect_music_session_id, ip_address, connection_stale_time, connection_expire_time)
music_session_id = nil
reconnected = false
@ -86,7 +86,7 @@ module JamRuby
end
sql =<<SQL
UPDATE connections SET (aasm_state, updated_at, music_session_id, joined_session_at, stale_time, expire_time) = ('#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression}, #{joined_session_at_expression}, #{connection_stale_time}, #{connection_expire_time})
UPDATE connections SET (channel_id, aasm_state, updated_at, music_session_id, joined_session_at, stale_time, expire_time) = ('#{channel_id}', '#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression}, #{joined_session_at_expression}, #{connection_stale_time}, #{connection_expire_time})
WHERE
client_id = '#{conn.client_id}'
RETURNING music_session_id
@ -184,7 +184,7 @@ SQL
# this number is used by notification logic elsewhere to know
# 'oh the user joined for the 1st time, so send a friend update', or
# 'don't bother because the user has connected somewhere else already'
def create_connection(user_id, client_id, ip_address, client_type, connection_stale_time, connection_expire_time, &blk)
def create_connection(user_id, client_id, channel_id, ip_address, client_type, connection_stale_time, connection_expire_time, &blk)
# validate client_type
raise "invalid client_type: #{client_type}" if client_type != 'client' && client_type != 'browser'
@ -216,8 +216,8 @@ SQL
lock_connections(conn)
conn.exec("INSERT INTO connections (user_id, client_id, ip_address, client_type, addr, locidispid, aasm_state, stale_time, expire_time) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
[user_id, client_id, ip_address, client_type, addr, locidispid, Connection::CONNECT_STATE.to_s, connection_stale_time, connection_expire_time]).clear
conn.exec("INSERT INTO connections (user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, aasm_state, stale_time, expire_time) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
[user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, Connection::CONNECT_STATE.to_s, connection_stale_time, connection_expire_time]).clear
# we just created a new connection-if this is the first time the user has shown up, we need to send out a message to his friends
conn.exec("SELECT count(user_id) FROM connections WHERE user_id = $1", [user_id]) do |result|

View File

@ -222,6 +222,17 @@ module JamRuby
)
end
# create a server bad state error
def server_duplicate_client_error
error = Jampb::ServerDuplicateClientError.new()
Jampb::ClientMessage.new(
:type => ClientMessage::Type::SERVER_DUPLICATE_CLIENT_ERROR,
:route_to => CLIENT_TARGET,
:server_duplicate_client_error => error
)
end
###################################### NOTIFICATIONS ######################################
# create a friend update message

View File

@ -19,6 +19,7 @@ module JamRuby
# or bootstrap a new latency_tester
def self.connect(options)
client_id = options[:client_id]
channel_id = options[:channel_id]
ip_address = options[:ip_address]
connection_stale_time = options[:connection_stale_time]
connection_expire_time = options[:connection_expire_time]
@ -69,6 +70,7 @@ module JamRuby
connection.stale_time = connection_stale_time
connection.expire_time = connection_expire_time
connection.as_musician = false
connection.channel_id = channel_id
unless connection.save
return connection
end

View File

@ -125,7 +125,8 @@ FactoryGirl.define do
addr 0
locidispid 0
client_type 'client'
last_jam_audio_latency { user.last_jam_audio_latency if user }
last_jam_audio_latency { user.last_jam_audio_latency if user }
# sequence(:channel_id) { |n| "Channel#{n}"}
association :user, factory: :user
end

View File

@ -9,6 +9,8 @@ describe ConnectionManager do
STALE_BUT_NOT_EXPIRED = 50
DEFINITELY_EXPIRED = 70
let(:channel_id) {'1'}
before do
@conn = PG::Connection.new(:dbname => SpecDb::TEST_DB_NAME, :user => "postgres", :password => "postgres", :host => "localhost")
@connman = ConnectionManager.new(:conn => @conn)
@ -46,8 +48,8 @@ describe ConnectionManager do
user.save!
user = nil
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
expect { @connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) }.to raise_error(PG::Error)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
expect { @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) }.to raise_error(PG::Error)
end
it "create connection then delete it" do
@ -56,7 +58,7 @@ describe ConnectionManager do
#user_id = create_user("test", "user2", "user2@jamkazam.com")
user = FactoryGirl.create(:user)
count = @connman.create_connection(user.id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
count.should == 1
@ -86,7 +88,7 @@ describe ConnectionManager do
#user_id = create_user("test", "user2", "user2@jamkazam.com")
user = FactoryGirl.create(:user)
count = @connman.create_connection(user.id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
count.should == 1
@ -102,7 +104,7 @@ describe ConnectionManager do
cc.addr.should == 0x01010101
cc.locidispid.should == 17192000002
@connman.reconnect(cc, nil, "33.1.2.3", STALE_TIME, EXPIRE_TIME)
@connman.reconnect(cc, channel_id, nil, "33.1.2.3", STALE_TIME, EXPIRE_TIME)
cc = Connection.find_by_client_id!(client_id)
cc.connected?.should be_true
@ -215,7 +217,7 @@ describe ConnectionManager do
it "flag stale connection" do
client_id = "client_id8"
user_id = create_user("test", "user8", "user8@jamkazam.com")
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
num = JamRuby::Connection.count(:conditions => ['aasm_state = ?','connected'])
num.should == 1
@ -256,7 +258,7 @@ describe ConnectionManager do
it "expires stale connection" do
client_id = "client_id8"
user_id = create_user("test", "user8", "user8@jamkazam.com")
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
conn = Connection.find_by_client_id(client_id)
set_updated_at(conn, Time.now - STALE_BUT_NOT_EXPIRED)
@ -282,7 +284,7 @@ describe ConnectionManager do
music_session_id = music_session.id
user = User.find(user_id)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
connection.errors.any?.should be_false
@ -318,8 +320,8 @@ describe ConnectionManager do
client_id2 = "client_id10.12"
user_id = create_user("test", "user10.11", "user10.11@jamkazam.com", :musician => true)
user_id2 = create_user("test", "user10.12", "user10.12@jamkazam.com", :musician => false)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id2, client_id2, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id2, client_id2, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
music_session = FactoryGirl.create(:active_music_session, user_id: user_id)
music_session_id = music_session.id
@ -338,7 +340,7 @@ describe ConnectionManager do
client_id = "client_id10.2"
user_id = create_user("test", "user10.2", "user10.2@jamkazam.com")
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
music_session = FactoryGirl.create(:active_music_session, user_id: user_id)
user = User.find(user_id)
@ -354,8 +356,8 @@ describe ConnectionManager do
fan_client_id = "client_id10.4"
musician_id = create_user("test", "user10.3", "user10.3@jamkazam.com")
fan_id = create_user("test", "user10.4", "user10.4@jamkazam.com", :musician => false)
@connman.create_connection(musician_id, musician_client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(fan_id, fan_client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(musician_id, musician_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(fan_id, fan_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
music_session = FactoryGirl.create(:active_music_session, :fan_access => false, user_id: musician_id)
music_session_id = music_session.id
@ -379,7 +381,7 @@ describe ConnectionManager do
music_session_id = music_session.id
user = User.find(user_id2)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
# specify real user id, but not associated with this session
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound)
end
@ -391,7 +393,7 @@ describe ConnectionManager do
user = User.find(user_id)
music_session = ActiveMusicSession.new
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
connection.errors.size.should == 1
connection.errors.get(:music_session).should == [ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED]
@ -405,7 +407,7 @@ describe ConnectionManager do
music_session_id = music_session.id
user = User.find(user_id2)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
# specify real user id, but not associated with this session
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound)
end
@ -419,7 +421,7 @@ describe ConnectionManager do
user = User.find(user_id)
dummy_music_session = ActiveMusicSession.new
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError)
end
@ -434,7 +436,7 @@ describe ConnectionManager do
dummy_music_session = ActiveMusicSession.new
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError)
end
@ -447,7 +449,7 @@ describe ConnectionManager do
music_session_id = music_session.id
user = User.find(user_id)
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
assert_session_exists(music_session_id, true)
@ -490,7 +492,7 @@ describe ConnectionManager do
user = User.find(user_id)
client_id1 = Faker::Number.number(20)
@connman.create_connection(user_id, client_id1, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id1, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
music_session1 = FactoryGirl.create(:active_music_session, :user_id => user_id)
connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS, 10)
connection1.errors.size.should == 0

View File

@ -2,7 +2,7 @@ require 'spec_helper'
describe LatencyTester do
let(:params) {{client_id: 'abc', ip_address: '10.1.1.1', connection_stale_time:40, connection_expire_time:60} }
let(:params) {{client_id: 'abc', ip_address: '10.1.1.1', connection_stale_time:40, connection_expire_time:60, channel_id: '1'} }
it "success" do
latency_tester = FactoryGirl.create(:latency_tester)

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

View File

@ -75,7 +75,8 @@
SERVER_BAD_STATE_RECOVERED: "SERVER_BAD_STATE_RECOVERED",
SERVER_GENERIC_ERROR : "SERVER_GENERIC_ERROR",
SERVER_REJECTION_ERROR : "SERVER_REJECTION_ERROR",
SERVER_BAD_STATE_ERROR : "SERVER_BAD_STATE_ERROR"
SERVER_BAD_STATE_ERROR : "SERVER_BAD_STATE_ERROR",
SERVER_DUPLICATE_CLIENT_ERROR : "SERVER_DUPLICATE_CLIENT_ERROR"
};
var route_to = context.JK.RouteToPrefix = {

View File

@ -33,6 +33,7 @@
var notificationLastSeenAt = undefined;
var notificationLastSeen = undefined;
var clientClosedConnection = false;
var initialConnectAttempt = true;
// reconnection logic
var connectDeferred = null;
@ -85,6 +86,7 @@
// handles logic if the websocket connection closes, and if it was in error then also prompt for reconnect
function closedCleanup(in_error) {
server.connected = false;
// stop future heartbeats
if (heartbeatInterval != null) {
@ -98,15 +100,16 @@
heartbeatAckCheckInterval = null;
}
if (server.connected) {
server.connected = false;
if (app.clientUpdating) {
// we don't want to do a 'cover the whole screen' dialog
// because the client update is already showing.
return;
}
clearConnectTimeout();
server.reconnecting = true;
// noReconnect is a global to suppress reconnect behavior, so check it first
// we don't show any reconnect dialog on the initial connect; so we have this one-time flag
// to cause reconnects in the case that the websocket is down on the initially connect
if (!server.noReconnect &&
(initialConnectAttempt || !server.connecting)) {
server.connecting = true;
initialConnectAttempt = false;
var result = activeElementEvent('beforeDisconnect');
@ -174,12 +177,16 @@
return mode == "client";
}
function loggedIn(header, payload) {
if (!connectTimeout) {
function clearConnectTimeout() {
if (connectTimeout) {
clearTimeout(connectTimeout);
connectTimeout = null;
}
}
function loggedIn(header, payload) {
clearConnectTimeout();
heartbeatStateReset();
@ -191,6 +198,13 @@
$.cookie('client_id', payload.client_id);
}
// this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios
// where there is no device on startup for the current profile.
// So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
// it to go through unless we've passed through .OnLoggedIn
server.connecting = false;
initialConnectAttempt = false;
heartbeatMS = payload.heartbeat_interval * 1000;
connection_expire_time = payload.connection_expire_time * 1000;
logger.info("jamkazam.js.loggedIn(): clientId=" + app.clientId + ", heartbeat=" + payload.heartbeat_interval + "s, expire_time=" + payload.connection_expire_time + 's');
@ -240,7 +254,7 @@
var start = new Date().getTime();
server.connect()
.done(function () {
guardAgainstRapidTransition(start, performReconnect);
guardAgainstRapidTransition(start, finishReconnect);
})
.fail(function () {
guardAgainstRapidTransition(start, closedOnReconnectAttempt);
@ -252,7 +266,7 @@
failedReconnect();
}
function performReconnect() {
function finishReconnect() {
if(!clientClosedConnection) {
lastDisconnectedReason = 'WEBSOCKET_CLOSED_REMOTELY'
@ -277,7 +291,6 @@
else {
window.location.reload();
}
server.reconnecting = false;
});
@ -459,16 +472,29 @@
connectDeferred = new $.Deferred();
channelId = context.JK.generateUUID(); // create a new channel ID for every websocket connection
var uri = context.gon.websocket_gateway_uri + '?channel_id=' + channelId; // Set in index.html.erb.
// we will log in one of 3 ways:
// browser: use session cookie, and auth with token
// native: use session cookie, and use the token
// latency_tester: ask for client ID from backend; no token (trusted)
var params = {
channel_id: channelId,
token: $.cookie("remember_token"),
client_type: isClientMode() ? context.JK.clientType : 'latency_tester',
client_id: isClientMode() ? (gon.global.env == "development" ? $.cookie('client_id') : null): context.jamClient.clientID
}
var uri = context.gon.websocket_gateway_uri + '?' + $.param(params); // Set in index.html.erb.
logger.debug("connecting websocket: " + uri);
server.connecting = true;
server.socket = new context.WebSocket(uri);
server.socket.onopen = server.onOpen;
server.socket.onmessage = server.onMessage;
server.socket.onclose = server.onClose;
connectTimeout = setTimeout(function () {
logger.debug("connection timeout fired")
connectTimeout = null;
if(connectDeferred.state() === 'pending') {
@ -505,12 +531,7 @@
server.onOpen = function () {
logger.debug("server.onOpen");
if(isClientMode()) {
server.rememberLogin();
}
else {
server.latencyTesterLogin();
}
// we should receive LOGIN_ACK very soon. we already set a timer elsewhere to give 4 seconds to receive it
};
server.onMessage = function (e) {
@ -675,7 +696,6 @@
}
this.initialize = initialize;
this.initiateReconnect = initiateReconnect;
return this;
}

View File

@ -33,11 +33,11 @@
//= require jquery.pulse
//= require jquery.browser
//= require jquery.custom-protocol
//= require AAC_underscore
//= require AAA_Log
//= require globals
//= require AAB_message_factory
//= require jam_rest
//= require AAC_underscore
//= require utils
//= require custom_controls
//= require_directory .

View File

@ -0,0 +1,108 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
// Class to intercept and delegate out all backend alerts as necessary
// better if modules that needed certain events would just register for them.
context.JK.BackendAlerts = function(app) {
var $document = $(document);
var ALERT_TYPES = context.JK.ALERT_TYPES;
function onNoValidAudioConfig(type, text) {
app.notify({
"title": ALERT_TYPES[type].title,
"text": text,
"icon_url": "/assets/content/icon_alert_big.png"
});
context.location = "/client#"; // leaveSession will be called in beforeHide below
}
function onStunEvent() {
var testResults = context.jamClient.NetworkTestResult();
$.each(testResults, function (index, val) {
if (val.bStunFailed) {
// if true we could not reach a stun server
}
else if (val.bRemoteUdpBocked) {
// if true the user cannot communicate with peer via UDP, although they could do LAN based session
}
});
}
function onGenericEvent(type, text) {
context.setTimeout(function() {
var alert = ALERT_TYPES[type];
if(alert && alert.title) {
app.notify({
"title": ALERT_TYPES[type].title,
"text": text,
"icon_url": "/assets/content/icon_alert_big.png"
});
}
else {
logger.debug("Unknown Backend Event type %o, data %o", type, text)
}
}, 1);
}
function alertCallback(type, text) {
function timeCallback() {
var start = new Date();
setTimeout(function() {
var timed = new Date().getTime() - start.getTime();
if(timed > 250) {
logger.warn("SLOW AlERT_CALLBACK. type: %o text: %o time: %o", type, text, timed);
}
}, 1);
}
timeCallback();
logger.debug("alert callback", type, text);
var alertData = ALERT_TYPES[type];
if(alertData) {
$document.triggerHandler(alertData.name, alertData);
}
if (type === 2) { // BACKEND_MIXER_CHANGE
context.JK.CurrentSessionModel.onBackendMixerChanged(type, text)
}
else if (type === 19) { // NO_VALID_AUDIO_CONFIG
onNoValidAudioConfig(type, text);
}
else if (type === 24) {
onStunEvent();
}
else if (type === 26) { // DEAD_USER_REMOVE_EVENT
context.JK.CurrentSessionModel.onDeadUserRemove(type, text);
}
else if (type === 27) { // WINDOW_CLOSE_BACKGROUND_MODE
context.JK.CurrentSessionModel.onWindowBackgrounded(type, text);
}
else if(type != 30 && type != 31 && type != 21){ // these are handled elsewhere
onGenericEvent(type, text);
}
}
function initialize() {
context.jamClient.SessionSetAlertCallback("JK.AlertCallback");
}
this.initialize = initialize;
context.JK.AlertCallback = alertCallback;
return this;
}
})(window, jQuery);

View File

@ -10,9 +10,17 @@
var logger = context.JK.logger;
var $banner = null;
// you can also do
// * showAlert('title', 'text')
// * showAlert('text')
function showAlert(options) {
if (typeof options == 'string' || options instanceof String) {
options = {html:options};
if(arguments.length == 2) {
options = {title: options, html:arguments[1]}
}
else {
options = {html:options};
}
}
options.type = 'alert'
return show(options);
@ -24,6 +32,13 @@
var text = options.text;
var html = options.html;
if(!options.title) {
options.title = 'alert'
}
var $h1 = $banner.find('h1');
$h1.html(options.title);
var newContent = null;
if (html) {
newContent = $('#banner .dialog-inner').html(html);

View File

@ -274,7 +274,6 @@
if(response.next == null) {
// if we less results than asked for, end searching
$chatMessagesScroller.infinitescroll('pause');
logger.debug("end of chatss");
if(currentPage > 0) {
// there are bugs with infinitescroll not removing the 'loading'.

View File

@ -80,7 +80,8 @@
}
function validateVoiceChatSettings() {
return voiceChatHelper.trySave();
//return voiceChatHelper.trySave(); // not necessary since we use saveImmediate now
return true;
}
function showMusicAudioPanel() {
@ -117,7 +118,9 @@
});
$btnCancel.click(function() {
app.layout.closeDialog('configure-tracks')
if(voiceChatHelper.cancel()) {
app.layout.closeDialog('configure-tracks')
}
return false;
});
@ -185,10 +188,12 @@
configureTracksHelper.reset();
voiceChatHelper.reset();
voiceChatHelper.beforeShow();
}
function afterHide() {
voiceChatHelper.beforeHide();
}
@ -212,11 +217,11 @@
$btnAddNewGear = $dialog.find('.btn-add-new-audio-gear');
$btnUpdateTrackSettings = $dialog.find('.btn-update-settings');
configureTracksHelper = new JK.ConfigureTracksHelper(app);
configureTracksHelper = new context.JK.ConfigureTracksHelper(app);
configureTracksHelper.initialize($dialog);
voiceChatHelper = new JK.VoiceChatHelper(app);
voiceChatHelper.initialize($dialog, false);
voiceChatHelper = new context.JK.VoiceChatHelper(app);
voiceChatHelper.initialize($dialog, 'configure_track_dialog', true, {vuType: "vertical", lightCount: 10, lightWidth: 3, lightHeight: 17}, 191);
events();
}

View File

@ -22,25 +22,30 @@
var $instrumentsHolder = null;
var isDragging = false;
function removeHoverer($hoverChannel) {
var $channel = $hoverChannel.data('original')
$channel.data('cloned', null);
$hoverChannel.remove();
}
function hoverIn($channel) {
if(isDragging) return;
$channel.css('color', 'white')
var $container = $channel.closest('.target');
var inTarget = $container.length > 0;
if(!inTarget) {
$container = $channel.closest('.channels-holder')
}
$channel.data('container', $container)
$channel.addClass('hovering');
var $inputs = $container.find('.ftue-input');
var index = $inputs.index($channel);
$channel.css('background-color', '#333');
// $channel.css('padding', '0 5px');
if(inTarget) {
$channel.data('container', $container)
$channel.addClass('hovering');
$channel.css('color', 'white')
$channel.css('background-color', '#333');
$channel.css('border', '#333');
$channel.css('border-radius', '2px');
$channel.css('min-width', '49%');
@ -49,10 +54,44 @@
$container.css('overflow', 'visible');
}
else {
// TODO: make the unassigned work
// $channel.css('min-width', $channel.css('width'));
// $channel.css('position', 'absolute');
// $container.addClass('compensate');
var $offsetParent = $channel.offsetParent();
var parentOffset = $offsetParent.offset();
var hoverChannel = $(context._.template($templateAssignablePort.html(), {id: 'bogus', name: $channel.text()}, { variable: 'data' }));
hoverChannel
.css('position', 'absolute')
.css('color', 'white')
.css('left', $channel.position().left)
.css('top', $channel.position().top)
.css('background-color', '#333')
.css('min-width', $channel.width())
.css('min-height', $channel.height())
.css('z-index', 10000)
.css('background-position', '4px 6px')
.css('padding-left', '14px')
.data('original', $channel);
$channel.data('cloned', hoverChannel);
hoverChannel
.hover(function(e) {
var hoverCheckTimeout = hoverChannel.data('hoverCheckTimeout');
if(hoverCheckTimeout) {
clearTimeout(hoverCheckTimeout);
hoverChannel.data('hoverCheckTimeout', null);
}
}, function() { removeHoverer($(this)); })
.mousedown(function(e) {
// because we have obscured the element the user wants to drag,
// we proxy a mousedown on the hover-element to the covered .ftue-input ($channel).
// this causes jquery.drag to get going even though the user clicked a different element
$channel.trigger(e)
})
hoverChannel.data('hoverCheckTimeout', setTimeout(function() {
// check if element has already been left
hoverChannel.data('hoverCheckTimeout', null);
removeHoverer(hoverChannel);
}, 500));
hoverChannel.prependTo($offsetParent);
}
$channel.css('z-index', 10000)
@ -69,6 +108,12 @@
}
function hoverOut($channel) {
var $cloned = $channel.data('cloned');
if($cloned) {
return; // let the cloned handle the rest of hover out logic when it's hovered-out
}
$channel
.removeClass('hovering')
.css('color', '')
@ -77,6 +122,8 @@
.css('width', '')
.css('background-color', '')
.css('padding', '')
.css('padding-left', '')
.css('background-position', '')
.css('border', '')
.css('border-radius', '')
.css('right', '')
@ -88,9 +135,9 @@
//var $container = $channel.closest('.target');
var $container = $channel.data('container');
$container.css('overflow', '')
$container.removeClass('compensate');
if($container) {
$container.css('overflow', '')
}
}
function fixClone($clone) {

View File

@ -13,7 +13,6 @@
var $draggingFader = null;
var draggingOrientation = null;
var subscribers = {};
var logger = g.JK.logger;
function faderClick(e) {
@ -21,7 +20,6 @@
var $fader = $(this);
draggingOrientation = $fader.attr('orientation');
var faderId = $fader.attr("fader-id");
var offset = $fader.offset();
var position = { top: e.pageY - offset.top, left: e.pageX - offset.left}
@ -31,12 +29,7 @@
return false;
}
// Notify subscribers of value change
g._.each(subscribers, function (changeFunc, index, list) {
if (faderId === index) {
changeFunc(faderId, faderPct, false);
}
});
$fader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: false})
setHandlePosition($fader, faderPct);
return false;
@ -110,7 +103,6 @@
}
function onFaderDrag(e, ui) {
var faderId = $draggingFader.attr("fader-id");
var faderPct = faderValue($draggingFader, e, ui.position);
// protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows
@ -118,12 +110,7 @@
return false;
}
// Notify subscribers of value change
g._.each(subscribers, function (changeFunc, index, list) {
if (faderId === index) {
changeFunc(faderId, faderPct, true);
}
});
$draggingFader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: true})
}
function onFaderDragStart(e, ui) {
@ -133,7 +120,6 @@
}
function onFaderDragStop(e, ui) {
var faderId = $draggingFader.attr("fader-id");
var faderPct = faderValue($draggingFader, e, ui.position);
// protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows
@ -142,12 +128,8 @@
return;
}
// Notify subscribers of value change
g._.each(subscribers, function (changeFunc, index, list) {
if (faderId === index) {
changeFunc(faderId, faderPct, false);
}
});
$draggingFader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: false});
$draggingFaderHandle = null;
$draggingFader = null;
draggingOrientation = null;
@ -159,26 +141,15 @@
g.JK.FaderHelpers = {
/**
* Subscribe to fader change events. Provide a subscriber id
* and a function in the form: change(faderId, newValue) which
* will be called anytime a fader changes value.
*/
subscribe: function (subscriberId, changeFunction) {
subscribers[subscriberId] = changeFunction;
},
/**
* Render a fader into the element identifed by the provided
* selector, with the provided options.
*/
renderFader: function (selector, userOptions) {
selector = $(selector);
if (userOptions === undefined) {
throw ("renderFader: userOptions is required");
}
if (!(userOptions.hasOwnProperty("faderId"))) {
throw ("renderFader: userOptions.faderId is required");
}
var renderDefaults = {
faderType: "vertical",
height: 83, // only used for vertical
@ -189,9 +160,9 @@
"#template-fader-h" : '#template-fader-v';
var templateSource = $(templateSelector).html();
$(selector).html(g._.template(templateSource, options));
selector.html(g._.template(templateSource, options));
$('div[control="fader-handle"]', $(selector)).draggable({
selector.find('div[control="fader-handle"]').draggable({
drag: onFaderDrag,
start: onFaderDragStart,
stop: onFaderDragStop,
@ -202,7 +173,7 @@
// Embed any custom styles, applied to the .fader below selector
if ("style" in options) {
for (var key in options.style) {
$(selector + ' .fader').css(key, options.style[key]);
selector.find(' .fader').css(key, options.style[key]);
}
}
},

View File

@ -110,11 +110,8 @@
});
}
function faderChange(faderId, newValue, dragging) {
var setFunction = faderMap[faderId];
// TODO - using hardcoded range of -80 to 20 for output levels.
var mixerLevel = newValue - 80; // Convert our [0-100] to [-80 - +20] range
setFunction(mixerLevel);
function faderChange(e, data) {
}
function setSaveButtonState($save, enabled) {
@ -1043,7 +1040,7 @@
context.JK.ftueVUCallback = function (dbValue, selector) {
// Convert DB into a value from 0.0 - 1.0
var floatValue = (dbValue + 80) / 100;
context.JK.VuHelpers.updateVU(selector, floatValue);
context.JK.VuHelpers.updateVU($(selector), floatValue);
};
context.JK.ftueAudioInputVUCallback = function (dbValue) {

View File

@ -85,10 +85,33 @@
google: 'google',
};
var audioTestFailReasons = {
latency : 'Latency',
ioVariance : 'ioVariance',
ioTarget : 'ioTarget'
}
var audioTestDataReasons = {
pass : 'Pass',
latencyFail : 'LatencyFail',
ioVarianceFail : 'ioVarianceFail',
ioTargetFail : 'ioTargetFail'
}
var networkTestFailReasons = {
stun : 'STUN',
bandwidth : 'Bandwidth',
packetRate : 'PacketRate',
jitter : 'Jitter',
jamerror : 'ServiceError',
noNetwork : 'NoNetwork'
}
var categories = {
register : "Register",
download : "DownloadClient",
audioTest : "AudioTest",
audioTestData : 'AudioTestData',
trackConfig : "AudioTrackConfig",
networkTest : "NetworkTest",
sessionCount : "SessionCount",
@ -105,7 +128,6 @@
jkFollow : 'jkFollow',
jkFavorite : 'jkFavorite',
jkComment : 'jkComment'
};
function translatePlatformForGA(platform) {
@ -289,18 +311,48 @@
context.ga('send', 'event', categories.networkTest, 'Passed', normalizedPlatform, numUsers);
}
function trackNetworkTestFailure(reason, data) {
assertOneOf(reason, networkTestFailReasons);
context.ga('send', 'event', categories.networkTest, 'Failed', reason, data);
}
function trackAudioTestCompletion(platform) {
var normalizedPlatform = translatePlatformForGA(platform);
context.ga('send', 'event', categories.audioTest, 'Passed', normalizedPlatform);
}
function trackAudioTestFailure(platform, reason, data) {
assertOneOf(reason, audioTestFailReasons);
var normalizedPlatform = translatePlatformForGA(platform);
if(normalizedPlatform == "Windows") {
var action = "FailedWin";
}
else if(normalizedPlatform == "Mac") {
var action = "FailedMac";
}
else {
var action = "FailedLinux";
}
context.ga('send', 'event', categories.audioTest, action, reason, data);
}
function trackConfigureTracksCompletion(platform) {
var normalizedPlatform = translatePlatformForGA(platform);
context.ga('send', 'event', categories.trackConfig, 'Passed', normalizedPlatform);
}
function trackAudioTestData(uniqueDeviceName, reason, data) {
assertOneOf(reason, audioTestDataReasons);
context.ga('send', 'event', categories.audioTestData, uniqueDeviceName, reason, data);
}
var GA = {};
GA.Categories = categories;
GA.SessionCreationTypes = sessionCreationTypes;
@ -310,11 +362,17 @@
GA.RecordingActions = recordingActions;
GA.BandActions = bandActions;
GA.JKSocialTargets = jkSocialTargets;
GA.AudioTestFailReasons = audioTestFailReasons;
GA.AudioTestDataReasons = audioTestDataReasons;
GA.NetworkTestFailReasons = networkTestFailReasons;
GA.trackRegister = trackRegister;
GA.trackDownload = trackDownload;
GA.trackFTUECompletion = trackFTUECompletion;
GA.trackNetworkTest = trackNetworkTest;
GA.trackNetworkTestFailure = trackNetworkTestFailure;
GA.trackAudioTestCompletion = trackAudioTestCompletion;
GA.trackAudioTestFailure = trackAudioTestFailure;
GA.trackAudioTestData = trackAudioTestData;
GA.trackConfigureTracksCompletion = trackConfigureTracksCompletion;
GA.trackSessionCount = trackSessionCount;
GA.trackSessionMusicians = trackSessionMusicians;

View File

@ -31,6 +31,118 @@
DIALOG_CLOSED : 'dialog_closed'
}
context.JK.ALERT_NAMES = {
NO_EVENT : 0,
BACKEND_ERROR : 1, //generic error - eg P2P message error
BACKEND_MIXER_CHANGE : 2, //event that controls have been regenerated
//network related
PACKET_JTR : 3,
PACKET_LOSS : 4,
PACKET_LATE : 5,
JTR_QUEUE_DEPTH : 6,
NETWORK_JTR : 7,
NETWORK_PING : 8,
BITRATE_THROTTLE_WARN : 9,
BANDWIDTH_LOW : 10,
//IO related events
INPUT_IO_RATE : 11,
INPUT_IO_JTR : 12,
OUTPUT_IO_RATE : 13,
OUTPUT_IO_JTR : 14,
// CPU load related
CPU_LOAD : 15,
DECODE_VIOLATIONS : 16,
LAST_THRESHOLD : 17,
WIFI_NETWORK_ALERT : 18, //user or peer is using wifi
NO_VALID_AUDIO_CONFIG : 19, // alert the user to popup a config
AUDIO_DEVICE_NOT_PRESENT : 20, // the audio device is not connected
RECORD_PLAYBACK_STATE : 21, // record/playback events have occurred
RUN_UPDATE_CHECK_BACKGROUND : 22, //this is auto check - do
RUN_UPDATE_CHECK_INTERACTIVE : 23, //this is initiated by user
STUN_EVENT : 24, // system completed stun test... come get the result
DEAD_USER_WARN_EVENT : 25, //the backend is not receiving audio from this peer
DEAD_USER_REMOVE_EVENT : 26, //the backend is removing the user from session as no audio is coming from this peer
WINDOW_CLOSE_BACKGROUND_MODE : 27, //the user has closed the window and the client is now in background mode
WINDOW_OPEN_FOREGROUND_MODE : 28, //the user has opened the window and the client is now in forground mode/
SESSION_LIVEBROADCAST_FAIL : 29, //error of some sort - so can't broadcast
SESSION_LIVEBROADCAST_ACTIVE : 30, //active
SESSION_LIVEBROADCAST_STOPPED : 31, //stopped by server/user
SESSION_LIVEBROADCAST_PINNED : 32, //node pinned by user
SESSION_LIVEBROADCAST_UNPINNED : 33, //node unpinned by user
BACKEND_STATUS_MSG : 34, //status/informational message
LOCAL_NETWORK_VARIANCE_HIGH : 35,//the ping time via a hairpin for the user network is unnaturally high or variable.
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
LOCAL_NETWORK_LATENCY_HIGH : 36,
RECORDING_CLOSE : 37, //update and remove tracks from front-end
PEER_REPORTS_NO_AUDIO_RECV : 38, //letting front-end know audio is not being received from a user in session
LAST_ALERT : 39
}
// recreate eThresholdType enum from MixerDialog.h
context.JK.ALERT_TYPES = {
0: {"title": "", "message": ""}, // NO_EVENT,
1: {"title": "", "message": ""}, // BACKEND_ERROR: generic error - eg P2P message error
2: {"title": "", "message": ""}, // BACKEND_MIXER_CHANGE, - event that controls have been regenerated
3: {"title": "High Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // PACKET_JTR,
4: {"title": "High Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // PACKET_LOSS
5: {"title": "High Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // PACKET_LATE,
6: {"title": "Large Jitter Queue", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // JTR_QUEUE_DEPTH,
7: {"title": "High Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // NETWORK_JTR,
8: {"title": "High Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // NETWORK_PING,
9: {"title": "Bandwidth Throttled", "message": "The available bandwidth on your network has diminished, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // BITRATE_THROTTLE_WARN,
10:{"title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // BANDWIDTH_LOW
// IO related events
11:{"title": "Variable Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // INPUT_IO_RATE
12:{"title": "High Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // INPUT_IO_JTR,
13:{"title": "Variable Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // OUTPUT_IO_RATE
14:{"title": "High Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // OUTPUT_IO_JTR,
// CPU load related
15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // CPU_LOAD
16: {"title": "Decode Violations", "message": ""}, // DECODE_VIOLATIONS,
17: {"title": "", "message": ""}, // LAST_THRESHOLD
18: {"title": "Wifi Alert", "message": ""}, // WIFI_NETWORK_ALERT, //user or peer is using wifi
19: {"title": "No Audio Configuration", "message": "You cannot join the session because you do not have a valid audio configuration."}, // NO_VALID_AUDIO_CONFIG,
20: {"title": "Audio Device Not Present", "message": ""}, // AUDIO_DEVICE_NOT_PRESENT, // the audio device is not connected
21: {"title": "", "message": ""}, // RECORD_PLAYBACK_STATE, // record/playback events have occurred
22: {"title": "", "message": ""}, // RUN_UPDATE_CHECK_BACKGROUND, //this is auto check - do
23: {"title": "", "message": ""}, // RUN_UPDATE_CHECK_INTERACTIVE, //this is initiated by user
24: {"title": "", "message": ""}, // STUN_EVENT, // system completed stun test... come get the result
25: {"title": "No Audio", "message": "Your system is no longer transmitting audio. Other session members are unable to hear you."}, // DEAD_USER_WARN_EVENT, //the backend is not receiving audio from this peer
26: {"title": "No Audio", "message": "Your system is no longer transmitting audio. Other session members are unable to hear you."}, // DEAD_USER_REMOVE_EVENT, //the backend is removing the user from session as no audio is coming from this peer
27: {"title": "", "message": ""}, // WINDOW_CLOSE_BACKGROUND_MODE, //the user has closed the window and the client is now in background mode
28: {"title": "", "message": ""}, // WINDOW_OPEN_FOREGROUND_MODE, //the user has opened the window and the client is now in forground mode/
29: {"title": "Failed to Broadcast", "message": ""}, // SESSION_LIVEBROADCAST_FAIL, //error of some sort - so can't broadcast
30: {"title": "", "message": ""}, // SESSION_LIVEBROADCAST_ACTIVE, //active
31: {"title": "", "message": ""}, // SESSION_LIVEBROADCAST_STOPPED, //stopped by server/user
32: {"title": "Client Pinned", "message": "This client will be the source of a broadcast."}, // SESSION_LIVEBROADCAST_PINNED, //node pinned by user
33: {"title": "Client No Longer Pinned", "message": "This client is no longer designated as the source of the broadcast."}, // SESSION_LIVEBROADCAST_UNPINNED, //node unpinned by user
34: {"title": "", "message": ""}, // BACKEND_STATUS_MSG, //status/informational message
35: {"title": "LAN Unpredictable", "message": "Your local network is adding considerable variance to transmit times. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // LOCAL_NETWORK_VARIANCE_HIGH,//the ping time via a hairpin for the user network is unnaturally high or variable.
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
36: {"title": "LAN High Latency", "message": "Your local network is adding considerable latency. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // LOCAL_NETWORK_LATENCY_HIGH,
37: {"title": "", "message": ""}, // RECORDING_CLOSE, //update and remove tracks from front-end
38: {"title": "No Audio Sent", "message": ""}, // PEER_REPORTS_NO_AUDIO_RECV, //update and remove tracks from front-end
39: {"title": "", "message": ""} // LAST_ALERT
};
// add the alert's name to the ALERT_TYPES structure
context._.each(context.JK.ALERT_NAMES, function(alert_code, alert_name) {
var alert_data = context.JK.ALERT_TYPES[alert_code];
alert_data.name = alert_name;
})
context.JK.MAX_TRACKS = 6;
context.JK.MAX_OUTPUTS = 2;
@ -113,36 +225,42 @@
context.JK.AUDIO_DEVICE_BEHAVIOR = {
MacOSX_builtin: {
display: 'MacOSX Built-In',
videoURL: undefined,
shortName: 'Built-In',
videoURL: "https://www.youtube.com/watch?v=7-9PW50ygHk",
showKnobs: false,
showASIO: false
},
MacOSX_interface: {
display: 'MacOSX external interface',
videoURL: undefined,
shortName: 'External',
videoURL: "https://www.youtube.com/watch?v=7BLld6ogm14",
showKnobs: false,
showASIO: false
},
Win32_wdm: {
display: 'Windows WDM',
videoURL: undefined,
shortName : 'WDM',
videoURL: "https://www.youtube.com/watch?v=L36UBkAV14c",
showKnobs: true,
showASIO: false
},
Win32_asio: {
display: 'Windows ASIO',
videoURL: undefined,
shortName : 'ASIO',
videoURL: "https://www.youtube.com/watch?v=PGUmISTVVMY",
showKnobs: false,
showASIO: true
},
Win32_asio4all: {
display: 'Windows ASIO4ALL',
videoURL: undefined,
shortName : 'ASIO4ALL',
videoURL: "https://www.youtube.com/watch?v=PGUmISTVVMY",
showKnobs: false,
showASIO: true
},
Linux: {
display: 'Linux',
shortName : 'linux',
videoURL: undefined,
showKnobs: true,
showASIO: false

View File

@ -718,10 +718,9 @@
/** check if the server is alive */
function serverHealthCheck(options) {
logger.debug("serverHealthCheck")
return $.ajax({
type: "GET",
url: "/api/versioncheck"
url: "/api/healthcheck"
});
}

View File

@ -94,22 +94,30 @@
context.jamClient.OnDownloadAvailable();
}
/**
* This most likely occurs when multiple tabs in the same browser are open, until we make a fix for this...
*/
function duplicateClientError() {
context.JK.Banner.showAlert("Duplicate Window (Development Mode Only)", "You have logged in another window in this browser. This window will continue to work but with degraded functionality. This limitation will soon be fixed.")
context.JK.JamServer.noReconnect = true;
}
function registerBadStateError() {
logger.debug("register for server_bad_state_error");
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_ERROR, serverBadStateError);
}
function registerBadStateRecovered() {
logger.debug("register for server_bad_state_recovered");
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_RECOVERED, serverBadStateRecovered);
}
function registerDownloadAvailable() {
logger.debug("register for download_available");
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.DOWNLOAD_AVAILABLE, downloadAvailable);
}
function registerDuplicateClientError() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_DUPLICATE_CLIENT_ERROR, duplicateClientError);
}
/**
* Generic error handler for Ajax calls.
*/
@ -330,6 +338,7 @@
registerBadStateRecovered();
registerBadStateError();
registerDownloadAvailable();
registerDuplicateClientError();
context.JK.FaderHelpers.initialize();
context.window.onunload = this.unloadFunction;

View File

@ -34,6 +34,7 @@
var $currentScore = null;
var $scoredClients = null;
var $subscore = null;
var $watchVideo = null;
var backendGuardTimeout = null;
var serverClientId = '';
@ -43,6 +44,12 @@
var $self = $(this);
var scoringZoneWidth = 100;//px
var inGearWizard = false;
var operatingSystem = null;
// these try to make it such that we only pass a NetworkTest Pass/Failed one time in a new user flow
var trackedPass = false;
var lastNetworkFailure = null;
var bandwidthSamples = [];
var NETWORK_TEST_START = 'network_test.start';
var NETWORK_TEST_DONE = 'network_test.done';
@ -66,7 +73,27 @@
}
}
// this averages bandwidthSamples; this method is meant just for GA data
function avgBandwidth(num_others) {
if(bandwidthSamples.length == 0) {
return 0;
}
else {
var total = 0;
context._.each(bandwidthSamples, function(sample) {
total += (sample * num_others * 400 * (100 + 28)); // sample is a percentage of 400. So sample * 400 gives us how many packets/sec. 100 is payload; 28 is UDP+ETHERNET overhead, to give us bandwidth
})
return total / bandwidthSamples.length;
}
}
function reset() {
trackedPass = false;
lastNetworkFailure = null;
resetTestState();
}
function resetTestState() {
serverClientId = '';
scoring = false;
numClientsToTest = STARTING_NUM_CLIENTS;
@ -77,6 +104,8 @@
$testText.empty();
$subscore.empty();
$currentScore.width(0);
bandwidthSamples = [];
}
function renderStartTest() {
@ -107,6 +136,17 @@
return '';
}
}
function getLastNetworkFailure() {
return lastNetworkFailure;
}
function storeLastNetworkFailure(reason, data) {
if(!trackedPass) {
lastNetworkFailure = {reason:reason, data:data};
}
}
function testFinished() {
var attempt = getCurrentAttempt();
@ -123,7 +163,14 @@
if(!testSummary.final.num_clients) {
testSummary.final.num_clients = attempt.num_clients;
}
context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients);
// context.jamClient.GetNetworkTestScore() == 0 is a rough approximation if the user has passed the FTUE before
if(inGearWizard || context.jamClient.GetNetworkTestScore() == 0) {
trackedPass = true;
lastNetworkFailure = null;
context.JK.GA.trackNetworkTest(context.JK.detectOS(), testSummary.final.num_clients);
}
context.jamClient.SetNetworkTestScore(attempt.num_clients);
if(testSummary.final.num_clients == 2) {
$testResults.addClass('acceptable');
@ -136,49 +183,65 @@
else if(reason == "minimum_client_threshold") {
context.jamClient.SetNetworkTestScore(0);
renderStopTest('', "We're sorry, but your router and Internet service will not effectively support JamKazam sessions. Please click the HELP button for more information.")
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.bandwidth, avgBandwidth(attempt.num_clients - 1));
}
else if(reason == "unreachable") {
else if(reason == "unreachable" || reason == "no-transmit") {
context.jamClient.SetNetworkTestScore(0);
renderStopTest('', "We're sorry, but your router will not support JamKazam in its current configuration. Please click the HELP button for more information.");
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.stun, attempt.num_clients);
}
else if(reason == "internal_error") {
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "remote_peer_cant_test") {
context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "server_comm_timeout") {
context.JK.alertSupportedNeeded("Communication with the JamKazam network service has timed out." + appendContextualStatement());
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'backend_gone') {
context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
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 + '.');
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.<br/><br/>Reason: " + attempt.backend_data.reason + '.');
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'no_servers') {
context.JK.alertSupportedNeeded("No network test servers are available." + appendContextualStatement());
renderStopTest('', '');
testedSuccessfully = true;
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'no_network') {
context.JK.Banner.showAlert("Please try again later. Your network appears down.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.noNetwork);
}
else if(reason == "rest_api_error") {
context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement());
testedSuccessfully = true;
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "timeout") {
context.JK.alertSupportedNeeded("Communication with a network test servers timed out." + appendContextualStatement());
context.JK.alertSupportedNeeded("Communication with the JamKazam network service timed out." + appendContextualStatement());
testedSuccessfully = true;
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else {
context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
numClientsToTest = STARTING_NUM_CLIENTS;
@ -204,8 +267,9 @@
}
function cancel() {
clearBackendGuard();
}
function clearBackendGuard() {
if(backendGuardTimeout) {
clearTimeout(backendGuardTimeout);
@ -213,6 +277,11 @@
}
}
function setBackendGuard() {
clearBackendGuard();
backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000);
}
function attemptTestPass() {
var attempt = {};
@ -230,7 +299,7 @@
updateProgress(0, false);
backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000);
setBackendGuard();
context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(), createTimeoutCallbackName(),
NETWORK_TEST_TYPES.PktTest400LowLatency,
@ -262,7 +331,7 @@
}
}
reset();
resetTestState();
scoring = true;
$self.triggerHandler(NETWORK_TEST_START);
renderStartTest();
@ -300,6 +369,7 @@
}
function updateProgress(throughput, showSubscore) {
var width = throughput * 100;
$currentScore.stop().data('showSubscore', showSubscore);
@ -322,9 +392,7 @@
;
}
function networkTestSuccess(data) {
clearBackendGuard();
function networkTestComplete(data) {
var attempt = getCurrentAttempt();
function refineTest(up) {
@ -364,6 +432,8 @@
if(data.progress === true) {
setBackendGuard();
var animate = true;
if(data.downthroughput && data.upthroughput) {
@ -376,12 +446,14 @@
// take the lower
var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput;
bandwidthSamples.push(data.upthroughput);
updateProgress(throughput, true);
}
}
}
else {
clearBackendGuard();
logger.debug("network test pass success. data: ", data);
if(data.reason == "unreachable") {
@ -390,6 +462,11 @@
attempt.reason = data.reason;
testFinished();
}
else if(data.reason == "no-transmit") {
logger.debug("network test: no-transmit (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)");
@ -402,6 +479,11 @@
attempt.reason = data.reason;
testFinished();
}
else if(data.reason == "server_comm_timeout") {
logger.debug("network test: server_comm_timeout (communication with server problem)")
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
@ -467,9 +549,17 @@
}
function beforeHide() {
clearBackendGuard();
}
function initializeVideoWatchButton() {
if(operatingSystem == "Win32") {
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=rhAdCVuwhBc');
}
else {
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=0r1py0AYJ4Y');
}
}
function initialize(_$step, _inGearWizard) {
$step = _$step;
inGearWizard = _inGearWizard;
@ -487,15 +577,19 @@
$currentScore = $step.find('.current-score');
$scoredClients= $step.find('.scored-clients');
$subscore = $step.find('.subscore');
$watchVideo = $step.find('.watch-video');
$startNetworkTestBtn.on('click', startNetworkTest);
operatingSystem = context.JK.GetOSAsString();
initializeVideoWatchButton();
// if this network test is instantiated anywhere else than the gearWizard, or a dialog, then this will have to be expanded
if(inGearWizard) {
context.JK.HandleNetworkTestSuccessForGearWizard = function(data) { networkTestSuccess(data)}; // pin to global for bridge callback
context.JK.HandleNetworkTestSuccessForGearWizard = function(data) { networkTestComplete(data)}; // pin to global for bridge callback
context.JK.HandleNetworkTestTimeoutForGearWizard = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback
}
else {
context.JK.HandleNetworkTestSuccessForDialog = function(data) { networkTestSuccess(data)}; // pin to global for bridge callback
context.JK.HandleNetworkTestSuccessForDialog = function(data) { networkTestComplete(data)}; // pin to global for bridge callback
context.JK.HandleNetworkTestTimeoutForDialog = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback
}
}
@ -505,6 +599,7 @@
this.initialize = initialize;
this.reset = reset;
this.cancel = cancel;
this.getLastNetworkFailure = getLastNetworkFailure;
this.NETWORK_TEST_START = NETWORK_TEST_START;
this.NETWORK_TEST_DONE = NETWORK_TEST_DONE;

View File

@ -30,7 +30,6 @@
var claimedRecording = null;
var playbackControls = null;
var promptLeave = false;
var backendMixerAlertThrottleTimer = null;
var rateSessionDialog = null;
var rest = context.JK.Rest();
@ -76,60 +75,6 @@
"PeerMediaTrackGroup": 10
};
// recreate eThresholdType enum from MixerDialog.h
var alert_type = {
0: {"title": "", "message": ""}, // NO_EVENT,
1: {"title": "", "message": ""}, // BACKEND_ERROR: generic error - eg P2P message error
2: {"title": "", "message": ""}, // BACKEND_MIXER_CHANGE, - event that controls have been regenerated
3: {"title": "High Packet Jitter", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // PACKET_JTR,
4: {"title": "High Packet Loss", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // PACKET_LOSS
5: {"title": "High Packet Late", "message": "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // PACKET_LATE,
6: {"title": "Large Jitter Queue", "message": "Your network connection is currently experiencing packet jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // JTR_QUEUE_DEPTH,
7: {"title": "High Network Jitter", "message": "Your network connection is currently experiencing network jitter at a level that is too high to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // NETWORK_JTR,
8: {"title": "High Session Latency", "message": "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // NETWORK_PING,
9: {"title": "Bandwidth Throttled", "message": "The available bandwidth on your network has diminished, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // BITRATE_THROTTLE_WARN,
10:{"title": "Low Bandwidth", "message": "The available bandwidth on your network has become too low, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // BANDWIDTH_LOW
// IO related events
11:{"title": "Variable Input Rate", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // INPUT_IO_RATE
12:{"title": "High Input Jitter", "message": "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // INPUT_IO_JTR,
13:{"title": "Variable Output Rate", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // OUTPUT_IO_RATE
14:{"title": "High Output Jitter", "message": "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // OUTPUT_IO_JTR,
// CPU load related
15: { "title": "CPU Utilization High", "message": "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>." }, // CPU_LOAD
16: {"title": "Decode Violations", "message": ""}, // DECODE_VIOLATIONS,
17: {"title": "", "message": ""}, // LAST_THRESHOLD
18: {"title": "Wifi Alert", "message": ""}, // WIFI_NETWORK_ALERT, //user or peer is using wifi
19: {"title": "No Audio Configuration", "message": "You cannot join the session because you do not have a valid audio configuration."}, // NO_VALID_AUDIO_CONFIG,
20: {"title": "Audio Device Not Present", "message": ""}, // AUDIO_DEVICE_NOT_PRESENT, // the audio device is not connected
21: {"title": "", "message": ""}, // RECORD_PLAYBACK_STATE, // record/playback events have occurred
22: {"title": "", "message": ""}, // RUN_UPDATE_CHECK_BACKGROUND, //this is auto check - do
23: {"title": "", "message": ""}, // RUN_UPDATE_CHECK_INTERACTIVE, //this is initiated by user
24: {"title": "", "message": ""}, // STUN_EVENT, // system completed stun test... come get the result
25: {"title": "No Audio", "message": "Your system is no longer transmitting audio. Other session members are unable to hear you."}, // DEAD_USER_WARN_EVENT, //the backend is not receiving audio from this peer
26: {"title": "No Audio", "message": "Your system is no longer transmitting audio. Other session members are unable to hear you."}, // DEAD_USER_REMOVE_EVENT, //the backend is removing the user from session as no audio is coming from this peer
27: {"title": "", "message": ""}, // WINDOW_CLOSE_BACKGROUND_MODE, //the user has closed the window and the client is now in background mode
28: {"title": "", "message": ""}, // WINDOW_OPEN_FOREGROUND_MODE, //the user has opened the window and the client is now in forground mode/
29: {"title": "Failed to Broadcast", "message": ""}, // SESSION_LIVEBROADCAST_FAIL, //error of some sort - so can't broadcast
30: {"title": "", "message": ""}, // SESSION_LIVEBROADCAST_ACTIVE, //active
31: {"title": "", "message": ""}, // SESSION_LIVEBROADCAST_STOPPED, //stopped by server/user
32: {"title": "Client Pinned", "message": "This client will be the source of a broadcast."}, // SESSION_LIVEBROADCAST_PINNED, //node pinned by user
33: {"title": "Client No Longer Pinned", "message": "This client is no longer designated as the source of the broadcast."}, // SESSION_LIVEBROADCAST_UNPINNED, //node unpinned by user
34: {"title": "", "message": ""}, // BACKEND_STATUS_MSG, //status/informational message
35: {"title": "LAN Unpredictable", "message": "Your local network is adding considerable variance to transmit times. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // LOCAL_NETWORK_VARIANCE_HIGH,//the ping time via a hairpin for the user network is unnaturally high or variable.
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
36: {"title": "LAN High Latency", "message": "Your local network is adding considerable latency. For troubleshooting tips, <a href='https://jamkazam.desk.com/customer/portal/articles/1288778-troubleshoot-session-quality-problems'>click here</a>."}, // LOCAL_NETWORK_LATENCY_HIGH,
37: {"title": "", "message": ""}, // RECORDING_CLOSE, //update and remove tracks from front-end
38: {"title": "No Audio Sent", "message": ""}, // PEER_REPORTS_NO_AUDIO_RECV, //update and remove tracks from front-end
39: {"title": "", "message": ""} // LAST_ALERT
};
function beforeShow(data) {
sessionId = data.id;
promptLeave = true;
@ -144,114 +89,6 @@
return { freezeInteraction: true };
}
function alertCallback(type, text) {
function timeCallback() {
var start = new Date();
setTimeout(function() {
var timed = new Date().getTime() - start.getTime();
if(timed > 250) {
logger.warn("SLOW AlERT_CALLBACK. type: %o text: %o time: %o", type, text, timed);
}
}, 1);
}
timeCallback();
logger.debug("alert callback", type, text);
if (type === 2) { // BACKEND_MIXER_CHANGE
logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text);
if(sessionModel.id() && text == "RebuildAudioIoControl") {
// the backend will send these events rapid-fire back to back.
// the server can still perform correctly, but it is nicer to wait 100 ms to let them all fall through
if(backendMixerAlertThrottleTimer) {clearTimeout(backendMixerAlertThrottleTimer);}
backendMixerAlertThrottleTimer = setTimeout(function() {
// this is a local change to our tracks. we need to tell the server about our updated track information
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
// create a trackSync request based on backend data
var syncTrackRequest = {};
syncTrackRequest.client_id = app.clientId;
syncTrackRequest.tracks = inputTracks;
syncTrackRequest.id = sessionModel.id();
rest.putTrackSyncChange(syncTrackRequest)
.done(function() {
})
.fail(function() {
app.notify({
"title": "Can't Sync Local Tracks",
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
"icon_url": "/assets/content/icon_alert_big.png"
});
})
}, 100);
}
else if(sessionModel.id() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) {
sessionModel.refreshCurrentSession(true);
}
}
else if (type === 19) { // NO_VALID_AUDIO_CONFIG
app.notify({
"title": alert_type[type].title,
"text": text,
"icon_url": "/assets/content/icon_alert_big.png"
});
context.location = "/client#"; // leaveSession will be called in beforeHide below
}
else if (type === 24) { // STUN_EVENT
var testResults = context.jamClient.NetworkTestResult();
$.each(testResults, function(index, val) {
if (val.bStunFailed) {
// if true we could not reach a stun server
}
else if (val.bRemoteUdpBocked) {
// if true the user cannot communicate with peer via UDP, although they could do LAN based session
}
});
}
else if (type === 26) {
var clientId = text;
var participant = sessionModel.getParticipant(clientId);
if(participant) {
app.notify({
"title": alert_type[type].title,
"text": participant.user.name + " is no longer sending audio.",
"icon_url": context.JK.resolveAvatarUrl(participant.user.photo_url)
});
var $track = $('div.track[client-id="' + clientId + '"]');
$('.disabled-track-overlay', $track).show();
}
}
else if (type === 27) { // WINDOW_CLOSE_BACKGROUND_MODE
// the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen
promptLeave = false;
context.location = '/client#/home'
}
else if(type != 30 && type != 31 && type != 21){ // these are handled elsewhere
context.setTimeout(function() {
var alert = alert_type[type];
if(alert && alert.title) {
app.notify({
"title": alert_type[type].title,
"text": text,
"icon_url": "/assets/content/icon_alert_big.png"
});
}
else {
logger.debug("Unknown Backend Event type %o, data %o", type, text)
}
}, 1);
}
}
function initializeSession() {
// indicate that the screen is active, so that
// body-scoped drag handlers can go active
@ -327,7 +164,8 @@
context.JK.CurrentSessionModel = sessionModel = new context.JK.SessionModel(
context.JK.app,
context.JK.JamServer,
context.jamClient
context.jamClient,
self
);
$(sessionModel.recordingModel)
@ -653,7 +491,8 @@
}
});
var faderId = mixerIds.join(',');
$('#volume').attr('mixer-id', faderId);
var $volume = $('#volume');
$volume.attr('mixer-id', faderId);
var faderOpts = {
faderId: faderId,
faderType: "horizontal",
@ -664,8 +503,9 @@
"height": "24px"
}
};
context.JK.FaderHelpers.renderFader("#volume", faderOpts);
context.JK.FaderHelpers.subscribe(faderId, faderChanged);
context.JK.FaderHelpers.renderFader($volume, faderOpts);
$volume.on('fader_change', faderChanged);
// Visually update fader to underlying mixer start value.
// Always do this, even if gainPercent is zero.
@ -699,8 +539,8 @@
"height": "24px"
}
};
context.JK.FaderHelpers.renderFader(faderId, faderOpts);
context.JK.FaderHelpers.subscribe(faderId, l2mChanged);
context.JK.FaderHelpers.renderFader($mixSlider, faderOpts);
$mixSlider.on('fader_change', l2mChanged);
var value = context.jamClient.SessionGetMasterLocalMix();
context.JK.FaderHelpers.setFaderValue(faderId, percentFromMixerValue(-80, 20, value));
@ -709,9 +549,9 @@
/**
* This has a specialized jamClient call, so custom handler.
*/
function l2mChanged(faderId, newValue, dragging) {
function l2mChanged(e, data) {
//var dbValue = context.JK.FaderHelpers.convertLinearToDb(newValue);
context.jamClient.SessionSetMasterLocalMix(newValue - 80);
context.jamClient.SessionSetMasterLocalMix(data.percentage - 80);
}
function _addVoiceChat() {
@ -723,7 +563,8 @@
var $voiceChat = $('#voice-chat');
$voiceChat.show();
$voiceChat.attr('mixer-id', mixer.id);
$('#voice-chat .voicechat-mute').attr('mixer-id', mixer.id);
var $voiceChatGain = $voiceChat.find('.voicechat-gain');
var $voiceChatMute = $voiceChat.find('.voicechat-mute').attr('mixer-id', mixer.id);
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
var faderOpts = {
@ -731,12 +572,11 @@
faderType: "horizontal",
width: 50
};
context.JK.FaderHelpers.renderFader("#voice-chat .voicechat-gain", faderOpts);
context.JK.FaderHelpers.subscribe(mixer.id, faderChanged);
context.JK.FaderHelpers.renderFader($voiceChatGain, faderOpts);
$voiceChatGain.on('fader_change', faderChanged);
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent);
if (mixer.mute) {
var $mute = $voiceChat.find('.voicechat-mute');
_toggleVisualMuteControl($mute, true);
_toggleVisualMuteControl($voiceChatMute, true);
}
}
});
@ -945,16 +785,17 @@
var vuLeftSelector = trackSelector + " .track-vu-left";
var vuRightSelector = trackSelector + " .track-vu-right";
var faderSelector = trackSelector + " .track-gain";
var $fader = $(faderSelector).attr('mixer-id', mixerId);
var $track = $(trackSelector);
// Set mixer-id attributes and render VU/Fader
context.JK.VuHelpers.renderVU(vuLeftSelector, vuOpts);
$track.find('.track-vu-left').attr('mixer-id', mixerId + '_vul');
context.JK.VuHelpers.renderVU(vuRightSelector, vuOpts);
$track.find('.track-vu-right').attr('mixer-id', mixerId + '_vur');
context.JK.FaderHelpers.renderFader(faderSelector, faderOpts);
context.JK.FaderHelpers.renderFader($fader, faderOpts);
// Set gain position
context.JK.FaderHelpers.setFaderValue(mixerId, gainPercent);
context.JK.FaderHelpers.subscribe(mixerId, faderChanged);
$fader.on('fader_change', faderChanged);
}
// Function called on an interval when participants change. Mixers seem to
@ -976,6 +817,8 @@
],
usedMixers);
if (mixer) {
var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name;
logger.debug("found mixer=" + mixer.id + ", participant=" + participant)
usedMixers[mixer.id] = true;
keysToDelete.push(key);
var gainPercent = percentFromMixerValue(
@ -998,6 +841,8 @@
$('.disabled-track-overlay', $track).show();
$('.track-connection', $track).removeClass('red yellow green').addClass('red');
}
var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name;
logger.debug("still looking for mixer for participant=" + participant + ", clientId=" + clientId)
}
}
@ -1029,14 +874,14 @@
if (!(mixer.stereo)) { // mono track
if (mixerId.substr(-4) === "_vul") {
// Do the left
selector = '#tracks [mixer-id="' + pureMixerId + '_vul"]';
selector = $('#tracks [mixer-id="' + pureMixerId + '_vul"]');
context.JK.VuHelpers.updateVU(selector, value);
// Do the right
selector = '#tracks [mixer-id="' + pureMixerId + '_vur"]';
selector = $('#tracks [mixer-id="' + pureMixerId + '_vur"]');
context.JK.VuHelpers.updateVU(selector, value);
} // otherwise, it's a mono track, _vur event - ignore.
} else { // stereo track
selector = '#tracks [mixer-id="' + mixerId + '"]';
selector = $('#tracks [mixer-id="' + mixerId + '"]');
context.JK.VuHelpers.updateVU(selector, value);
}
}
@ -1102,12 +947,14 @@
* Will be called when fader changes. The fader id (provided at subscribe time),
* the new value (0-100) and whether the fader is still being dragged are passed.
*/
function faderChanged(faderId, newValue, dragging) {
function faderChanged(e, data) {
var $target = $(this);
var faderId = $target.attr('mixer-id');
var mixerIds = faderId.split(',');
$.each(mixerIds, function(i,v) {
var broadcast = !(dragging); // If fader is still dragging, don't broadcast
var broadcast = !(data.dragging); // If fader is still dragging, don't broadcast
fillTrackVolumeObject(v, broadcast);
setMixerVolume(v, newValue);
setMixerVolume(v, data.percentage);
});
}
@ -1568,7 +1415,6 @@
context.JK.HandleVolumeChangeCallback = handleVolumeChangeCallback;
context.JK.HandleBridgeCallback = handleBridgeCallback;
context.JK.AlertCallback = alertCallback;
};
})(window,jQuery);

View File

@ -7,7 +7,10 @@
context.JK = context.JK || {};
var logger = context.JK.logger;
context.JK.SessionModel = function(app, server, client) {
// screen can be null
context.JK.SessionModel = function(app, server, client, sessionScreen) {
var ALERT_TYPES = context.JK.ALERT_TYPES;
var clientId = client.clientID;
var currentSessionId = null; // Set on join, prior to setting currentSession.
var currentSession = null;
@ -19,6 +22,7 @@
var pendingSessionRefresh = false;
var recordingModel = new context.JK.RecordingModel(app, this, rest, context.jamClient);
var currentTrackChanges = 0;
var backendMixerAlertThrottleTimer = null;
// we track all the clientIDs of all the participants ever seen by this session, so that we can reliably convert a clientId from the backend into a username/avatar
var participantsEverSeen = {};
var $self = $(this);
@ -29,6 +33,10 @@
return currentSession ? currentSession.id : null;
}
function inSession() {
return !!currentSessionId;
}
function participants() {
if (currentSession) {
return currentSession.participants;
@ -131,7 +139,7 @@
// 'unregister' for callbacks
context.jamClient.SessionRegisterCallback("");
context.jamClient.SessionSetAlertCallback("");
//context.jamClient.SessionSetAlertCallback("");
context.jamClient.SessionSetConnectionStatusRefreshRate(0);
updateCurrentSession(null);
$(document).trigger('jamkazam.session_stopped', {session: {id: currentSessionId}});
@ -214,6 +222,12 @@
* the provided callback when complete.
*/
function refreshCurrentSessionRest(callback, force) {
if(!inSession()) {
logger.debug("refreshCurrentSession skipped: ")
return;
}
var url = "/api/sessions/" + currentSessionId;
if(requestingSessionRefresh) {
// if someone asks for a refresh while one is going on, we ask for another to queue up
@ -239,7 +253,14 @@
logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter);
}
},
error: function(jqXHR) { app.notifyServerError(jqXHR, "Unable to refresh session data") },
error: function(jqXHR) {
if(jqXHR.status != 404) {
app.notifyServerError(jqXHR, "Unable to refresh session data")
}
else {
logger.debug("refreshCurrentSessionRest: could not refresh data for session because it's gone")
}
},
complete: function() {
requestingSessionRefresh = false;
if(pendingSessionRefresh) {
@ -411,6 +432,73 @@
return $.Deferred().reject().promise();
}
function onDeadUserRemove(type, text) {
if(!inSession()) return;
var clientId = text;
var participant = participantsEverSeen[clientId];
if(participant) {
app.notify({
"title": ALERT_TYPES[type].title,
"text": participant.user.name + " is no longer sending audio.",
"icon_url": context.JK.resolveAvatarUrl(participant.user.photo_url)
});
var $track = $('div.track[client-id="' + clientId + '"]');
$('.disabled-track-overlay', $track).show();
}
}
function onWindowBackgrounded(type, text) {
if(!inSession()) return;
// the window was closed; just attempt to nav to home, which will cause all the right REST calls to happen
if(sessionScreen) {
sessionScreen.setPromptLeave(false);
context.location = '/client#/home'
}
}
function onBackendMixerChanged(type, text) {
logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text);
if(inSession() && text == "RebuildAudioIoControl") {
// the backend will send these events rapid-fire back to back.
// the server can still perform correctly, but it is nicer to wait 100 ms to let them all fall through
if(backendMixerAlertThrottleTimer) {clearTimeout(backendMixerAlertThrottleTimer);}
backendMixerAlertThrottleTimer = setTimeout(function() {
// this is a local change to our tracks. we need to tell the server about our updated track information
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
// create a trackSync request based on backend data
var syncTrackRequest = {};
syncTrackRequest.client_id = app.clientId;
syncTrackRequest.tracks = inputTracks;
syncTrackRequest.id = id();
rest.putTrackSyncChange(syncTrackRequest)
.done(function() {
})
.fail(function(jqXHR) {
if(jqXHR.status != 404) {
app.notify({
"title": "Can't Sync Local Tracks",
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
"icon_url": "/assets/content/icon_alert_big.png"
});
}
else {
logger.debug("Unable to sync local tracks because session is gone.")
}
})
}, 100);
}
else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) {
refreshCurrentSession(true);
}
}
// Public interface
this.id = id;
this.recordedTracks = recordedTracks;
@ -425,6 +513,12 @@
this.onWebsocketDisconnected = onWebsocketDisconnected;
this.recordingModel = recordingModel;
this.findUserBy = findUserBy;
// ALERT HANDLERS
this.onBackendMixerChanged = onBackendMixerChanged;
this.onDeadUserRemove = onDeadUserRemove;
this.onWindowBackgrounded = onWindowBackgrounded;
this.getCurrentSession = function() {
return currentSession;
};

View File

@ -9,6 +9,9 @@
var logger = context.JK.logger;
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
var ALERT_NAMES = context.JK.ALERT_NAMES;
var ALERT_TYPES = context.JK.ALERT_TYPES;
var days = new Array("Sun", "Mon", "Tue",
"Wed", "Thu", "Fri", "Sat");
@ -448,6 +451,20 @@
$('.dialog-overlay').hide();
}
// usage: context.JK.onBackendEvent(ALERT_NAMES.SOME_EVENT)
context.JK.onBackendEvent = function(type, namespace, callback) {
var alertData = ALERT_TYPES[type];
if(!alertData) {throw "onBackendEvent: unknown alert type " + type};
logger.debug("onBackendEvent: " + alertData.name + '.' + namespace)
$(document).on(alertData.name + '.' + namespace, callback);
}
context.JK.offBackendEvent = function(type, namespace, callback) {
var alertData = ALERT_TYPES[type];
if(!alertData) {throw "offBackendEvent: unknown alert type " + type};
logger.debug("offBackendEvent: " + alertData.name + '.' + namespace, alertData)
$(document).off(alertData.name + '.' + namespace);
}
/*
* Loads a listbox or dropdown with the values in input_array, setting the option value
* to the id_field and the option text to text_field. It will preselect the option with
@ -529,8 +546,8 @@
return ret;
}
context.JK.alertSupportedNeeded = function(additionalContext) {
var $item = context.JK.Banner.showAlert(additionalContext + ' Please <a href="http://jamkazam.desk.com" rel="external">contact support</a>');
context.JK.alertSupportedNeeded = function(additionalContext) {
var $item = context.JK.Banner.showAlert(additionalContext + '<br/><br/>Please <a href="http://jamkazam.desk.com" rel="external">contact support</a>.');
context.JK.popExternalLinks($item);
return $item;
}

View File

@ -5,6 +5,7 @@
context.JK = context.JK || {};
context.JK.VoiceChatHelper = function (app) {
var logger = context.JK.logger;
var ALERT_NAMES = context.JK.ALERT_NAMES;
var ASSIGNMENT = context.JK.ASSIGNMENT;
var VOICE_CHAT = context.JK.VOICE_CHAT;
var MAX_TRACKS = context.JK.MAX_TRACKS;
@ -18,10 +19,19 @@
var $chatInputs = null;
var $templateChatInput = null;
var $selectedChatInput = null;// should only be used if isChatEnabled = true
var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist
var $voiceChatVuLeft = null;
var $voiceChatVuRight = null;
var $voiceChatFader = null;
var saveImmediate = null; // if true, then every action by the user results in a save to the backend immediately, false means you have to call trySave to persist
var uniqueCallbackName = null;
// needed because iCheck fires iChecked event even when you programmatically change it, unlike when using .val(x)
var ignoreICheckEvent = false;
var lastSavedTime = new Date();
var vuOptions = null;
var faderHeight = null;
var startingState = null;
var resettedOnInvalidDevice = false; // this is set to true when we clear chat, and set to false when the user interacts in anyway
function defaultReuse() {
suppressChange(function(){
@ -34,6 +44,48 @@
return $useChatInputRadio.is(':checked');
}
function onInvalidAudioDevice(e, data) {
logger.debug("voice_chat_helper: onInvalidAudioDevice")
if(resettedOnInvalidDevice) {
// we've already tried to clear the audio device, and the user hasn't interacted, but still we are getting this event
// we can't keep taking action, so stop
logger.error("voice_chat_helper: onInvalidAudioDevice: ignoring event because we have already tried to handle it");
return;
}
resettedOnInvalidDevice = true;
$selectedChatInput = null;
// you can't do this in the event callback; it hangs the app indefinitely, and somehow 'sticks' the mic input into bad state until reboot
setTimeout(function() {
context.jamClient.FTUEClearChatInput();
context.jamClient.TrackSetChatEnable(true);
var result = context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
context.JK.Banner.showAlert('It appears the selected chat input is not functioning. Please try another chat input.');
}
else {
context.JK.alertSupportedNeeded("Unable to unwind invalid chat input selection.")
}
}, 1);
}
function beforeShow() {
userInteracted();
renderNoVolume();
context.JK.onBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'voice_chat_helper', onInvalidAudioDevice);
registerVuCallbacks();
}
function beforeHide() {
context.JK.offBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'voice_chat_helper', onInvalidAudioDevice);
jamClient.FTUERegisterVUCallbacks('', '', '');
}
function userInteracted() {
resettedOnInvalidDevice = false;
}
function reset(forceDisabledChat) {
$selectedChatInput = null;
@ -62,24 +114,36 @@
$selectedChatInput = $chatInput;
$selectedChatInput.attr('checked', 'checked');
}
$chat.hide(); // we'll show it once it's styled with iCheck
//$chat.hide(); // we'll show it once it's styled with iCheck
$chatInputs.append($chat);
});
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
context.JK.checkbox($radioButtons).on('ifChecked', function(e) {
userInteracted();
var $input = $(e.currentTarget);
$selectedChatInput = $input; // for use in handleNext
if(saveImmediate) {
var channelId = $input.attr('data-channel-id');
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.CHAT);
lastSavedTime = new Date();
context.jamClient.TrackSetChatInput(channelId);
lastSavedTime = new Date();
//context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.CHAT);
var result = context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
// success
}
else {
context.JK.Banner.showAlert('Unable to save assignments. ' + result);
context.jamClient.FTUEClearChatInput();
context.jamClient.TrackSetChatEnable(true);
var result = context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
context.JK.Banner.showAlert('Unable to save chat selection. ' + result);
}
else {
context.JK.alertSupportedNeeded("Unable to unwind invalid chat selection.")
}
return false;
}
}
@ -89,18 +153,17 @@
disableChatButtonsUI();
}
$chatInputs.find('.chat-input').show().on('click', function() {
// obnoxious; remove soon XXX
// if(!isChatEnabled()) {
// context.JK.prodBubble($parent.find('.use-chat-input h3'), 'chat-not-enabled', {}, { positions:['left']});
// }
})
renderVolumes();
startingState = getCurrentState();
}
function disableChatButtonsUI() {
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
$radioButtons.iCheck('disable')
$chatInputs.addClass('disabled');
$radioButtons.iCheck('uncheck');
$selectedChatInput = null;
}
function enableChatButtonsUI() {
@ -133,6 +196,7 @@
var result = context.jamClient.TrackSaveAssignments();
if(!result || result.length == 0) {
renderNoVolume();
// success
suppressChange(function() {
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
@ -151,7 +215,7 @@
$useChatInputRadio.removeAttr('checked');
})
}
disableChatButtonsUI()
disableChatButtonsUI();
}
function enableChat(applyToBackend) {
@ -181,6 +245,7 @@
}
enableChatButtonsUI();
renderVolumes();
}
function handleChatEnabledToggle() {
@ -191,8 +256,18 @@
$reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute');
$useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute');
$reuseAudioInputRadio.on('ifChecked', function() { if(!ignoreICheckEvent) disableChat(true) });
$useChatInputRadio.on('ifChecked', function() { if(!ignoreICheckEvent) enableChat(true) });
$reuseAudioInputRadio.on('ifChecked', function() {
if(!ignoreICheckEvent) {
userInteracted();
disableChat(true);
}
});
$useChatInputRadio.on('ifChecked', function() {
if(!ignoreICheckEvent) {
userInteracted();
enableChat(true)
}
});
}
// gets the state of the UI
@ -208,9 +283,17 @@
return state;
}
function trySave() {
function cancel() {
logger.debug("canceling voice chat state");
return trySave(startingState);
}
var state = getCurrentState();
function trySave(state) {
if(!state) {
state = getCurrentState();
}
if(state.enabled && state.chat_channel) {
logger.debug("enabling chat. chat_channel=" + state.chat_channel);
@ -231,6 +314,9 @@
if(!result || result.length == 0) {
// success
if(!state.enabled) {
renderNoVolume();
}
return true;
}
else {
@ -239,21 +325,80 @@
}
}
function initialize(_$step, _saveImmediate) {
function initializeVUMeters() {
context.JK.VuHelpers.renderVU($voiceChatVuLeft, vuOptions);
context.JK.VuHelpers.renderVU($voiceChatVuRight, vuOptions);
context.JK.FaderHelpers.renderFader($voiceChatFader, {faderId: '', faderType: "vertical", height: faderHeight});
$voiceChatFader.on('fader_change', faderChange);
}
// renders volumes based on what the backend says
function renderVolumes() {
var $fader = $voiceChatFader.find('[control="fader"]');
var db = context.jamClient.FTUEGetChatInputVolume();
var faderPct = db + 80;
context.JK.FaderHelpers.setHandlePosition($fader, faderPct);
}
function renderNoVolume() {
var $fader = $voiceChatFader.find('[control="fader"]');
context.JK.FaderHelpers.setHandlePosition($fader, 50);
context.JK.VuHelpers.updateVU($voiceChatVuLeft, 0);
context.JK.VuHelpers.updateVU($voiceChatVuRight, 0);
}
function faderChange(e, data) {
// TODO - using hardcoded range of -80 to 20 for output levels.
var mixerLevel = data.percentage - 80; // Convert our [0-100] to [-80 - +20] range
context.jamClient.FTUESetChatInputVolume(mixerLevel);
}
function registerVuCallbacks() {
logger.debug("voice-chat-helper: registering vu callbacks");
jamClient.FTUERegisterVUCallbacks(
"JK.voiceChatHelperAudioOutputVUCallback",
"JK.voiceChatHelperAudioInputVUCallback",
"JK." + uniqueCallbackName
);
jamClient.SetVURefreshRate(200);
}
function initialize(_$step, caller, _saveImmediate, _vuOptions, _faderHeight) {
$parent = _$step;
saveImmediate = _saveImmediate;
vuOptions = _vuOptions;
faderHeight = _faderHeight;
$reuseAudioInputRadio = $parent.find('.reuse-audio-input input');
$useChatInputRadio = $parent.find('.use-chat-input input');
$chatInputs = $parent.find('.chat-inputs');
$templateChatInput = $('#template-chat-input');
$voiceChatVuLeft = $parent.find('.voice-chat-vu-left');
$voiceChatVuRight = $parent.find('.voice-chat-vu-right');
$voiceChatFader = $parent.find('.chat-fader')
handleChatEnabledToggle();
initializeVUMeters();
renderVolumes();
uniqueCallbackName = 'voiceChatHelperChatInputVUCallback' + caller;
context.JK[uniqueCallbackName] = function(dbValue) {
context.JK.ftueVUCallback(dbValue, $voiceChatVuLeft);
context.JK.ftueVUCallback(dbValue, $voiceChatVuRight);
}
}
context.JK.voiceChatHelperAudioInputVUCallback = function (dbValue) {};
context.JK.voiceChatHelperAudioOutputVUCallback = function (dbValue) {};
this.reset = reset;
this.trySave = trySave;
this.cancel = cancel;
this.initialize = initialize;
this.beforeShow = beforeShow;
this.beforeHide = beforeHide;
return this;
};

View File

@ -19,6 +19,7 @@
* vuType can be either "horizontal" or "vertical"
*/
renderVU: function(selector, userOptions) {
selector = $(selector);
/**
* The default options for rendering a VU
*/
@ -35,25 +36,25 @@
templateSelector = "#template-vu-h";
}
var templateSource = $(templateSelector).html();
$(selector).empty();
selector.empty();
$(selector).html(context._.template(templateSource, options, {variable: 'data'}));
selector.html(context._.template(templateSource, options, {variable: 'data'}));
},
/**
* Given a selector representing a container for a VU meter and
* a value between 0.0 and 1.0, light the appropriate lights.
*/
updateVU: function (selector, value) {
updateVU: function ($selector, value) {
// There are 13 VU lights. Figure out how many to
// light based on the incoming value.
var countSelector = 'tr';
var horizontal = ($('table.horizontal', selector).length);
var horizontal = ($selector.find('table.horizontal').length);
if (horizontal) {
countSelector = 'td';
}
var lightCount = $(countSelector, selector).length;
var lightCount = $selector.find(countSelector).length;
var i = 0;
var state = 'on';
var lights = Math.round(value * lightCount);
@ -61,15 +62,15 @@
var $light = null;
var colorClass = 'vu-green-';
var lightSelectorPrefix = selector + ' td.vu';
var lightSelectorPrefix = $selector.find('td.vu');
var thisLightSelector = null;
// Remove all light classes from all lights
var allLightsSelector = selector + ' td.vulight';
var allLightsSelector = $selector.find('td.vulight');
$(allLightsSelector).removeClass('vu-green-off vu-green-on vu-red-off vu-red-on');
// Set the lights
for (i=0; i<lightCount; i++) {
for (i = 0; i < lightCount; i++) {
colorClass = 'vu-green-';
state = 'on';
if (i >= redSwitch) {
@ -78,9 +79,9 @@
if (i >= lights) {
state = 'off';
}
thisLightSelector = lightSelectorPrefix + i;
$light = $(thisLightSelector);
$light.addClass(colorClass + state);
var lightIndex = horizontal ? i : lightCount - i - 1;
allLightsSelector.eq(lightIndex).addClass(colorClass + state);
}
}

View File

@ -105,7 +105,30 @@
}
function reportFailureAnalytics() {
// on cancel, see if the user is leaving without having passed the audio test; if so, report it
var lastAnalytics = stepSelectGear.getLastAudioTestFailAnalytics();
if(lastAnalytics) {
logger.debug("reporting audio test failed")
context.JK.GA.trackAudioTestFailure(lastAnalytics.platform, lastAnalytics.reason, lastAnalytics.data);
}
else {
logger.debug("audiotest failure: nothing to report");
}
var lastAnalytics = stepNetworkTest.getLastNetworkFailAnalytics();
if(lastAnalytics) {
logger.debug("reporting network test failed");
context.JK.GA.trackNetworkTestFailure(lastAnalytics.reason, lastAnalytics.data);
}
else {
logger.debug("network test failure: nothing to report");
}
}
function onCanceled() {
reportFailureAnalytics();
if (app.cancelFtue) {
app.cancelFtue();
app.afterFtue = null;
@ -116,6 +139,8 @@
}
function onClosed() {
reportFailureAnalytics();
if (app.afterFtue) {
// If there's a function to invoke, invoke it.
app.afterFtue();

View File

@ -28,10 +28,14 @@
var forceDisabledChat = firstTimeShown;
voiceChatHelper.reset(forceDisabledChat);
voiceChatHelper.beforeShow();
firstTimeShown = false;
}
function beforeHide() {
voiceChatHelper.beforeHide();
}
function handleNext() {
return true;
}
@ -39,13 +43,14 @@
function initialize(_$step) {
$step = _$step;
voiceChatHelper.initialize($step, true);
voiceChatHelper.initialize($step, 'configure_voice_gear_wizard', true, {vuType: "vertical", lightCount: 8, lightWidth: 3, lightHeight: 10}, 101);
}
this.handleNext = handleNext;
this.newSession = newSession;
this.beforeShow = beforeShow;
this.beforeHide = beforeHide;
this.initialize = initialize;
return this;

View File

@ -5,6 +5,7 @@
context.JK = context.JK || {};
context.JK.StepDirectMonitoring = function (app) {
var logger = context.JK.logger;
var $step = null;
var $directMonitoringBtn = null;
var isPlaying = false;
@ -58,6 +59,7 @@
function beforeShow() {
context.jamClient.SessionRemoveAllPlayTracks();
logger.debug("adding test sound");
if(!context.jamClient.SessionAddPlayTrack("skin:jktest-audio.wav")) {
context.JK.alertSupportedNeeded('Unable to open test sound');
}
@ -68,6 +70,7 @@
stopPlay();
}
logger.debug("removing test sound")
context.jamClient.SessionRemoveAllPlayTracks();
if(playCheckInterval) {

View File

@ -11,6 +11,10 @@
var $step = null;
function getLastNetworkFailAnalytics() {
return networkTest.getLastNetworkFailure();
}
function networkTestDone() {
updateButtons();
}
@ -62,6 +66,7 @@
this.beforeHide = beforeHide;
this.beforeShow = beforeShow;
this.initialize = initialize;
this.getLastNetworkFailAnalytics = getLastNetworkFailAnalytics;
return this;
}

View File

@ -5,6 +5,7 @@
context.JK = context.JK || {};
context.JK.StepSelectGear = function (app, dialog) {
var ALERT_NAMES = context.JK.ALERT_NAMES;
var EVENTS = context.JK.EVENTS;
var ASSIGNMENT = context.JK.ASSIGNMENT;
var VOICE_CHAT = context.JK.VOICE_CHAT;
@ -19,6 +20,10 @@
var gearTest = new context.JK.GearTest(app);
var loopbackShowing = false;
// the goal of lastFailureAnalytics and trackedPass are to send only a single AudioTest 'Pass' or 'Failed' event, per FTUE wizard open/close
var lastFailureAnalytics = {};
var trackedPass = false;
var $watchVideoInput = null;
var $watchVideoOutput = null;
var $audioInput = null;
@ -135,17 +140,11 @@
if(!audioInputDeviceId || audioInputDeviceId == '') {
context.JK.prodBubble($audioInput.closest('.easydropdown-wrapper'), 'select-input', {}, {positions:['right', 'top']});
}
//context.JK.Banner.showAlert('To be a valid input audio device, the device must have at least 1 input channel.');
return false;
}
var $allOutputs = $outputChannels.find('input[type="checkbox"]');
if ($allOutputs.length < 2) {
if(!audioOutputDeviceId || audioOutputDeviceId == '') {
context.JK.prodBubble($audioOutput.closest('.easydropdown-wrapper'), 'select-output', {}, {positions:['right', 'top'], duration:7000});
else {
// this path should be impossible because we filter output devices with 0 inputs from the input device dropdown
// but we might flip that, so it's nice to leave this in to catch us later
context.JK.Banner.showAlert('To be a valid input audio device, the device must have at least 1 input port.');
}
// ERROR: not enough channels
//context.JK.Banner.showAlert('To be a valid output audio device, the device must have at least 2 output channels.');
return false;
}
@ -166,6 +165,20 @@
}
}
var $allOutputs = $outputChannels.find('input[type="checkbox"]');
if ($allOutputs.length < 2) {
if(!audioOutputDeviceId || audioOutputDeviceId == '') {
context.JK.prodBubble($audioOutput.closest('.easydropdown-wrapper'), 'select-output', {}, {positions:['right', 'top'], duration:7000});
}
else {
// this path indicates that the user has deliberately chosen a device, so we need to tell them that this device does not work with JamKazam
context.JK.prodBubble($audioOutput.closest('.easydropdown-wrapper'), 'select-output', {}, {positions:['right', 'top'], duration:7000});
}
return false;
}
// ensure 2 outputs are selected
var $assignedOutputs = $outputChannels.find('input[type="checkbox"]:checked');
var $unassignedOutputs = $outputChannels.find('input[type="checkbox"]:not(:checked)');
@ -435,10 +448,19 @@
function initializeASIOButtons() {
$asioInputControlBtn.unbind('click').click(function () {
if(gearTest.isScoring()) {
return false;
}
context.jamClient.FTUEOpenControlPanel(selectedAudioInput());
return false;
});
$asioOutputControlBtn.unbind('click').click(function () {
if(gearTest.isScoring()) {
return false;
}
context.jamClient.FTUEOpenControlPanel(selectedAudioOutput());
return false;
});
}
@ -715,7 +737,7 @@
// * reuse IO score if it was good/acceptable
// * rescore IO if it was bad or skipped from previous try
function attemptScore(refocused) {
gearTest.attemptScore(refocused);
gearTest.attemptScore(selectedDeviceInfo, refocused);
}
function initializeAudioInputChanged() {
@ -735,9 +757,42 @@
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
}
function getLastAudioTestFailAnalytics() {
return lastFailureAnalytics;
}
function storeLastFailureForAnalytics(platform, reason, data) {
if(!trackedPass) {
lastFailureAnalytics = {
platform: platform,
reason: reason,
data: data
}
}
}
function onGearTestFail(e, data) {
renderScoringStopped();
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
if(data.reason == "latency") {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
}
else if(data.reason = "io") {
if(data.ioTarget == 'bad') {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.ioTarget, data.ioTargetScore);
}
else {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.ioVariance, data.ioVarianceScore);
}
}
else {
logger.error("unknown reason in onGearTestFail: " + data.reason)
}
}
function onGearTestInvalidated(e, data) {
initializeNextButtonState();
}
function handleNext() {
@ -775,8 +830,10 @@
return false;
}
else {
savedProfile = true;
context.JK.GA.trackAudioTestCompletion(context.JK.detectOS());
trackedPass = true;
lastFailureAnalytics = null;
savedProfile = true;
return true;
}
}
@ -800,14 +857,21 @@
initializeResync();
}
function beforeWizardShow() {
lastFailureAnalytics = null;
trackedPass = false;
}
function beforeShow() {
$(window).on('focus', onFocus);
initializeNextButtonState();
context.JK.onBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'gear_test', gearTest.onInvalidAudioDevice);
}
function beforeHide() {
logger.debug("unregistering focus watch")
$(window).off('focus', onFocus);
context.JK.offBackendEvent(ALERT_NAMES.AUDIO_DEVICE_NOT_PRESENT, 'gear_test', gearTest.onInvalidAudioDevice);
}
function resetState() {
@ -849,12 +913,15 @@
.on(gearTest.GEAR_TEST_START, onGearTestStarted)
.on(gearTest.GEAR_TEST_DONE, onGearTestDone)
.on(gearTest.GEAR_TEST_FAIL, onGearTestFail)
.on(gearTest.GEAR_TEST_INVALIDATED_ASYNC, onGearTestInvalidated)
}
this.getLastAudioTestFailAnalytics = getLastAudioTestFailAnalytics;
this.handleNext = handleNext;
this.newSession = newSession;
this.beforeShow = beforeShow;
this.beforeHide = beforeHide;
this.beforeWizardShow = beforeWizardShow;
this.initialize = initialize;
self = this;

View File

@ -14,6 +14,11 @@
var validIOScore = false;
var latencyScore = null;
var ioScore = null;
var lastSavedTime = new Date();
// this should be marked TRUE when the backend sends an invalid_audio_device alert
var asynchronousInvalidDevice = false;
var selectedDeviceInfo = null;
var $scoreReport = null;
var $ioHeader = null;
@ -31,6 +36,7 @@
var $ioScoreSection = null;
var $latencyScoreSection = null;
var $self = $(this);
var GEAR_TEST_START = "gear_test.start";
@ -41,9 +47,10 @@
var GEAR_TEST_DONE = "gear_test.done";
var GEAR_TEST_FAIL = "gear_test.fail";
var GEAR_TEST_IO_PROGRESS = "gear_test.io_progress";
var GEAR_TEST_INVALIDATED_ASYNC = "gear_test.async_invalidated"; // happens when backend alerts us device is invalid
function isGoodFtue() {
return validLatencyScore && validIOScore;
return validLatencyScore && validIOScore && !asynchronousInvalidDevice;
}
function processIOScore(io) {
@ -93,13 +100,15 @@
$self.triggerHandler(GEAR_TEST_DONE)
}
else {
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io'});
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std});
}
}
function automaticScore() {
logger.debug("automaticScore: calling FTUESave(false)");
lastSavedTime = new Date(); // save before and after FTUESave, because the event happens in a multithreaded way
var result = jamClient.FTUESave(false);
lastSavedTime = new Date();
if(result && result != "") {
logger.debug("unable to FTUESave(false). reason=" + result);
return false;
@ -125,12 +134,14 @@
// on refocus=true:
// * reuse IO score if it was good/acceptable
// * rescore IO if it was bad or skipped from previous try
function attemptScore(refocused) {
function attemptScore(_selectedDeviceInfo, refocused) {
if(scoring) {
logger.debug("gear-test: already scoring");
return;
}
selectedDeviceInfo = _selectedDeviceInfo;
scoring = true;
asynchronousInvalidDevice = false;
$self.triggerHandler(GEAR_TEST_START);
$self.triggerHandler(GEAR_TEST_LATENCY_START);
validLatencyScore = false;
@ -197,7 +208,7 @@
}
else {
scoring = false;
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency'})
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', latencyScore: latencyScore.latency})
}
})
}, 250);
@ -276,6 +287,18 @@
return ioScore;
}
function getLastSavedTime() {
return lastSavedTime;
}
function onInvalidAudioDevice() {
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.')
}
function showLoopbackDone() {
$loopbackCompleted.show();
}
@ -301,6 +324,7 @@
function invalidateScore() {
validLatencyScore = false;
validIOScore = false;
asynchronousInvalidDevice = false;
resetScoreReport();
}
@ -337,6 +361,18 @@
$ioCountdownSecs.text(secondsLeft);
}
function uniqueDeviceName() {
try {
return selectedDeviceInfo.input.info.displayName + '(' + selectedDeviceInfo.input.behavior.shortName + ')' + '-' +
selectedDeviceInfo.output.info.displayName + '(' + selectedDeviceInfo.output.behavior.shortName + ')' + '-' +
context.JK.GetOSAsString();
}
catch(e){
logger.error("unable to devise unique device name for stats: " + e.toString());
return "Unknown";
}
}
function handleUI($testResults) {
if(!$testResults.is('.ftue-box.results')) {
@ -394,6 +430,9 @@
function onGearTestDone(e, data) {
$resultsText.attr('scored', 'complete');
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.pass, latencyScore);
rest.userCertifiedGear({success: true, client_id: app.clientId, audio_latency: getLatencyScore()});
}
@ -405,6 +444,21 @@
}
rest.userCertifiedGear({success: false});
if(data.reason == "latency") {
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.latencyFail, data.latencyScore);
}
else if(data.reason = "io") {
if(data.ioTarget == 'bad') {
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.ioTargetFail, data.ioTargetScore);
}
else {
context.JK.GA.trackAudioTestData(uniqueDeviceName(), context.JK.GA.AudioTestDataReasons.ioVarianceFail, data.ioVarianceScore);
}
}
else {
logger.error("unknown reason in onGearTestFail: " + data.reason)
}
}
$self
@ -445,6 +499,7 @@
this.GEAR_TEST_DONE = GEAR_TEST_DONE;
this.GEAR_TEST_FAIL = GEAR_TEST_FAIL;
this.GEAR_TEST_IO_PROGRESS = GEAR_TEST_IO_PROGRESS;
this.GEAR_TEST_INVALIDATED_ASYNC = GEAR_TEST_INVALIDATED_ASYNC;
this.initialize = initialize;
this.isScoring = isScoring;
@ -457,6 +512,8 @@
this.isGoodFtue = isGoodFtue;
this.getLatencyScore = getLatencyScore;
this.getIOScore = getIOScore;
this.getLastSavedTime = getLastSavedTime;
this.onInvalidAudioDevice = onInvalidAudioDevice;
return this;
}

View File

@ -192,7 +192,6 @@
rest.createDiagnostic({
type: 'GEAR_SELECTION',
data: {
logs: logger.logCache,
client_type: context.JK.clientType(),
client_id:
context.JK.JamServer.clientID,
@ -249,7 +248,6 @@
}
})
logger.debug("chatInputs:", chatInputs)
return chatInputs;
}

View File

@ -28,6 +28,12 @@
var $outputChannels = null;
var $templateAudioPort = null;
var $scoreReport = null;
var $audioInputVuLeft = null;
var $audioInputVuRight = null;
var $audioInputFader = null;
var $audioOutputVuLeft = null;
var $audioOutputVuRight = null;
var $audioOutputFader = null;
var faderMap = {
'loopback-audio-input-fader': jamClient.FTUESetInputVolume,
@ -42,7 +48,7 @@
function attemptScore() {
gearTest.attemptScore();
gearTest.attemptScore(selectedDeviceInfo);
}
@ -220,43 +226,45 @@
}
function initializeVUMeters() {
var vuMeters = [
'#loopback-audio-input-vu-left',
'#loopback-audio-input-vu-right',
'#loopback-audio-output-vu-left',
'#loopback-audio-output-vu-right'
];
$.each(vuMeters, function () {
context.JK.VuHelpers.renderVU(this,
{vuType: "horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3});
});
var vuOptions = {vuType: "horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3};
var faders = context._.keys(faderMap);
$.each(faders, function () {
var fid = this;
context.JK.FaderHelpers.renderFader('#' + fid,
{faderId: fid, faderType: "horizontal", width: 163});
context.JK.FaderHelpers.subscribe(fid, faderChange);
});
context.JK.VuHelpers.renderVU($audioInputVuLeft, vuOptions);
context.JK.VuHelpers.renderVU($audioInputVuRight, vuOptions);
context.JK.VuHelpers.renderVU($audioOutputVuLeft, vuOptions);
context.JK.VuHelpers.renderVU($audioOutputVuRight, vuOptions);
var faderOptions = {faderId: '', faderType: "horizontal", width: 163};
context.JK.FaderHelpers.renderFader($audioInputFader, faderOptions);
context.JK.FaderHelpers.renderFader($audioOutputFader, faderOptions);
$audioInputFader.on('fader_change', inputFaderChange);
$audioOutputFader.on('fader_change', outputFaderChange);
}
// renders volumes based on what the backend says
function renderVolumes() {
$.each(context._.keys(faderReadMap), function (index, faderId) {
// faderChange takes a value from 0-100
var $fader = $('[fader-id="' + faderId + '"]');
var db = faderReadMap[faderId]();
var faderPct = db + 80;
context.JK.FaderHelpers.setHandlePosition($fader, faderPct);
});
// input
var $inputFader = $audioInputFader.find('[control="fader"]');
var db = context.jamClient.FTUEGetInputVolume();
var faderPct = db + 80;
context.JK.FaderHelpers.setHandlePosition($inputFader, faderPct);
// output
var $outputFader = $audioOutputFader.find('[control="fader"]');
var db = context.jamClient.FTUEGetOutputVolume();
var faderPct = db + 80;
context.JK.FaderHelpers.setHandlePosition($outputFader, faderPct);
}
function faderChange(faderId, newValue, dragging) {
var setFunction = faderMap[faderId];
// TODO - using hardcoded range of -80 to 20 for output levels.
var mixerLevel = newValue - 80; // Convert our [0-100] to [-80 - +20] range
setFunction(mixerLevel);
function inputFaderChange(e, data) {
var mixerLevel = data.percentage - 80; // Convert our [0-100] to [-80 - +20] range
context.jamClient.FTUESetInputVolume(mixerLevel);
}
function outputFaderChange(e, data) {
var mixerLevel = data.percentage - 80;
context.jamClient.FTUESetOutputVolume(mixerLevel);
}
function registerVuCallbacks() {
@ -329,6 +337,13 @@
$outputChannels = $step.find('.output-ports')
$templateAudioPort = $('#template-audio-port');
$scoreReport = $step.find('.results');
$audioInputVuLeft = $step.find('.audio-input-vu-left');
$audioInputVuRight = $step.find('.audio-input-vu-right');
$audioInputFader= $step.find('.audio-input-fader');
$audioOutputVuLeft = $step.find('.audio-output-vu-left');
$audioOutputVuRight = $step.find('.audio-output-vu-right');
$audioOutputFader= $step.find('.audio-output-fader');
operatingSystem = context.JK.GetOSAsString();
frameBuffers.initialize($step.find('.frame-and-buffers'));
@ -348,18 +363,19 @@
initializeASIOButtons();
initializeResync();
}
context.JK.loopbackAudioInputVUCallback = function (dbValue) {
context.JK.ftueVUCallback(dbValue, '#loopback-audio-input-vu-left');
context.JK.ftueVUCallback(dbValue, '#loopback-audio-input-vu-right');
};
context.JK.loopbackAudioOutputVUCallback = function (dbValue) {
context.JK.ftueVUCallback(dbValue, '#loopback-audio-output-vu-left');
context.JK.ftueVUCallback(dbValue, '#loopback-audio-output-vu-right');
};
context.JK.loopbackChatInputVUCallback = function (dbValue) {
};
context.JK.loopbackAudioInputVUCallback = function (dbValue) {
context.JK.ftueVUCallback(dbValue, $audioInputVuLeft);
context.JK.ftueVUCallback(dbValue, $audioInputVuRight);
};
context.JK.loopbackAudioOutputVUCallback = function (dbValue) {
context.JK.ftueVUCallback(dbValue, $audioOutputVuLeft);
context.JK.ftueVUCallback(dbValue, $audioOutputVuRight);
};
context.JK.loopbackChatInputVUCallback = function (dbValue) {
};
}
this.getGearTest = getGearTest;
this.handleNext = handleNext;

View File

@ -139,6 +139,13 @@
function onBeforeShow(args) {
context._.each(STEPS, function(step) {
// let every step know the wizard is being shown
if(step.beforeWizardShow) {
step.beforeWizardShow(args);
}
});
$currentWizardStep = null;
previousStep = null;

View File

@ -38,7 +38,7 @@
float:left;
vertical-align:top;
@include border_box_sizing;
padding: 20px 20px 0 0;
padding: 0 20px 0 0;
}
.sub-column {
@ -48,6 +48,25 @@
@include border_box_sizing;
}
.ftue-box.chat-inputs {
height:224px;
width:90%;
@include border_box_sizing;
}
.vu-meter {
width:10%;
height:224px;
padding: 0 3px;
.ftue-controls {
height: 224px;
}
}
.tab {
padding-top:20px;
}
.tab[tab-id="music-audio"] {
.column {
&:nth-of-type(1) {

View File

@ -27,10 +27,16 @@
}
}
.channels-holder {
.ftue-input {
background-position:4px 6px;
padding-left:14px;
}
}
.unassigned-input-channels {
min-height: 22px;
min-height: 240px;
overflow-y: auto;
max-height:23%;
max-height:93%;
//padding-right:18px; // to keep draggables off of scrollbar. maybe necessary
@ -39,6 +45,7 @@
}
}
.ftue-input {
}
&.drag-in-progress {
@ -84,6 +91,7 @@
font-size: 12px;
cursor: move;
padding: 4px;
padding-left:10px;
border: solid 1px #999;
margin-bottom: 15px;
white-space: nowrap;
@ -91,6 +99,9 @@
text-overflow: ellipsis;
text-align: left;
line-height:20px;
background-image:url('/assets/content/icon_drag_handle.png');
background-position:0 3px;
background-repeat:no-repeat;
//direction: rtl;
&.ui-draggable-dragging {
margin-bottom: 0;
@ -126,10 +137,13 @@
.ftue-input {
padding: 0;
padding-left:10px;
border: 0;
margin-bottom: 0;
&.ui-draggable-dragging {
padding: 4px;
padding-left:14px;
background-position:4px 6px;
border: solid 1px #999;
overflow: visible;
}
@ -163,7 +177,7 @@
}*/
}
&:nth-of-type(2) {
padding-left:2px;
padding-left:10px;
float: right;
}
}

View File

@ -9,8 +9,14 @@
display:inline;
}
.recording-controls {
position:relative;
.recording-position {
margin: 5px 0 0 -15px;
width: 95%;
display:inline-block;
}
.recording-current {
position:absolute;
}
.icheckbuttons {

View File

@ -40,6 +40,31 @@
top:85px;
left:12px;
}
.recording-position {
display:inline-block;
width:80%;
font-family:Arial, Helvetica, sans-serif;
font-size:11px;
height:18px;
vertical-align:top;
margin-left:15px;
}
.recording-controls {
position: absolute;
bottom: 0;
height: 25px;
.play-button {
top:2px;
}
}
.playback-mode-buttons {
display:none;
}
}
@ -509,18 +534,6 @@ table.vu td {
}
}
.recording-controls {
display:none;
.play-button {
outline:none;
}
.play-button img.pausebutton {
display:none;
}
}
#recording-finished-dialog .recording-controls {
display:block;
}
@ -624,16 +637,6 @@ table.vu td {
text-align:center;
}
.recording-position {
display:inline-block;
width:80%;
font-family:Arial, Helvetica, sans-serif;
font-size:11px;
height:18px;
vertical-align:top;
}
.recording-time {
display:inline-block;
height:16px;
@ -672,6 +675,22 @@ table.vu td {
font-size:18px;
}
.recording-controls {
display:none;
.play-button {
outline:none;
}
.play-button img.pausebutton {
display:none;
}
}
.playback-mode-buttons {
display:none;
}
.currently-recording {
background-color: $ColorRecordingBackground;
}

View File

@ -1,8 +1,47 @@
@import "client/common.css.scss";
@charset "UTF-8";
.dialog.configure-tracks, .dialog.gear-wizard {
table.vu {
position:absolute;
}
.ftue-controls {
position:relative;
width: 55px;
background-color: #222;
float:right;
}
.ftue-vu-left {
position: relative;
left: 0px;
}
.ftue-vu-right {
position: relative;
left: 46px;
}
.ftue-fader {
//margin:5px 6px;
position: absolute;
top: 8px;
left: 14px;
}
.gain-label {
color: $ColorScreenPrimary;
position: absolute;
bottom: -1px;
left: 10px;
}
.voicechat-option {
position: relative;
float:left;
width:50%;
height:109px;
div {
@ -33,14 +72,23 @@
}
}
.ftue-box {
.vu-meter {
@include border_box_sizing;
float:left;
}
.ftue-box {
@include border_box_sizing;
background-color: #222222;
font-size: 13px;
padding: 8px;
&.chat-inputs {
height: 230px !important;
//height: 230px !important;
overflow: auto;
color:white;
float:left;
&.disabled {
color:gray;
@ -50,6 +98,7 @@
display: inline-block;
height: 32px;
vertical-align: middle;
line-height:20px;
}
.chat-input {
@ -60,5 +109,7 @@
}
}
}
}
}

View File

@ -99,6 +99,7 @@
}
ul.results-text {
margin-left:-5px;
padding: 10px 8px;
li {

View File

@ -78,7 +78,7 @@
}
&.instructions {
height: 268px !important;
height: 248px !important;
line-height: 16px;
@include border_box_sizing;
@ -203,14 +203,40 @@
width: 25%;
&:nth-of-type(2) {
width: 50%;
width: 75%;
}
}
.instructions {
height: 268px !important;
}
.watch-video {
margin-top: 25px;
}
.ftue-box.chat-inputs {
height:124px;
width:85%;
@include border_box_sizing;
}
.vu-meter {
width:15%;
padding: 0 3px;
.ftue-controls {
height: 124px;
}
.chat-fader {
top:4px !important;
}
}
.voice-chat-header {
margin-top:10px;
}
}
.wizard-step[layout-wizard-step="4"] {

View File

@ -74,7 +74,7 @@
}
.ftue-controls {
margin-top: 16px;
margin-top: 10px;
position:relative;
height: 48px;
width: 220px;
@ -101,11 +101,12 @@
}
.ports-header {
margin-top:20px;
margin-top:10px;
}
.ports {
height:100px;
overflow: auto;
}
.asio-settings-input-btn, .asio-settings-output-btn {
@ -114,7 +115,7 @@
}
.resync-btn {
margin-top:35px;
margin-top:29px;
}
.run-test-btn {
@ -124,7 +125,7 @@
}
.frame-and-buffers {
margin-top:10px;
margin-top:4px;
}
.test-results-header {

View File

@ -15,6 +15,10 @@ class ArtifactsController < ApiController
render :json => result, :status => :ok
end
def healthcheck
render :json => {}
end
def versioncheck

View File

@ -5,7 +5,7 @@
<!-- dialog header -->
<div class="content-head">
<%= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon") %><h1>alert</h1>
<%= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon") %><h1></h1>
</div>

View File

@ -48,21 +48,26 @@
.tab{'tab-id' => 'voice-chat'}
.column
%form.select-voice-chat-option.section
.sub-header Select Voice Chat Option
.voicechat-option.reuse-audio-input
%input{type:"radio", name: "voicechat", checked:"checked"}
%h3 Use Music Microphone
%p I am already using a microphone to capture my vocal or instrumental music, so I can talk with other musicians using that microphone
.voicechat-option.use-chat-input
%input{type:"radio", name: "voicechat"}
%h3 Use Chat Microphone
%p I am not using a microphone for acoustic instruments or vocals, so use the input selected to the right for voice chat during my sessions
.column
.select-voice-chat
.sub-header Voice Chat Input
.ftue-box.chat-inputs
%form.select-voice-chat-option.section.voice
.sub-header Select Voice Chat Option
.voicechat-option.reuse-audio-input
%input{type:"radio", name: "voicechat", checked:"checked"}
%h3 Use Music Microphone
%p I am already using a microphone to capture my vocal or instrumental music, so I can talk with other musicians using that microphone
.voicechat-option.use-chat-input
%input{type:"radio", name: "voicechat"}
%h3 Use Chat Microphone
%p I am not using a microphone for acoustic instruments or vocals, so use the input selected to the right for voice chat during my sessions
.clearall
.select-voice-chat
.sub-header Voice Chat Input
.ftue-box.chat-inputs
.vu-meter
.ftue-controls
.ftue-vu-left.voice-chat-vu-left
.ftue-fader.chat-fader
.gain-label GAIN
.ftue-vu-right.voice-chat-vu-right
.clearall
.buttons

View File

@ -32,4 +32,8 @@
<script type="text/template" id="template-help-move-on-loopback-success">
You can move to the next step now.
</script>
<script type="text/template" id="template-help-minimum-output-channels">
To be a valid output audio device, it must have at least 2 output ports.
</script>

View File

@ -277,10 +277,6 @@
var testBridgeScreen = new JK.TestBridgeScreen(JK.app);
testBridgeScreen.initialize();
if(!connected) {
jamServer.initiateReconnect(null, true);
}
JK.app.initialRouting();
JK.hideCurtain(300);
}
@ -317,8 +313,12 @@
});
// this ensures that there is always a CurrentSessionModel, even if it's for a non-active session
JK.CurrentSessionModel = new JK.SessionModel(JK.app, JK.JamServer, window.jamClient);
JK.CurrentSessionModel = new JK.SessionModel(JK.app, JK.JamServer, window.jamClient, null);
}
// make surethe CurrentSessionModel exists before initializing backend alerts
var backendAlerts = new JK.BackendAlerts(JK.app);
backendAlerts.initialize();
JK.bindHoverEvents();
})

View File

@ -42,10 +42,6 @@
function _initAfterConnect(connected) {
if (this.didInitAfterConnect) return;
this.didInitAfterConnect = true
if(!connected) {
jamServer.initiateReconnect(null, true);
}
}
JK.app = JK.JamKazam();

View File

@ -21,13 +21,13 @@
.clearall
%ul.results-text
%li.latency-good Your latency is good.
%li.latency-acceptable Your latency is acceptable.
%li.latency-acceptable Your latency is fair.
%li.latency-bad Your latency is poor.
%li.io-rate-good Your I/O rate is good.
%li.io-rate-acceptable Your I/O rate is acceptable.
%li.io-rate-acceptable Your I/O rate is fair.
%li.io-rate-bad Your I/O rate is poor.
%li.io-var-good Your I/O variance is good.
%li.io-var-acceptable Your I/O variance is acceptable.
%li.io-var-acceptable Your I/O variance is fair.
%li.io-var-bad Your I/O variance is poor.
%li.success You may proceed to the next step.
%li.failure

View File

@ -73,7 +73,7 @@
%li Drag and drop the input port(s) from your audio interface to each track.
%li Select the instrument for each track.
.center
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
%a.button-orange.watch-video{href:'https://www.youtube.com/watch?v=SjMeMZpKNR4', rel:'external'} WATCH VIDEO
.wizard-step-column
%h2 Unassigned Ports
.unassigned-input-channels.channels-holder
@ -97,9 +97,9 @@
%p Determine if you need to set up a voice chat input.
%p If you do, then assign the audio input to use to capture voice chat.
.center
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
%a.button-orange.watch-video{href:'https://www.youtube.com/watch?v=f7niycdWm7Y', rel:'external'} WATCH VIDEO
.wizard-step-column
%h2 Select Voice Chat Option
%h2.sub-header Select Voice Chat Option
%form.voice
.voicechat-option.reuse-audio-input
%input{type:"radio", name: "voicechat", checked:"checked"}
@ -109,9 +109,16 @@
%input{type:"radio", name: "voicechat"}
%h3 Use Chat Microphone
%p I am not using a microphone for acoustic instruments or vocals, so use the input selected to the right for voice chat during my sessions
.wizard-step-column
%h2 Voice Chat Input
.clearall
// .wizard-step-column
%h2.sub-header.voice-chat-header Voice Chat Input
.ftue-box.chat-inputs
.vu-meter
.ftue-controls
.ftue-vu-left.voice-chat-vu-left
.ftue-fader.chat-fader
.gain-label GAIN
.ftue-vu-right.voice-chat-vu-right
.wizard-step{ 'layout-wizard-step' => "4", 'dialog-title' => "Turn Off Direct Monitoring", 'dialog-purpose' => "DirectMonitoring" }
@ -127,7 +134,7 @@
%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.
.center
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
%a.button-orange.watch-video{href:'https://www.youtube.com/watch?v=-nC-D3JBHnk', rel:'external'} WATCH VIDEO
.wizard-step-column
.help-content
When you have fully turned off the direct monitoring control (if any) on your audio interface,

View File

@ -46,10 +46,10 @@
%h2.ports-header Audio Input Ports
.ftue-box.input-ports.ports
.ftue-controls
.ftue-vu-left#loopback-audio-input-vu-left
.ftue-fader#loopback-audio-input-fader
.ftue-vu-left.audio-input-vu-left
.ftue-fader.audio-input-fader
.gain-label GAIN
.ftue-vu-right#loopback-audio-input-vu-right
.ftue-vu-right.audio-input-vu-right
= render :partial => "/clients/wizard/framebuffers"
.wizard-step-column
%h2
@ -59,10 +59,10 @@
%h2.ports-header Audio Output Ports
.ftue-box.output-ports.ports
.ftue-controls
.ftue-vu-left#loopback-audio-output-vu-left
.ftue-fader#loopback-audio-output-fader
.ftue-vu-left.audio-output-vu-left
.ftue-fader.audio-output-fader
.gain-label GAIN
.ftue-vu-right#loopback-audio-output-vu-right
.ftue-vu-right.audio-output-vu-right
%a.button-orange.resync-btn RESYNC
.wizard-step-column
%h2.test-results-header

View File

@ -408,6 +408,9 @@ SampleApp::Application.routes.draw do
# version check for JamClient
match '/versioncheck' => 'artifacts#versioncheck'
# no-op method to see if server is running
match '/healthcheck' => 'artifacts#healthcheck'
# list all uris for available clients on mac, windows, linux, if available
match '/artifacts/clients' => 'artifacts#client_downloads'

View File

@ -3,6 +3,11 @@ description "jam-web"
start on startup
start on runlevel [2345]
stop on runlevel [016]
limit nofile 20000 20000
limit core unlimited unlimited
respawn
respawn limit 10 5
pre-start script
set -e

View File

@ -143,7 +143,8 @@ FactoryGirl.define do
addr {JamIsp.ip_to_num(ip_address)}
locidispid 0
client_type 'client'
last_jam_audio_latency { user.last_jam_audio_latency if user }
last_jam_audio_latency { user.last_jam_audio_latency if user }
# sequence(:channel_id) { |n| "Channel#{n}"}
end
factory :friendship, :class => JamRuby::Friendship do

View File

@ -21,7 +21,7 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru
# step 2 - select gear
find('.ftue-step-title', text: 'Select & Test Audio Gear')
jk_select('Built-in', 'div[layout-wizard-step="1"] select.select-audio-input-device')
find('.btn-next.button-orange').trigger(:click)
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
# step 3 - configure tracks
find('.ftue-step-title', text: 'Configure Tracks')
@ -31,22 +31,22 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru
track_slot = first('.track-target')
input.drag_to(track_slot)
find('.btn-next.button-orange').trigger(:click)
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
# step 4 - configure voice chat
find('.ftue-step-title', text: 'Configure Voice Chat')
find('.btn-next.button-orange').trigger(:click)
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
# step 5 - configure direct monitoring
find('.ftue-step-title', text: 'Turn Off Direct Monitoring')
find('.btn-next.button-orange').trigger(:click)
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
# step 6 - Test Router & Network
find('.ftue-step-title', text: 'Test Router & Network')
find('.button-orange.start-network-test').trigger(:click)
find('.user-btn', text: 'RUN NETWORK TEST ANYWAY').trigger(:click)
find('.button-orange.start-network-test')
find('.btn-next.button-orange').trigger(:click)
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
# step 7 - Success
find('.ftue-step-title', text: 'Success!')

View File

@ -73,12 +73,17 @@ describe "User Progression", :js => true, :type => :feature, :capybara_feature
describe "certified gear" do
before(:each) do
sign_in_poltergeist user
FactoryGirl.create(:latency_tester)
visit '/client#/account/audio'
# step 1 - intro
find("div.account-audio a[data-purpose='add-profile']").trigger(:click)
find('.btn-next').trigger(:click)
jk_select('Built-in', 'div[layout-wizard-step="1"] select.select-audio-input-device')
find('.btn-next.button-orange').trigger(:click)
# step 2 - select gear
find('.ftue-step-title', text: 'Select & Test Audio Gear')
jk_select('Built-in', 'div[layout-wizard-step="1"] select.select-audio-input-device')
find('.btn-next.button-orange:not(.disabled)').trigger(:click)
sleep 1
end

View File

@ -11,22 +11,6 @@ module EventMachine
module WebSocket
class Connection < EventMachine::Connection
attr_accessor :encode_json, :channel_id, :client_id, :user_id, :context, :trusted # client_id is uuid we give to each client to track them as we like
# http://stackoverflow.com/questions/11150147/how-to-check-if-eventmachineconnection-is-open
attr_accessor :connected
def connection_completed
connected = true
super
end
def connected?
!!connected
end
def unbind
connected = false
super
end
end
end
end
@ -226,6 +210,40 @@ module JamWebsockets
MQRouter.client_exchange = @clients_exchange
end
# this method allows you to translate exceptions into websocket channel messages and behavior safely.
# pass in your block, throw an error in your logic, and have the right things happen on the websocket channel
def websocket_comm(client, original_message_id, &blk)
begin
blk.call
rescue SessionError => e
@log.info "ending client session deliberately due to malformed client behavior. reason=#{e}"
begin
# wrap the message up and send it down
error_msg = @message_factory.server_rejection_error(e.to_s)
send_to_client(client, error_msg)
ensure
cleanup_client(client)
end
rescue PermissionError => e
@log.info "permission error. reason=#{e.to_s}"
@log.info e
# wrap the message up and send it down
error_msg = @message_factory.server_permission_error(original_message_id, e.to_s)
send_to_client(client, error_msg)
rescue => e
@log.error "ending client session due to server programming or runtime error. reason=#{e.to_s}"
@log.error e
begin
# wrap the message up and send it down
error_msg = @message_factory.server_generic_error(e.to_s)
send_to_client(client, error_msg)
ensure
cleanup_client(client)
end
end
end
def new_client(client, is_trusted)
# default to using json instead of pb
@ -246,6 +264,9 @@ module JamWebsockets
client.encode_json = false
end
websocket_comm(client, nil) do
handle_login(client, handshake.query)
end
}
client.onclose {
@ -261,50 +282,26 @@ module JamWebsockets
end
}
client.onmessage { |msg|
client.onmessage { |data|
# TODO: set a max message size before we put it through PB?
# TODO: rate limit?
pb_msg = nil
msg = nil
begin
# extract the message safely
websocket_comm(client, nil) do
if client.encode_json
#example: {"type":"LOGIN", "target":"server", "login" : {"username":"hi"}}
parse = JSON.parse(msg)
pb_msg = Jampb::ClientMessage.json_create(parse)
self.route(pb_msg, client)
json = JSON.parse(data)
msg = Jampb::ClientMessage.json_create(json)
else
pb_msg = Jampb::ClientMessage.parse(msg.to_s)
self.route(pb_msg, client)
msg = Jampb::ClientMessage.parse(data.to_s)
end
rescue SessionError => e
@log.info "ending client session deliberately due to malformed client behavior. reason=#{e}"
begin
# wrap the message up and send it down
error_msg = @message_factory.server_rejection_error(e.to_s)
send_to_client(client, error_msg)
ensure
cleanup_client(client)
end
rescue PermissionError => e
@log.info "permission error. reason=#{e.to_s}"
@log.info e
end
# wrap the message up and send it down
error_msg = @message_factory.server_permission_error(pb_msg.message_id, e.to_s)
send_to_client(client, error_msg)
rescue => e
@log.error "ending client session due to server programming or runtime error. reason=#{e.to_s}"
@log.error e
begin
# wrap the message up and send it down
error_msg = @message_factory.server_generic_error(e.to_s)
send_to_client(client, error_msg)
ensure
cleanup_client(client)
end
# then route it internally
websocket_comm(client, msg.message_id) do
self.route(msg, client)
end
}
end
@ -391,9 +388,9 @@ module JamWebsockets
# removes all resources associated with a client
def cleanup_client(client)
@semaphore.synchronize do
client.close if client.connected?
client.close
@semaphore.synchronize do
pending = client.context.nil? # presence of context implies this connection has been logged into
if pending
@ -514,6 +511,7 @@ module JamWebsockets
heartbeat_interval, connection_stale_time, connection_expire_time = determine_connection_times(nil, client_type)
latency_tester = LatencyTester.connect({
client_id: client_id,
channel_id: client.channel_id,
ip_address: remote_ip,
connection_stale_time: connection_stale_time,
connection_expire_time: connection_expire_time})
@ -543,15 +541,15 @@ module JamWebsockets
end
end
def handle_login(login, client)
username = login.username if login.value_for_tag(1)
password = login.password if login.value_for_tag(2)
token = login.token if login.value_for_tag(3)
client_id = login.client_id if login.value_for_tag(4)
reconnect_music_session_id = login.reconnect_music_session_id if login.value_for_tag(5)
client_type = login.client_type if login.value_for_tag(6)
def handle_login(client, options)
username = options["username"]
password = options["password"]
token = options["token"]
client_id = options["client_id"]
reconnect_music_session_id = options["music_session_id"]
client_type = options["client_type"]
@log.info("*** handle_login: token=#{token}; client_id=#{client_id}, client_type=#{client_type}")
@log.info("handle_login: client_type=#{client_type} token=#{token} client_id=#{client_id} channel_id=#{client.channel_id}")
if client_type == Connection::TYPE_LATENCY_TESTER
handle_latency_tester_login(client_id, client_type, client)
@ -568,17 +566,21 @@ module JamWebsockets
user = valid_login(username, password, token, client_id)
# XXX This logic needs to instead be handled by a broadcast out to all websockets indicating dup
# kill any websocket connections that have this same client_id, which can happen in race conditions
# this code must happen here, before we go any further, so that there is only one websocket connection per client_id
existing_context = @client_lookup[client_id]
if existing_context
# in some reconnect scenarios, we may have in memory a websocket client still.
# in some reconnect scenarios, we may have in memory a websocket client still.
# let's whack it, and tell the other client, if still connected, that this is a duplicate login attempt
@log.info "duplicate client: #{existing_context}"
Diagnostic.duplicate_client(existing_context.user, existing_context) if existing_context.client.connected
Diagnostic.duplicate_client(existing_context.user, existing_context)
error_msg = @message_factory.server_duplicate_client_error
send_to_client(existing_context.client, error_msg)
cleanup_client(existing_context.client)
end
connection = JamRuby::Connection.find_by_client_id(client_id)
connection = Connection.find_by_client_id(client_id)
# if this connection is reused by a different user (possible in logout/login scenarios), then whack the connection
# because it will recreate a new connection lower down
if connection && user && connection.user != user
@ -608,7 +610,7 @@ module JamWebsockets
recording_id = nil
ConnectionManager.active_record_transaction do |connection_manager|
music_session_id, reconnected = connection_manager.reconnect(connection, reconnect_music_session_id, remote_ip, connection_stale_time, connection_expire_time)
music_session_id, reconnected = connection_manager.reconnect(connection, client.channel_id, reconnect_music_session_id, remote_ip, connection_stale_time, connection_expire_time)
if music_session_id.nil?
# if this is a reclaim of a connection, but music_session_id comes back null, then we need to check if this connection was IN a music session before.
@ -644,7 +646,7 @@ module JamWebsockets
unless connection
# log this connection in the database
ConnectionManager.active_record_transaction do |connection_manager|
connection_manager.create_connection(user.id, client.client_id, remote_ip, client_type, connection_stale_time, connection_expire_time) do |conn, count|
connection_manager.create_connection(user.id, client.client_id, client.channel_id, remote_ip, client_type, connection_stale_time, connection_expire_time) do |conn, count|
if count == 1
Notification.send_friend_update(user.id, true, conn)
end
@ -755,7 +757,7 @@ module JamWebsockets
if !token.nil? && token != ''
@log.debug "logging in via token"
# attempt login with token
user = JamRuby::User.find_by_remember_token(token)
user = User.find_by_remember_token(token)
if user.nil?
@log.debug "no user found with token #{token}"

View File

@ -3,6 +3,11 @@ description "websocket-gateway"
start on startup
start on runlevel [2345]
stop on runlevel [016]
limit nofile 20000 20000
limit core unlimited unlimited
respawn
respawn limit 10 5
pre-start script
set -e

View File

@ -87,6 +87,7 @@ FactoryGirl.define do
ip_address '1.1.1.1'
as_musician true
client_type 'client'
sequence(:channel_id) { |n| "Channel#{n}"}
end
factory :instrument, :class => JamRuby::Instrument do

View File

@ -41,7 +41,7 @@ end
# does a login and returns client
def login(router, user, password, client_id)
def login(router, user, password, client_id, token, client_type)
message_factory = MessageFactory.new
client = LoginClient.new
@ -60,15 +60,9 @@ def login(router, user, password, client_id)
@router.new_client(client, false)
handshake = double("handshake")
handshake.should_receive(:query).twice.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid })
handshake.should_receive(:query).exactly(3).times.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid, "client_id" => client_id, "token" => token, "client_type" => client_type })
client.onopenblock.call handshake
# create a login message, and pass it into the router via onmsgblock.call
login = message_factory.login_with_user_pass(user.email, password, :client_id => client_id, :client_type => 'client')
# first log in
client.onmsgblock.call login.to_s
client
end
@ -98,15 +92,9 @@ def login_latency_tester(router, latency_tester, client_id)
@router.new_client(client, true)
handshake = double("handshake")
handshake.should_receive(:query).twice.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid })
handshake.should_receive(:query).exactly(3).times.and_return({ "pb" => "true", "channel_id" => SecureRandom.uuid, "client_type" => "latency_tester", "client_id" => client_id })
client.onopenblock.call handshake
# create a login message, and pass it into the router via onmsgblock.call
login = message_factory.login_with_client_id(client_id)
# first log in
client.onmsgblock.call login.to_s
client
end
@ -239,7 +227,7 @@ describe Router do
it "should allow login of valid user", :mq => true do
@user = FactoryGirl.create(:user,
:password => "foobar", :password_confirmation => "foobar")
client1 = login(@router, @user, "foobar", "1")
client1 = login(@router, @user, "foobar", "1", @user.remember_token, "client")
done
end
@ -271,7 +259,7 @@ describe Router do
# make a music_session and define two members
# create client 1, log him in, and log him in to music session
client1 = login(@router, user1, "foobar", "1")
client1 = login(@router, user1, "foobar", "1", user1.remember_token, "client")
done
end
@ -284,9 +272,9 @@ describe Router do
# create client 1, log him in, and log him in to music session
client1 = login(@router, user1, "foobar", "1")
client1 = login(@router, user1, "foobar", "1", user1.remember_token, "client")
client2 = login(@router, user2, "foobar", "2")
client2 = login(@router, user2, "foobar", "2", user2.remember_token, "client")
# make a music_session and define two members
@ -305,10 +293,10 @@ describe Router do
music_session = FactoryGirl.create(:active_music_session, :creator => user1)
# create client 1, log him in, and log him in to music session
client1 = login(@router, user1, "foobar", "1")
client1 = login(@router, user1, "foobar", "1", user1.remember_token, "client")
#login_music_session(@router, client1, music_session)
client2 = login(@router, user2, "foobar", "2")
client2 = login(@router, user2, "foobar", "2", user2.remember_token, "client")
#login_music_session(@router, client2, music_session)
# by creating