merge develop
This commit is contained in:
commit
e211871168
|
|
@ -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}, "
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
''
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE connections ADD COLUMN channel_id VARCHAR(256) NOT NULL;
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 .
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +99,7 @@
|
|||
}
|
||||
|
||||
ul.results-text {
|
||||
margin-left:-5px;
|
||||
padding: 10px 8px;
|
||||
|
||||
li {
|
||||
|
|
|
|||
|
|
@ -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"] {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ class ArtifactsController < ApiController
|
|||
render :json => result, :status => :ok
|
||||
end
|
||||
|
||||
def healthcheck
|
||||
render :json => {}
|
||||
end
|
||||
|
||||
|
||||
def versioncheck
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -42,10 +42,6 @@
|
|||
function _initAfterConnect(connected) {
|
||||
if (this.didInitAfterConnect) return;
|
||||
this.didInitAfterConnect = true
|
||||
|
||||
if(!connected) {
|
||||
jamServer.initiateReconnect(null, true);
|
||||
}
|
||||
}
|
||||
|
||||
JK.app = JK.JamKazam();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue