merge develop
This commit is contained in:
commit
5fc9696d01
|
|
@ -99,7 +99,7 @@ gem 'debugger'
|
|||
|
||||
group :development, :test do
|
||||
gem 'capybara'
|
||||
gem 'rspec-rails'
|
||||
gem 'rspec-rails', '2.14.2'
|
||||
gem 'guard-rspec', '0.5.5'
|
||||
gem 'jasmine', '1.3.1'
|
||||
gem 'execjs', '1.4.0'
|
||||
|
|
|
|||
|
|
@ -169,4 +169,6 @@ periodic_emails.sql
|
|||
remember_extra_scoring_data.sql
|
||||
indexing_for_regions.sql
|
||||
latency_tester.sql
|
||||
scheduled_sessions_next_session_scheduled.sql
|
||||
scheduled_sessions_next_session_scheduled.sql
|
||||
fix_users_location_fields.sql
|
||||
audio_latency.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE connections ADD COLUMN last_jam_audio_latency double precision;
|
||||
ALTER TABLE users RENAME COLUMN audio_latency TO last_jam_audio_latency;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE users DROP COLUMN addr;
|
||||
ALTER TABLE users DROP COLUMN locidispid;
|
||||
ALTER TABLE users DROP COLUMN internet_service_provider;
|
||||
ALTER TABLE users ADD COLUMN statecode varchar(2);
|
||||
ALTER TABLE users ADD COLUMN countrycode varchar(2);
|
||||
|
|
@ -339,7 +339,7 @@ SQL
|
|||
end
|
||||
end
|
||||
|
||||
def join_music_session(user, client_id, music_session, as_musician, tracks)
|
||||
def join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency)
|
||||
connection = nil
|
||||
|
||||
ConnectionManager.active_record_transaction do |connection_manager|
|
||||
|
|
@ -347,7 +347,7 @@ SQL
|
|||
|
||||
connection = Connection.find_by_client_id_and_user_id!(client_id, user.id)
|
||||
|
||||
connection.join_the_session(music_session, as_musician, tracks, user)
|
||||
connection.join_the_session(music_session, as_musician, tracks, user, audio_latency)
|
||||
# connection.music_session_id = music_session.id
|
||||
# connection.as_musician = as_musician
|
||||
# connection.joining_session = true
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ module JamRuby
|
|||
|
||||
validates :as_musician, :inclusion => {:in => [true, false]}
|
||||
validates :client_type, :inclusion => {:in => [TYPE_CLIENT, TYPE_BROWSER, TYPE_LATENCY_TESTER]}
|
||||
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
|
||||
validate :can_join_music_session, :if => :joining_session?
|
||||
validate :user_or_latency_tester_present
|
||||
|
||||
|
|
@ -152,7 +153,7 @@ module JamRuby
|
|||
true
|
||||
end
|
||||
|
||||
def join_the_session(music_session, as_musician, tracks, user)
|
||||
def join_the_session(music_session, as_musician, tracks, user, audio_latency)
|
||||
self.music_session_id = music_session.id
|
||||
self.as_musician = as_musician
|
||||
self.joining_session = true
|
||||
|
|
@ -163,6 +164,7 @@ module JamRuby
|
|||
# if user joins the session as a musician, update their addr and location
|
||||
if as_musician
|
||||
user.update_addr_loc(self, 'j')
|
||||
user.update_audio_latency(self, audio_latency)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -185,7 +187,6 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def require_at_least_one_track_when_in_session
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ module JamRuby
|
|||
|
||||
BATCH_SIZE = 500
|
||||
SINCE_WEEKS = 2
|
||||
NUM_IDX = 3
|
||||
|
||||
SUBTYPES = [:client_notdl, # Registered Musician Has Not Downloaded Client
|
||||
:client_dl_notrun, # Registered Musician Has Downloaded Client But Not Yet Run It
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ module JamRuby
|
|||
unless locidispid.nil?
|
||||
# score_join of left allows for null scores, whereas score_join of inner requires a score however good or bad
|
||||
# this is ANY_SCORE:
|
||||
score_join = 'left' # or 'inner'
|
||||
score_join = 'left outer' # or 'inner'
|
||||
score_min = nil
|
||||
score_max = nil
|
||||
case score_limit
|
||||
|
|
@ -213,14 +213,14 @@ module JamRuby
|
|||
# the default of ANY setup above applies
|
||||
end
|
||||
|
||||
rel = rel.joins("#{score_join} join scores on scores.alocidispid = users.last_jam_locidispid")
|
||||
.where(['scores.blocidispid = ?', locidispid])
|
||||
rel = rel.joins("#{score_join} join current_scores on current_scores.alocidispid = users.last_jam_locidispid")
|
||||
.where(['(current_scores.blocidispid = ? or current_scores.blocidispid is null)', locidispid])
|
||||
|
||||
rel = rel.where(['scores.score > ?', score_min]) unless score_min.nil?
|
||||
rel = rel.where(['scores.score <= ?', score_max]) unless score_max.nil?
|
||||
rel = rel.where(['current_scores.score > ?', score_min]) unless score_min.nil?
|
||||
rel = rel.where(['current_scores.score <= ?', score_max]) unless score_max.nil?
|
||||
|
||||
rel = rel.select('scores.score')
|
||||
rel = rel.group('scores.score')
|
||||
rel = rel.select('current_scores.score')
|
||||
rel = rel.group('current_scores.score')
|
||||
end
|
||||
|
||||
ordering = self.order_param(params)
|
||||
|
|
@ -242,7 +242,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
unless locidispid.nil?
|
||||
rel = rel.order('scores.score ASC NULLS LAST')
|
||||
rel = rel.order('current_scores.score ASC NULLS LAST')
|
||||
end
|
||||
|
||||
rel = rel.order('users.created_at DESC')
|
||||
|
|
@ -391,10 +391,10 @@ module JamRuby
|
|||
|
||||
rel = User.musicians_geocoded
|
||||
.where(['created_at >= ? AND users.id != ?', since_date, usr.id])
|
||||
.joins('inner join scores on users.last_jam_locidispid = scores.alocidispid')
|
||||
.where(['scores.blocidispid = ?', locidispid])
|
||||
.where(['scores.score <= ?', score_limit])
|
||||
.order('scores.score') # best scores first
|
||||
.joins('inner join current_scores on users.last_jam_locidispid = current_scores.alocidispid')
|
||||
.where(['current_scores.blocidispid = ?', locidispid])
|
||||
.where(['current_scores.score <= ?', score_limit])
|
||||
.order('current_scores.score') # best scores first
|
||||
.order('users.created_at DESC') # then most recent
|
||||
.limit(limit)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ module JamRuby
|
|||
validates :musician, :inclusion => {:in => [true, false]}
|
||||
validates :show_whats_next, :inclusion => {:in => [nil, true, false]}
|
||||
validates :mods, json: true
|
||||
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
|
||||
|
||||
# custom validators
|
||||
validate :validate_musician_instruments
|
||||
|
|
@ -292,7 +293,8 @@ module JamRuby
|
|||
|
||||
def joined_score
|
||||
nil unless has_attribute?(:score)
|
||||
read_attribute(:score).to_i
|
||||
a = read_attribute(:score)
|
||||
a.nil? ? nil : a.to_i
|
||||
end
|
||||
|
||||
# mods comes back as text; so give ourselves a parsed version
|
||||
|
|
@ -1168,6 +1170,18 @@ module JamRuby
|
|||
self.save
|
||||
end
|
||||
|
||||
def update_audio_latency(connection, audio_latency)
|
||||
|
||||
# updating the connection is best effort
|
||||
if connection
|
||||
connection.last_jam_audio_latency = audio_latency
|
||||
connection.save
|
||||
end
|
||||
|
||||
self.last_jam_audio_latency = audio_latency
|
||||
self.save
|
||||
end
|
||||
|
||||
def top_followings
|
||||
@topf ||= User.joins("INNER JOIN follows ON follows.followable_id = users.id AND follows.followable_type = '#{self.class.to_s}'")
|
||||
.where(['follows.user_id = ?', self.id])
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ describe ConnectionManager do
|
|||
user = User.find(user_id)
|
||||
|
||||
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
|
||||
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
|
||||
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
|
||||
|
||||
connection.errors.any?.should be_false
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ describe ConnectionManager do
|
|||
|
||||
user = User.find(user_id)
|
||||
|
||||
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS) }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -325,11 +325,11 @@ describe ConnectionManager do
|
|||
music_session_id = music_session.id
|
||||
user = User.find(user_id)
|
||||
|
||||
@connman.join_music_session(user, client_id, music_session, true, TRACKS)
|
||||
@connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
|
||||
|
||||
user = User.find(user_id2)
|
||||
|
||||
connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS)
|
||||
connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS, 10)
|
||||
connection.errors.size.should == 1
|
||||
connection.errors.get(:as_musician).should == [ValidationMessages::FAN_CAN_NOT_JOIN_AS_MUSICIAN]
|
||||
end
|
||||
|
|
@ -343,7 +343,7 @@ describe ConnectionManager do
|
|||
music_session = FactoryGirl.create(:active_music_session, user_id: user_id)
|
||||
user = User.find(user_id)
|
||||
|
||||
connection = @connman.join_music_session(user, client_id, music_session, 'blarg', TRACKS)
|
||||
connection = @connman.join_music_session(user, client_id, music_session, 'blarg', TRACKS, 10)
|
||||
connection.errors.size.should == 0
|
||||
connection.as_musician.should be_false
|
||||
end
|
||||
|
|
@ -362,11 +362,11 @@ describe ConnectionManager do
|
|||
|
||||
user = User.find(musician_id)
|
||||
|
||||
@connman.join_music_session(user, musician_client_id, music_session, true, TRACKS)
|
||||
@connman.join_music_session(user, musician_client_id, music_session, true, TRACKS, 10)
|
||||
|
||||
# now join the session as a fan, bt fan_access = false
|
||||
user = User.find(fan_id)
|
||||
connection = @connman.join_music_session(user, fan_client_id, music_session, false, TRACKS)
|
||||
connection = @connman.join_music_session(user, fan_client_id, music_session, false, TRACKS, 10)
|
||||
connection.errors.size.should == 1
|
||||
end
|
||||
|
||||
|
|
@ -381,7 +381,7 @@ describe ConnectionManager do
|
|||
|
||||
@connman.create_connection(user_id, client_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) } .to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it "join_music_session fails if no music_session" do
|
||||
|
|
@ -392,7 +392,7 @@ describe ConnectionManager do
|
|||
music_session = ActiveMusicSession.new
|
||||
|
||||
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
|
||||
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
|
||||
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]
|
||||
end
|
||||
|
|
@ -407,7 +407,7 @@ describe ConnectionManager do
|
|||
|
||||
@connman.create_connection(user_id, client_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) } .to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -435,7 +435,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.join_music_session(user, client_id, music_session, true, TRACKS)
|
||||
@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
|
||||
|
||||
|
|
@ -448,7 +448,7 @@ describe ConnectionManager do
|
|||
user = User.find(user_id)
|
||||
|
||||
@connman.create_connection(user_id, client_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
|
||||
@connman.join_music_session(user, client_id, music_session, true, TRACKS)
|
||||
@connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
|
||||
|
||||
assert_session_exists(music_session_id, true)
|
||||
|
||||
|
|
@ -492,11 +492,11 @@ describe ConnectionManager do
|
|||
client_id1 = Faker::Number.number(20)
|
||||
@connman.create_connection(user_id, client_id1, "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)
|
||||
connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS, 10)
|
||||
connection1.errors.size.should == 0
|
||||
|
||||
music_session2 = FactoryGirl.create(:active_music_session, :user_id => user_id)
|
||||
connection2 = @connman.join_music_session(user, client_id1, music_session2, true, TRACKS)
|
||||
connection2 = @connman.join_music_session(user, client_id1, music_session2, true, TRACKS, 10)
|
||||
|
||||
connection2.errors.size.should == 1
|
||||
connection2.errors.get(:music_session).should == [ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS]
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ describe ActiveMusicSession do
|
|||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save!
|
||||
@connection.join_the_session(@music_session, true, nil, @user1)
|
||||
@connection.join_the_session(@music_session, true, nil, @user1, 10)
|
||||
end
|
||||
|
||||
describe "not recording" do
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe ClaimedRecording do
|
|||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save
|
||||
@connection.join_the_session(@music_session, true, nil, @user)
|
||||
@connection.join_the_session(@music_session, true, nil, @user, 10)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ require 'spec_helper'
|
|||
describe JamRuby::Connection do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let (:music_session) { FactoryGirl.create(:active_music_session, :creator => user) }
|
||||
let (:conn) { FactoryGirl.create(:connection,
|
||||
:user => user,
|
||||
:music_session => music_session,
|
||||
:ip_address => "1.1.1.1",
|
||||
:client_id => "1") }
|
||||
|
||||
it 'starts in the correct state' do
|
||||
connection = FactoryGirl.create(:connection,
|
||||
|
|
@ -48,4 +53,22 @@ describe JamRuby::Connection do
|
|||
user.lng.should == geocode.lng
|
||||
end
|
||||
|
||||
describe "audio latency" do
|
||||
it "allow update" do
|
||||
conn.last_jam_audio_latency = 1
|
||||
conn.save!
|
||||
end
|
||||
|
||||
it "prevent negative or 0" do
|
||||
conn.last_jam_audio_latency = 0
|
||||
conn.save.should be_false
|
||||
conn.errors[:last_jam_audio_latency].should == ['must be greater than 0']
|
||||
end
|
||||
|
||||
it "prevent non numerical" do
|
||||
conn.last_jam_audio_latency = 'a'
|
||||
conn.save.should be_false
|
||||
conn.errors[:last_jam_audio_latency].should == ['is not a number']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -61,28 +61,22 @@ describe EmailBatch do
|
|||
# before { pending }
|
||||
|
||||
def handles_new_users(ebatch, user)
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) }
|
||||
EmailBatchProgression::NUM_IDX.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) }
|
||||
|
||||
dd = user.created_at + ebatch.days_past_for_trigger_index(0).days
|
||||
Timecop.travel(dd)
|
||||
vals = [1,0,0]
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) }
|
||||
ebatch.make_set(user, 0)
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) }
|
||||
|
||||
dd = dd + ebatch.days_past_for_trigger_index(1).days
|
||||
Timecop.travel(dd)
|
||||
vals = [0,1,0]
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) }
|
||||
ebatch.make_set(user, 1)
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) }
|
||||
|
||||
dd = dd + ebatch.days_past_for_trigger_index(2).days
|
||||
Timecop.travel(dd)
|
||||
vals = [0,0,1]
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn]) }
|
||||
ebatch.make_set(user, 2)
|
||||
3.times { |nn| expect(ebatch.fetch_recipients(nn).count).to eq(0) }
|
||||
dd = user.created_at
|
||||
EmailBatchProgression::NUM_IDX.times do |idx|
|
||||
dd = dd + ebatch.days_past_for_trigger_index(idx).days
|
||||
Timecop.travel(dd)
|
||||
vals = Array.new(3,0)
|
||||
vals[idx] = 1
|
||||
EmailBatchProgression::NUM_IDX.times do |nn|
|
||||
expect(ebatch.fetch_recipients(nn).count).to eq(vals[nn])
|
||||
end
|
||||
ebatch.make_set(user, idx)
|
||||
EmailBatchProgression::NUM_IDX.times do |nn|
|
||||
expect(ebatch.fetch_recipients(nn).count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handles_existing_users(ebatch, user)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ describe Mix do
|
|||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save
|
||||
@connection.join_the_session(@music_session, true, nil, @user)
|
||||
@connection.join_the_session(@music_session, true, nil, @user, 10)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.claim(@user, "name", "description", Genre.first, true)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,167 @@ describe MusicSession do
|
|||
music_session.valid?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "nindex" do
|
||||
it "nindex orders two sessions by created_at starting with most recent" do
|
||||
creator = FactoryGirl.create(:user)
|
||||
creator2 = FactoryGirl.create(:user)
|
||||
|
||||
earlier_session = FactoryGirl.create(:active_music_session, :creator => creator, :description => "Earlier Session")
|
||||
c1 = FactoryGirl.create(:connection, user: creator, music_session: earlier_session, addr: 0x01020304, locidispid: 1)
|
||||
|
||||
later_session = FactoryGirl.create(:active_music_session, :creator => creator2, :description => "Later Session")
|
||||
c2 = FactoryGirl.create(:connection, user: creator2, music_session: later_session, addr: 0x21020304, locidispid: 2)
|
||||
|
||||
user = FactoryGirl.create(:user)
|
||||
c3 = FactoryGirl.create(:connection, user: user, locidispid: 3)
|
||||
|
||||
Score.createx(c1.locidispid, c1.client_id, c1.addr, c3.locidispid, c3.client_id, c3.addr, 20, nil);
|
||||
Score.createx(c2.locidispid, c2.client_id, c2.addr, c3.locidispid, c3.client_id, c3.addr, 30, nil);
|
||||
|
||||
# scores!
|
||||
|
||||
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
music_sessions = ActiveMusicSession.nindex(user, client_id: c3.client_id).take(100)
|
||||
#music_sessions = MusicSession.index(user).take(100)
|
||||
#ActiveRecord::Base.logger = nil
|
||||
|
||||
music_sessions.length.should == 2
|
||||
music_sessions[0].id.should == later_session.id
|
||||
music_sessions[1].id.should == earlier_session.id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it 'uninvited users cant join approval-required sessions without invitation' do
|
||||
user1 = FactoryGirl.create(:user) # in the jam session
|
||||
user2 = FactoryGirl.create(:user) # in the jam session
|
||||
|
||||
music_session = FactoryGirl.create(:active_music_session, :creator => user1, :musician_access => true, :approval_required => true)
|
||||
|
||||
connection1 = FactoryGirl.create(:connection, :user => user1, :music_session => music_session)
|
||||
expect { FactoryGirl.create(:connection, :user => user2, :music_session => music_session, :joining_session => true) }.to raise_error(ActiveRecord::RecordInvalid)
|
||||
|
||||
end
|
||||
|
||||
|
||||
it "is_recording? returns false if not recording" do
|
||||
user1 = FactoryGirl.create(:user)
|
||||
music_session = FactoryGirl.build(:active_music_session, :creator => user1)
|
||||
music_session.is_recording?.should be_false
|
||||
end
|
||||
|
||||
describe "recordings" do
|
||||
|
||||
before(:each) do
|
||||
@user1 = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user1)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save!
|
||||
@connection.join_the_session(@music_session, true, nil, @user1, 10)
|
||||
end
|
||||
|
||||
describe "not recording" do
|
||||
it "stop_recording should return nil if not recording" do
|
||||
@music_session.stop_recording.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "currently recording" do
|
||||
before(:each) do
|
||||
@recording = FactoryGirl.create(:recording, :music_session => @music_session, :owner => @user1)
|
||||
end
|
||||
|
||||
it "is_recording? returns true if recording" do
|
||||
@music_session.is_recording?.should be_true
|
||||
end
|
||||
|
||||
it "stop_recording should return recording object if recording" do
|
||||
@music_session.stop_recording.should == @recording
|
||||
end
|
||||
end
|
||||
|
||||
describe "claim a recording" do
|
||||
|
||||
before(:each) do
|
||||
@recording = Recording.start(@music_session, @user1)
|
||||
@recording.errors.any?.should be_false
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@claimed_recording = @recording.claim(@user1, "name", "description", Genre.first, true)
|
||||
@claimed_recording.errors.any?.should be_false
|
||||
end
|
||||
|
||||
it "allow a claimed recording to be associated" do
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.reload
|
||||
@music_session.claimed_recording.should == @claimed_recording
|
||||
@music_session.claimed_recording_initiator.should == @user1
|
||||
end
|
||||
|
||||
it "allow a claimed recording to be removed" do
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.claimed_recording_stop
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.reload
|
||||
@music_session.claimed_recording.should be_nil
|
||||
@music_session.claimed_recording_initiator.should be_nil
|
||||
end
|
||||
|
||||
it "disallow a claimed recording to be started when already started by someone else" do
|
||||
@user2 = FactoryGirl.create(:user)
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.claimed_recording_start(@user2, @claimed_recording)
|
||||
@music_session.errors.any?.should be_true
|
||||
@music_session.errors[:claimed_recording] == [ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS]
|
||||
end
|
||||
|
||||
it "allow a claimed recording to be started when already started by self" do
|
||||
@user2 = FactoryGirl.create(:user)
|
||||
@claimed_recording2 = @recording.claim(@user1, "name", "description", Genre.first, true)
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording)
|
||||
@music_session.errors.any?.should be_false
|
||||
@music_session.claimed_recording_start(@user1, @claimed_recording2)
|
||||
@music_session.errors.any?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_connection_ids" do
|
||||
before(:each) do
|
||||
@user1 = FactoryGirl.create(:user)
|
||||
@user2 = FactoryGirl.create(:user)
|
||||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true)
|
||||
@connection1 = FactoryGirl.create(:connection, :user => @user1, :music_session => @music_session, :as_musician => true)
|
||||
@connection2 = FactoryGirl.create(:connection, :user => @user2, :music_session => @music_session, :as_musician => false)
|
||||
|
||||
end
|
||||
|
||||
it "get all connections" do
|
||||
@music_session.get_connection_ids().should == [@connection1.client_id, @connection2.client_id]
|
||||
end
|
||||
|
||||
it "exclude non-musicians" do
|
||||
@music_session.get_connection_ids(as_musician: true).should == [@connection1.client_id]
|
||||
end
|
||||
|
||||
it "exclude musicians" do
|
||||
@music_session.get_connection_ids(as_musician: false).should == [@connection2.client_id]
|
||||
end
|
||||
|
||||
it "exclude particular client" do
|
||||
@music_session.get_connection_ids(exclude_client_id: @connection1.client_id).should == [@connection2.client_id]
|
||||
end
|
||||
|
||||
it "exclude particular client and exclude non-musicians" do
|
||||
@music_session.get_connection_ids(exclude_client_id: @connection2.client_id, as_musician: true).should == [@connection1.client_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ describe 'Musician search' do
|
|||
music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true)
|
||||
# music_session.connections << connection
|
||||
# music_session.save
|
||||
connection.join_the_session(music_session, true, nil, usr)
|
||||
connection.join_the_session(music_session, true, nil, usr, 10)
|
||||
recording = Recording.start(music_session, usr)
|
||||
recording.stop
|
||||
recording.reload
|
||||
|
|
@ -186,7 +186,7 @@ describe 'Musician search' do
|
|||
music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true)
|
||||
# music_session.connections << connection
|
||||
# music_session.save
|
||||
connection.join_the_session(music_session, true, nil, usr)
|
||||
connection.join_the_session(music_session, true, nil, usr, 10)
|
||||
end
|
||||
|
||||
context 'musician stat counters' do
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ describe Recording do
|
|||
@track2 = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument2)
|
||||
|
||||
# @music_session.connections << @connection2
|
||||
@connection2.join_the_session(@music_session, true, nil, @user2)
|
||||
@connection2.join_the_session(@music_session, true, nil, @user2, 10)
|
||||
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@user.recordings.length.should == 0
|
||||
|
|
@ -179,7 +179,7 @@ describe Recording do
|
|||
@track = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument)
|
||||
# @music_session.connections << @connection2
|
||||
@music_session.save
|
||||
@connection2.join_the_session(@music_session, true, nil, @user2)
|
||||
@connection2.join_the_session(@music_session, true, nil, @user2, 10)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
|
|
|
|||
|
|
@ -12,19 +12,21 @@ describe User do
|
|||
|
||||
subject { @user }
|
||||
|
||||
it { should respond_to(:first_name) }
|
||||
it { should respond_to(:last_name) }
|
||||
it { should respond_to(:email) }
|
||||
it { should respond_to(:password) }
|
||||
it { should respond_to(:password_confirmation) }
|
||||
it { should respond_to(:remember_token) }
|
||||
it { should respond_to(:admin) }
|
||||
it { should respond_to(:valid_password?) }
|
||||
it { should respond_to(:can_invite) }
|
||||
it { should respond_to(:mods) }
|
||||
|
||||
it { should be_valid }
|
||||
it { should_not be_admin }
|
||||
it {
|
||||
should respond_to(:first_name)
|
||||
should respond_to(:last_name)
|
||||
should respond_to(:email)
|
||||
should respond_to(:password)
|
||||
should respond_to(:password_confirmation)
|
||||
should respond_to(:remember_token)
|
||||
should respond_to(:admin)
|
||||
should respond_to(:valid_password?)
|
||||
should respond_to(:can_invite)
|
||||
should respond_to(:mods)
|
||||
should respond_to(:last_jam_audio_latency)
|
||||
should be_valid
|
||||
should_not be_admin
|
||||
}
|
||||
|
||||
describe "accessible attributes" do
|
||||
it "should not allow access to admin" do
|
||||
|
|
@ -471,6 +473,25 @@ describe User do
|
|||
@user.connection_expire_time_client.should == 5
|
||||
end
|
||||
end
|
||||
|
||||
describe "audio latency" do
|
||||
it "allow update" do
|
||||
@user.last_jam_audio_latency = 1
|
||||
@user.save!
|
||||
end
|
||||
|
||||
it "prevent negative or 0" do
|
||||
@user.last_jam_audio_latency = 0
|
||||
@user.save.should be_false
|
||||
@user.errors[:last_jam_audio_latency].should == ['must be greater than 0']
|
||||
end
|
||||
|
||||
it "prevent non numerical" do
|
||||
@user.last_jam_audio_latency = 'a'
|
||||
@user.save.should be_false
|
||||
@user.errors[:last_jam_audio_latency].should == ['is not a number']
|
||||
end
|
||||
end
|
||||
=begin
|
||||
describe "update avatar" do
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,23 @@ if ENV['COVERAGE'] == "1"
|
|||
|
||||
SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
|
||||
|
||||
SimpleCov.start do
|
||||
add_filter "/test/"
|
||||
add_filter "/bin/"
|
||||
add_filter "/scripts/"
|
||||
add_filter "/tmp/"
|
||||
add_filter "/vendor/"
|
||||
add_filter "/spec/"
|
||||
add_filter "/features/"
|
||||
SimpleCov.start :rails do
|
||||
# remove the :root_filter so that we can see coverage of external dependencies (i.e., jam_ruby)
|
||||
filters.clear
|
||||
|
||||
# ignore Ruby itself (...not to be confused with jam_ruby)
|
||||
add_filter "/lib/ruby/"
|
||||
|
||||
# ignore Rails subfolders which don't contain app code
|
||||
%w{config coverage db doc features log script spec test tmp}.each do |dir|
|
||||
add_filter "#{dir}/"
|
||||
end
|
||||
|
||||
# ignore all gem code except our jam gems
|
||||
add_filter {|src| src.filename =~ /ruby.*\/gems\// unless src.filename =~ /\/gems\/jam/ }
|
||||
|
||||
# categorize JamRuby in the coverage report:
|
||||
add_group 'Jam Ruby', 'jam_ruby'
|
||||
end
|
||||
|
||||
all_files = Dir['**/*.rb']
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ gem 'iso-639'
|
|||
gem 'language_list'
|
||||
|
||||
group :development, :test do
|
||||
gem 'rspec-rails', "2.14.2"
|
||||
gem 'rspec-rails', '2.14.2'
|
||||
gem "activerecord-import", "~> 0.4.1"
|
||||
gem 'guard-rspec', '0.5.5'
|
||||
gem 'jasmine', '1.3.1'
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@
|
|||
clientType = context.JK.clientType();
|
||||
}
|
||||
if(!mode) {
|
||||
mode = context.jamClient.getOperatingMode();
|
||||
mode = context.jamClient.getOperatingMode ? context.jamClient.getOperatingMode() : 'client';
|
||||
}
|
||||
|
||||
connectDeferred = new $.Deferred();
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.AccountAudioProfile = function (app) {
|
||||
var self = this;
|
||||
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var self = this;
|
||||
var userId;
|
||||
|
||||
function beforeShow(data) {
|
||||
|
|
@ -35,28 +38,26 @@
|
|||
}
|
||||
|
||||
function populateAccountAudio() {
|
||||
var all = context.jamClient.FTUEGetAllAudioConfigurations();
|
||||
var good = context.jamClient.FTUEGetGoodAudioConfigurations();
|
||||
var current = context.jamClient.FTUEGetMusicProfileName();
|
||||
var profiles = gearUtils.getProfiles();
|
||||
|
||||
var profiles = [];
|
||||
context._.each(all, function(item) {
|
||||
profiles.push({id: item, good: false, class:'bad', current: current == item, active_text: current == item ? '(active)' : ''})
|
||||
context._.each(profiles, function(profile) {
|
||||
profile.active_text = profile.current ? '(active)' : '';
|
||||
});
|
||||
|
||||
if(good) {
|
||||
for(var i = 0; i < good.length; i++) {
|
||||
for(var j = 0; j < profiles.length; j++) {
|
||||
if(good[i] == profiles[j].id) {
|
||||
profiles[j].good = true;
|
||||
profiles[j].class = 'good';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If you are in the FTUE, and you close the client and/or it crashes
|
||||
// then you will have 'FTUE' (incomplete) profiles. This is the only time
|
||||
|
||||
var template = context._.template($('#template-account-audio').html(), {profiles: profiles}, {variable: 'data'});
|
||||
var cleansedProfiles = [];
|
||||
context._.each(profiles, function(profile) {
|
||||
if(profile.id.indexOf('FTUE') == 0) {
|
||||
context.jamClient.TrackDeleteProfile(profile.id);
|
||||
}
|
||||
else {
|
||||
cleansedProfiles.push(profile)
|
||||
}
|
||||
});
|
||||
|
||||
var template = context._.template($('#template-account-audio').html(), {is_admin: context.JK.currentUserAdmin, profiles: cleansedProfiles}, {variable: 'data'});
|
||||
|
||||
appendAudio(template);
|
||||
}
|
||||
|
|
@ -77,6 +78,43 @@
|
|||
populateAccountAudio();
|
||||
}
|
||||
|
||||
function handleLoopbackAudioProfile(audioProfileId) {
|
||||
|
||||
if(audioProfileId != context.jamClient.FTUEGetMusicProfileName()) {
|
||||
var result = context.jamClient.FTUELoadAudioConfiguration(audioProfileId);
|
||||
|
||||
if(!result) {
|
||||
logger.error("unable to activate audio configuration: " + audioProfileId);
|
||||
context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + audioProfileId);
|
||||
}
|
||||
else {
|
||||
// redraw after activation of profile
|
||||
populateAccountAudio();
|
||||
}
|
||||
}
|
||||
|
||||
app.layout.showDialog('loopback-wizard');
|
||||
}
|
||||
|
||||
function handleConfigureAudioProfile(audioProfileId) {
|
||||
|
||||
if(audioProfileId != context.jamClient.FTUEGetMusicProfileName()) {
|
||||
var result = context.jamClient.FTUELoadAudioConfiguration(audioProfileId);
|
||||
|
||||
if(!result) {
|
||||
logger.error("unable to activate audio configuration: " + audioProfileId);
|
||||
context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + audioProfileId);
|
||||
}
|
||||
else {
|
||||
// redraw after activation of profile
|
||||
populateAccountAudio();
|
||||
}
|
||||
}
|
||||
|
||||
app.layout.showDialog('configure-tracks')
|
||||
.one(EVENTS.DIALOG_CLOSED, populateAccountAudio)
|
||||
}
|
||||
|
||||
function handleActivateAudioProfile(audioProfileId) {
|
||||
logger.debug("activating audio profile: " + audioProfileId);
|
||||
|
||||
|
|
@ -143,6 +181,34 @@
|
|||
return false;
|
||||
});
|
||||
|
||||
|
||||
$root.on('click', 'a[data-purpose=loopback-audio-profile]', function (evt) {
|
||||
evt.stopPropagation();
|
||||
var $btn = $(this);
|
||||
var status = $btn.closest('tr').attr('data-status');
|
||||
if(status == "good") {
|
||||
handleLoopbackAudioProfile($btn.attr('data-id'));
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert("Unable to loopback test this profile. Please verify that the devices associated are connected.");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$root.on('click', 'a[data-purpose=configure-audio-profile]', function (evt) {
|
||||
evt.stopPropagation();
|
||||
var $btn = $(this);
|
||||
var status = $btn.closest('tr').attr('data-status');
|
||||
if(status == "good") {
|
||||
handleConfigureAudioProfile($btn.attr('data-id'));
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert("Unable to configure this profile. Please verify that the devices associated are connected.");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
$root.on('click', 'a[data-purpose=add-profile]', function (evt) {
|
||||
evt.stopPropagation();
|
||||
handleStartAudioQualification();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
sessionScreen.setPromptLeave(false);
|
||||
|
||||
app.layout.closeDialog('configure-audio');
|
||||
app.layout.closeDialog('configure-tracks');
|
||||
|
||||
context.location = "/client#/home";
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
message['message'] = $textBox.val();
|
||||
message['music_session'] = $sessionId;
|
||||
message['client_id'] = context.JK.clientId;
|
||||
message['client_id'] = app.clientId;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.ConfigureTracksDialog = function (app) {
|
||||
var logger = context.JK.logger;
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
|
||||
var $dialog = null;
|
||||
var $instructions = null;
|
||||
var $musicAudioTab = null;
|
||||
var $musicAudioTabSelector = null;
|
||||
var $voiceChatTab = null;
|
||||
var $voiceChatTabSelector = null;
|
||||
var $certifiedAudioProfile = null;
|
||||
var $btnCancel = null;
|
||||
var $btnAddNewGear = null;
|
||||
var $btnUpdateTrackSettings = null;
|
||||
|
||||
var configureTracksHelper = null;
|
||||
var voiceChatHelper = null;
|
||||
var profiles = null;
|
||||
var currentProfile = null;
|
||||
|
||||
|
||||
var configure_audio_instructions = {
|
||||
"Win32": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " +
|
||||
"to your tracks, to indicate what instrument you are playing on each track, and to assign audio outputs for listening. " +
|
||||
"If you want to use a new audio device you have not tested/certified for latency using JamKazam, click the Add New Audio " +
|
||||
"Gear button to test that device.",
|
||||
|
||||
"MacOSX": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " +
|
||||
"to your tracks, to indicate what instrument you are playing on each track, and to assign audio outputs for listening. " +
|
||||
"If you want to use a new audio device you have not tested/certified for latency using JamKazam, click the Add New Audio " +
|
||||
"Gear button to test that device.",
|
||||
|
||||
"Unix": "Choose the audio device you would like to use for this session. If needed, use arrow buttons to assign audio inputs " +
|
||||
"to your tracks, to indicate what instrument you are playing on each track, and to assign audio outputs for listening. " +
|
||||
"If you want to use a new audio device you have not tested/certified for latency using JamKazam, click the Add New Audio " +
|
||||
"Gear button to test that device."
|
||||
};
|
||||
|
||||
var configure_voice_instructions = "If you are using a microphone to capture your instrumental or vocal audio, you can simply use that mic " +
|
||||
"for both music and chat. Otherwise, choose a device to use for voice chat, and use arrow buttons to " +
|
||||
"select an input on that device.";
|
||||
|
||||
function setInstructions(type) {
|
||||
if (type === 'audio') {
|
||||
$instructions.html('Choose your audio device. Drag and drop to assign input ports to tracks, and specify the instrument for each track. Drag and drop to assign a pair of output ports for session stereo audio monitoring.')
|
||||
return;
|
||||
var os = context.jamClient.GetOSAsString();
|
||||
$instructions.html(configure_audio_instructions[os]);
|
||||
}
|
||||
else if (type === 'voice') {
|
||||
$instructions.html(configure_voice_instructions);
|
||||
}
|
||||
else {
|
||||
throw "unknown type in setInstructions(" + type + ')';
|
||||
}
|
||||
}
|
||||
|
||||
function activateTab(type) {
|
||||
if (type === 'voice') {
|
||||
$musicAudioTab.hide();
|
||||
$voiceChatTab.show();
|
||||
|
||||
$musicAudioTabSelector.removeClass('selected');
|
||||
$voiceChatTabSelector.addClass('selected');
|
||||
}
|
||||
else {
|
||||
$musicAudioTab.show();
|
||||
$voiceChatTab.hide();
|
||||
|
||||
$musicAudioTabSelector.addClass('selected');
|
||||
$voiceChatTabSelector.removeClass('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function validateVoiceChatSettings() {
|
||||
return voiceChatHelper.trySave();
|
||||
}
|
||||
|
||||
function showMusicAudioPanel() {
|
||||
setInstructions('audio');
|
||||
activateTab('audio');
|
||||
}
|
||||
|
||||
function validateAudioSettings() {
|
||||
return configureTracksHelper.trySave();
|
||||
}
|
||||
|
||||
function showVoiceChatPanel() {
|
||||
setInstructions('voice');
|
||||
activateTab('voice');
|
||||
}
|
||||
|
||||
function events() {
|
||||
$musicAudioTabSelector.click(function () {
|
||||
// validate voice chat settings
|
||||
if (validateVoiceChatSettings()) {
|
||||
configureTracksHelper.reset();
|
||||
voiceChatHelper.reset();
|
||||
showMusicAudioPanel();
|
||||
}
|
||||
});
|
||||
|
||||
$voiceChatTabSelector.click(function () {
|
||||
// validate audio settings
|
||||
if (validateAudioSettings()) {
|
||||
configureTracksHelper.reset();
|
||||
voiceChatHelper.reset();
|
||||
showVoiceChatPanel();
|
||||
}
|
||||
});
|
||||
|
||||
$btnCancel.click(function() {
|
||||
app.layout.closeDialog('configure-tracks')
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
//$btnAddNewGear.click(function() {
|
||||
|
||||
// return false;
|
||||
//});
|
||||
|
||||
$btnUpdateTrackSettings.click(function() {
|
||||
if(configureTracksHelper.trySave() && voiceChatHelper.trySave()) {
|
||||
app.layout.closeDialog('configure-tracks');
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$certifiedAudioProfile.change(deviceChanged);
|
||||
}
|
||||
|
||||
function renderCertifiedGearDropdown() {
|
||||
|
||||
var optionsHtml = '';
|
||||
context._.each(profiles, function (profile) {
|
||||
if(profile.good) {
|
||||
optionsHtml += '<option title="' + profile.id + '" value="' + profile.id + '"' + (profile.current ? 'selected="selected"' : '') + '>' + profile.id + '</option>';
|
||||
}
|
||||
});
|
||||
$certifiedAudioProfile.html(optionsHtml);
|
||||
|
||||
context.JK.dropdown($certifiedAudioProfile);
|
||||
}
|
||||
|
||||
function deviceChanged() {
|
||||
var profile = $certifiedAudioProfile.val();
|
||||
|
||||
if(currentProfile == profile) {
|
||||
return; // just bail early, because the easydropdown fires change events even when you select the same value
|
||||
}
|
||||
logger.debug("activating audio profile: " + profile);
|
||||
|
||||
var result = context.jamClient.FTUELoadAudioConfiguration(profile);
|
||||
|
||||
if(!result) {
|
||||
logger.error("unable to activate audio configuration: " + profile);
|
||||
context.JK.alertSupportedNeeded("Unable to activate audio configuration for profile named: " + profile);
|
||||
renderCertifiedGearDropdown(); // force the dropdown to be reflective of the actually active profile
|
||||
}
|
||||
else {
|
||||
configureTracksHelper.reset();
|
||||
}
|
||||
|
||||
}
|
||||
function beforeShow() {
|
||||
profiles = gearUtils.getProfiles();
|
||||
renderCertifiedGearDropdown();
|
||||
showMusicAudioPanel();
|
||||
|
||||
currentProfile = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
if(currentProfile != $certifiedAudioProfile.val()) {
|
||||
logger.error("the currently active profile (" + currentProfile + ") is not the same as the Certified Audio Gear dropdown (" + $certifiedAudioProfile.val() + ")");
|
||||
context.JK.alertSupportedNeeded("Unable to determine the current profile.");
|
||||
}
|
||||
|
||||
configureTracksHelper.reset();
|
||||
voiceChatHelper.reset();
|
||||
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow,
|
||||
'afterHide': afterHide
|
||||
};
|
||||
|
||||
app.bindDialog('configure-tracks', dialogBindings);
|
||||
|
||||
$dialog = $('#configure-tracks-dialog');
|
||||
$instructions = $dialog.find('.instructions span');
|
||||
$musicAudioTab = $dialog.find('div[tab-id="music-audio"]');
|
||||
$musicAudioTabSelector = $dialog.find('.tab-configure-audio');
|
||||
$voiceChatTab = $dialog.find('div[tab-id="voice-chat"]');
|
||||
$voiceChatTabSelector = $dialog.find('.tab-configure-voice');
|
||||
$certifiedAudioProfile = $dialog.find('.certified-audio-profile');
|
||||
$btnCancel = $dialog.find('.btn-cancel');
|
||||
$btnAddNewGear = $dialog.find('.btn-add-new-audio-gear');
|
||||
$btnUpdateTrackSettings = $dialog.find('.btn-update-settings');
|
||||
|
||||
configureTracksHelper = new JK.ConfigureTracksHelper(app);
|
||||
configureTracksHelper.initialize($dialog);
|
||||
|
||||
voiceChatHelper = new JK.VoiceChatHelper(app);
|
||||
voiceChatHelper.initialize($dialog, false);
|
||||
|
||||
events();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,530 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.ConfigureTracksHelper = function (app) {
|
||||
var logger = context.JK.logger;
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var MAX_TRACKS = context.JK.MAX_TRACKS;
|
||||
var MAX_OUTPUTS = context.JK.MAX_OUTPUTS;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
|
||||
var $parent = null;
|
||||
var $templateAssignablePort = null;
|
||||
var $templateTrackTarget = null;
|
||||
var $templateOutputTarget = null;
|
||||
var $unassignedInputsHolder = null;
|
||||
var $unassignedOutputsHolder = null;
|
||||
var $tracksHolder = null;
|
||||
var $outputChannelHolder = null;
|
||||
var $instrumentsHolder = null;
|
||||
var isDragging = false;
|
||||
|
||||
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.css('border', '#333');
|
||||
$channel.css('border-radius', '2px');
|
||||
$channel.css('min-width', '49%');
|
||||
$channel.css('width', 'auto');
|
||||
$channel.css('position', 'absolute');
|
||||
$container.css('overflow', 'visible');
|
||||
}
|
||||
else {
|
||||
// TODO: make the unassigned work
|
||||
// $channel.css('min-width', $channel.css('width'));
|
||||
// $channel.css('position', 'absolute');
|
||||
// $container.addClass('compensate');
|
||||
}
|
||||
|
||||
$channel.css('z-index', 10000)
|
||||
if(inTarget && $inputs.length == 2) {
|
||||
|
||||
if(index == 0) {
|
||||
$channel.css('right', '50%')
|
||||
}
|
||||
else {
|
||||
$channel.css('left', '51%')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function hoverOut($channel) {
|
||||
$channel
|
||||
.removeClass('hovering')
|
||||
.css('color', '')
|
||||
.css('font-weight', '')
|
||||
.css('position', '')
|
||||
.css('width', '')
|
||||
.css('background-color', '')
|
||||
.css('padding', '')
|
||||
.css('border', '')
|
||||
.css('border-radius', '')
|
||||
.css('right', '')
|
||||
.css('left', '')
|
||||
.css('min-width', '')
|
||||
.css('z-index', '')
|
||||
.css('margin-left', '')
|
||||
.css('max-width', 'auto');
|
||||
|
||||
//var $container = $channel.closest('.target');
|
||||
var $container = $channel.data('container');
|
||||
$container.css('overflow', '')
|
||||
$container.removeClass('compensate');
|
||||
|
||||
}
|
||||
|
||||
function fixClone($clone) {
|
||||
$clone
|
||||
.css('color', '')
|
||||
.css('font-weight', '')
|
||||
.css('width', 'auto')
|
||||
.css('background-color', '')
|
||||
.css('padding', '')
|
||||
.css('border', '')
|
||||
.css('border-radius', '')
|
||||
.css('right', '')
|
||||
.css('min-width', '')
|
||||
}
|
||||
|
||||
function loadChannels(forceInputsToUnassign) {
|
||||
var musicPorts = jamClient.FTUEGetChannels();
|
||||
|
||||
$unassignedInputsHolder.empty();
|
||||
$unassignedOutputsHolder.empty();
|
||||
|
||||
// reset all counts
|
||||
$tracksHolder.find('.track-target').attr('track-count', '0');
|
||||
$outputChannelHolder.find('.output-target').attr('output-count', '0');
|
||||
$tracksHolder.find('.ftue-input').remove();
|
||||
$outputChannelHolder.find('.ftue-input').remove();
|
||||
|
||||
var inputChannels = musicPorts.inputs;
|
||||
var outputChannels = musicPorts.outputs;
|
||||
|
||||
// uncomment to add a bunch of bogus channels
|
||||
//inputChannels = inputChannels.concat(inputChannels.concat(inputChannels.concat(inputChannels)))
|
||||
|
||||
context._.each(inputChannels, function (inputChannel) {
|
||||
var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' }));
|
||||
|
||||
$channel.hover(
|
||||
function() { hoverIn ($(this)) },
|
||||
function() { hoverOut($(this)) }
|
||||
);
|
||||
|
||||
if(forceInputsToUnassign || inputChannel.assignment == ASSIGNMENT.UNASSIGNED) {
|
||||
unassignInputChannel($channel);
|
||||
}
|
||||
else if(inputChannel.assignment == ASSIGNMENT.CHAT) {
|
||||
// well, we can't show it as unused... if there were a place to show chat inputs, we would put it there.
|
||||
// but we don't have it, so just skip
|
||||
logger.debug("skipping channel ", inputChannel)
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// find the track this belongs in
|
||||
|
||||
var trackNumber = inputChannel.assignment - 1;
|
||||
|
||||
var $track = $tracksHolder.find('.track[data-num="' + trackNumber + '"]')
|
||||
|
||||
if($track.length == 0) {
|
||||
context.JK.alertSupportedNeeded('Unable to find a track for channel with assignment ' + inputChannel.assignment);
|
||||
return false;
|
||||
}
|
||||
addChannelToTrack($channel, $track.find('.track-target'));
|
||||
}
|
||||
|
||||
$channel.draggable({
|
||||
helper: 'clone',
|
||||
start: function(e, ui) {
|
||||
isDragging = true;
|
||||
var $channel = $(this);
|
||||
fixClone(ui.helper);
|
||||
var $track = $channel.closest('.track-target');
|
||||
var isUnassigned = $track.length == 0;
|
||||
if(isUnassigned) {
|
||||
$tracksHolder.find('.track-target').addClass('possible-target');
|
||||
}
|
||||
else {
|
||||
$tracksHolder.find('.track-target').addClass('possible-target');
|
||||
$unassignedInputsHolder.addClass('possible-target');
|
||||
}
|
||||
},
|
||||
stop: function() {
|
||||
isDragging = false;
|
||||
$tracksHolder.find('.track-target').removeClass('possible-target');
|
||||
$unassignedInputsHolder.removeClass('possible-target')
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
var outputAssignment = 0;
|
||||
context._.each(outputChannels, function (outputChannel, index) {
|
||||
var $channel = $(context._.template($templateAssignablePort.html(), outputChannel, { variable: 'data' }));
|
||||
|
||||
$channel.hover(
|
||||
function() { hoverIn ($(this)) },
|
||||
function() { hoverOut($(this)) }
|
||||
);
|
||||
|
||||
if(outputChannel.assignment == ASSIGNMENT.UNASSIGNED) {
|
||||
unassignOutputChannel($channel);
|
||||
}
|
||||
else {
|
||||
var $output = $outputChannelHolder.find('.output[data-num="' + index + '"]')
|
||||
|
||||
if($output.length == 0) {
|
||||
context.JK.alertSupportedNeeded('Unable to find an output for channel with assignment ' + outputChannel.assignment);
|
||||
return false;
|
||||
}
|
||||
addChannelToOutput($channel, $output.find('.output-target'));
|
||||
}
|
||||
|
||||
$channel.draggable({
|
||||
helper: 'clone',
|
||||
start: function(e,ui) {
|
||||
isDragging = true;
|
||||
var $channel = $(this);
|
||||
fixClone(ui.helper);
|
||||
var $output = $channel.closest('.output-target');
|
||||
var isUnassigned = $output.length == 0;
|
||||
if(isUnassigned) {
|
||||
$outputChannelHolder.find('.output-target').addClass('possible-target');
|
||||
}
|
||||
else {
|
||||
$outputChannelHolder.find('.output-target').addClass('possible-target');
|
||||
$unassignedOutputsHolder.addClass('possible-target');
|
||||
}
|
||||
},
|
||||
stop: function() {
|
||||
isDragging = false;
|
||||
$outputChannelHolder.find('.output-target').removeClass('possible-target');
|
||||
$unassignedOutputsHolder.removeClass('possible-target')
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// iterates through the dom and returns a pure data structure for track associations and output channels
|
||||
function getCurrentState() {
|
||||
|
||||
var state = {};
|
||||
state.tracks = [];
|
||||
state.unassignedChannels = [];
|
||||
state.outputs = [];
|
||||
var $unassignedInputChannels = $unassignedInputsHolder.find('.ftue-input');
|
||||
var $unassignedOutputChannels = $unassignedOutputsHolder.find('.ftue-input');
|
||||
var $tracks = $tracksHolder.find('.track-target');
|
||||
var $outputs = $outputChannelHolder.find('.output-target');
|
||||
|
||||
context._.each($unassignedInputChannels, function($unassignedInput) {
|
||||
$unassignedInput = $($unassignedInput);
|
||||
var channelId = $unassignedInput.attr('data-input-id');
|
||||
state.unassignedChannels.push(channelId);
|
||||
})
|
||||
|
||||
context._.each($unassignedOutputChannels, function($unassignedOutput) {
|
||||
$unassignedOutput = $($unassignedOutput);
|
||||
var channelId = $unassignedOutput.attr('data-input-id');
|
||||
state.unassignedChannels.push(channelId);
|
||||
})
|
||||
|
||||
context._.each($tracks, function($track, index) {
|
||||
$track = $($track);
|
||||
var $assignedChannels = $track.find('.ftue-input');
|
||||
|
||||
var track = {index: index, channels:[]};
|
||||
context._.each($assignedChannels, function($assignedChannel) {
|
||||
$assignedChannel = $($assignedChannel);
|
||||
track.channels.push($assignedChannel.attr('data-input-id'))
|
||||
});
|
||||
|
||||
// sparse array
|
||||
if(track.channels.length > 0) {
|
||||
state.tracks.push(track);
|
||||
}
|
||||
var $instrument = $instrumentsHolder.find('[data-num="' + index + '"]').find('.icon-instrument-select');
|
||||
track.instrument_id = $instrument.data('instrument_id');
|
||||
})
|
||||
|
||||
context._.each($outputs, function($output, index) {
|
||||
$output = $($output);
|
||||
var $assignedChannel = $output.find('.ftue-input');
|
||||
|
||||
// this is overkill since there should only be 1 or 0 .ftue-inputs in a given .output
|
||||
var outputSlot = {index: index, channels:[]};
|
||||
context._.each($assignedChannel, function($assignedChannel) {
|
||||
$assignedChannel = $($assignedChannel);
|
||||
outputSlot.channels.push($assignedChannel.attr('data-input-id'))
|
||||
});
|
||||
|
||||
// sparse array
|
||||
if(outputSlot.channels.length > 0) {
|
||||
state.outputs.push(outputSlot);
|
||||
}
|
||||
})
|
||||
return state;
|
||||
}
|
||||
|
||||
function validate(tracks) {
|
||||
// there must be at least one assigned channel
|
||||
if(tracks.tracks.length == 0) {
|
||||
logger.debug("ConfigureTracks validation error: must have assigned at least one input port to a track.");
|
||||
context.JK.Banner.showAlert('Must have assigned at least one input port to a track.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// there must be some instruments
|
||||
context._.each(tracks.tracks, function(track) {
|
||||
if(!track.instrument_id) {
|
||||
logger.debug("ConfigureTracks validation error: all tracks with ports assigned must specify an instrument.");
|
||||
context.JK.Banner.showAlert('All tracks with ports assigned must specify an instrument.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// there must be exactly 2 output channels assigned
|
||||
if(tracks.outputs.length != 2 || (tracks.outputs[0].channels.length != 1 && track.outputs[1].channels.length != 1)) {
|
||||
logger.debug("ConfigureTracks validation error: must have assigned exactly two output ports");
|
||||
context.JK.Banner.showAlert('Must have assigned exactly 2 output ports.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function save(state) {
|
||||
|
||||
context._.each(state.unassignedChannels, function(unassignedChannelId) {
|
||||
context.jamClient.TrackSetAssignment(unassignedChannelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
});
|
||||
|
||||
// save input/tracks
|
||||
context._.each(state.tracks, function(track, index) {
|
||||
|
||||
var trackNumber = index + 1;
|
||||
|
||||
context._.each(track.channels, function(channelId) {
|
||||
context.jamClient.TrackSetAssignment(channelId, true, trackNumber);
|
||||
|
||||
});
|
||||
logger.debug("context.jamClient.TrackSetInstrument(trackNumber, track.instrument_id)", trackNumber, track.instrument_id);
|
||||
context.jamClient.TrackSetInstrument(trackNumber, context.JK.instrument_id_to_instrument[track.instrument_id].client_id);
|
||||
});
|
||||
|
||||
// save outputs
|
||||
context._.each(state.outputs, function(output, index) {
|
||||
context._.each(output.channels, function(channelId) {
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.OUTPUT);
|
||||
});
|
||||
});
|
||||
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to save assignments. ' + result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadTrackInstruments(forceInputsToUnassign) {
|
||||
var $trackInstruments = $instrumentsHolder.find('.track-instrument');
|
||||
|
||||
context._.each($trackInstruments, function(trackInstrument) {
|
||||
var $trackInstrument = $(trackInstrument);
|
||||
|
||||
var trackIndex = parseInt($trackInstrument.attr('data-num')) + 1;
|
||||
|
||||
var clientInstrument = context.jamClient.TrackGetInstrument(trackIndex);
|
||||
|
||||
var instrument = context.JK.client_to_server_instrument_map[clientInstrument];
|
||||
|
||||
$trackInstrument.instrumentSelectorSet(instrument ? instrument.server_id : instrument);
|
||||
});
|
||||
}
|
||||
|
||||
function trySave() {
|
||||
var state = getCurrentState();
|
||||
|
||||
if(!validate(state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var saved = save(state);
|
||||
|
||||
if(saved) {
|
||||
context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS());
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
function reset(forceInputsToUnassign) {
|
||||
loadChannels(forceInputsToUnassign);
|
||||
loadTrackInstruments(forceInputsToUnassign);
|
||||
}
|
||||
|
||||
function unassignOutputChannel($channel) {
|
||||
var $originallyAssignedTrack = $channel.closest('.output-target');
|
||||
$unassignedOutputsHolder.append($channel);
|
||||
$originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length);
|
||||
}
|
||||
|
||||
function unassignInputChannel($channel) {
|
||||
var $originallyAssignedTrack = $channel.closest('.track-target');
|
||||
$unassignedInputsHolder.append($channel);
|
||||
$originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length);
|
||||
|
||||
}
|
||||
|
||||
function addChannelToTrack($channel, $track) {
|
||||
var $originallyAssignedTrack = $channel.closest('.track-target');
|
||||
$track.append($channel);
|
||||
$track.attr('track-count', $track.find('.ftue-input:not(.ui-draggable-dragging)').length);
|
||||
$originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length)
|
||||
}
|
||||
|
||||
function addChannelToOutput($channel, $slot) {
|
||||
var $originallyAssignedTrack = $channel.closest('.output-target');
|
||||
$slot.append($channel);
|
||||
$slot.attr('output-count', $slot.find('.ftue-input:not(.ui-draggable-dragging)').length);
|
||||
$originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length)
|
||||
}
|
||||
|
||||
|
||||
function initializeUnassignedOutputDroppable() {
|
||||
$unassignedOutputsHolder.droppable(
|
||||
{
|
||||
activeClass: 'drag-in-progress',
|
||||
hoverClass: 'drag-hovering',
|
||||
drop: function( event, ui ) {
|
||||
var $channel = ui.draggable;
|
||||
|
||||
//$channel.css('left', '0').css('top', '0');
|
||||
unassignOutputChannel($channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeUnassignedInputDroppable() {
|
||||
$unassignedInputsHolder.droppable(
|
||||
{
|
||||
activeClass: 'drag-in-progress',
|
||||
hoverClass: 'drag-hovering',
|
||||
drop: function( event, ui ) {
|
||||
var $channel = ui.draggable;
|
||||
//$channel.css('left', '0').css('top', '0');
|
||||
unassignInputChannel($channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeOutputDroppables() {
|
||||
var i;
|
||||
for(i = 0; i < MAX_OUTPUTS; i++) {
|
||||
var $target = $(context._.template($templateOutputTarget.html(), {num: i }, { variable: 'data' }));
|
||||
$outputChannelHolder.append($target);
|
||||
$target.find('.output-target').droppable(
|
||||
{
|
||||
activeClass: 'drag-in-progress',
|
||||
hoverClass: 'drag-hovering',
|
||||
drop: function( event, ui ) {
|
||||
var $slot = $(this);
|
||||
if($slot.attr('output-count') == 1) {
|
||||
return false; // max of 1 output per slot
|
||||
}
|
||||
var $channel = ui.draggable;
|
||||
//$channel.css('left', '0').css('top', '0');
|
||||
addChannelToOutput($channel, $slot);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initializeTrackDroppables() {
|
||||
var i;
|
||||
for(i = 0; i < MAX_TRACKS; i++) {
|
||||
var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' }));
|
||||
$tracksHolder.append($target);
|
||||
$target.find('.track-target').droppable(
|
||||
{
|
||||
activeClass: 'drag-in-progress',
|
||||
hoverClass: 'drag-hovering',
|
||||
drop: function( event, ui ) {
|
||||
var $track = $(this);
|
||||
if($track.attr('track-count') == 2) {
|
||||
return false; // max of 2 inputs per track
|
||||
}
|
||||
|
||||
var $channel = ui.draggable;
|
||||
//$channel.css('left', '0').css('top', '0');
|
||||
addChannelToTrack($channel, $track);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initializeInstrumentDropdown() {
|
||||
var i;
|
||||
for(i = 0; i < MAX_TRACKS; i++) {
|
||||
var $root = $('<div class="track-instrument"></div>');
|
||||
$root.instrumentSelector().attr('data-num', i);
|
||||
$instrumentsHolder.append($root);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function initialize(_$parent) {
|
||||
$parent = _$parent;
|
||||
|
||||
$templateAssignablePort = $('#template-assignable-port');
|
||||
$templateTrackTarget = $('#template-track-target');
|
||||
$templateOutputTarget = $('#template-output-target');
|
||||
$unassignedInputsHolder = $parent.find('.unassigned-input-channels')
|
||||
$unassignedOutputsHolder = $parent.find('.unassigned-output-channels');
|
||||
$tracksHolder = $parent.find('.tracks');
|
||||
$instrumentsHolder = $parent.find('.instruments');
|
||||
$outputChannelHolder = $parent.find('.output-channels');
|
||||
|
||||
|
||||
initializeUnassignedInputDroppable();
|
||||
initializeTrackDroppables();
|
||||
initializeInstrumentDropdown();
|
||||
|
||||
initializeUnassignedOutputDroppable();
|
||||
initializeOutputDroppables();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
this.trySave = trySave;
|
||||
this.reset = reset;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -172,6 +172,7 @@
|
|||
if ($('#band-list option:selected').val() !== '') {
|
||||
data.band = $('#band-list option:selected').val();
|
||||
}
|
||||
data.audio_latency = context.jamClient.FTUEGetExpectedLatency().latency;
|
||||
|
||||
// 1. If no previous session data, a single stereo track with the
|
||||
// top instrument in the user's profile.
|
||||
|
|
|
|||
|
|
@ -168,6 +168,9 @@
|
|||
]
|
||||
};
|
||||
}
|
||||
|
||||
function FTUEClearChannelAssignments(){}
|
||||
function FTUEClearChatInput(){}
|
||||
function FTUEGetAudioDevices() {
|
||||
return {
|
||||
"devices": [
|
||||
|
|
@ -235,21 +238,25 @@
|
|||
}
|
||||
|
||||
function FTUEGetGoodConfigurationList() {
|
||||
return ['a'];
|
||||
return ['default'];
|
||||
}
|
||||
|
||||
function FTUEGetAllAudioConfigurations() {
|
||||
return ['a'];
|
||||
return ['default'];
|
||||
}
|
||||
|
||||
function FTUEGetGoodAudioConfigurations() {
|
||||
return ['a'];
|
||||
return ['default'];
|
||||
}
|
||||
|
||||
function FTUEGetConfigurationDevice() {
|
||||
return 'Good Device';
|
||||
}
|
||||
|
||||
function FTUELoadAudioConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function FTUEIsMusicDeviceWDM() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -850,6 +857,9 @@
|
|||
this.FTUEGetGoodAudioConfigurations = FTUEGetGoodAudioConfigurations;
|
||||
this.FTUEGetConfigurationDevice = FTUEGetConfigurationDevice;
|
||||
this.FTUEIsMusicDeviceWDM = FTUEIsMusicDeviceWDM;
|
||||
this.FTUELoadAudioConfiguration = FTUELoadAudioConfiguration;
|
||||
this.FTUEClearChannelAssignments = FTUEClearChannelAssignments;
|
||||
this.FTUEClearChatInput = FTUEClearChatInput;
|
||||
|
||||
// Session
|
||||
this.SessionAddTrack = SessionAddTrack;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
function score_to_text(score) {
|
||||
// these are raw scores as reported by client (round trip times)
|
||||
if (score == null) return "n/a";
|
||||
return Math.round(score / 2) + " ms";
|
||||
}
|
||||
|
||||
function score_to_color(score) {
|
||||
// these are raw scores as reported by client (round trip times)
|
||||
if (score == null) return "purple";
|
||||
if (0 < score && score <= 40) return "green";
|
||||
if (40 < score && score <= 80) return "yellow";
|
||||
if (80 < score && score <= 120) return "red";
|
||||
return "blue";
|
||||
}
|
||||
|
||||
function score_to_color_alt(score) {
|
||||
// these are raw scores as reported by client (round trip times)
|
||||
if (score == null) return "missing";
|
||||
if (0 < score && score <= 40) return "good";
|
||||
if (40 < score && score <= 80) return "moderate";
|
||||
if (80 < score && score <= 120) return "poor";
|
||||
return "unacceptable";
|
||||
}
|
||||
|
||||
function renderMusicians() {
|
||||
var ii, len;
|
||||
var mTemplate = $('#template-find-musician-row').html();
|
||||
|
|
@ -108,7 +132,7 @@
|
|||
user_id: aFollow.user_id,
|
||||
musician_name: aFollow.name,
|
||||
profile_url: '/client#/profile/' + aFollow.user_id,
|
||||
avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url),
|
||||
avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url)
|
||||
};
|
||||
follows += context.JK.fillTemplate(fTemplate, followVals);
|
||||
if (2 == jj) break;
|
||||
|
|
@ -125,6 +149,7 @@
|
|||
};
|
||||
var musician_actions = context.JK.fillTemplate(aTemplate, actionVals);
|
||||
|
||||
var joined_score = musician['joined_score']
|
||||
mVals = {
|
||||
avatar_url: context.JK.resolveAvatarUrl(musician.photo_url),
|
||||
profile_url: "/client#/profile/" + musician.id,
|
||||
|
|
@ -138,7 +163,10 @@
|
|||
session_count: musician['session_count'],
|
||||
musician_id: musician['id'],
|
||||
musician_follow_template: follows,
|
||||
musician_action_template: musician_actions
|
||||
musician_action_template: musician_actions,
|
||||
musician_one_way_score: score_to_text(joined_score),
|
||||
musician_score_color: score_to_color(joined_score),
|
||||
musician_score_color_alt: score_to_color_alt(joined_score)
|
||||
};
|
||||
var musician_row = context.JK.fillTemplate(mTemplate, mVals);
|
||||
renderings += musician_row;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,12 @@
|
|||
CHAT: "1"
|
||||
};
|
||||
|
||||
context.JK.EVENTS = {
|
||||
DIALOG_CLOSED : 'dialog_closed'
|
||||
}
|
||||
|
||||
context.JK.MAX_TRACKS = 6;
|
||||
context.JK.MAX_OUTPUTS = 2;
|
||||
|
||||
// TODO: store these client_id values in instruments table, or store
|
||||
// server_id as the client_id to prevent maintenance nightmares. As it's
|
||||
|
|
|
|||
|
|
@ -1119,6 +1119,17 @@
|
|||
});
|
||||
}
|
||||
|
||||
function updateAudioLatency(options) {
|
||||
var id = getId(options);
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: '/api/users/' + id + '/audio_latency',
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: options,
|
||||
});
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
return self;
|
||||
}
|
||||
|
|
@ -1216,6 +1227,7 @@
|
|||
this.getChatMessages = getChatMessages;
|
||||
this.createDiagnostic = createDiagnostic;
|
||||
this.getLatencyTester = getLatencyTester;
|
||||
this.updateAudioLatency = updateAudioLatency;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
// privates
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var NOT_HANDLED = "not handled";
|
||||
|
||||
var me = null; // Reference to this instance for context sanity.
|
||||
|
|
@ -433,8 +434,9 @@
|
|||
var $overlay = $('.dialog-overlay');
|
||||
unstackDialogs($overlay);
|
||||
$dialog.hide();
|
||||
$dialog.triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length});
|
||||
$(context).triggerHandler(EVENTS.DIALOG_CLOSED, {name: dialog, dialogCount: openDialogs.length})
|
||||
dialogEvent(dialog, 'afterHide');
|
||||
$(me).triggerHandler('dialog_closed', {dialogCount: openDialogs.length})
|
||||
}
|
||||
|
||||
function screenEvent(screen, evtName, data) {
|
||||
|
|
@ -538,6 +540,11 @@
|
|||
*/
|
||||
layout();
|
||||
|
||||
// add an attribute to any dialogs, which let's it know it's current screen (useful for contextual styling)
|
||||
context._.each(openDialogs, function(dialog) {
|
||||
addScreenContextToDialog($(dialog));
|
||||
})
|
||||
|
||||
screenEvent(previousScreen, 'afterHide', data);
|
||||
screenEvent(currentScreen, 'afterShow', data);
|
||||
|
||||
|
|
@ -631,6 +638,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
function addScreenContextToDialog($dialog) {
|
||||
$dialog.attr('current-screen', currentScreen); // useful for contextual styling of dialogs
|
||||
}
|
||||
|
||||
function showDialog(dialog, options) {
|
||||
if (dialogEvent(dialog, 'beforeShow', options) === false) {
|
||||
return;
|
||||
|
|
@ -649,8 +660,10 @@
|
|||
centerDialog(dialog);
|
||||
var $dialog = $('[layout-id="' + dialog + '"]');
|
||||
stackDialogs($dialog, $overlay);
|
||||
addScreenContextToDialog($dialog)
|
||||
$dialog.show();
|
||||
dialogEvent(dialog, 'afterShow', options);
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
function centerDialog(dialog) {
|
||||
|
|
@ -910,7 +923,7 @@
|
|||
}
|
||||
|
||||
this.showDialog = function (dialog, options) {
|
||||
showDialog(dialog, options);
|
||||
return showDialog(dialog, options);
|
||||
};
|
||||
|
||||
this.dialogObscuredNotification = function(payload) {
|
||||
|
|
@ -933,6 +946,9 @@
|
|||
return activeElementEvent(evtName, data);
|
||||
}
|
||||
|
||||
this.getCurrentScreen = function() {
|
||||
return currentScreen; // will be a string of the layout-id of the active screen
|
||||
}
|
||||
this.close = function (evt) {
|
||||
close(evt);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,515 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.NetworkTest = function (app) {
|
||||
|
||||
var NETWORK_TEST_TYPES = {
|
||||
RestPhase : 0,
|
||||
PktTest100NormalLatency : 1,
|
||||
PktTest200MediumLatency : 2,
|
||||
PktTest400LowLatency : 3,
|
||||
PktTestRateSweep : 4,
|
||||
RcvOnly : 5
|
||||
}
|
||||
var STARTING_NUM_CLIENTS = 4;
|
||||
var PAYLOAD_SIZE = 100;
|
||||
var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2;
|
||||
|
||||
var rest = context.JK.Rest();
|
||||
var logger = context.JK.logger;
|
||||
var $step = null;
|
||||
var TEST_SUCCESS_CALLBACK = 'JK.HandleNetworkTestSuccess';
|
||||
var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout';
|
||||
|
||||
var $startNetworkTestBtn = null;
|
||||
var $testResults = null;
|
||||
var $testScore = null;
|
||||
var $testText = null;
|
||||
var testedSuccessfully = false;
|
||||
var $scoringBar = null;
|
||||
var $goodMarker = null;
|
||||
var $goodLine = null;
|
||||
var $currentScore = null;
|
||||
var $scoredClients = null;
|
||||
var $subscore = null;
|
||||
var backendGuardTimeout = null;
|
||||
|
||||
var serverClientId = '';
|
||||
var scoring = false;
|
||||
var numClientsToTest = STARTING_NUM_CLIENTS;
|
||||
var testSummary = {attempts : [], final:null}
|
||||
var $self = $(this);
|
||||
var scoringZoneWidth = 100;//px
|
||||
var inGearWizard = false;
|
||||
|
||||
var NETWORK_TEST_START = 'network_test.start';
|
||||
var NETWORK_TEST_DONE = 'network_test.done';
|
||||
var NETWORK_TEST_FAIL = 'network_test.fail';
|
||||
|
||||
function createSuccessCallbackName() {
|
||||
if(inGearWizard) {
|
||||
return TEST_SUCCESS_CALLBACK + 'ForGearWizard';
|
||||
}
|
||||
else {
|
||||
return TEST_SUCCESS_CALLBACK + 'ForDialog';
|
||||
}
|
||||
}
|
||||
|
||||
function createTimeoutCallbackName() {
|
||||
if(inGearWizard) {
|
||||
return TEST_TIMEOUT_CALLBACK + 'ForGearWizard';
|
||||
}
|
||||
else {
|
||||
return TEST_TIMEOUT_CALLBACK + 'ForDialog';
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
serverClientId = '';
|
||||
scoring = false;
|
||||
numClientsToTest = STARTING_NUM_CLIENTS;
|
||||
testSummary = {attempts : []};
|
||||
configureStartButton();
|
||||
$scoredClients.empty();
|
||||
$testResults.removeClass('good acceptable bad testing');
|
||||
$testText.empty();
|
||||
$subscore.empty();
|
||||
$currentScore.width(0);
|
||||
}
|
||||
|
||||
function renderStartTest() {
|
||||
configureStartButton();
|
||||
$testResults.addClass('testing');
|
||||
$goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
|
||||
$goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
|
||||
}
|
||||
|
||||
function renderStopTest(score, text) {
|
||||
$scoredClients.html(score);
|
||||
$testText.html(text);
|
||||
$testResults.removeClass('testing');
|
||||
}
|
||||
|
||||
function postDiagnostic() {
|
||||
rest.createDiagnostic({
|
||||
type: 'NETWORK_TEST_RESULT',
|
||||
data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary:testSummary}
|
||||
});
|
||||
}
|
||||
|
||||
function appendContextualStatement() {
|
||||
if(inGearWizard) {
|
||||
return "You can skip this step for now."
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
function testFinished() {
|
||||
var attempt = getCurrentAttempt();
|
||||
|
||||
if(!testSummary.final) {
|
||||
testSummary.final = {reason : attempt.reason};
|
||||
}
|
||||
|
||||
var reason = testSummary.final.reason;
|
||||
var success = false;
|
||||
|
||||
if(reason == "success") {
|
||||
renderStopTest(attempt.num_clients, "Your router and Internet service will support sessions of up to " + attempt.num_clients + " JamKazam musicians.")
|
||||
testedSuccessfully = true;
|
||||
if(!testSummary.final.num_clients) {
|
||||
testSummary.final.num_clients = attempt.num_clients;
|
||||
}
|
||||
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');
|
||||
}
|
||||
else {
|
||||
$testResults.addClass('good');
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
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.")
|
||||
}
|
||||
else if(reason == "unreachable") {
|
||||
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.");
|
||||
}
|
||||
else if(reason == "internal_error") {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "remote_peer_cant_test") {
|
||||
context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == 'backend_gone') {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "invalid_response") {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection. Reason=" + attempt.backend_data.reason + '.');
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == 'no_servers') {
|
||||
context.JK.alertSupportedNeeded("No network test servers are available." + appendContextualStatement());
|
||||
renderStopTest('', '');
|
||||
testedSuccessfully = true;
|
||||
}
|
||||
else if(reason == 'no_network') {
|
||||
context.JK.Banner.showAlert("Please try again later. Your network appears down.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "rest_api_error") {
|
||||
context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement());
|
||||
testedSuccessfully = true;
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "timeout") {
|
||||
context.JK.alertSupportedNeeded("Communication with a network test servers timed out." + appendContextualStatement());
|
||||
testedSuccessfully = true;
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
|
||||
numClientsToTest = STARTING_NUM_CLIENTS;
|
||||
scoring = false;
|
||||
configureStartButton();
|
||||
postDiagnostic();
|
||||
|
||||
if(success) {
|
||||
$self.triggerHandler(NETWORK_TEST_DONE)
|
||||
}
|
||||
else {
|
||||
$self.triggerHandler(NETWORK_TEST_FAIL)
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentAttempt() {
|
||||
return testSummary.attempts[testSummary.attempts.length - 1];
|
||||
}
|
||||
|
||||
function backendTimedOut() {
|
||||
testSummary.final = {reason: 'backend_gone'}
|
||||
testFinished();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
clearBackendGuard();
|
||||
}
|
||||
function clearBackendGuard() {
|
||||
if(backendGuardTimeout) {
|
||||
clearTimeout(backendGuardTimeout);
|
||||
backendGuardTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
function attemptTestPass() {
|
||||
|
||||
var attempt = {};
|
||||
attempt.payload_size = PAYLOAD_SIZE;
|
||||
attempt.duration = gon.ftue_network_test_duration;
|
||||
attempt.test_type = 'PktTest400LowLatency';
|
||||
attempt.num_clients = numClientsToTest;
|
||||
attempt.server_client_id = serverClientId;
|
||||
attempt.received_progress = false;
|
||||
testSummary.attempts.push(attempt);
|
||||
|
||||
//context.jamClient.StopNetworkTest('');
|
||||
|
||||
$testText.text("Simulating the network traffic of a " + numClientsToTest + "-person session.");
|
||||
|
||||
updateProgress(0, false);
|
||||
|
||||
backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000);
|
||||
|
||||
context.jamClient.TestNetworkPktBwRate(serverClientId, createSuccessCallbackName(), createTimeoutCallbackName(),
|
||||
NETWORK_TEST_TYPES.PktTest400LowLatency,
|
||||
gon.ftue_network_test_duration,
|
||||
numClientsToTest,
|
||||
PAYLOAD_SIZE);
|
||||
}
|
||||
|
||||
|
||||
function startNetworkTest(checkWireless) {
|
||||
|
||||
if(scoring) return false;
|
||||
|
||||
if(checkWireless) {
|
||||
// check if on Wifi 1st
|
||||
var isWireless = context.jamClient.IsMyNetworkWireless();
|
||||
if(isWireless == -1) {
|
||||
logger.warn("unable to determine if the user is on wireless or not for network test. skipping prompt.")
|
||||
}
|
||||
else if(isWireless == 1) {
|
||||
context.JK.Banner.showAlert({buttons: [
|
||||
{name: 'RUN NETWORK TEST ANYWAY', click: function() {startNetworkTest(false)}},
|
||||
{name: 'CANCEL', click: function() {}}],
|
||||
html: "<p>It appears that your computer is connected to your network using WiFi.</p>" +
|
||||
"<p>We strongly advise against running the JamKazam application on a WiFi connection. " +
|
||||
"We recommend using a wired Ethernet connection from your computer to your router. " +
|
||||
"A WiFi connection is likely to cause significant issues in both latency and audio quality.</p>"})
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
scoring = true;
|
||||
$self.triggerHandler(NETWORK_TEST_START);
|
||||
renderStartTest();
|
||||
rest.getLatencyTester()
|
||||
.done(function(response) {
|
||||
// ensure there are no tests ongoing
|
||||
|
||||
serverClientId = response.client_id;
|
||||
|
||||
|
||||
testSummary.serverClientId = serverClientId;
|
||||
|
||||
logger.info("beginning network test against client_id: " + serverClientId);
|
||||
|
||||
attemptTestPass();
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status == 404) {
|
||||
// means there are no network testers available.
|
||||
// we have to skip this part of the UI
|
||||
testSummary.final = {reason: 'no_servers'}
|
||||
}
|
||||
else {
|
||||
if(context.JK.isNetworkError(arguments)) {
|
||||
testSummary.final = {reason: 'no_network'}
|
||||
}
|
||||
else {
|
||||
testSummary.final = {reason: 'rest_api_error'}
|
||||
}
|
||||
}
|
||||
testFinished();
|
||||
})
|
||||
logger.info("starting network test");
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateProgress(throughput, showSubscore) {
|
||||
var width = throughput * 100;
|
||||
|
||||
$currentScore.stop().data('showSubscore', showSubscore);
|
||||
|
||||
if(!showSubscore) {
|
||||
$subscore.text('');
|
||||
}
|
||||
|
||||
$currentScore.animate({
|
||||
duration: 1000,
|
||||
width: width + '%'
|
||||
}, {
|
||||
step: function (now, fx) {
|
||||
if(showSubscore) {
|
||||
var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) );
|
||||
$subscore.text((Math.round(newWidth * 10) / 10) + '%');
|
||||
}
|
||||
}
|
||||
}).css('overflow', 'visible');
|
||||
;
|
||||
}
|
||||
|
||||
function networkTestSuccess(data) {
|
||||
clearBackendGuard();
|
||||
|
||||
var attempt = getCurrentAttempt();
|
||||
|
||||
function refineTest(up) {
|
||||
if(up) {
|
||||
if(numClientsToTest == gon.ftue_network_test_max_clients) {
|
||||
attempt.reason = "success";
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
numClientsToTest++;
|
||||
logger.debug("increasing number of clients to " + numClientsToTest);
|
||||
setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm
|
||||
}
|
||||
}
|
||||
else {
|
||||
// reduce numclients if we can
|
||||
if(numClientsToTest == MINIMUM_ACCEPTABLE_SESSION_SIZE) {
|
||||
// we are too low already. fail the user
|
||||
attempt.reason = "minimum_client_threshold";
|
||||
testFinished();
|
||||
}
|
||||
else if(numClientsToTest > STARTING_NUM_CLIENTS) {
|
||||
// this means we've gone up before... so don't go back down (i.e., creating a loop)
|
||||
attempt.reason = "success";
|
||||
testSummary.final = { reason:'success', num_clients: numClientsToTest - 1 }
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
numClientsToTest--;
|
||||
logger.debug("reducing number of clients to " + numClientsToTest);
|
||||
setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attempt.backend_data = data;
|
||||
|
||||
if(data.progress === true) {
|
||||
|
||||
var animate = true;
|
||||
if(data.downthroughput && data.upthroughput) {
|
||||
|
||||
if(data.downthroughput > 0 || data.upthroughput > 0) {
|
||||
attempt.received_progress = true;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(attempt.received_progress) {
|
||||
// take the lower
|
||||
var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput;
|
||||
|
||||
updateProgress(throughput, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
logger.debug("network test pass success. data: ", data);
|
||||
|
||||
if(data.reason == "unreachable") {
|
||||
// STUN
|
||||
logger.debug("network test: unreachable (STUN issue or similar)");
|
||||
attempt.reason = data.reason;
|
||||
testFinished();
|
||||
}
|
||||
else if(data.reason == "internal_error") {
|
||||
// oops
|
||||
logger.debug("network test: internal_error (client had a unexpected problem)");
|
||||
attempt.reason = data.reason;
|
||||
testFinished();
|
||||
}
|
||||
else if(data.reason == "remote_peer_cant_test") {
|
||||
// old client
|
||||
logger.debug("network test: remote_peer_cant_test (old client)")
|
||||
attempt.reason = data.reason;
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
if(!data.downthroughput || !data.upthroughput) {
|
||||
// we have to assume this is bad. just not a reason we know about in code
|
||||
logger.debug("network test: no test data (unknown issue? " + data.reason + ")")
|
||||
attempt.reason = "invalid_response";
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
// success... but we still have to verify if this data is within threshold
|
||||
if(data.downthroughput < gon.ftue_packet_rate_treshold) {
|
||||
logger.debug("network test: downthroughput too low. downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
|
||||
refineTest(false);
|
||||
}
|
||||
else if(data.upthroughput < gon.ftue_packet_rate_treshold) {
|
||||
logger.debug("network test: upthroughput too low. upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
|
||||
refineTest(false);
|
||||
}
|
||||
else {
|
||||
// true success. we can accept this score
|
||||
logger.debug("network test: success")
|
||||
refineTest(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VRFS-1742
|
||||
// context.jamClient.StopNetworkTest(serverClientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function networkTestTimeout(data) {
|
||||
clearBackendGuard();
|
||||
|
||||
logger.warn("network timeout when testing latency test: " + data);
|
||||
|
||||
var attempt = getCurrentAttempt();
|
||||
attempt.reason = 'timeout';
|
||||
attempt.backend_data = data;
|
||||
testFinished();
|
||||
}
|
||||
|
||||
function hasScoredNetworkSuccessfully() {
|
||||
return testedSuccessfully;
|
||||
}
|
||||
|
||||
function configureStartButton() {
|
||||
if(scoring) {
|
||||
$startNetworkTestBtn.text('NETWORK TEST RUNNING...').removeClass('button-orange').addClass('button-grey')
|
||||
}
|
||||
else {
|
||||
$startNetworkTestBtn.text('START NETWORK TEST').removeClass('button-grey').addClass('button-orange');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
$dialog.setNextState(hasScoredNetworkSuccessfully() && !scoring);
|
||||
}
|
||||
|
||||
function initializeBackButtonState() {
|
||||
$dialog.setBackState(!scoring);
|
||||
}
|
||||
|
||||
function beforeHide() {
|
||||
clearBackendGuard();
|
||||
}
|
||||
|
||||
function initialize(_$step, _inGearWizard) {
|
||||
$step = _$step;
|
||||
inGearWizard = _inGearWizard;
|
||||
|
||||
$startNetworkTestBtn = $step.find('.start-network-test');
|
||||
|
||||
if($startNetworkTestBtn.length == 0) throw 'no start network test button found in network-test'
|
||||
|
||||
$testResults = $step.find('.network-test-results');
|
||||
$testScore = $step.find('.network-test-score');
|
||||
$testText = $step.find('.network-test-text');
|
||||
$scoringBar = $step.find('.scoring-bar');
|
||||
$goodMarker = $step.find('.good-marker');
|
||||
$goodLine =$step.find('.good-line');
|
||||
$currentScore = $step.find('.current-score');
|
||||
$scoredClients= $step.find('.scored-clients');
|
||||
$subscore = $step.find('.subscore');
|
||||
$startNetworkTestBtn.on('click', startNetworkTest);
|
||||
|
||||
// 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.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.HandleNetworkTestTimeoutForDialog = function(data) { networkTestTimeout(data)}; // pin to global for bridge callback
|
||||
}
|
||||
}
|
||||
|
||||
this.isScoring = function () { return scoring; };
|
||||
this.hasScoredNetworkSuccessfully = hasScoredNetworkSuccessfully;
|
||||
this.initialize = initialize;
|
||||
this.reset = reset;
|
||||
this.cancel = cancel;
|
||||
|
||||
this.NETWORK_TEST_START = NETWORK_TEST_START;
|
||||
this.NETWORK_TEST_DONE = NETWORK_TEST_DONE;
|
||||
this.NETWORK_TEST_FAIL = NETWORK_TEST_FAIL;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.NetworkTestDialog = function (app) {
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var $dialog = null;
|
||||
var $btnCancel = null;
|
||||
var $btnClose = null;
|
||||
var $btnHelp = null;
|
||||
var networkTest = new context.JK.NetworkTest(app);
|
||||
|
||||
function networkTestDone() {
|
||||
unfreezeInteractions();
|
||||
}
|
||||
|
||||
function networkTestFail() {
|
||||
unfreezeInteractions();
|
||||
}
|
||||
|
||||
function networkTestStart() {
|
||||
freezeInteractions();
|
||||
}
|
||||
|
||||
|
||||
function freezeInteractions() {
|
||||
$btnClose.removeClass('button-orange').addClass('button-grey');
|
||||
}
|
||||
|
||||
function unfreezeInteractions() {
|
||||
$btnClose.removeClass('button-grey').addClass('button-orange');
|
||||
}
|
||||
|
||||
function events() {
|
||||
$btnCancel.on('click', function() {
|
||||
// should we stop this if the test is going?
|
||||
app.layout.closeDialog('network-test')
|
||||
return false;
|
||||
})
|
||||
|
||||
$btnClose.on('click', function(e) {
|
||||
if(!networkTest.isScoring()) {
|
||||
app.layout.closeDialog('network-test');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
$btnHelp.on('click', function(e) {
|
||||
context.JK.Banner.showAlert('No help is yet available for the network test.');
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
if(!networkTest.isScoring()) {
|
||||
networkTest.reset();
|
||||
}
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
var dialogBindings = {
|
||||
'beforeShow' : beforeShow,
|
||||
'afterHide': afterHide
|
||||
};
|
||||
|
||||
app.bindDialog('network-test', dialogBindings);
|
||||
|
||||
$dialog = $('#network-test-dialog');
|
||||
$btnCancel = $dialog.find('.btn-cancel');
|
||||
$btnHelp = $dialog.find('.btn-help');
|
||||
$btnClose = $dialog.find('.btn-close');
|
||||
|
||||
networkTest.initialize($dialog, false);
|
||||
$(networkTest)
|
||||
.on(networkTest.NETWORK_TEST_DONE, networkTestDone)
|
||||
.on(networkTest.NETWORK_TEST_FAIL, networkTestFail)
|
||||
.on(networkTest.NETWORK_TEST_START, networkTestStart)
|
||||
|
||||
events();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.NotificationPanel = function(app) {
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var logger = context.JK.logger;
|
||||
var friends = [];
|
||||
var rest = context.JK.Rest();
|
||||
|
|
@ -138,7 +139,7 @@
|
|||
}
|
||||
|
||||
function events() {
|
||||
$(app.layout).on('dialog_closed', function(e, data) {if(data.dialogCount == 0) userCameBack(); });
|
||||
$(context).on(EVENTS.DIALOG_CLOSED, function(e, data) {if(data.dialogCount == 0) userCameBack(); });
|
||||
$(window).focus(userCameBack);
|
||||
$(window).blur(windowBlurred);
|
||||
app.user()
|
||||
|
|
|
|||
|
|
@ -368,7 +368,8 @@
|
|||
ip_address: server.publicIP,
|
||||
as_musician: true,
|
||||
tracks: tracks,
|
||||
session_id: sessionId
|
||||
session_id: sessionId,
|
||||
audio_latency: context.jamClient.FTUEGetExpectedLatency().latency
|
||||
};
|
||||
|
||||
return rest.joinSession(data);
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@
|
|||
invitationDialog.showFacebookDialog(e);
|
||||
});
|
||||
|
||||
$('.shortcuts .test-network').on('click', function(e) {
|
||||
app.layout.showDialog('network-test');
|
||||
return false;
|
||||
})
|
||||
|
||||
$('#header-avatar').on('avatar_changed', function (event, newAvatarUrl) {
|
||||
updateAvatar(newAvatarUrl);
|
||||
event.preventDefault();
|
||||
|
|
@ -84,7 +89,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// initially show avatar
|
||||
function showAvatar() {
|
||||
var photoUrl = context.JK.resolveAvatarUrl(userMe.photo_url);
|
||||
|
|
|
|||
|
|
@ -142,6 +142,15 @@
|
|||
$element.data("prodTimer", null);
|
||||
$element.btOff();
|
||||
}, options['duration']));
|
||||
|
||||
$element.on('remove', function() {
|
||||
var timer = $element.data('prodTimer')
|
||||
if(timer) {
|
||||
clearTimeout(timer);
|
||||
$element.data("prodTimer", null);
|
||||
$element.btOff();
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Associates a bubble on hover (by default) with the specified $element, using jquery.bt.js (BeautyTips)
|
||||
|
|
@ -574,111 +583,6 @@
|
|||
return keys;
|
||||
};
|
||||
|
||||
context.JK.createProfileName = function(deviceInfo, chatName) {
|
||||
var isSameInOut = deviceInfo.input.id == deviceInfo.output.id;
|
||||
|
||||
var name = null;
|
||||
if(isSameInOut) {
|
||||
name = "In/Out: " + deviceInfo.input.info.displayName;
|
||||
}
|
||||
else {
|
||||
name = "In: " + deviceInfo.input.info.displayName + ", Out: " + deviceInfo.output.info.displayName
|
||||
}
|
||||
|
||||
logger.debug("creating profile name: " + name);
|
||||
return name;
|
||||
}
|
||||
|
||||
context.JK.selectedDeviceInfo = function(audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
|
||||
|
||||
if(!deviceInformation) {
|
||||
deviceInformation = context.JK.loadDeviceInfo();
|
||||
}
|
||||
|
||||
var input = deviceInformation[audioInputDeviceId];
|
||||
var output = deviceInformation[audioOutputDeviceId];
|
||||
|
||||
var inputBehavior = AUDIO_DEVICE_BEHAVIOR[input.type];
|
||||
var outputBehavior = AUDIO_DEVICE_BEHAVIOR[output.type];
|
||||
|
||||
return {
|
||||
input: {
|
||||
id: audioInputDeviceId,
|
||||
info: input,
|
||||
behavior: inputBehavior
|
||||
},
|
||||
output: {
|
||||
id: audioOutputDeviceId,
|
||||
info: output,
|
||||
behavior: outputBehavior
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.JK.loadDeviceInfo = function() {
|
||||
|
||||
var operatingSystem = context.JK.GetOSAsString();
|
||||
// should return one of:
|
||||
// * MacOSX_builtin
|
||||
// * MACOSX_interface
|
||||
// * Win32_wdm
|
||||
// * Win32_asio
|
||||
// * Win32_asio4all
|
||||
// * Linux
|
||||
function determineDeviceType(deviceId, displayName) {
|
||||
if (operatingSystem == "MacOSX") {
|
||||
if (displayName.toLowerCase().trim() == "built-in") {
|
||||
return "MacOSX_builtin";
|
||||
}
|
||||
else {
|
||||
return "MacOSX_interface";
|
||||
}
|
||||
}
|
||||
else if (operatingSystem == "Win32") {
|
||||
if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) {
|
||||
return "Win32_wdm";
|
||||
}
|
||||
else if (displayName.toLowerCase().indexOf("asio4all") > -1) {
|
||||
return "Win32_asio4all"
|
||||
}
|
||||
else {
|
||||
return "Win32_asio";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Linux";
|
||||
}
|
||||
}
|
||||
|
||||
var devices = context.jamClient.FTUEGetAudioDevices();
|
||||
logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices));
|
||||
|
||||
var loadedDevices = {};
|
||||
|
||||
// augment these devices by determining their type
|
||||
context._.each(devices.devices, function (device) {
|
||||
|
||||
if (device.name == "JamKazam Virtual Monitor") {
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceInfo = {};
|
||||
|
||||
deviceInfo.id = device.guid;
|
||||
deviceInfo.type = determineDeviceType(device.guid, device.display_name);
|
||||
deviceInfo.displayType = AUDIO_DEVICE_BEHAVIOR[deviceInfo.type].display;
|
||||
deviceInfo.displayName = device.display_name;
|
||||
deviceInfo.inputCount = device.input_count;
|
||||
deviceInfo.outputCount = device.output_count;
|
||||
|
||||
loadedDevices[device.guid] = deviceInfo;
|
||||
})
|
||||
|
||||
logger.debug(context.JK.dlen(loadedDevices) + " devices loaded.", loadedDevices);
|
||||
|
||||
return loadedDevices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first error associated with the field.
|
||||
* @param fieldName the name of the field
|
||||
|
|
|
|||
|
|
@ -0,0 +1,261 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.VoiceChatHelper = function (app) {
|
||||
var logger = context.JK.logger;
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var MAX_TRACKS = context.JK.MAX_TRACKS;
|
||||
var MAX_OUTPUTS = context.JK.MAX_OUTPUTS;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
|
||||
var $parent = null;
|
||||
|
||||
var $reuseAudioInputRadio = null;
|
||||
var $useChatInputRadio = null;
|
||||
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
|
||||
|
||||
// needed because iCheck fires iChecked event even when you programmatically change it, unlike when using .val(x)
|
||||
var ignoreICheckEvent = false;
|
||||
|
||||
function defaultReuse() {
|
||||
suppressChange(function(){
|
||||
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
|
||||
$useChatInputRadio.removeAttr('checked');
|
||||
})
|
||||
}
|
||||
|
||||
function isChatEnabled() {
|
||||
return $useChatInputRadio.is(':checked');
|
||||
}
|
||||
|
||||
function reset(forceDisabledChat) {
|
||||
|
||||
$selectedChatInput = null;
|
||||
|
||||
if(!forceDisabledChat && context.jamClient.TrackGetChatEnable()) {
|
||||
enableChat(false);
|
||||
}
|
||||
else {
|
||||
disableChat(false);
|
||||
}
|
||||
|
||||
$chatInputs.empty();
|
||||
|
||||
var chatInputs = gearUtils.getChatInputs();
|
||||
|
||||
context._.each(chatInputs, function(chatInput) {
|
||||
if(chatInput.assignment > 0) {
|
||||
return;
|
||||
}
|
||||
var chatChannelName = chatInput.name;
|
||||
var chatChannelId = chatInput.id;
|
||||
var isCurrentlyChat = chatInput.assignment == ASSIGNMENT.CHAT;
|
||||
var $chat = $(context._.template($templateChatInput.html(), {id: chatChannelId, name: chatChannelName}, { variable: 'data' }));
|
||||
var $chatInput = $chat.find('input');
|
||||
if(isCurrentlyChat) {
|
||||
$selectedChatInput = $chatInput;
|
||||
$selectedChatInput.attr('checked', 'checked');
|
||||
}
|
||||
$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) {
|
||||
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);
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to save assignments. ' + result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(!isChatEnabled()) {
|
||||
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']});
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
function disableChatButtonsUI() {
|
||||
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
|
||||
$radioButtons.iCheck('disable')
|
||||
$chatInputs.addClass('disabled');
|
||||
}
|
||||
|
||||
function enableChatButtonsUI() {
|
||||
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
|
||||
$radioButtons.iCheck('enable')
|
||||
$chatInputs.removeClass('disabled');
|
||||
|
||||
}
|
||||
function suppressChange(proc) {
|
||||
|
||||
ignoreICheckEvent = true;
|
||||
|
||||
try {
|
||||
proc();
|
||||
}
|
||||
finally {
|
||||
ignoreICheckEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
function disableChat(applyToBackend) {
|
||||
if(saveImmediate && applyToBackend) {
|
||||
logger.debug("voiceChatHelper: disabling chat to backend");
|
||||
var state = getCurrentState();
|
||||
//context.jamClient.TrackSetChatEnable(false);
|
||||
//if(state.chat_channel) {
|
||||
// context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED);
|
||||
//}
|
||||
context.jamClient.FTUEClearChatInput();
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
suppressChange(function() {
|
||||
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
|
||||
$useChatInputRadio.removeAttr('checked');
|
||||
})
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to disable chat. ' + result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug("voiceChatHelper: disabling chat UI only");
|
||||
suppressChange(function() {
|
||||
$reuseAudioInputRadio.iCheck('check').attr('checked', 'checked');
|
||||
$useChatInputRadio.removeAttr('checked');
|
||||
})
|
||||
}
|
||||
disableChatButtonsUI()
|
||||
}
|
||||
|
||||
function enableChat(applyToBackend) {
|
||||
if(saveImmediate && applyToBackend) {
|
||||
logger.debug("voiceChatHelper: enabling chat to backend");
|
||||
context.jamClient.TrackSetChatEnable(true);
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
suppressChange(function() {
|
||||
$useChatInputRadio.iCheck('check').attr('checked', 'checked');
|
||||
$reuseAudioInputRadio.removeAttr('checked');
|
||||
})
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to enable chat. ' + result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug("voiceChatHelper: enabling chat UI only");
|
||||
suppressChange(function() {
|
||||
$useChatInputRadio.iCheck('check').attr('checked', 'checked');
|
||||
$reuseAudioInputRadio.removeAttr('checked');
|
||||
})
|
||||
}
|
||||
|
||||
enableChatButtonsUI();
|
||||
}
|
||||
|
||||
function handleChatEnabledToggle() {
|
||||
context.JK.checkbox($reuseAudioInputRadio);
|
||||
context.JK.checkbox($useChatInputRadio);
|
||||
|
||||
// plugin sets to relative on the element; have to do this as an override
|
||||
$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) });
|
||||
}
|
||||
|
||||
// gets the state of the UI
|
||||
function getCurrentState() {
|
||||
var state = {
|
||||
enabled:null,
|
||||
chat_channel:null
|
||||
};
|
||||
|
||||
state.enabled = $useChatInputRadio.is(':checked');
|
||||
state.chat_channel = $selectedChatInput && $selectedChatInput.attr('data-channel-id');
|
||||
logger.debug("desired chat state: enabled=" + state.enabled + ", chat_channel=" + state.chat_channel)
|
||||
return state;
|
||||
}
|
||||
|
||||
function trySave() {
|
||||
|
||||
var state = getCurrentState();
|
||||
|
||||
if(state.enabled && state.chat_channel) {
|
||||
logger.debug("enabling chat. chat_channel=" + state.chat_channel);
|
||||
context.jamClient.TrackSetChatEnable(true);
|
||||
context.jamClient.FTUESetChatInput(state.chat_channel);
|
||||
//context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.CHAT);
|
||||
}
|
||||
else {
|
||||
logger.debug("disabling chat.");
|
||||
context.jamClient.FTUEClearChatInput();
|
||||
//context.jamClient.TrackSetChatEnable(false);
|
||||
//if(state.chat_channel) {
|
||||
//context.jamClient.TrackSetAssignment(state.chat_channel, true, ASSIGNMENT.UNASSIGNED);
|
||||
//}
|
||||
}
|
||||
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to save chat assignments. ' + result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function initialize(_$step, _saveImmediate) {
|
||||
$parent = _$step;
|
||||
saveImmediate = _saveImmediate;
|
||||
|
||||
$reuseAudioInputRadio = $parent.find('.reuse-audio-input input');
|
||||
$useChatInputRadio = $parent.find('.use-chat-input input');
|
||||
$chatInputs = $parent.find('.chat-inputs');
|
||||
$templateChatInput = $('#template-chat-input');
|
||||
|
||||
handleChatEnabledToggle();
|
||||
}
|
||||
|
||||
this.reset = reset;
|
||||
this.trySave = trySave;
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.FrameBuffers = function (app) {
|
||||
|
||||
var $knobs = null;
|
||||
var $bufferIn = null;
|
||||
var $bufferOut = null;
|
||||
var $frameSize = null;
|
||||
var $self = $(this);
|
||||
|
||||
var FRAMESIZE_CHANGED = 'frame_buffers.framesize_changed';
|
||||
var BUFFER_IN_CHANGED = 'frame_buffers.buffer_in_changed';
|
||||
var BUFFER_OUT_CHANGED = 'frame_buffers.buffer_out_changed';
|
||||
|
||||
function selectedFramesize() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferIn() {
|
||||
return parseFloat($bufferIn.val());
|
||||
}
|
||||
|
||||
function selectedBufferOut() {
|
||||
return parseFloat($bufferOut.val());
|
||||
}
|
||||
|
||||
function setFramesize(value) {
|
||||
context.JK.dropdown($frameSize.val(value).easyDropDown('select', value.toString(), true))
|
||||
}
|
||||
|
||||
function setBufferIn(value) {
|
||||
context.JK.dropdown($bufferIn.val(value).easyDropDown('select', value.toString(), true));
|
||||
}
|
||||
|
||||
function setBufferOut(value) {
|
||||
context.JK.dropdown($bufferOut.val(value).easyDropDown('select', value.toString(), true))
|
||||
}
|
||||
|
||||
function render() {
|
||||
context.JK.dropdown($frameSize);
|
||||
context.JK.dropdown($bufferIn);
|
||||
context.JK.dropdown($bufferOut);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
$frameSize.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferIn.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferOut.attr("disabled", "disabled").easyDropDown('disable');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
$frameSize.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferIn.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferOut.removeAttr("disabled").easyDropDown('enable');
|
||||
}
|
||||
|
||||
function resetValues() {
|
||||
$frameSize.val('2.5');
|
||||
$bufferIn.val('0');
|
||||
$bufferOut.val('0');
|
||||
}
|
||||
|
||||
function events() {
|
||||
$frameSize.unbind('change').change(function () {
|
||||
$self.triggerHandler(FRAMESIZE_CHANGED, {value: selectedFramesize()});
|
||||
});
|
||||
|
||||
$bufferIn.unbind('change').change(function () {
|
||||
logger.debug("buffer-in changed: " + selectedBufferIn());
|
||||
$self.triggerHandler(BUFFER_IN_CHANGED, {value: selectedBufferIn()});
|
||||
});
|
||||
|
||||
$bufferOut.unbind('change').change(function () {
|
||||
logger.debug("buffer-out changed: " + selectedBufferOut());
|
||||
$self.triggerHandler(BUFFER_OUT_CHANGED, {value: selectedBufferOut()});
|
||||
});
|
||||
}
|
||||
|
||||
function initialize(_$knobs) {
|
||||
|
||||
$knobs = _$knobs;
|
||||
if(!$knobs.is('.frame-and-buffers')) {
|
||||
throw "$knobs != .frame-and-buffers"
|
||||
}
|
||||
|
||||
$bufferIn = $knobs.find('.select-buffer-in');
|
||||
$bufferOut = $knobs.find('.select-buffer-out');
|
||||
$frameSize = $knobs.find('.select-frame-size');
|
||||
|
||||
events();
|
||||
render();
|
||||
}
|
||||
|
||||
this.FRAMESIZE_CHANGED = FRAMESIZE_CHANGED;
|
||||
this.BUFFER_IN_CHANGED = BUFFER_IN_CHANGED;
|
||||
this.BUFFER_OUT_CHANGED = BUFFER_OUT_CHANGED;
|
||||
this.initialize = initialize;
|
||||
this.selectedFramesize = selectedFramesize;
|
||||
this.selectedBufferIn = selectedBufferIn;
|
||||
this.selectedBufferOut = selectedBufferOut;
|
||||
this.setFramesize = setFramesize;
|
||||
this.setBufferIn = setBufferIn;
|
||||
this.setBufferOut = setBufferOut;
|
||||
this.render = render;
|
||||
this.enable = enable;
|
||||
this.disable = disable;
|
||||
this.resetValues = resetValues;
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
var wizard = null;
|
||||
var $wizardSteps = null;
|
||||
var $templateSteps = null;
|
||||
var loopbackWizard = null;
|
||||
|
||||
var self = this;
|
||||
|
||||
|
|
@ -74,6 +75,7 @@
|
|||
var newProfileName = 'FTUEAttempt-' + new Date().getTime().toString();
|
||||
logger.debug("setting FTUE-prefixed profile name to: " + newProfileName);
|
||||
context.jamClient.FTUESetMusicProfileName(newProfileName);
|
||||
context.jamClient.FTUEClearChannelAssignments();
|
||||
newSession();
|
||||
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
|
@ -145,7 +147,9 @@
|
|||
}
|
||||
|
||||
|
||||
function initialize() {
|
||||
function initialize(_loopbackWizard) {
|
||||
|
||||
loopbackWizard = _loopbackWizard;
|
||||
|
||||
// on initial page load, we are not in the FTUE. so cancel the FTUE and call FTUESetStatus(true) if needed
|
||||
if(context.jamClient.FTUEGetStatus() == false) {
|
||||
|
|
@ -179,6 +183,8 @@
|
|||
this.setBackState = setBackState;
|
||||
this.initialize = initialize;
|
||||
this.createFTUEProfile = createFTUEProfile;
|
||||
this.getWizard = function() {return wizard; }
|
||||
this.getLoopbackWizard = function() { return loopbackWizard; };
|
||||
|
||||
self = this;
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -10,257 +10,39 @@
|
|||
var MAX_TRACKS = context.JK.MAX_TRACKS;
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var configureTracksHelper = new context.JK.ConfigureTracksHelper(app);
|
||||
|
||||
var $step = null;
|
||||
var $templateAssignablePort = null;
|
||||
var $templateTrackTarget = null;
|
||||
var $unassignedChannelsHolder = null;
|
||||
var $tracksHolder = null;
|
||||
var $instrumentsHolder = null;
|
||||
|
||||
|
||||
function loadChannels() {
|
||||
var musicPorts = jamClient.FTUEGetChannels();
|
||||
|
||||
$unassignedChannelsHolder.empty();
|
||||
$tracksHolder.find('.ftue-input').remove();
|
||||
|
||||
var inputChannels = musicPorts.inputs;
|
||||
|
||||
context._.each(inputChannels, function (inputChannel) {
|
||||
if(inputChannel.assignment == ASSIGNMENT.UNASSIGNED) {
|
||||
var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' }));
|
||||
unassignChannel($channel);
|
||||
}
|
||||
else {
|
||||
var $channel = $(context._.template($templateAssignablePort.html(), inputChannel, { variable: 'data' }));
|
||||
|
||||
// find the track this belongs in
|
||||
|
||||
var trackNumber = inputChannel.assignment - 1;
|
||||
|
||||
var $track = $tracksHolder.find('.track[data-num="' + trackNumber + '"]')
|
||||
|
||||
if($track.length == 0) {
|
||||
context.JK.alertSupportedNeeded('Unable to find a track for channel with assignment ' + inputChannel.assignment);
|
||||
return false;
|
||||
}
|
||||
addChannelToTrack($channel, $track.find('.track-target'));
|
||||
}
|
||||
|
||||
$channel.draggable({
|
||||
helper: 'clone',
|
||||
start: function() {
|
||||
var $channel = $(this);
|
||||
var $track = $channel.closest('.track-target');
|
||||
var isUnassigned = $track.length == 0;
|
||||
if(isUnassigned) {
|
||||
$tracksHolder.find('.track-target').addClass('possible-target');
|
||||
}
|
||||
else {
|
||||
$tracksHolder.find('.track-target').addClass('possible-target');
|
||||
$unassignedChannelsHolder.addClass('possible-target');
|
||||
}
|
||||
},
|
||||
stop: function() {
|
||||
$tracksHolder.find('.track-target').removeClass('possible-target');
|
||||
$unassignedChannelsHolder.removeClass('possible-target')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// iterates through the dom and returns a pure data structure for track associations
|
||||
function trackAssociations() {
|
||||
|
||||
var tracks = {};
|
||||
tracks.tracks = [];
|
||||
tracks.unassignedChannels = [];
|
||||
var $unassignedChannels = $unassignedChannelsHolder.find('.ftue-input');
|
||||
var $tracks = $tracksHolder.find('.track-target');
|
||||
|
||||
context._.each($unassignedChannels, function($unassignedTrack) {
|
||||
$unassignedTrack = $($unassignedTrack);
|
||||
var channelId = $unassignedTrack.attr('data-input-id');
|
||||
tracks.unassignedChannels.push(channelId);
|
||||
})
|
||||
|
||||
context._.each($tracks, function($track, index) {
|
||||
$track = $($track);
|
||||
var $assignedChannels = $track.find('.ftue-input');
|
||||
|
||||
var track = {index: index, channels:[]};
|
||||
context._.each($assignedChannels, function($assignedChannel) {
|
||||
$assignedChannel = $($assignedChannel);
|
||||
track.channels.push($assignedChannel.attr('data-input-id'))
|
||||
});
|
||||
|
||||
// sparse array
|
||||
if(track.channels.length > 0) {
|
||||
tracks.tracks.push(track);
|
||||
}
|
||||
var $instrument = $instrumentsHolder.find('[data-num="' + index + '"]').find('.icon-instrument-select');
|
||||
track.instrument_id = $instrument.data('instrument_id');
|
||||
})
|
||||
return tracks;
|
||||
}
|
||||
|
||||
function validate(tracks) {
|
||||
// there must be at least one assigned channel
|
||||
|
||||
if(tracks.tracks.length == 0) {
|
||||
logger.debug("ConfigureTracks validation error: must have assigned at least one input port to a track.");
|
||||
context.JK.Banner.showAlert('Must have assigned at least one input port to a track.');
|
||||
return false;
|
||||
}
|
||||
|
||||
context._.each(tracks.tracks, function(track) {
|
||||
if(!track.instrument_id) {
|
||||
logger.debug("ConfigureTracks validation error: all tracks with ports assigned must specify an instrument.");
|
||||
context.JK.Banner.showAlert('All tracks with ports assigned must specify an instrument.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function save(tracks) {
|
||||
|
||||
context._.each(tracks.unassignedChannels, function(unassignedChannelId) {
|
||||
context.jamClient.TrackSetAssignment(unassignedChannelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
});
|
||||
|
||||
context._.each(tracks.tracks, function(track, index) {
|
||||
|
||||
var trackNumber = index + 1;
|
||||
|
||||
context._.each(track.channels, function(channelId) {
|
||||
context.jamClient.TrackSetAssignment(channelId, true, trackNumber);
|
||||
|
||||
});
|
||||
logger.debug("context.jamClient.TrackSetInstrument(trackNumber, track.instrument_id)", trackNumber, track.instrument_id);
|
||||
context.jamClient.TrackSetInstrument(trackNumber, context.JK.instrument_id_to_instrument[track.instrument_id].client_id);
|
||||
});
|
||||
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to save assignments. ' + result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadTrackInstruments() {
|
||||
var $trackInstruments = $instrumentsHolder.find('.track-instrument');
|
||||
|
||||
context._.each($trackInstruments, function(trackInstrument) {
|
||||
var $trackInstrument = $(trackInstrument);
|
||||
|
||||
var trackIndex = parseInt($trackInstrument.attr('data-num')) + 1;
|
||||
|
||||
var clientInstrument = context.jamClient.TrackGetInstrument(trackIndex);
|
||||
|
||||
var instrument = context.JK.client_to_server_instrument_map[clientInstrument];
|
||||
|
||||
$trackInstrument.instrumentSelectorSet(instrument ? instrument.server_id : instrument);
|
||||
});
|
||||
}
|
||||
var successfullyAssignedOnce = false;
|
||||
|
||||
function handleNext() {
|
||||
var tracks = trackAssociations();
|
||||
|
||||
if(!validate(tracks)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var saved = save(tracks);
|
||||
var saved = configureTracksHelper.trySave();
|
||||
|
||||
if(saved) {
|
||||
context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS());
|
||||
successfullyAssignedOnce = true;
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
function newSession() {
|
||||
successfullyAssignedOnce = false;
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
loadChannels();
|
||||
loadTrackInstruments();
|
||||
}
|
||||
var forceInputsToUnassigned = !successfullyAssignedOnce;
|
||||
|
||||
function unassignChannel($channel) {
|
||||
var $originallyAssignedTrack = $channel.closest('.track-target');
|
||||
$unassignedChannelsHolder.append($channel);
|
||||
$originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length);
|
||||
|
||||
}
|
||||
function addChannelToTrack($channel, $track) {
|
||||
var $originallyAssignedTrack = $channel.closest('.track-target');
|
||||
$track.append($channel);
|
||||
$track.attr('track-count', $track.find('.ftue-input:not(.ui-draggable-dragging)').length);
|
||||
$originallyAssignedTrack.attr('track-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length)
|
||||
}
|
||||
|
||||
function initializeUnassignedDroppable() {
|
||||
$unassignedChannelsHolder.droppable(
|
||||
{
|
||||
activeClass: 'drag-in-progress',
|
||||
hoverClass: 'drag-hovering',
|
||||
drop: function( event, ui ) {
|
||||
var $channel = ui.draggable;
|
||||
//$channel.css('left', '0').css('top', '0');
|
||||
unassignChannel($channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeTrackDroppables() {
|
||||
var i;
|
||||
for(i = 0; i < MAX_TRACKS; i++) {
|
||||
var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' }));
|
||||
$tracksHolder.append($target);
|
||||
$target.find('.track-target').droppable(
|
||||
{
|
||||
activeClass: 'drag-in-progress',
|
||||
hoverClass: 'drag-hovering',
|
||||
drop: function( event, ui ) {
|
||||
var $track = $(this);
|
||||
var $channel = ui.draggable;
|
||||
//$channel.css('left', '0').css('top', '0');
|
||||
addChannelToTrack($channel, $track);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initializeInstrumentDropdown() {
|
||||
var i;
|
||||
for(i = 0; i < MAX_TRACKS; i++) {
|
||||
var $root = $('<div class="track-instrument"></div>');
|
||||
$root.instrumentSelector().attr('data-num', i);
|
||||
$instrumentsHolder.append($root);
|
||||
}
|
||||
configureTracksHelper.reset(forceInputsToUnassigned)
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
|
||||
$templateAssignablePort = $('#template-assignable-port');
|
||||
$templateTrackTarget = $('#template-track-target');
|
||||
$unassignedChannelsHolder = $step.find('.unassigned-channels');
|
||||
$tracksHolder = $step.find('.tracks');
|
||||
$instrumentsHolder = $step.find('.instruments');
|
||||
|
||||
|
||||
initializeUnassignedDroppable();
|
||||
initializeTrackDroppables();
|
||||
initializeInstrumentDropdown();
|
||||
configureTracksHelper.initialize($step);
|
||||
}
|
||||
|
||||
this.newSession = newSession;
|
||||
this.handleNext = handleNext;
|
||||
this.beforeShow = beforeShow;
|
||||
this.initialize = initialize;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var $step = null;
|
||||
|
|
@ -16,144 +17,30 @@
|
|||
var $templateChatInput = null;
|
||||
var $selectedChatInput = null;// should only be used if isChatEnabled = true
|
||||
|
||||
var voiceChatHelper = new context.JK.VoiceChatHelper(app);
|
||||
var firstTimeShown = false;
|
||||
|
||||
function newSession() {
|
||||
$reuseAudioInputRadio.attr('checked', 'checked').iCheck('check');
|
||||
}
|
||||
|
||||
function isChannelAvailableForChat(chatChannelId, musicPorts) {
|
||||
var result = true;
|
||||
context._.each(musicPorts.input, function(inputChannel) {
|
||||
// if the channel is currently assigned to a track, it not unassigned
|
||||
if(inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) {
|
||||
result = false;
|
||||
return false; // break
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isChatEnabled() {
|
||||
return $useChatInputRadio.is(':checked');
|
||||
firstTimeShown = true;
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
var forceDisabledChat = firstTimeShown;
|
||||
|
||||
if(isChatEnabled()) {
|
||||
enableChat();
|
||||
}
|
||||
else {
|
||||
disableChat();
|
||||
}
|
||||
voiceChatHelper.reset(forceDisabledChat);
|
||||
|
||||
var musicPorts = jamClient.FTUEGetChannels();
|
||||
var chatInputs = context.jamClient.FTUEGetChatInputs();
|
||||
|
||||
$chatInputs.empty();
|
||||
|
||||
context._.each(chatInputs, function(chatChannelName, chatChannelId) {
|
||||
if(isChannelAvailableForChat(chatChannelId, musicPorts)) {
|
||||
var $chat = $(context._.template($templateChatInput.html(), {id: chatChannelId, name: chatChannelName}, { variable: 'data' }));
|
||||
$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) {
|
||||
var $input = $(e.currentTarget);
|
||||
$selectedChatInput = $input; // for use in handleNext
|
||||
var channelId = $input.attr('data-channel-id');
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(!isChatEnabled()) {
|
||||
$radioButtons.iCheck('disable');
|
||||
}
|
||||
|
||||
$chatInputs.find('.chat-input').show().on('click', function() {
|
||||
if(!isChatEnabled()) {
|
||||
context.JK.prodBubble($step.find('.use-chat-input h3'), 'chat-not-enabled', {}, { positions:['left']});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function disableChat() {
|
||||
logger.debug("FTUE: disabling chat");
|
||||
context.jamClient.TrackSetChatEnable(false);
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to disable chat. ' + result);
|
||||
return false;
|
||||
}
|
||||
|
||||
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
|
||||
$radioButtons.iCheck('disable');
|
||||
}
|
||||
|
||||
function enableChat() {
|
||||
logger.debug("FTUE: enabling chat");
|
||||
context.jamClient.TrackSetChatEnable(true);
|
||||
var result = context.jamClient.TrackSaveAssignments();
|
||||
|
||||
if(!result || result.length == 0) {
|
||||
// success
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('Unable to enable chat. ' + result);
|
||||
return false;
|
||||
}
|
||||
|
||||
var $radioButtons = $chatInputs.find('input[name="chat-device"]');
|
||||
$radioButtons.iCheck('enable');
|
||||
}
|
||||
|
||||
function handleChatEnabledToggle() {
|
||||
context.JK.checkbox($reuseAudioInputRadio);
|
||||
context.JK.checkbox($useChatInputRadio);
|
||||
|
||||
// plugin sets to relative on the element; have to do this as an override
|
||||
$reuseAudioInputRadio.closest('.iradio_minimal').css('position', 'absolute');
|
||||
$useChatInputRadio.closest('.iradio_minimal').css('position', 'absolute');
|
||||
|
||||
$reuseAudioInputRadio.on('ifChecked', disableChat);
|
||||
$useChatInputRadio.on('ifChecked', enableChat)
|
||||
firstTimeShown = false;
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
var selectedDeviceInfo = context.JK.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice());
|
||||
|
||||
var chatName = null;
|
||||
if(isChatEnabled()) {
|
||||
chatName = $selectedChatInput.attr('data-channel-name');
|
||||
}
|
||||
context.jamClient.FTUESetMusicProfileName(context.JK.createProfileName(selectedDeviceInfo, chatName));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
|
||||
$reuseAudioInputRadio = $step.find('.reuse-audio-input input');
|
||||
$useChatInputRadio = $step.find('.use-chat-input input');
|
||||
$chatInputs = $step.find('.chat-inputs');
|
||||
$templateChatInput = $('#template-chat-input');
|
||||
voiceChatHelper.initialize($step, true);
|
||||
|
||||
handleChatEnabledToggle();
|
||||
}
|
||||
|
||||
this.handleNext = handleNext;
|
||||
|
|
|
|||
|
|
@ -3,454 +3,59 @@
|
|||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepNetworkTest = function (app, $dialog) {
|
||||
context.JK.StepNetworkTest = function (app, dialog) {
|
||||
|
||||
var NETWORK_TEST_TYPES = {
|
||||
RestPhase : 0,
|
||||
PktTest100NormalLatency : 1,
|
||||
PktTest200MediumLatency : 2,
|
||||
PktTest400LowLatency : 3,
|
||||
PktTestRateSweep : 4,
|
||||
RcvOnly : 5
|
||||
}
|
||||
var STARTING_NUM_CLIENTS = 4;
|
||||
var PAYLOAD_SIZE = 100;
|
||||
var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2;
|
||||
|
||||
var rest = context.JK.Rest();
|
||||
var logger = context.JK.logger;
|
||||
var networkTest = new context.JK.NetworkTest(app);
|
||||
var $step = null;
|
||||
var TEST_SUCCESS_CALLBACK = 'JK.HandleNetworkTestSuccess';
|
||||
var TEST_TIMEOUT_CALLBACK = 'JK.HandleNetworkTestTimeout';
|
||||
|
||||
var $startNetworkTestBtn = null;
|
||||
var $testResults = null;
|
||||
var $testScore = null;
|
||||
var $testText = null;
|
||||
var testedSuccessfully = false;
|
||||
var $scoringBar = null;
|
||||
var $goodMarker = null;
|
||||
var $goodLine = null;
|
||||
var $currentScore = null;
|
||||
var $scoredClients = null;
|
||||
var $subscore = null;
|
||||
var backendGuardTimeout = null;
|
||||
|
||||
var serverClientId = '';
|
||||
var isScoring = false;
|
||||
var numClientsToTest = STARTING_NUM_CLIENTS;
|
||||
var testSummary = {attempts : [], final:null}
|
||||
|
||||
var scoringZoneWidth = 100;//px
|
||||
|
||||
|
||||
function reset() {
|
||||
serverClientId = '';
|
||||
isScoring = false;
|
||||
numClientsToTest = STARTING_NUM_CLIENTS;
|
||||
testSummary = {attempts : []};
|
||||
updateControlsState();
|
||||
function networkTestDone() {
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function renderStartTest() {
|
||||
$scoredClients.empty();
|
||||
$testResults.removeClass('good acceptable bad').addClass('testing');
|
||||
$testText.empty();
|
||||
$subscore.empty();
|
||||
updateControlsState();
|
||||
$currentScore.width(0);
|
||||
$goodLine.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
|
||||
$goodMarker.css('left', (gon.ftue_packet_rate_treshold * 100) + '%');
|
||||
function networkTestFail() {
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function renderStopTest(score, text) {
|
||||
$scoredClients.html(score);
|
||||
$testText.html(text);
|
||||
$testResults.removeClass('testing');
|
||||
function networkTestStart() {
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function postDiagnostic() {
|
||||
rest.createDiagnostic({
|
||||
type: 'NETWORK_TEST_RESULT',
|
||||
data: {client_type: context.JK.clientType(), client_id: context.JK.JamServer.clientID, summary:testSummary}
|
||||
});
|
||||
}
|
||||
|
||||
function testFinished() {
|
||||
var attempt = getCurrentAttempt();
|
||||
|
||||
if(!testSummary.final) {
|
||||
testSummary.final = {reason : attempt.reason};
|
||||
}
|
||||
|
||||
var reason = testSummary.final.reason;
|
||||
|
||||
if(reason == "success") {
|
||||
renderStopTest(attempt.num_clients, "Your router and Internet service will support sessions of up to " + attempt.num_clients + " JamKazam musicians.")
|
||||
testedSuccessfully = true;
|
||||
if(!testSummary.final.num_clients) {
|
||||
testSummary.final.num_clients = attempt.num_clients;
|
||||
}
|
||||
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');
|
||||
}
|
||||
else {
|
||||
$testResults.addClass('good');
|
||||
}
|
||||
}
|
||||
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.")
|
||||
}
|
||||
else if(reason == "unreachable") {
|
||||
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.");
|
||||
}
|
||||
else if(reason == "internal_error") {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "remote_peer_cant_test") {
|
||||
context.JK.alertSupportedNeeded("The JamKazam service is experiencing technical difficulties.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == 'backend_gone') {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client is experiencing technical difficulties.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "invalid_response") {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection. Reason=" + attempt.backend_data.reason + '.');
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == 'no_servers') {
|
||||
context.JK.alertSupportedNeeded("No network test servers are available. You can skip this step for now.");
|
||||
renderStopTest('', '');
|
||||
testedSuccessfully = true;
|
||||
}
|
||||
else if(reason == 'no_network') {
|
||||
context.JK.Banner.showAlert("Please try again later. Your network appears down.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "rest_api_error") {
|
||||
context.JK.alertSupportedNeeded("Unable to acquire a network test server. You can skip this step for now.");
|
||||
testedSuccessfully = true;
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else if(reason == "timeout") {
|
||||
context.JK.alertSupportedNeeded("Communication with a network test servers timed out. You can skip this step for now.");
|
||||
testedSuccessfully = true;
|
||||
renderStopTest('', '');
|
||||
}
|
||||
else {
|
||||
context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
|
||||
renderStopTest('', '');
|
||||
}
|
||||
|
||||
numClientsToTest = STARTING_NUM_CLIENTS;
|
||||
isScoring = false;
|
||||
updateControlsState();
|
||||
postDiagnostic();
|
||||
}
|
||||
|
||||
function getCurrentAttempt() {
|
||||
return testSummary.attempts[testSummary.attempts.length - 1];
|
||||
}
|
||||
|
||||
function backendTimedOut() {
|
||||
testSummary.final = {reason: 'backend_gone'}
|
||||
testFinished();
|
||||
}
|
||||
|
||||
function clearBackendGuard() {
|
||||
if(backendGuardTimeout) {
|
||||
clearTimeout(backendGuardTimeout);
|
||||
backendGuardTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
function attemptTestPass() {
|
||||
|
||||
var attempt = {};
|
||||
attempt.payload_size = PAYLOAD_SIZE;
|
||||
attempt.duration = gon.ftue_network_test_duration;
|
||||
attempt.test_type = 'PktTest400LowLatency';
|
||||
attempt.num_clients = numClientsToTest;
|
||||
attempt.server_client_id = serverClientId;
|
||||
attempt.received_progress = false;
|
||||
testSummary.attempts.push(attempt);
|
||||
|
||||
//context.jamClient.StopNetworkTest('');
|
||||
|
||||
$testText.text("Simulating the network traffic of a " + numClientsToTest + "-person session.");
|
||||
|
||||
updateProgress(0, false);
|
||||
|
||||
backendGuardTimeout = setTimeout(function(){backendTimedOut()}, (gon.ftue_network_test_duration + 1) * 1000);
|
||||
|
||||
context.jamClient.TestNetworkPktBwRate(serverClientId, TEST_SUCCESS_CALLBACK, TEST_TIMEOUT_CALLBACK,
|
||||
NETWORK_TEST_TYPES.PktTest400LowLatency,
|
||||
gon.ftue_network_test_duration,
|
||||
numClientsToTest,
|
||||
PAYLOAD_SIZE);
|
||||
}
|
||||
|
||||
|
||||
function startNetworkTest(checkWireless) {
|
||||
|
||||
if(isScoring) return false;
|
||||
|
||||
if(checkWireless) {
|
||||
// check if on Wifi 1st
|
||||
var isWireless = context.jamClient.IsMyNetworkWireless();
|
||||
if(isWireless == -1) {
|
||||
logger.warn("unable to determine if the user is on wireless or not for network test. skipping prompt.")
|
||||
}
|
||||
else if(isWireless == 1) {
|
||||
context.JK.Banner.showAlert({buttons: [
|
||||
{name: 'RUN NETWORK TEST ANYWAY', click: function() {startNetworkTest(false)}},
|
||||
{name: 'CANCEL', click: function() {}}],
|
||||
html: "<p>It appears that your computer is connected to your network using WiFi.</p>" +
|
||||
"<p>We strongly advise against running the JamKazam application on a WiFi connection. " +
|
||||
"We recommend using a wired Ethernet connection from your computer to your router. " +
|
||||
"A WiFi connection is likely to cause significant issues in both latency and audio quality.</p>"})
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
isScoring = true;
|
||||
renderStartTest();
|
||||
rest.getLatencyTester()
|
||||
.done(function(response) {
|
||||
// ensure there are no tests ongoing
|
||||
|
||||
serverClientId = response.client_id;
|
||||
|
||||
logger.info("beginning network test against client_id: " + serverClientId);
|
||||
|
||||
attemptTestPass();
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status == 404) {
|
||||
// means there are no network testers available.
|
||||
// we have to skip this part of the UI
|
||||
testSummary.final = {reason: 'no_servers'}
|
||||
}
|
||||
else {
|
||||
if(context.JK.isNetworkError(arguments)) {
|
||||
testSummary.final = {reason: 'no_network'}
|
||||
}
|
||||
else {
|
||||
testSummary.final = {reason: 'rest_api_error'}
|
||||
}
|
||||
}
|
||||
testFinished();
|
||||
})
|
||||
logger.info("starting network test");
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateProgress(throughput, showSubscore) {
|
||||
var width = throughput * 100;
|
||||
|
||||
$currentScore.stop().data('showSubscore', showSubscore);
|
||||
|
||||
if(!showSubscore) {
|
||||
$subscore.text('');
|
||||
}
|
||||
|
||||
$currentScore.animate({
|
||||
duration: 1000,
|
||||
width: width + '%'
|
||||
}, {
|
||||
step: function (now, fx) {
|
||||
if(showSubscore) {
|
||||
var newWidth = ( 100 * parseFloat($currentScore.css('width')) / parseFloat($currentScore.parent().css('width')) );
|
||||
$subscore.text((Math.round(newWidth * 10) / 10) + '%');
|
||||
}
|
||||
}
|
||||
}).css('overflow', 'visible');
|
||||
;
|
||||
}
|
||||
|
||||
function networkTestSuccess(data) {
|
||||
clearBackendGuard();
|
||||
|
||||
var attempt = getCurrentAttempt();
|
||||
|
||||
function refineTest(up) {
|
||||
if(up) {
|
||||
if(numClientsToTest == gon.ftue_network_test_max_clients) {
|
||||
attempt.reason = "success";
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
numClientsToTest++;
|
||||
logger.debug("increasing number of clients to " + numClientsToTest);
|
||||
setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm
|
||||
}
|
||||
}
|
||||
else {
|
||||
// reduce numclients if we can
|
||||
if(numClientsToTest == MINIMUM_ACCEPTABLE_SESSION_SIZE) {
|
||||
// we are too low already. fail the user
|
||||
attempt.reason = "minimum_client_threshold";
|
||||
testFinished();
|
||||
}
|
||||
else if(numClientsToTest > STARTING_NUM_CLIENTS) {
|
||||
// this means we've gone up before... so don't go back down (i.e., creating a loop)
|
||||
attempt.reason = "success";
|
||||
testSummary.final = { reason:'success', num_clients: numClientsToTest - 1 }
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
numClientsToTest--;
|
||||
logger.debug("reducing number of clients to " + numClientsToTest);
|
||||
setTimeout(attemptTestPass, 500); // wait a second to avoid race conditions with client/server comm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attempt.backend_data = data;
|
||||
|
||||
if(data.progress === true) {
|
||||
|
||||
var animate = true;
|
||||
if(data.downthroughput && data.upthroughput) {
|
||||
|
||||
if(data.downthroughput > 0 || data.upthroughput > 0) {
|
||||
attempt.received_progress = true;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(attempt.received_progress) {
|
||||
// take the lower
|
||||
var throughput= data.downthroughput < data.upthroughput ? data.downthroughput : data.upthroughput;
|
||||
|
||||
updateProgress(throughput, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
logger.debug("network test pass success. data: ", data);
|
||||
|
||||
if(data.reason == "unreachable") {
|
||||
// STUN
|
||||
logger.debug("network test: unreachable (STUN issue or similar)");
|
||||
attempt.reason = data.reason;
|
||||
testFinished();
|
||||
}
|
||||
else if(data.reason == "internal_error") {
|
||||
// oops
|
||||
logger.debug("network test: internal_error (client had a unexpected problem)");
|
||||
attempt.reason = data.reason;
|
||||
testFinished();
|
||||
}
|
||||
else if(data.reason == "remote_peer_cant_test") {
|
||||
// old client
|
||||
logger.debug("network test: remote_peer_cant_test (old client)")
|
||||
attempt.reason = data.reason;
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
if(!data.downthroughput || !data.upthroughput) {
|
||||
// we have to assume this is bad. just not a reason we know about in code
|
||||
logger.debug("network test: no test data (unknown issue? " + data.reason + ")")
|
||||
attempt.reason = "invalid_response";
|
||||
testFinished();
|
||||
}
|
||||
else {
|
||||
// success... but we still have to verify if this data is within threshold
|
||||
if(data.downthroughput < gon.ftue_packet_rate_treshold) {
|
||||
logger.debug("network test: downthroughput too low. downthroughput: " + data.downthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
|
||||
refineTest(false);
|
||||
}
|
||||
else if(data.upthroughput < gon.ftue_packet_rate_treshold) {
|
||||
logger.debug("network test: upthroughput too low. upthroughput: " + data.upthroughput + ", threshold: " + gon.ftue_packet_rate_treshold);
|
||||
refineTest(false);
|
||||
}
|
||||
else {
|
||||
// true success. we can accept this score
|
||||
logger.debug("network test: success")
|
||||
refineTest(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VRFS-1742
|
||||
// context.jamClient.StopNetworkTest(serverClientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function networkTestTimeout(data) {
|
||||
clearBackendGuard();
|
||||
|
||||
logger.warn("network timeout when testing latency test: " + data);
|
||||
|
||||
var attempt = getCurrentAttempt();
|
||||
attempt.reason = 'timeout';
|
||||
attempt.backend_data = data;
|
||||
testFinished();
|
||||
}
|
||||
|
||||
function hasScoredNetworkSuccessfully() {
|
||||
return testedSuccessfully;
|
||||
}
|
||||
|
||||
function configureStartButton() {
|
||||
if(isScoring) {
|
||||
$startNetworkTestBtn.text('NETWORK TEST RUNNING...').removeClass('button-orange').addClass('button-grey')
|
||||
}
|
||||
else {
|
||||
$startNetworkTestBtn.text('START NETWORK TEST').removeClass('button-grey').addClass('button-orange');
|
||||
}
|
||||
|
||||
}
|
||||
function updateControlsState() {
|
||||
function updateButtons() {
|
||||
initializeNextButtonState();
|
||||
initializeBackButtonState();
|
||||
configureStartButton();
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
$dialog.setNextState(hasScoredNetworkSuccessfully() && !isScoring);
|
||||
dialog.setNextState(networkTest.hasScoredNetworkSuccessfully() && !networkTest.isScoring());
|
||||
}
|
||||
|
||||
function initializeBackButtonState() {
|
||||
$dialog.setBackState(!isScoring);
|
||||
dialog.setBackState(!networkTest.isScoring());
|
||||
}
|
||||
|
||||
function newSession() {
|
||||
reset();
|
||||
//context.jamClient.StopNetworkTest('');
|
||||
networkTest.reset();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
reset();
|
||||
networkTest.cancel();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function beforeHide() {
|
||||
clearBackendGuard();
|
||||
networkTest.cancel();
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
|
||||
$startNetworkTestBtn = $step.find('.start-network-test');
|
||||
$testResults = $step.find('.network-test-results');
|
||||
$testScore = $step.find('.network-test-score');
|
||||
$testText = $step.find('.network-test-text');
|
||||
$scoringBar = $step.find('.scoring-bar');
|
||||
$goodMarker = $step.find('.good-marker');
|
||||
$goodLine =$step.find('.good-line');
|
||||
$currentScore = $step.find('.current-score');
|
||||
$scoredClients= $step.find('.scored-clients');
|
||||
$subscore = $step.find('.subscore');
|
||||
$startNetworkTestBtn.on('click', startNetworkTest);
|
||||
networkTest.initialize($step, true);
|
||||
$(networkTest)
|
||||
.on(networkTest.NETWORK_TEST_DONE, networkTestDone)
|
||||
.on(networkTest.NETWORK_TEST_FAIL, networkTestFail)
|
||||
.on(networkTest.NETWORK_TEST_START, networkTestStart)
|
||||
}
|
||||
|
||||
this.newSession = newSession;
|
||||
|
|
@ -458,9 +63,6 @@
|
|||
this.beforeShow = beforeShow;
|
||||
this.initialize = initialize;
|
||||
|
||||
context.JK.HandleNetworkTestSuccess = networkTestSuccess; // pin to global for bridge callback
|
||||
context.JK.HandleNetworkTestTimeout = networkTestTimeout; // pin to global for bridge callback
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -3,40 +3,30 @@
|
|||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepSelectGear = function (app, $dialog) {
|
||||
context.JK.StepSelectGear = function (app, dialog) {
|
||||
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
|
||||
var self = null;
|
||||
var $step = null;
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var frameBuffers = new context.JK.FrameBuffers(app);
|
||||
var gearTest = new context.JK.GearTest(app);
|
||||
var loopbackShowing = false;
|
||||
|
||||
var $watchVideoInput = null;
|
||||
var $watchVideoOutput = null;
|
||||
var $audioInput = null;
|
||||
var $audioOutput = null;
|
||||
var $bufferIn = null;
|
||||
var $bufferOut = null;
|
||||
var $frameSize = null;
|
||||
var $inputChannels = null;
|
||||
var $outputChannels = null;
|
||||
var $knobs = null;
|
||||
var $scoreReport = null;
|
||||
var $latencyScoreSection = null;
|
||||
var $latencyScore = null;
|
||||
var $latencyHeader = null;
|
||||
var $ioHeader = null;
|
||||
var $ioScoreSection = null;
|
||||
var $ioRate = null;
|
||||
var $ioRateScore = null;
|
||||
var $ioVar = null;
|
||||
var $ioVarScore = null;
|
||||
var $ioCountdown = null;
|
||||
var $ioCountdownSecs = null;
|
||||
var $resultsText = null;
|
||||
var $unknownText = null;
|
||||
var $asioInputControlBtn = null;
|
||||
var $asioOutputControlBtn = null;
|
||||
var $resyncBtn = null;
|
||||
|
|
@ -46,7 +36,6 @@
|
|||
|
||||
var operatingSystem = null;
|
||||
var iCheckIgnore = false;
|
||||
var scoring = false; // are we currently scoring
|
||||
var validDevice = false; // do we currently have a device selected that we can score against?
|
||||
|
||||
// cached values between
|
||||
|
|
@ -57,20 +46,8 @@
|
|||
|
||||
var selectedDeviceInfo = null;
|
||||
var musicPorts = null;
|
||||
var validLatencyScore = false;
|
||||
var validIOScore = false;
|
||||
var lastLatencyScore = null;
|
||||
var ioScore = null;
|
||||
var latencyScore = null;
|
||||
|
||||
var savedProfile = false;
|
||||
|
||||
|
||||
|
||||
function isGoodFtue() {
|
||||
return validLatencyScore && validIOScore;
|
||||
}
|
||||
|
||||
// returns a deviceInfo hash for the device matching the deviceId, or undefined.
|
||||
function findDevice(deviceId) {
|
||||
return deviceInformation[deviceId];
|
||||
|
|
@ -84,56 +61,35 @@
|
|||
return $audioOutput.val();
|
||||
}
|
||||
|
||||
function selectedFramesize() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferIn() {
|
||||
return parseFloat($bufferIn.val());
|
||||
}
|
||||
|
||||
function selectedBufferOut() {
|
||||
return parseFloat($bufferOut.val());
|
||||
}
|
||||
|
||||
function setFramesize(value) {
|
||||
context.JK.dropdown($frameSize.val(value).easyDropDown('select', value.toString(), true))
|
||||
}
|
||||
|
||||
function setBufferIn(value) {
|
||||
context.JK.dropdown($bufferIn.val(value).easyDropDown('select', value.toString(), true));
|
||||
}
|
||||
|
||||
function setBufferOut(value) {
|
||||
context.JK.dropdown($bufferOut.val(value).easyDropDown('select', value.toString(), true))
|
||||
}
|
||||
|
||||
function setInputAudioDevice(value) {
|
||||
context.JK.dropdown($audioInput.val(value).easyDropDown('select', value.toString(), true))
|
||||
}
|
||||
|
||||
function setOutputAudioDevice(value) {
|
||||
if(value != "" && value == selectedAudioInput()) {
|
||||
value = ''; // to force Same as Input
|
||||
}
|
||||
context.JK.dropdown($audioOutput.val(value).easyDropDown('select', value.toString(), true))
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
$dialog.setNextState(isGoodFtue());
|
||||
dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue());
|
||||
}
|
||||
|
||||
function initializeBackButtonState() {
|
||||
$dialog.setBackState(!scoring);
|
||||
dialog.setBackState(!gearTest.isScoring());
|
||||
}
|
||||
|
||||
function initializeAudioInput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Choose...</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
|
||||
if(deviceInfo.inputCount > 0) {
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
}
|
||||
});
|
||||
|
||||
console.log("INITIALIZE AUDIO INPUT: " + optionsHtml)
|
||||
$audioInput.html(optionsHtml);
|
||||
context.JK.dropdown($audioInput);
|
||||
$audioInput.easyDropDown('enable')
|
||||
|
|
@ -156,16 +112,6 @@
|
|||
initializeAudioOutputChanged();
|
||||
}
|
||||
|
||||
function initializeFramesize() {
|
||||
context.JK.dropdown($frameSize);
|
||||
}
|
||||
|
||||
function initializeBuffers() {
|
||||
context.JK.dropdown($bufferIn);
|
||||
context.JK.dropdown($bufferOut);
|
||||
}
|
||||
|
||||
|
||||
// reloads the backend's channel state for the currently selected audio devices,
|
||||
// and update's the UI accordingly
|
||||
function initializeChannels() {
|
||||
|
|
@ -246,7 +192,7 @@
|
|||
function newInputAssignment() {
|
||||
var assigned = 0;
|
||||
context._.each(musicPorts.inputs, function (inputChannel) {
|
||||
if (isChannelAssigned(inputChannel)) {
|
||||
if (gearUtils.isChannelAssigned(inputChannel)) {
|
||||
assigned += 1;
|
||||
}
|
||||
});
|
||||
|
|
@ -324,18 +270,13 @@
|
|||
initializeChannels();
|
||||
}
|
||||
|
||||
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
|
||||
function isChannelAssigned(channel) {
|
||||
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
|
||||
}
|
||||
|
||||
function initializeInputPorts(musicPorts) {
|
||||
$inputChannels.empty();
|
||||
var inputPorts = musicPorts.inputs;
|
||||
context._.each(inputPorts, function (inputChannel) {
|
||||
var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' }));
|
||||
var $checkbox = $inputChannel.find('input');
|
||||
if (isChannelAssigned(inputChannel)) {
|
||||
if (gearUtils.isChannelAssigned(inputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
|
|
@ -350,7 +291,7 @@
|
|||
context._.each(outputChannels, function (outputChannel) {
|
||||
var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' }));
|
||||
var $checkbox = $outputPort.find('input');
|
||||
if (isChannelAssigned(outputChannel)) {
|
||||
if (gearUtils.isChannelAssigned(outputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
|
|
@ -361,8 +302,23 @@
|
|||
|
||||
function initializeLoopback() {
|
||||
$launchLoopbackBtn.unbind('click').click(function() {
|
||||
app.setWizardStep(1);
|
||||
app.layout.showDialog('ftue');
|
||||
|
||||
$(dialog.getLoopbackWizard().getDialog()).one(EVENTS.DIALOG_CLOSED, function() {
|
||||
loopbackShowing = false;
|
||||
|
||||
if(dialog.getLoopbackWizard().getGearTest().isGoodFtue()) {
|
||||
gearTest.resetScoreReport();
|
||||
gearTest.showLoopbackDone();
|
||||
context.JK.prodBubble(dialog.getWizard().getNextButton(), 'move-on-loopback-success', {}, {positions:['top']});
|
||||
|
||||
}
|
||||
|
||||
initializeNextButtonState();
|
||||
initializeBackButtonState();
|
||||
})
|
||||
|
||||
loopbackShowing = true;
|
||||
app.layout.showDialog('loopback-wizard')
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
|
@ -372,17 +328,9 @@
|
|||
|
||||
initializeAudioInput();
|
||||
initializeAudioOutput();
|
||||
initializeFramesize();
|
||||
initializeBuffers();
|
||||
initializeLoopback();
|
||||
}
|
||||
|
||||
function resetFrameBuffers() {
|
||||
$frameSize.val('2.5');
|
||||
$bufferIn.val('0');
|
||||
$bufferOut.val('0');
|
||||
}
|
||||
|
||||
function clearInputPorts() {
|
||||
$inputChannels.empty();
|
||||
}
|
||||
|
|
@ -391,92 +339,6 @@
|
|||
$outputChannels.empty();
|
||||
}
|
||||
|
||||
function resetScoreReport() {
|
||||
$ioHeader.hide();
|
||||
$latencyHeader.hide();
|
||||
$ioRate.hide();
|
||||
$ioRateScore.empty();
|
||||
$ioVar.hide();
|
||||
$ioVarScore.empty();
|
||||
$latencyScore.empty();
|
||||
$resultsText.removeAttr('latency-score');
|
||||
$resultsText.removeAttr('io-var-score');
|
||||
$resultsText.removeAttr('io-rate-score');
|
||||
$resultsText.removeAttr('scored');
|
||||
$unknownText.hide();
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip');
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting')
|
||||
}
|
||||
|
||||
function renderLatencyScore(latencyValue, latencyClass) {
|
||||
// latencyValue == null implies starting condition
|
||||
if (latencyValue) {
|
||||
$latencyScore.text(latencyValue + ' ms');
|
||||
}
|
||||
else {
|
||||
$latencyScore.text('');
|
||||
}
|
||||
|
||||
|
||||
if(latencyClass == 'unknown') {
|
||||
$latencyScore.text('Unknown');
|
||||
$unknownText.show();
|
||||
}
|
||||
|
||||
$latencyHeader.show();
|
||||
$resultsText.attr('latency-score', latencyClass);
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass);
|
||||
}
|
||||
|
||||
// std deviation is the worst value between in/out
|
||||
// media is the worst value between in/out
|
||||
// io is the value returned by the backend, which has more info
|
||||
// ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad'
|
||||
function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) {
|
||||
$ioRateScore.text(median !== null ? median : '');
|
||||
$ioVarScore.text(std !== null ? std : '');
|
||||
if (ioClass && ioClass != "starting" && ioClass != "skip") {
|
||||
$ioRate.show();
|
||||
$ioVar.show();
|
||||
}
|
||||
if(ioClass == 'starting' || ioClass == 'skip') {
|
||||
$ioHeader.show();
|
||||
}
|
||||
$resultsText.attr('io-rate-score', ioRateClass);
|
||||
$resultsText.attr('io-var-score', ioVarClass);
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip')
|
||||
if (ioClass) {
|
||||
$ioScoreSection.addClass(ioClass);
|
||||
}
|
||||
// TODO: show help bubble of all data in IO data
|
||||
}
|
||||
|
||||
function updateScoreReport(latencyResult) {
|
||||
var latencyClass = "neutral";
|
||||
var latencyValue = null;
|
||||
var validLatency = false;
|
||||
if (latencyResult && latencyResult.latencyknown) {
|
||||
var latencyValue = latencyResult.latency;
|
||||
latencyValue = Math.round(latencyValue * 100) / 100;
|
||||
if (latencyValue <= 10) {
|
||||
latencyClass = "good";
|
||||
validLatency = true;
|
||||
} else if (latencyValue <= gon.ftue_maximum_gear_latency) {
|
||||
latencyClass = "acceptable";
|
||||
validLatency = true;
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
}
|
||||
}
|
||||
else {
|
||||
latencyClass = 'unknown';
|
||||
}
|
||||
|
||||
validLatencyScore = validLatency;
|
||||
|
||||
renderLatencyScore(latencyValue, latencyClass);
|
||||
}
|
||||
|
||||
function audioInputDeviceUnselected() {
|
||||
validDevice = false;
|
||||
setOutputAudioDevice('');
|
||||
|
|
@ -484,28 +346,22 @@
|
|||
}
|
||||
|
||||
function renderScoringStarted() {
|
||||
resetScoreReport();
|
||||
initializeBackButtonState();
|
||||
initializeNextButtonState();
|
||||
freezeAudioInteraction();
|
||||
renderLatencyScore(null, 'starting');
|
||||
renderIOScore(null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
function renderScoringStopped() {
|
||||
initializeNextButtonState();
|
||||
unfreezeAudioInteraction();
|
||||
$resultsText.attr('scored', 'complete');
|
||||
scoring = false;
|
||||
initializeBackButtonState();
|
||||
unfreezeAudioInteraction();
|
||||
}
|
||||
|
||||
function freezeAudioInteraction() {
|
||||
logger.debug("freezing audio interaction");
|
||||
$audioInput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$audioOutput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$frameSize.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferIn.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferOut.attr("disabled", "disabled").easyDropDown('disable');
|
||||
frameBuffers.disable();
|
||||
$asioInputControlBtn.on("click", false);
|
||||
$asioOutputControlBtn.on("click", false);
|
||||
$resyncBtn.on('click', false);
|
||||
|
|
@ -518,9 +374,7 @@
|
|||
logger.debug("unfreezing audio interaction");
|
||||
$audioInput.removeAttr("disabled").easyDropDown('enable');
|
||||
$audioOutput.removeAttr("disabled").easyDropDown('enable');
|
||||
$frameSize.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferIn.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferOut.removeAttr("disabled").easyDropDown('enable');
|
||||
frameBuffers.enable();
|
||||
$asioInputControlBtn.off("click", false);
|
||||
$asioOutputControlBtn.off("click", false);
|
||||
$resyncBtn.off('click', false);
|
||||
|
|
@ -574,9 +428,8 @@
|
|||
}
|
||||
|
||||
function invalidateScore() {
|
||||
validLatencyScore = false;
|
||||
validIOScore = false;
|
||||
resetScoreReport();
|
||||
gearTest.invalidateScore();
|
||||
dialog.getLoopbackWizard().getGearTest().invalidateScore();
|
||||
initializeNextButtonState();
|
||||
}
|
||||
|
||||
|
|
@ -589,61 +442,23 @@
|
|||
});
|
||||
}
|
||||
|
||||
function initializeKnobs() {
|
||||
$frameSize.unbind('change').change(function () {
|
||||
logger.debug("frameize changed: " + selectedFramesize());
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
updateDefaultBuffers();
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
invalidateScore();
|
||||
});
|
||||
|
||||
$bufferIn.unbind('change').change(function () {
|
||||
logger.debug("buffer-in changed: " + selectedBufferIn());
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
invalidateScore();
|
||||
});
|
||||
|
||||
$bufferOut.unbind('change').change(function () {
|
||||
logger.debug("buffer-out changed: " + selectedBufferOut());
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
invalidateScore();
|
||||
});
|
||||
function onFramesizeChanged() {
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
updateDefaultBuffers();
|
||||
jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize());
|
||||
invalidateScore();
|
||||
}
|
||||
|
||||
function ftueSummary() {
|
||||
return {
|
||||
os: operatingSystem,
|
||||
version: context.jamClient.ClientUpdateVersion(),
|
||||
success: isGoodFtue(),
|
||||
score: {
|
||||
validLatencyScore: validLatencyScore,
|
||||
validIOScore: validIOScore,
|
||||
latencyScore: latencyScore,
|
||||
ioScore : ioScore,
|
||||
},
|
||||
audioParameters: {
|
||||
frameSize: selectedFramesize(),
|
||||
bufferIn: selectedBufferIn(),
|
||||
bufferOut: selectedBufferOut(),
|
||||
},
|
||||
devices: deviceInformation,
|
||||
selectedDevice: selectedDeviceInfo
|
||||
}
|
||||
|
||||
function onBufferInChanged() {
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
|
||||
invalidateScore();
|
||||
}
|
||||
|
||||
function postDiagnostic() {
|
||||
rest.createDiagnostic({
|
||||
type: 'GEAR_SELECTION',
|
||||
data: {
|
||||
logs: logger.logCache,
|
||||
client_type: context.JK.clientType(),
|
||||
client_id:
|
||||
context.JK.JamServer.clientID,
|
||||
summary:ftueSummary()}
|
||||
});
|
||||
|
||||
function onBufferOutChanged() {
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
|
||||
invalidateScore();
|
||||
}
|
||||
|
||||
function getSelectedInputs() {
|
||||
|
|
@ -672,19 +487,6 @@
|
|||
})
|
||||
}
|
||||
|
||||
function renderIOScoringStarted(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
$ioCountdown.show();
|
||||
}
|
||||
|
||||
function renderIOScoringStopped() {
|
||||
$ioCountdown.hide();
|
||||
}
|
||||
|
||||
function renderIOCountdown(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
}
|
||||
|
||||
// sets selectedDeviceInfo, which contains id, behavior, and info for input and output device
|
||||
function cacheCurrentAudioInfo() {
|
||||
|
||||
|
|
@ -717,7 +519,7 @@
|
|||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
logger.debug("invaliding previously saved profile: " + profileName);
|
||||
|
||||
$dialog.createFTUEProfile();
|
||||
dialog.createFTUEProfile();
|
||||
// restore user selections because newSession is called by createFTUEProfile(), invalidating dropdowns
|
||||
setInputAudioDevice(audioInputDeviceId);
|
||||
setOutputAudioDevice(audioOutputDeviceId);
|
||||
|
|
@ -731,7 +533,7 @@
|
|||
|
||||
lastSelectedDeviceInfo = selectedDeviceInfo;
|
||||
|
||||
selectedDeviceInfo = context.JK.selectedDeviceInfo(audioInputDeviceId, audioOutputDeviceId, deviceInformation)
|
||||
selectedDeviceInfo = gearUtils.selectedDeviceInfo(audioInputDeviceId, audioOutputDeviceId, deviceInformation)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -761,9 +563,21 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
|
||||
jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize());
|
||||
|
||||
// prod user to watch video if the previous type and new type changed
|
||||
if(!shownInputProdOnce && isInputAudioTypeDifferentFromLastTime()) {
|
||||
context.JK.prodBubble($watchVideoInput, 'ftue-watch-video', {}, {positions:['top', 'right']});
|
||||
shownInputProdOnce = true;
|
||||
}
|
||||
|
||||
// prod user to watch video if the previous type and new type changed
|
||||
if(!shownOutputProdOnce && isOutputAudioTypeDifferentFromLastTime()) {
|
||||
context.JK.prodBubble($watchVideoOutput, 'ftue-watch-video', {}, {positions:['top', 'right']});
|
||||
shownOutputProdOnce = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -798,7 +612,6 @@
|
|||
else {
|
||||
var inputBehavior = null;
|
||||
var outputBehavior = null;
|
||||
|
||||
}
|
||||
|
||||
// deal with watch video
|
||||
|
|
@ -820,7 +633,7 @@
|
|||
$knobs.hide();
|
||||
}
|
||||
|
||||
// handle ASIO
|
||||
// handle ASIO visibility
|
||||
if (inputBehavior) {
|
||||
if (inputBehavior.showASIO) {
|
||||
$asioInputControlBtn.show();
|
||||
|
|
@ -856,103 +669,45 @@
|
|||
|
||||
function updateDefaultFrameSize() {
|
||||
if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) {
|
||||
setFramesize('10');
|
||||
frameBuffers.setFramesize('10');
|
||||
}
|
||||
else {
|
||||
setFramesize('2.5')
|
||||
frameBuffers.setFramesize('2.5')
|
||||
}
|
||||
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize());
|
||||
}
|
||||
|
||||
function updateDefaultBuffers() {
|
||||
|
||||
// handle specific framesize settings
|
||||
if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) {
|
||||
var framesize = selectedFramesize();
|
||||
var framesize = frameBuffers.selectedFramesize();
|
||||
|
||||
if(framesize == 2.5) {
|
||||
logger.debug("setting default buffers to 1/1");
|
||||
setBufferIn('1');
|
||||
setBufferOut('1');
|
||||
frameBuffers.setBufferIn('1');
|
||||
frameBuffers.setBufferOut('1');
|
||||
}
|
||||
else if(framesize == 5) {
|
||||
logger.debug("setting default buffers to 3/2");
|
||||
setBufferIn('3');
|
||||
setBufferOut('2');
|
||||
frameBuffers.setBufferIn('3');
|
||||
frameBuffers.setBufferOut('2');
|
||||
}
|
||||
else {
|
||||
logger.debug("setting default buffers to 6/5");
|
||||
setBufferIn('6');
|
||||
setBufferOut('5');
|
||||
frameBuffers.setBufferIn('6');
|
||||
frameBuffers.setBufferOut('5');
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug("setting default buffers to 0/0");
|
||||
setBufferIn(0);
|
||||
setBufferOut(0);
|
||||
frameBuffers.setBufferIn(0);
|
||||
frameBuffers.setBufferOut(0);
|
||||
}
|
||||
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
}
|
||||
|
||||
function processIOScore(io) {
|
||||
// take the higher variance, which is apparently actually std dev
|
||||
var std = io.in_var > io.out_var ? io.in_var : io.out_var;
|
||||
std = Math.round(std * 100) / 100;
|
||||
// take the furthest-off-from-target io rate
|
||||
var median = Math.abs(io.in_median - io.in_target) > Math.abs(io.out_median - io.out_target) ? [io.in_median, io.in_target] : [io.out_median, io.out_target];
|
||||
var medianTarget = median[1];
|
||||
median = Math.round(median[0]);
|
||||
|
||||
var stdIOClass = 'bad';
|
||||
if (std <= 0.50) {
|
||||
stdIOClass = 'good';
|
||||
}
|
||||
else if (std <= 1.00) {
|
||||
stdIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
var medianIOClass = 'bad';
|
||||
if (Math.abs(median - medianTarget) <= 1) {
|
||||
medianIOClass = 'good';
|
||||
}
|
||||
else if (Math.abs(median - medianTarget) <= 2) {
|
||||
medianIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
// take worst between median or std
|
||||
var ioClassToNumber = {bad: 2, acceptable: 1, good: 0}
|
||||
var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass;
|
||||
|
||||
// now base the overall IO score based on both values.
|
||||
renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass);
|
||||
|
||||
if(aggregrateIOClass == "bad") {
|
||||
validIOScore = false;
|
||||
}
|
||||
else {
|
||||
validIOScore = true;
|
||||
}
|
||||
|
||||
if(isGoodFtue()) {
|
||||
onSuccessfulScore();
|
||||
}
|
||||
else {
|
||||
onFailedScore();
|
||||
}
|
||||
|
||||
renderScoringStopped();
|
||||
postDiagnostic();
|
||||
}
|
||||
|
||||
function onFailedScore() {
|
||||
rest.userCertifiedGear({success: false});
|
||||
}
|
||||
|
||||
function onSuccessfulScore() {
|
||||
rest.userCertifiedGear({success: true});
|
||||
jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
|
||||
}
|
||||
|
||||
// refocused affects how IO testing occurs.
|
||||
|
|
@ -960,85 +715,7 @@
|
|||
// * reuse IO score if it was good/acceptable
|
||||
// * rescore IO if it was bad or skipped from previous try
|
||||
function attemptScore(refocused) {
|
||||
if(scoring) {return;}
|
||||
scoring = true;
|
||||
initializeBackButtonState();
|
||||
validLatencyScore = false;
|
||||
latencyScore = null;
|
||||
if(!refocused) {
|
||||
// don't reset a valid IO score on refocus
|
||||
validIOScore = false;
|
||||
ioScore = null;
|
||||
}
|
||||
|
||||
renderScoringStarted();
|
||||
|
||||
// this timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in
|
||||
setTimeout(function () {
|
||||
logger.debug("Calling FTUESave(false)");
|
||||
jamClient.FTUESave(false);
|
||||
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
latencyScore = latency;
|
||||
|
||||
// prod user to watch video if the previous type and new type changed
|
||||
if(!shownInputProdOnce && isInputAudioTypeDifferentFromLastTime()) {
|
||||
context.JK.prodBubble($watchVideoInput, 'ftue-watch-video', {}, {positions:['top', 'right']});
|
||||
shownInputProdOnce = true;
|
||||
}
|
||||
|
||||
// prod user to watch video if the previous type and new type changed
|
||||
if(!shownOutputProdOnce && isOutputAudioTypeDifferentFromLastTime()) {
|
||||
context.JK.prodBubble($watchVideoOutput, 'ftue-watch-video', {}, {positions:['top', 'right']});
|
||||
shownOutputProdOnce = true;
|
||||
}
|
||||
|
||||
updateScoreReport(latency);
|
||||
|
||||
if(refocused) {
|
||||
context.JK.prodBubble($scoreReport, 'refocus-rescore', {validIOScore: validIOScore}, {positions:['top', 'left']});
|
||||
}
|
||||
|
||||
// if there was a valid latency score, go on to the next step
|
||||
if (validLatencyScore) {
|
||||
// reuse valid IO score if this is on refocus
|
||||
if(refocused && validIOScore) {
|
||||
renderIOScore(null, null, null, 'starting', 'starting', 'starting');
|
||||
processIOScore(ioScore);
|
||||
}
|
||||
else {
|
||||
renderIOScore(null, null, null, 'starting', 'starting', 'starting');
|
||||
var testTimeSeconds = gon.ftue_io_wait_time; // allow time for IO to establish itself
|
||||
var startTime = testTimeSeconds / 2; // start measuring half way through the test, to get past IO oddities
|
||||
renderIOScoringStarted(testTimeSeconds);
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
var interval = setInterval(function () {
|
||||
testTimeSeconds -= 1;
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
|
||||
if(testTimeSeconds == startTime) {
|
||||
logger.debug("Starting IO Perf Test starting at " + startTime + "s in")
|
||||
context.jamClient.FTUEStartIoPerfTest();
|
||||
}
|
||||
|
||||
if (testTimeSeconds == 0) {
|
||||
clearInterval(interval);
|
||||
renderIOScoringStopped();
|
||||
logger.debug("Ending IO Perf Test at " + testTimeSeconds + "s in")
|
||||
var io = context.jamClient.FTUEGetIoPerfData();
|
||||
ioScore = io;
|
||||
processIOScore(io);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
else {
|
||||
onFailedScore();
|
||||
renderIOScore(null, null, null, 'skip', 'skip', 'skip');
|
||||
renderScoringStopped();
|
||||
postDiagnostic();
|
||||
}
|
||||
}, 250);
|
||||
gearTest.attemptScore(refocused);
|
||||
}
|
||||
|
||||
function initializeAudioInputChanged() {
|
||||
|
|
@ -1049,6 +726,20 @@
|
|||
$audioOutput.unbind('change').change(audioDeviceChanged);
|
||||
}
|
||||
|
||||
function onGearTestStarted(e, data) {
|
||||
renderScoringStarted();
|
||||
}
|
||||
|
||||
function onGearTestDone(e, data) {
|
||||
renderScoringStopped();
|
||||
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
|
||||
}
|
||||
|
||||
function onGearTestFail(e, data) {
|
||||
renderScoringStopped();
|
||||
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
|
||||
if(!savedProfile) {
|
||||
|
|
@ -1075,37 +766,43 @@
|
|||
return false;
|
||||
}
|
||||
else {
|
||||
context.jamClient.FTUESetMusicProfileName(context.JK.createProfileName(selectedDeviceInfo));
|
||||
context.jamClient.FTUESave(true);
|
||||
savedProfile = true;
|
||||
context.JK.GA.trackAudioTestCompletion(context.JK.detectOS());
|
||||
return true;
|
||||
context.jamClient.FTUESetMusicProfileName(gearUtils.createProfileName(selectedDeviceInfo));
|
||||
var result = context.jamClient.FTUESave(true);
|
||||
if(result && result != "") {
|
||||
// failed
|
||||
logger.warn("unable to FTUESave(true). reason:" + result);
|
||||
context.JK.Banner.alertSupportedNeeded("Unable to persist the audio profile. " + result);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
savedProfile = true;
|
||||
context.JK.GA.trackAudioTestCompletion(context.JK.detectOS());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
if(!scoring && validDevice && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) {
|
||||
// in the case the user has been unselecting ports, re-enforce minimum viable channels
|
||||
validDevice = autoSelectMinimumValidChannels();
|
||||
if(validDevice && !loopbackShowing && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) {
|
||||
attemptScore(true);
|
||||
}
|
||||
}
|
||||
|
||||
function newSession() {
|
||||
savedProfile = false;
|
||||
deviceInformation = context.JK.loadDeviceInfo();
|
||||
deviceInformation = gearUtils.loadDeviceInfo();
|
||||
resetState();
|
||||
initializeFormElements();
|
||||
initializeNextButtonState();
|
||||
initializeWatchVideo();
|
||||
initializeASIOButtons();
|
||||
initializeKnobs();
|
||||
initializeResync();
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
$(window).on('focus', onFocus);
|
||||
initializeNextButtonState();
|
||||
}
|
||||
|
||||
function beforeHide() {
|
||||
|
|
@ -1118,7 +815,7 @@
|
|||
invalidateScore();
|
||||
clearInputPorts();
|
||||
clearOutputPorts();
|
||||
resetFrameBuffers();
|
||||
frameBuffers.resetValues();
|
||||
updateDialogForCurrentDevices();
|
||||
}
|
||||
|
||||
|
|
@ -1129,26 +826,11 @@
|
|||
$watchVideoOutput = $step.find('.watch-video.audio-output');
|
||||
$audioInput = $step.find('.select-audio-input-device');
|
||||
$audioOutput = $step.find('.select-audio-output-device');
|
||||
$bufferIn = $step.find('.select-buffer-in');
|
||||
$bufferOut = $step.find('.select-buffer-out');
|
||||
$frameSize = $step.find('.select-frame-size');
|
||||
|
||||
$inputChannels = $step.find('.input-ports');
|
||||
$outputChannels = $step.find('.output-ports');
|
||||
$knobs = $step.find('.frame-and-buffers');
|
||||
$scoreReport = $step.find('.results');
|
||||
$latencyScoreSection = $scoreReport.find('.latency-score-section');
|
||||
$latencyScore = $scoreReport.find('.latency-score');
|
||||
$latencyHeader = $scoreReport.find('.latency');
|
||||
$ioHeader = $scoreReport.find('.io');
|
||||
$ioScoreSection = $scoreReport.find('.io-score-section');
|
||||
$ioRate = $scoreReport.find('.io-rate');
|
||||
$ioRateScore = $scoreReport.find('.io-rate-score');
|
||||
$ioVar = $scoreReport.find('.io-var');
|
||||
$ioVarScore = $scoreReport.find('.io-var-score');
|
||||
$ioCountdown = $scoreReport.find('.io-countdown');
|
||||
$ioCountdownSecs = $scoreReport.find('.io-countdown .secs');
|
||||
$resultsText = $scoreReport.find('.results-text');
|
||||
$unknownText = $scoreReport.find('.unknown-text');
|
||||
$asioInputControlBtn = $step.find('.asio-settings-input-btn');
|
||||
$asioOutputControlBtn = $step.find('.asio-settings-output-btn');
|
||||
$resyncBtn = $step.find('.resync-btn');
|
||||
|
|
@ -1156,6 +838,17 @@
|
|||
$launchLoopbackBtn = $('.loopback-test');
|
||||
$instructions = $('.instructions');
|
||||
operatingSystem = context.JK.GetOSAsString();
|
||||
frameBuffers.initialize($knobs);
|
||||
$(frameBuffers)
|
||||
.on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged)
|
||||
.on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged)
|
||||
.on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged)
|
||||
|
||||
gearTest.initialize($scoreReport, true)
|
||||
$(gearTest)
|
||||
.on(gearTest.GEAR_TEST_START, onGearTestStarted)
|
||||
.on(gearTest.GEAR_TEST_DONE, onGearTestDone)
|
||||
.on(gearTest.GEAR_TEST_FAIL, onGearTestFail)
|
||||
}
|
||||
|
||||
this.handleNext = handleNext;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
function beforeShow() {
|
||||
var $watchVideo = $step.find('.watch-video');
|
||||
var videoUrl = 'https://www.youtube.com/watch?v=VexH4834o9I';
|
||||
var videoUrl = 'https://www.youtube.com/watch?v=NiELWY769Tw';
|
||||
if (operatingSystem == "Win32") {
|
||||
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=VexH4834o9I');
|
||||
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=Z1GxCljtdCY');
|
||||
}
|
||||
$watchVideo.attr('href', videoUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,465 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.GearTest = function (app) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var isAutomated = false;
|
||||
var drawUI = false;
|
||||
var scoring = false;
|
||||
var validLatencyScore = false;
|
||||
var validIOScore = false;
|
||||
var latencyScore = null;
|
||||
var ioScore = null;
|
||||
|
||||
var $scoreReport = null;
|
||||
var $ioHeader = null;
|
||||
var $latencyHeader = null;
|
||||
var $ioRate = null;
|
||||
var $ioRateScore = null;
|
||||
var $ioVar = null;
|
||||
var $ioVarScore = null;
|
||||
var $ioCountdown = null;
|
||||
var $ioCountdownSecs = null;
|
||||
var $latencyScore = null;
|
||||
var $resultsText = null;
|
||||
var $unknownText = null;
|
||||
var $loopbackCompleted = null;
|
||||
var $ioScoreSection = null;
|
||||
var $latencyScoreSection = null;
|
||||
|
||||
var $self = $(this);
|
||||
|
||||
var GEAR_TEST_START = "gear_test.start";
|
||||
var GEAR_TEST_IO_START = "gear_test.io_start";
|
||||
var GEAR_TEST_IO_DONE = "gear_test.io_done";
|
||||
var GEAR_TEST_LATENCY_START = "gear_test.latency_start";
|
||||
var GEAR_TEST_LATENCY_DONE = "gear_test.latency_done";
|
||||
var GEAR_TEST_DONE = "gear_test.done";
|
||||
var GEAR_TEST_FAIL = "gear_test.fail";
|
||||
var GEAR_TEST_IO_PROGRESS = "gear_test.io_progress";
|
||||
|
||||
function isGoodFtue() {
|
||||
return validLatencyScore && validIOScore;
|
||||
}
|
||||
|
||||
function processIOScore(io) {
|
||||
// take the higher variance, which is apparently actually std dev
|
||||
var std = io.in_var > io.out_var ? io.in_var : io.out_var;
|
||||
std = Math.round(std * 100) / 100;
|
||||
// take the furthest-off-from-target io rate
|
||||
var median = Math.abs(io.in_median - io.in_target) > Math.abs(io.out_median - io.out_target) ? [io.in_median, io.in_target] : [io.out_median, io.out_target];
|
||||
var medianTarget = median[1];
|
||||
median = Math.round(median[0]);
|
||||
|
||||
var stdIOClass = 'bad';
|
||||
if (std <= 0.50) {
|
||||
stdIOClass = 'good';
|
||||
}
|
||||
else if (std <= 1.00) {
|
||||
stdIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
var medianIOClass = 'bad';
|
||||
if (Math.abs(median - medianTarget) <= 1) {
|
||||
medianIOClass = 'good';
|
||||
}
|
||||
else if (Math.abs(median - medianTarget) <= 2) {
|
||||
medianIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
// take worst between median or std
|
||||
var ioClassToNumber = {bad: 2, acceptable: 1, good: 0}
|
||||
var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass;
|
||||
|
||||
// now base the overall IO score based on both values.
|
||||
|
||||
$self.triggerHandler(GEAR_TEST_IO_DONE, {std:std, median:median, io:io, aggregrateIOClass: aggregrateIOClass, medianIOClass : medianIOClass, stdIOClass: stdIOClass})
|
||||
//renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass);
|
||||
|
||||
if(aggregrateIOClass == "bad") {
|
||||
validIOScore = false;
|
||||
}
|
||||
else {
|
||||
validIOScore = true;
|
||||
}
|
||||
|
||||
scoring = false;
|
||||
|
||||
if(isGoodFtue()) {
|
||||
$self.triggerHandler(GEAR_TEST_DONE)
|
||||
}
|
||||
else {
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io'});
|
||||
}
|
||||
}
|
||||
|
||||
function automaticScore() {
|
||||
logger.debug("automaticScore: calling FTUESave(false)");
|
||||
var result = jamClient.FTUESave(false);
|
||||
if(result && result != "") {
|
||||
logger.debug("unable to FTUESave(false). reason=" + result);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
context.JK.GearTest.testDeferred.resolve(latency);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function loopbackScore() {
|
||||
var cbFunc = 'JK.loopbackLatencyCallback';
|
||||
logger.debug("Registering loopback latency callback: " + cbFunc);
|
||||
context.jamClient.FTUERegisterLatencyCallback('JK.loopbackLatencyCallback');
|
||||
var now = new Date();
|
||||
logger.debug("Starting Latency Test..." + now);
|
||||
context.jamClient.FTUEStartLatency();
|
||||
}
|
||||
|
||||
// refocused affects how IO testing occurs.
|
||||
// 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) {
|
||||
if(scoring) {
|
||||
logger.debug("gear-test: already scoring");
|
||||
return;
|
||||
}
|
||||
scoring = true;
|
||||
$self.triggerHandler(GEAR_TEST_START);
|
||||
$self.triggerHandler(GEAR_TEST_LATENCY_START);
|
||||
validLatencyScore = false;
|
||||
latencyScore = null;
|
||||
if(!refocused) {
|
||||
// don't reset a valid IO score on refocus
|
||||
validIOScore = false;
|
||||
ioScore = null;
|
||||
}
|
||||
|
||||
// this timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in
|
||||
setTimeout(function () {
|
||||
|
||||
context.JK.GearTest.testDeferred = new $.Deferred();
|
||||
if(isAutomated) {
|
||||
// use automated latency score mechanism
|
||||
automaticScore()
|
||||
}
|
||||
else {
|
||||
// use loopback score mechanism
|
||||
loopbackScore();
|
||||
}
|
||||
|
||||
context.JK.GearTest.testDeferred.done(function(latency) {
|
||||
latencyScore = latency;
|
||||
|
||||
if(isAutomated) {
|
||||
// uncomment to do a manual loopback test
|
||||
//latency.latencyknown = false;
|
||||
}
|
||||
|
||||
updateScoreReport(latency, refocused);
|
||||
|
||||
// if there was a valid latency score, go on to the next step
|
||||
if (validLatencyScore) {
|
||||
$self.triggerHandler(GEAR_TEST_IO_START);
|
||||
// reuse valid IO score if this is on refocus
|
||||
if(refocused && validIOScore) {
|
||||
processIOScore(ioScore);
|
||||
}
|
||||
else {
|
||||
var testTimeSeconds = gon.ftue_io_wait_time; // allow time for IO to establish itself
|
||||
var startTime = testTimeSeconds / 2; // start measuring half way through the test, to get past IO oddities
|
||||
$self.trigger(GEAR_TEST_IO_PROGRESS, {countdown:testTimeSeconds, first:true})
|
||||
|
||||
var interval = setInterval(function () {
|
||||
testTimeSeconds -= 1;
|
||||
$self.trigger(GEAR_TEST_IO_PROGRESS, {countdown:testTimeSeconds, first:false})
|
||||
|
||||
if(testTimeSeconds == startTime) {
|
||||
logger.debug("Starting IO Perf Test starting at " + startTime + "s in")
|
||||
context.jamClient.FTUEStartIoPerfTest();
|
||||
}
|
||||
|
||||
if (testTimeSeconds == 0) {
|
||||
clearInterval(interval);
|
||||
logger.debug("Ending IO Perf Test at " + testTimeSeconds + "s in")
|
||||
var io = context.jamClient.FTUEGetIoPerfData();
|
||||
ioScore = io;
|
||||
processIOScore(io);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
else {
|
||||
scoring = false;
|
||||
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency'})
|
||||
}
|
||||
})
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function updateScoreReport(latencyResult, refocused) {
|
||||
var latencyClass = "neutral";
|
||||
var latencyValue = null;
|
||||
var validLatency = false;
|
||||
if (latencyResult && latencyResult.latencyknown) {
|
||||
var latencyValue = latencyResult.latency;
|
||||
latencyValue = Math.round(latencyValue * 100) / 100;
|
||||
if (latencyValue <= 10) {
|
||||
latencyClass = "good";
|
||||
validLatency = true;
|
||||
} else if (latencyValue <= gon.ftue_maximum_gear_latency) {
|
||||
latencyClass = "acceptable";
|
||||
validLatency = true;
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
}
|
||||
}
|
||||
else {
|
||||
latencyClass = 'unknown';
|
||||
}
|
||||
|
||||
validLatencyScore = validLatency;
|
||||
|
||||
if(refocused) {
|
||||
context.JK.prodBubble($scoreReport, 'refocus-rescore', {validIOScore: validIOScore}, {positions:['top', 'left']});
|
||||
}
|
||||
|
||||
$self.triggerHandler(GEAR_TEST_LATENCY_DONE, {latencyValue: latencyValue, latencyClass: latencyClass, refocused: refocused});
|
||||
}
|
||||
|
||||
// std deviation is the worst value between in/out
|
||||
// media is the worst value between in/out
|
||||
// io is the value returned by the backend, which has more info
|
||||
// ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad'
|
||||
function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) {
|
||||
$ioRateScore.text(median !== null ? median : '');
|
||||
$ioVarScore.text(std !== null ? std : '');
|
||||
if (ioClass && ioClass != "starting" && ioClass != "skip") {
|
||||
$ioRate.show();
|
||||
$ioVar.show();
|
||||
}
|
||||
if(ioClass == 'starting' || ioClass == 'skip') {
|
||||
$ioHeader.show();
|
||||
}
|
||||
$resultsText.attr('io-rate-score', ioRateClass);
|
||||
$resultsText.attr('io-var-score', ioVarClass);
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip')
|
||||
if (ioClass) {
|
||||
$ioScoreSection.addClass(ioClass);
|
||||
}
|
||||
// TODO: show help bubble of all data in IO data
|
||||
}
|
||||
|
||||
function isScoring() {
|
||||
return scoring;
|
||||
}
|
||||
|
||||
function isValidLatencyScore() {
|
||||
return validLatencyScore;
|
||||
}
|
||||
|
||||
function isValidIOScore() {
|
||||
return validIOScore;
|
||||
}
|
||||
|
||||
function getLatencyScore() {
|
||||
return latencyScore;
|
||||
}
|
||||
|
||||
function getIOScore() {
|
||||
return ioScore;
|
||||
}
|
||||
|
||||
function showLoopbackDone() {
|
||||
$loopbackCompleted.show();
|
||||
}
|
||||
|
||||
function resetScoreReport() {
|
||||
$ioHeader.hide();
|
||||
$latencyHeader.hide();
|
||||
$ioRate.hide();
|
||||
$ioRateScore.empty();
|
||||
$ioVar.hide();
|
||||
$ioVarScore.empty();
|
||||
$latencyScore.empty();
|
||||
$resultsText.removeAttr('latency-score');
|
||||
$resultsText.removeAttr('io-var-score');
|
||||
$resultsText.removeAttr('io-rate-score');
|
||||
$resultsText.removeAttr('scored');
|
||||
$unknownText.hide();
|
||||
$loopbackCompleted.hide();
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip');
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting')
|
||||
}
|
||||
|
||||
function invalidateScore() {
|
||||
validLatencyScore = false;
|
||||
validIOScore = false;
|
||||
resetScoreReport();
|
||||
}
|
||||
|
||||
function renderLatencyScore(latencyValue, latencyClass) {
|
||||
// latencyValue == null implies starting condition
|
||||
if (latencyValue) {
|
||||
$latencyScore.text(latencyValue + ' ms');
|
||||
}
|
||||
else {
|
||||
$latencyScore.text('');
|
||||
}
|
||||
|
||||
|
||||
if(latencyClass == 'unknown') {
|
||||
$latencyScore.text('Unknown');
|
||||
$unknownText.show();
|
||||
}
|
||||
|
||||
$latencyHeader.show();
|
||||
$resultsText.attr('latency-score', latencyClass);
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass);
|
||||
}
|
||||
|
||||
function renderIOScoringStarted(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
$ioCountdown.show();
|
||||
}
|
||||
|
||||
function renderIOScoringStopped() {
|
||||
$ioCountdown.hide();
|
||||
}
|
||||
|
||||
function renderIOCountdown(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
}
|
||||
|
||||
function handleUI($testResults) {
|
||||
|
||||
if(!$testResults.is('.ftue-box.results')) {
|
||||
throw "GearTest != .ftue-box.results"
|
||||
}
|
||||
|
||||
$scoreReport = $testResults;
|
||||
$ioHeader = $scoreReport.find('.io');;
|
||||
$latencyHeader = $scoreReport.find('.latency');
|
||||
$ioRate = $scoreReport.find('.io-rate');
|
||||
$ioRateScore = $scoreReport.find('.io-rate-score');
|
||||
$ioVar = $scoreReport.find('.io-var');
|
||||
$ioVarScore = $scoreReport.find('.io-var-score');
|
||||
$ioScoreSection = $scoreReport.find('.io-score-section');
|
||||
$latencyScore = $scoreReport.find('.latency-score');
|
||||
$ioCountdown = $scoreReport.find('.io-countdown');
|
||||
$ioCountdownSecs = $scoreReport.find('.io-countdown .secs');
|
||||
$resultsText = $scoreReport.find('.results-text');
|
||||
$unknownText = $scoreReport.find('.unknown-text');
|
||||
$loopbackCompleted = $scoreReport.find('.loopback-completed')
|
||||
$latencyScoreSection = $scoreReport.find('.latency-score-section');
|
||||
|
||||
function onGearTestStart(e, data) {
|
||||
renderIOScore(null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
function onGearTestIOStart(e, data) {
|
||||
renderIOScore(null, null, null, 'starting', 'starting', 'starting');
|
||||
}
|
||||
|
||||
function onGearTestLatencyStart(e, data) {
|
||||
resetScoreReport();
|
||||
renderLatencyScore(null, 'starting');
|
||||
}
|
||||
|
||||
function onGearTestLatencyDone(e, data) {
|
||||
renderLatencyScore(data.latencyValue, data.latencyClass);
|
||||
}
|
||||
|
||||
function onGearTestIOProgress(e, data) {
|
||||
if(data.first) {
|
||||
renderIOScoringStarted(data.countdown);
|
||||
}
|
||||
|
||||
renderIOCountdown(data.countdown);
|
||||
|
||||
if(data.countdown == 0) {
|
||||
renderIOScoringStopped();
|
||||
}
|
||||
}
|
||||
|
||||
function onGearTestIODone(e, data) {
|
||||
renderIOScore(data.std, data.median, data.io, data.aggregrateIOClass, data.medianIOClass, data.stdIOClass);
|
||||
}
|
||||
|
||||
function onGearTestDone(e, data) {
|
||||
$resultsText.attr('scored', 'complete');
|
||||
rest.userCertifiedGear({success: true, client_id: app.clientId, audio_latency: getLatencyScore()});
|
||||
}
|
||||
|
||||
function onGearTestFail(e, data) {
|
||||
$resultsText.attr('scored', 'complete');
|
||||
|
||||
if(data.reason == "latency") {
|
||||
renderIOScore(null, null, null, 'skip', 'skip', 'skip');
|
||||
}
|
||||
|
||||
rest.userCertifiedGear({success: false});
|
||||
}
|
||||
|
||||
$self
|
||||
.on(GEAR_TEST_START, onGearTestStart)
|
||||
.on(GEAR_TEST_IO_START, onGearTestIOStart)
|
||||
.on(GEAR_TEST_LATENCY_START, onGearTestLatencyStart)
|
||||
.on(GEAR_TEST_LATENCY_DONE, onGearTestLatencyDone)
|
||||
.on(GEAR_TEST_IO_PROGRESS, onGearTestIOProgress)
|
||||
.on(GEAR_TEST_IO_DONE, onGearTestIODone)
|
||||
.on(GEAR_TEST_DONE, onGearTestDone)
|
||||
.on(GEAR_TEST_FAIL, onGearTestFail);
|
||||
}
|
||||
|
||||
function initialize($testResults, automated, noUI) {
|
||||
|
||||
isAutomated = automated;
|
||||
drawUI = !noUI;
|
||||
|
||||
if(drawUI) {
|
||||
handleUI($testResults);
|
||||
}
|
||||
}
|
||||
|
||||
// Latency Test Callback
|
||||
context.JK.loopbackLatencyCallback = function (latencyMS) {
|
||||
// Unregister callback:
|
||||
context.jamClient.FTUERegisterLatencyCallback('');
|
||||
|
||||
logger.debug("loopback test done: " + latencyMS);
|
||||
context.JK.GearTest.testDeferred.resolve({latency: latencyMS, latencyknown:true})
|
||||
};
|
||||
|
||||
this.GEAR_TEST_START = GEAR_TEST_START;
|
||||
this.GEAR_TEST_IO_START = GEAR_TEST_IO_START;
|
||||
this.GEAR_TEST_IO_DONE = GEAR_TEST_IO_DONE;
|
||||
this.GEAR_TEST_LATENCY_START = GEAR_TEST_LATENCY_START;
|
||||
this.GEAR_TEST_LATENCY_DONE = GEAR_TEST_LATENCY_DONE;
|
||||
this.GEAR_TEST_DONE = GEAR_TEST_DONE;
|
||||
this.GEAR_TEST_FAIL = GEAR_TEST_FAIL;
|
||||
this.GEAR_TEST_IO_PROGRESS = GEAR_TEST_IO_PROGRESS;
|
||||
|
||||
this.initialize = initialize;
|
||||
this.isScoring = isScoring;
|
||||
this.attemptScore = attemptScore;
|
||||
this.resetScoreReport = resetScoreReport;
|
||||
this.showLoopbackDone = showLoopbackDone;
|
||||
this.invalidateScore = invalidateScore;
|
||||
this.isValidLatencyScore = isValidLatencyScore;
|
||||
this.isValidIOScore = isValidIOScore;
|
||||
this.isGoodFtue = isGoodFtue;
|
||||
this.getLatencyScore = getLatencyScore;
|
||||
this.getIOScore = getIOScore;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* Common utility functions.
|
||||
*/
|
||||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
var gearUtils = {};
|
||||
var rest = new context.JK.Rest();
|
||||
context.JK.GearUtils = gearUtils;
|
||||
var logger = context.JK.logger;
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
|
||||
|
||||
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
|
||||
gearUtils.isChannelAssigned = function (channel) {
|
||||
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
|
||||
}
|
||||
|
||||
|
||||
gearUtils.createProfileName = function(deviceInfo, chatName) {
|
||||
var isSameInOut = deviceInfo.input.id == deviceInfo.output.id;
|
||||
|
||||
var name = null;
|
||||
if(isSameInOut) {
|
||||
name = "In/Out: " + deviceInfo.input.info.displayName;
|
||||
}
|
||||
else {
|
||||
name = "In: " + deviceInfo.input.info.displayName + ", Out: " + deviceInfo.output.info.displayName
|
||||
}
|
||||
|
||||
logger.debug("creating profile name: " + name);
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
gearUtils.selectedDeviceInfo = function(audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
|
||||
|
||||
|
||||
if(!audioInputDeviceId) {
|
||||
logger.debug("gearUtils.selectedDeviceInfo: no active input device");
|
||||
return null;
|
||||
}
|
||||
if(!audioOutputDeviceId) {
|
||||
logger.debug("gearUtils.selectedDeviceInfo: no active output device");
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!deviceInformation) {
|
||||
deviceInformation = gearUtils.loadDeviceInfo();
|
||||
}
|
||||
|
||||
var input = deviceInformation[audioInputDeviceId];
|
||||
var output = deviceInformation[audioOutputDeviceId];
|
||||
|
||||
var inputBehavior = AUDIO_DEVICE_BEHAVIOR[input.type];
|
||||
var outputBehavior = AUDIO_DEVICE_BEHAVIOR[output.type];
|
||||
|
||||
return {
|
||||
input: {
|
||||
id: audioInputDeviceId,
|
||||
info: input,
|
||||
behavior: inputBehavior
|
||||
},
|
||||
output: {
|
||||
id: audioOutputDeviceId,
|
||||
info: output,
|
||||
behavior: outputBehavior
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gearUtils.loadDeviceInfo = function() {
|
||||
|
||||
var operatingSystem = context.JK.GetOSAsString();
|
||||
// should return one of:
|
||||
// * MacOSX_builtin
|
||||
// * MACOSX_interface
|
||||
// * Win32_wdm
|
||||
// * Win32_asio
|
||||
// * Win32_asio4all
|
||||
// * Linux
|
||||
function determineDeviceType(deviceId, displayName) {
|
||||
if (operatingSystem == "MacOSX") {
|
||||
if (displayName.toLowerCase().trim().indexOf("built-in") == 0) {
|
||||
return "MacOSX_builtin";
|
||||
}
|
||||
else {
|
||||
return "MacOSX_interface";
|
||||
}
|
||||
}
|
||||
else if (operatingSystem == "Win32") {
|
||||
if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) {
|
||||
return "Win32_wdm";
|
||||
}
|
||||
else if (displayName.toLowerCase().indexOf("asio4all") > -1) {
|
||||
return "Win32_asio4all"
|
||||
}
|
||||
else {
|
||||
return "Win32_asio";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Linux";
|
||||
}
|
||||
}
|
||||
|
||||
var devices = context.jamClient.FTUEGetAudioDevices();
|
||||
logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices));
|
||||
|
||||
var loadedDevices = {};
|
||||
|
||||
// augment these devices by determining their type
|
||||
context._.each(devices.devices, function (device) {
|
||||
|
||||
if (device.name == "JamKazam Virtual Monitor") {
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceInfo = {};
|
||||
|
||||
deviceInfo.id = device.guid;
|
||||
deviceInfo.type = determineDeviceType(device.guid, device.display_name);
|
||||
deviceInfo.displayType = AUDIO_DEVICE_BEHAVIOR[deviceInfo.type].display;
|
||||
deviceInfo.displayName = device.display_name;
|
||||
deviceInfo.inputCount = device.input_count;
|
||||
deviceInfo.outputCount = device.output_count;
|
||||
|
||||
loadedDevices[device.guid] = deviceInfo;
|
||||
})
|
||||
|
||||
logger.debug(context.JK.dlen(loadedDevices) + " devices loaded.", loadedDevices);
|
||||
|
||||
return loadedDevices;
|
||||
}
|
||||
|
||||
gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
|
||||
return {
|
||||
os: operatingSystem,
|
||||
version: context.jamClient.ClientUpdateVersion(),
|
||||
success: gearTest.isGoodFtue(),
|
||||
automated: isAutomated,
|
||||
score: {
|
||||
validLatencyScore: gearTest.isValidLatencyScore(),
|
||||
validIOScore: gearTest.isValidIOScore(),
|
||||
latencyScore: gearTest.getLatencyScore(),
|
||||
ioScore : gearTest.getIOScore(),
|
||||
},
|
||||
audioParameters: {
|
||||
frameSize: frameBuffers.selectedFramesize(),
|
||||
bufferIn: frameBuffers.selectedBufferIn(),
|
||||
bufferOut: frameBuffers.selectedBufferOut(),
|
||||
},
|
||||
devices: deviceInformation,
|
||||
selectedDevice: selectedDeviceInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all profiles, but marks profiles good: true/false.
|
||||
* Also, current:true/false indicates which profile is active. (at most 1 profile will be marked current)
|
||||
* This is to provide a unified view of FTUEGetAllAudioConfigurations & FTUEGetGoodAudioConfigurations
|
||||
* @returns an array of profiles, where each profile is: {id: profile-name, good: boolean, class: 'bad' | 'good', current: boolean }
|
||||
*/
|
||||
gearUtils.getProfiles = function() {
|
||||
var all = context.jamClient.FTUEGetAllAudioConfigurations();
|
||||
var good = context.jamClient.FTUEGetGoodAudioConfigurations();
|
||||
var current = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
var profiles = [];
|
||||
context._.each(all, function(item) {
|
||||
profiles.push({id: item, good: false, class:'bad', current: current == item})
|
||||
});
|
||||
|
||||
if(good) {
|
||||
for(var i = 0; i < good.length; i++) {
|
||||
for(var j = 0; j < profiles.length; j++) {
|
||||
if(good[i] == profiles[j].id) {
|
||||
profiles[j].good = true;
|
||||
profiles[j].class = 'good';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
gearUtils.postDiagnostic = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
|
||||
rest.createDiagnostic({
|
||||
type: 'GEAR_SELECTION',
|
||||
data: {
|
||||
logs: logger.logCache,
|
||||
client_type: context.JK.clientType(),
|
||||
client_id:
|
||||
context.JK.JamServer.clientID,
|
||||
summary:gearUtils.ftueSummary(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated)}
|
||||
});
|
||||
}
|
||||
|
||||
// complete list of possibly chatInputs, whether currently assigned as the chat channel or not
|
||||
// each item should be {id: channelId, name: channelName, assignment: channel assignment}
|
||||
|
||||
gearUtils.getChatInputs = function(){
|
||||
|
||||
var musicPorts = jamClient.FTUEGetChannels();
|
||||
//var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true);
|
||||
var chatsOnOtherDevices = context.jamClient.FTUEGetChatInputs(false);
|
||||
|
||||
var chatInputs = [];
|
||||
//context._.each(musicPorts.inputs, function(input) {
|
||||
// chatInputs.push({id: input.id, name: input.name, assignment:input.assignment});
|
||||
//});
|
||||
|
||||
var deDupper = {};
|
||||
|
||||
context._.each(musicPorts.inputs, function(input) {
|
||||
|
||||
var chatInput = {id: input.id, name: input.name, assignment:input.assignment};
|
||||
if(!deDupper[input.id]) {
|
||||
if(input.assignment <= 0) {
|
||||
chatInputs.push(chatInput);
|
||||
deDupper[input.id] = chatInput;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**context._.each(chatsOnCurrentDevice, function(chatChannelName, chatChannelId) {
|
||||
var chatInput = {id: chatChannelId, name: chatChannelName, assignment: ASSIGNMENT.UNASSIGNED};
|
||||
if(!deDupper[chatInput.id]) {
|
||||
var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true);
|
||||
if(assignment <= 0) {
|
||||
chatInputs.push(chatInput);
|
||||
deDupper[chatInput.id] = chatInput;
|
||||
}
|
||||
}
|
||||
})*/
|
||||
|
||||
context._.each(chatsOnOtherDevices, function(chatChannelName, chatChannelId) {
|
||||
var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null};
|
||||
if(!deDupper[chatInput.id]) {
|
||||
var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true);
|
||||
chatInput.assignment = assignment;
|
||||
|
||||
chatInputs.push(chatInput);
|
||||
deDupper[chatInput.id] = chatInput;
|
||||
}
|
||||
})
|
||||
|
||||
logger.debug("chatInputs:", chatInputs)
|
||||
return chatInputs;
|
||||
}
|
||||
|
||||
gearUtils.isChannelAvailableForChat = function(chatChannelId, musicPorts) {
|
||||
var result = true;
|
||||
context._.each(musicPorts.inputs, function(inputChannel) {
|
||||
// if the channel is currently assigned to a track, it not unassigned
|
||||
if(inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) {
|
||||
result = false;
|
||||
return false; // break
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gearUtils.updateAudioLatency = function(app) {
|
||||
var latency = jamClient.FTUEGetExpectedLatency().latency;
|
||||
return rest.updateAudioLatency({client_id: app.clientId, audio_latency: latency})
|
||||
.fail(function(jqXHR) {
|
||||
app.notifyServerError(jqXHR, "Unable to sync audio latency")
|
||||
});
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -2,19 +2,20 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.LoopbackWizard = function (app) {
|
||||
|
||||
var EVENTS = context.JK.EVENTS;
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var $dialog = null;
|
||||
var wizard = null;
|
||||
var $wizardSteps = null;
|
||||
var $self = $(this);
|
||||
|
||||
var step1 = new context.JK.Step1(app, this);
|
||||
var step2 = new context.JK.Step2(app, this);
|
||||
var step3 = new context.JK.Step3(app, this);
|
||||
var step1 = new context.JK.StepLoopbackIntro(app, this);
|
||||
var step2 = new context.JK.StepLoopbackTest(app, this);
|
||||
var step3 = new context.JK.StepLoopbackResult(app, this);
|
||||
|
||||
var STEPS = {
|
||||
0: step1,
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
|
||||
function closeDialog() {
|
||||
wizard.onCloseDialog();
|
||||
app.layout.closeDialog('your-wizard');
|
||||
app.layout.closeDialog('loopback-wizard');
|
||||
}
|
||||
|
||||
function setNextState(enabled) {
|
||||
|
|
@ -60,6 +61,9 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
function getGearTest() {
|
||||
return step2.getGearTest();
|
||||
}
|
||||
|
||||
function events() {
|
||||
$(wizard).on('step_changed', onStepChanged);
|
||||
|
|
@ -67,19 +71,20 @@
|
|||
$(wizard).on('wizard_close', onClosed);
|
||||
}
|
||||
|
||||
function getDialog() {
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
|
||||
var dialogBindings = { beforeShow: beforeShow, afterHide: afterHide };
|
||||
|
||||
app.bindDialog('your-wizard', dialogBindings);
|
||||
$dialog = $('#your-wizard');
|
||||
app.bindDialog('loopback-wizard', dialogBindings);
|
||||
$dialog = $('#loopback-wizard-dialog');
|
||||
$wizardSteps = $dialog.find('.wizard-step');
|
||||
|
||||
step1.initialize($wizardSteps.filter($('[layout-wizard-step=0]')));
|
||||
step2.initialize($wizardSteps.filter($('[layout-wizard-step=1]')));
|
||||
step3.initialize($wizardSteps.filter($('[layout-wizard-step=2]')));
|
||||
|
||||
wizard = new context.JK.Wizard(app);
|
||||
wizard.initialize($dialog, $wizardSteps, STEPS);
|
||||
|
|
@ -90,6 +95,8 @@
|
|||
this.setNextState = setNextState;
|
||||
this.setBackState = setBackState;
|
||||
this.initialize = initialize;
|
||||
this.getGearTest = getGearTest;
|
||||
this.getDialog = getDialog;
|
||||
|
||||
return this;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepLoopbackIntro = function (app, $dialog) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function videoLinkClicked(evt) {
|
||||
var myOS = jamClient.GetOSAsString();
|
||||
var link;
|
||||
if (myOS === 'MacOSX') {
|
||||
link = $(evt.currentTarget).attr('external-link-mac');
|
||||
} else if (myOS === 'Win32') {
|
||||
link = $(evt.currentTarget).attr('external-link-win');
|
||||
}
|
||||
if (link) {
|
||||
context.jamClient.OpenSystemBrowser(link);
|
||||
}
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
|
||||
$step.find('.ftue-video-link').on('click', videoLinkClicked);
|
||||
}
|
||||
|
||||
this.beforeShow = beforeShow;
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepLoopbackResult = function (app, $dialog) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var $step = null;
|
||||
|
||||
function beforeShow() {
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
this.beforeShow = beforeShow;
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepLoopbackTest = function (app, dialog) {
|
||||
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
|
||||
var gearUtils = context.JK.GearUtils;
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var $step = null;
|
||||
var frameBuffers = new context.JK.FrameBuffers(app);
|
||||
var gearTest = new context.JK.GearTest(app);
|
||||
var deviceInformation = null;
|
||||
var operatingSystem = null;
|
||||
var selectedDeviceInfo = null;
|
||||
var musicPorts = null;
|
||||
|
||||
var $asioInputControlBtn = null;
|
||||
var $asioOutputControlBtn = null;
|
||||
var $resyncBtn = null;
|
||||
var $runTestBtn = null;
|
||||
var $audioInputDevice = null;
|
||||
var $audioOutputDevice = null;
|
||||
var $inputChannels = null;
|
||||
var $outputChannels = null;
|
||||
var $templateAudioPort = null;
|
||||
var $scoreReport = null;
|
||||
|
||||
var faderMap = {
|
||||
'loopback-audio-input-fader': jamClient.FTUESetInputVolume,
|
||||
'loopback-audio-output-fader': jamClient.FTUESetOutputVolume
|
||||
};
|
||||
|
||||
var faderReadMap = {
|
||||
'loopback-audio-input-fader': jamClient.FTUEGetInputVolume,
|
||||
'loopback-audio-output-fader': jamClient.FTUEGetOutputVolume
|
||||
};
|
||||
|
||||
|
||||
|
||||
function attemptScore() {
|
||||
gearTest.attemptScore();
|
||||
}
|
||||
|
||||
|
||||
function invalidateScore() {
|
||||
gearTest.invalidateScore();
|
||||
initializeNextButtonState();
|
||||
}
|
||||
|
||||
function updateDefaultBuffers() {
|
||||
|
||||
// handle specific framesize settings
|
||||
if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) {
|
||||
var framesize = frameBuffers.selectedFramesize();
|
||||
|
||||
if(framesize == 2.5) {
|
||||
logger.debug("setting default buffers to 1/1");
|
||||
frameBuffers.setBufferIn('1');
|
||||
frameBuffers.setBufferOut('1');
|
||||
}
|
||||
else if(framesize == 5) {
|
||||
logger.debug("setting default buffers to 3/2");
|
||||
frameBuffers.setBufferIn('3');
|
||||
frameBuffers.setBufferOut('2');
|
||||
}
|
||||
else {
|
||||
logger.debug("setting default buffers to 6/5");
|
||||
frameBuffers.setBufferIn('6');
|
||||
frameBuffers.setBufferOut('5');
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug("setting default buffers to 0/0");
|
||||
frameBuffers.setBufferIn(0);
|
||||
frameBuffers.setBufferOut(0);
|
||||
}
|
||||
|
||||
jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
|
||||
}
|
||||
|
||||
function onFramesizeChanged() {
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
updateDefaultBuffers();
|
||||
jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize());
|
||||
invalidateScore();
|
||||
}
|
||||
|
||||
function onBufferInChanged() {
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
|
||||
invalidateScore();
|
||||
}
|
||||
|
||||
function onBufferOutChanged() {
|
||||
context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']});
|
||||
jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
|
||||
invalidateScore();
|
||||
}
|
||||
|
||||
function freezeAudioInteraction() {
|
||||
logger.debug("loopback: freezing audio interaction");
|
||||
frameBuffers.disable();
|
||||
$asioInputControlBtn.on("click", false);
|
||||
$asioOutputControlBtn.on("click", false);
|
||||
$resyncBtn.on('click', false);
|
||||
$runTestBtn.on('click', false);
|
||||
}
|
||||
|
||||
function unfreezeAudioInteraction() {
|
||||
logger.debug("unfreezing audio interaction");
|
||||
frameBuffers.enable();
|
||||
$asioInputControlBtn.off("click", false);
|
||||
$asioOutputControlBtn.off("click", false);
|
||||
$resyncBtn.off('click', false);
|
||||
$runTestBtn.off('click', false);
|
||||
}
|
||||
|
||||
function getGearTest() {
|
||||
return gearTest;
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function render(){
|
||||
|
||||
if(selectedDeviceInfo) {
|
||||
var inputBehavior = selectedDeviceInfo.input.behavior;
|
||||
var outputBehavior = selectedDeviceInfo.output.behavior;
|
||||
}
|
||||
else {
|
||||
var inputBehavior = null;
|
||||
var outputBehavior = null;
|
||||
|
||||
}
|
||||
|
||||
// handle ASIO visibility
|
||||
if (inputBehavior) {
|
||||
if (inputBehavior.showASIO) {
|
||||
$asioInputControlBtn.show();
|
||||
}
|
||||
else {
|
||||
$asioInputControlBtn.hide();
|
||||
}
|
||||
if(outputBehavior.showASIO && (selectedDeviceInfo.input.id != selectedDeviceInfo.output.id)) {
|
||||
$asioOutputControlBtn.show();
|
||||
}
|
||||
else {
|
||||
$asioOutputControlBtn.hide();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// show no ASIO buttons
|
||||
$asioInputControlBtn.hide();
|
||||
$asioOutputControlBtn.hide();
|
||||
}
|
||||
|
||||
if(selectedDeviceInfo) {
|
||||
$audioInputDevice.text(selectedDeviceInfo.input.info.displayName);
|
||||
$audioOutputDevice.text(selectedDeviceInfo.output.info.displayName);
|
||||
}
|
||||
else {
|
||||
$audioInputDevice.text('Unassigned');
|
||||
$audioOutputDevice.text('Unassigned');
|
||||
}
|
||||
|
||||
$inputChannels.empty();
|
||||
context._.each(musicPorts.inputs, function (inputChannel) {
|
||||
var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' }));
|
||||
var $checkbox = $inputChannel.find('input');
|
||||
if (gearUtils.isChannelAssigned(inputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox).iCheck('disable');
|
||||
$inputChannels.append($inputChannel);
|
||||
});
|
||||
|
||||
$outputChannels.empty();
|
||||
context._.each(musicPorts.outputs, function (outputChannel) {
|
||||
var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' }));
|
||||
var $checkbox = $outputPort.find('input');
|
||||
if (gearUtils.isChannelAssigned(outputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox).iCheck('disable');
|
||||
$outputChannels.append($outputPort);
|
||||
});
|
||||
|
||||
initializeVUMeters();
|
||||
renderVolumes();
|
||||
registerVuCallbacks();
|
||||
}
|
||||
|
||||
function initializeASIOButtons() {
|
||||
$asioInputControlBtn.unbind('click').click(function () {
|
||||
context.jamClient.FTUEOpenControlPanel(selectedDeviceInfo.input.id);
|
||||
});
|
||||
$asioOutputControlBtn.unbind('click').click(function () {
|
||||
context.jamClient.FTUEOpenControlPanel(selectedDeviceInfo.output.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function initializeResync() {
|
||||
$resyncBtn.unbind('click').click(function () {
|
||||
attemptScore();
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
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 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);
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
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 registerVuCallbacks() {
|
||||
logger.debug("loopback-wizard: registering vu callbacks");
|
||||
jamClient.FTUERegisterVUCallbacks(
|
||||
"JK.loopbackAudioOutputVUCallback",
|
||||
"JK.loopbackAudioInputVUCallback",
|
||||
"JK.loopbackChatInputVUCallback"
|
||||
);
|
||||
jamClient.SetVURefreshRate(200);
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
dialog.setNextState(gearTest.isGoodFtue());
|
||||
}
|
||||
|
||||
function initializeBackButtonState() {
|
||||
dialog.setBackState(!gearTest.isScoring());
|
||||
}
|
||||
|
||||
function renderScoringStarted() {
|
||||
initializeBackButtonState();
|
||||
initializeNextButtonState();
|
||||
freezeAudioInteraction();
|
||||
}
|
||||
|
||||
function renderScoringStopped() {
|
||||
initializeNextButtonState();
|
||||
initializeBackButtonState();
|
||||
unfreezeAudioInteraction();
|
||||
}
|
||||
|
||||
function onGearTestStarted(e, data) {
|
||||
renderScoringStarted();
|
||||
}
|
||||
|
||||
function onGearTestDone(e, data) {
|
||||
renderScoringStopped();
|
||||
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
|
||||
}
|
||||
|
||||
function onGearTestFail(e, data) {
|
||||
renderScoringStopped();
|
||||
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice());
|
||||
deviceInformation = gearUtils.loadDeviceInfo();
|
||||
musicPorts = jamClient.FTUEGetChannels();
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
function beforeHide() {
|
||||
logger.debug("loopback-wizard: unregistering vu callbacks");
|
||||
jamClient.FTUERegisterVUCallbacks('', '', '');
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
|
||||
$asioInputControlBtn = $step.find('.asio-settings-input-btn');
|
||||
$asioOutputControlBtn = $step.find('.asio-settings-output-btn');
|
||||
$resyncBtn = $step.find('.resync-btn');
|
||||
$runTestBtn = $step.find('.run-test-btn');
|
||||
$audioInputDevice = $step.find('.audio-device.input')
|
||||
$audioOutputDevice = $step.find('.audio-device.output')
|
||||
$inputChannels = $step.find('.input-ports')
|
||||
$outputChannels = $step.find('.output-ports')
|
||||
$templateAudioPort = $('#template-audio-port');
|
||||
$scoreReport = $step.find('.results');
|
||||
operatingSystem = context.JK.GetOSAsString();
|
||||
|
||||
frameBuffers.initialize($step.find('.frame-and-buffers'));
|
||||
$(frameBuffers)
|
||||
.on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged)
|
||||
.on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged)
|
||||
.on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged)
|
||||
|
||||
|
||||
gearTest.initialize($scoreReport, false)
|
||||
$(gearTest)
|
||||
.on(gearTest.GEAR_TEST_START, onGearTestStarted)
|
||||
.on(gearTest.GEAR_TEST_DONE, onGearTestDone)
|
||||
.on(gearTest.GEAR_TEST_FAIL, onGearTestFail)
|
||||
|
||||
$runTestBtn.click(attemptScore);
|
||||
|
||||
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) {
|
||||
};
|
||||
|
||||
this.getGearTest = getGearTest;
|
||||
this.handleNext = handleNext;
|
||||
this.handleBack = handleBack;
|
||||
this.beforeShow = beforeShow;
|
||||
this.beforeHide = beforeHide;
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -13,11 +13,13 @@
|
|||
var $wizardSteps = null;
|
||||
var $currentWizardStep = null;
|
||||
var $wizardButtons = null;
|
||||
var options = {};
|
||||
var $btnHelp = null;
|
||||
var $btnNext = null;
|
||||
var $btnBack = null;
|
||||
var $btnClose = null;
|
||||
var $btnCancel = null;
|
||||
var dialogName = null;
|
||||
var self = this;
|
||||
var $self = $(this);
|
||||
|
||||
|
|
@ -51,7 +53,7 @@
|
|||
}
|
||||
|
||||
function back() {
|
||||
if ($(this).is('.button-grey')) return false;
|
||||
if ($(this).is('.disabled')) return false;
|
||||
previousStep = step;
|
||||
step = step - 1;
|
||||
moveToStep();
|
||||
|
|
@ -59,7 +61,7 @@
|
|||
}
|
||||
|
||||
function next() {
|
||||
if ($(this).is('.button-grey')) return false;
|
||||
if ($(this).is('.disabled')) return false;
|
||||
|
||||
var stepInfo = STEPS[step];
|
||||
if(stepInfo.handleNext) {
|
||||
|
|
@ -83,7 +85,7 @@
|
|||
|
||||
$currentWizardStep = $nextWizardStep;
|
||||
|
||||
context.JK.GA.virtualPageView(location.pathname + location.search + location.hash + '/d1=' + step, $currentWizardStep.attr('dialog-title'));
|
||||
context.JK.GA.virtualPageView('/client/' + dialogName, dialogName + ": " + $currentWizardStep.attr('dialog-title'));
|
||||
|
||||
$self.triggerHandler('step_changed', {step:step});
|
||||
|
||||
|
|
@ -150,31 +152,32 @@
|
|||
step = null;
|
||||
}
|
||||
|
||||
function getNextButton() {
|
||||
return $btnNext;
|
||||
}
|
||||
|
||||
function setNextState(enabled) {
|
||||
|
||||
if(!$btnNext) return;
|
||||
|
||||
$btnNext.removeClass('button-orange button-grey');
|
||||
|
||||
if (enabled) {
|
||||
$btnNext.addClass('button-orange');
|
||||
if(enabled) {
|
||||
$btnNext.removeClass('disabled');
|
||||
}
|
||||
else {
|
||||
$btnNext.addClass('button-grey');
|
||||
$btnNext.addClass('disabled');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setBackState(enabled) {
|
||||
|
||||
if(!$btnBack) return;
|
||||
|
||||
$btnBack.removeClass('button-orange button-grey');
|
||||
|
||||
if (enabled) {
|
||||
$btnBack.addClass('button-orange');
|
||||
if(enabled) {
|
||||
$btnBack.removeClass('disabled');
|
||||
}
|
||||
else {
|
||||
$btnBack.addClass('button-grey');
|
||||
$btnBack.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,15 +195,21 @@
|
|||
return $currentWizardStep;
|
||||
}
|
||||
|
||||
function initialize(_$dialog, _$wizardSteps, _STEPS) {
|
||||
function initialize(_$dialog, _$wizardSteps, _STEPS, _options) {
|
||||
$dialog = _$dialog;
|
||||
dialogName = $dialog.attr('layout-id');
|
||||
if (!dialogName) throw "no dialog name (layout-id) in wizard";
|
||||
$wizardSteps = _$wizardSteps;
|
||||
STEPS = _STEPS;
|
||||
|
||||
$wizardButtons = $dialog.find('.wizard-buttons');
|
||||
$templateButtons = $('#template-wizard-buttons');
|
||||
|
||||
if(_options) {_options = {}};
|
||||
options = _options;
|
||||
}
|
||||
|
||||
this.getNextButton = getNextButton;
|
||||
this.setNextState = setNextState;
|
||||
this.setBackState = setBackState;
|
||||
this.getCurrentStep = getCurrentStep;
|
||||
|
|
|
|||
|
|
@ -162,11 +162,11 @@
|
|||
}
|
||||
}
|
||||
.lcol {
|
||||
width: 148px;
|
||||
width: 200px;
|
||||
}
|
||||
.whitespace {
|
||||
// equal to lcol width.
|
||||
padding-left: 148px;
|
||||
padding-left: 200px;
|
||||
}
|
||||
.instruments {
|
||||
width:128px;
|
||||
|
|
|
|||
|
|
@ -34,12 +34,19 @@
|
|||
*= require ./search
|
||||
*= require ./ftue
|
||||
*= require ./jamServer
|
||||
*= require ./dragDropTracks
|
||||
*= require ./voiceChatHelper
|
||||
*= require ./wizard/gearResults
|
||||
*= require ./wizard/framebuffers
|
||||
*= require ./wizard/wizard
|
||||
*= require ./wizard/gearWizard
|
||||
*= require ./wizard/loopbackWizard
|
||||
*= require ./networkTestDialog
|
||||
*= require ./whatsNextDialog
|
||||
*= require ./invitationDialog
|
||||
*= require ./shareDialog
|
||||
*= require ./hoverBubble
|
||||
*= require ./configureTracksDialog
|
||||
*= require ./recordingFinishedDialog
|
||||
*= require ./localRecordingsDialog
|
||||
*= require ./serverErrorDialog
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
@import "client/common.css.scss";
|
||||
@charset "UTF-8";
|
||||
|
||||
#configure-tracks-dialog {
|
||||
min-height: 700px;
|
||||
max-height: 700px;
|
||||
width:800px;
|
||||
|
||||
&[current-screen="account/audio"] {
|
||||
.btn-add-new-audio-gear {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-header {
|
||||
color:white;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
.certified-audio-profile-section {
|
||||
|
||||
height:53px;
|
||||
|
||||
.easydropdown {
|
||||
width:120px;
|
||||
}
|
||||
|
||||
.easydropdown-wrapper {
|
||||
width:120px;
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
min-width:138px;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
position:relative;
|
||||
float:left;
|
||||
vertical-align:top;
|
||||
@include border_box_sizing;
|
||||
padding: 20px 20px 0 0;
|
||||
}
|
||||
|
||||
.sub-column {
|
||||
position:relative;
|
||||
float:left;
|
||||
vertical-align:top;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
.tab[tab-id="music-audio"] {
|
||||
.column {
|
||||
&:nth-of-type(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-column {
|
||||
&:nth-of-type(1) {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab[tab-id="voice-chat"] {
|
||||
.column {
|
||||
&:nth-of-type(1) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unused-audio-inputs-section {
|
||||
margin-top:20px;
|
||||
height:270px;
|
||||
}
|
||||
|
||||
.unused-audio-outputs-section {
|
||||
margin-top:20px;
|
||||
height:80px;
|
||||
}
|
||||
|
||||
.input-tracks-section {
|
||||
height:363px;
|
||||
}
|
||||
|
||||
.output-channels-section {
|
||||
height:80px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
bottom: 25px;
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
left:25px;
|
||||
}
|
||||
|
||||
.btn-add-new-audio-gear {
|
||||
float:left;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
float:right;
|
||||
}
|
||||
|
||||
.btn-update-settings {
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
|
|
@ -305,7 +305,7 @@ ul.shortcuts {
|
|||
padding:2px;
|
||||
}
|
||||
|
||||
.account-home, .band-setup, .audio, .get-help, .download-app, .invite-friends {
|
||||
.account-home, .band-setup, .audio, .get-help, .invite-friends, .test-network{
|
||||
border-bottom:1px;
|
||||
border-style:solid;
|
||||
border-color:#ED3618;
|
||||
|
|
|
|||
|
|
@ -433,12 +433,17 @@ ul.shortcuts {
|
|||
padding:2px;
|
||||
}
|
||||
|
||||
.account-home, .band-setup, .account-menu-group, .get-help, .download-app, .community-forum, .invite-friends {
|
||||
.account-home, .band-setup, .account-menu-group, .get-help, .community-forum, .invite-friends {
|
||||
border-bottom:1px;
|
||||
border-style:solid;
|
||||
border-color:#ED3618;
|
||||
}
|
||||
|
||||
.community-forum {
|
||||
border-top:1px;
|
||||
border-style:solid;
|
||||
border-color:#ED3618;
|
||||
}
|
||||
|
||||
|
||||
span.arrow-right {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
@import "client/common.css.scss";
|
||||
@charset "UTF-8";
|
||||
|
||||
.dialog.gear-wizard, #configure-tracks-dialog {
|
||||
|
||||
.icon-instrument-select {
|
||||
padding: 3px 0; // to combine 24 of .current-instrument + 3x on either side
|
||||
margin: 0 0 15px 25px; // 15 margin-bottom to match tracks on the left
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.unassigned-output-channels {
|
||||
min-height: 90px;
|
||||
overflow-y: auto;
|
||||
max-height: 90px;
|
||||
//padding-right:18px; // to keep draggables off of scrollbar. maybe necessary
|
||||
|
||||
&.drag-in-progress {
|
||||
}
|
||||
|
||||
&.possible-target {
|
||||
border: solid 1px white;
|
||||
|
||||
&.drag-hovering {
|
||||
border: solid 1px #ED3618;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.unassigned-input-channels {
|
||||
min-height: 22px;
|
||||
overflow-y: auto;
|
||||
max-height:23%;
|
||||
|
||||
//padding-right:18px; // to keep draggables off of scrollbar. maybe necessary
|
||||
|
||||
&.compensate {
|
||||
.ftue-input:not('.hovering') {
|
||||
}
|
||||
}
|
||||
.ftue-input {
|
||||
}
|
||||
|
||||
&.drag-in-progress {
|
||||
}
|
||||
|
||||
&.possible-target {
|
||||
border: solid 1px white;
|
||||
|
||||
&.drag-hovering {
|
||||
border: solid 1px #ED3618;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.num {
|
||||
position: absolute;
|
||||
height: 29px;
|
||||
line-height: 29px;
|
||||
}
|
||||
.track, .output {
|
||||
margin-bottom: 15px;
|
||||
.track-target, .output-target {
|
||||
&.possible-target {
|
||||
border-color: white;
|
||||
}
|
||||
&.drag-hovering {
|
||||
border-color: #ED3618;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do not show tracks with 2 channels as a possible target
|
||||
.track .track-target.possible-target[track-count="2"] {
|
||||
border-color:#999;
|
||||
}
|
||||
|
||||
// do now show output slots with 1 channel as a possible target
|
||||
.output .output-target.possible-target[output-count="1"] {
|
||||
border-color:#999;
|
||||
}
|
||||
|
||||
.ftue-input {
|
||||
font-size: 12px;
|
||||
cursor: move;
|
||||
padding: 4px;
|
||||
border: solid 1px #999;
|
||||
margin-bottom: 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
line-height:20px;
|
||||
//direction: rtl;
|
||||
&.ui-draggable-dragging {
|
||||
margin-bottom: 0;
|
||||
z-index:10000;
|
||||
background-color:#333;
|
||||
}
|
||||
|
||||
/**
|
||||
&:hover {
|
||||
color:white;
|
||||
overflow:visible;
|
||||
background-color:#333;
|
||||
width: auto !important;
|
||||
direction:ltr;
|
||||
position:absolute;
|
||||
line-height:19.5px;
|
||||
}*/
|
||||
}
|
||||
|
||||
.track-target, .output-target {
|
||||
|
||||
position:relative;
|
||||
cursor: move;
|
||||
padding: 4px;
|
||||
border: solid 1px #999;
|
||||
margin-left: 15px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
&.drag-in-progress {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ftue-input {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin-bottom: 0;
|
||||
&.ui-draggable-dragging {
|
||||
padding: 4px;
|
||||
border: solid 1px #999;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&[track-count="0"], &[output-count="0"] {
|
||||
.placeholder {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
&[track-count="2"] {
|
||||
.ftue-input {
|
||||
width: 49%;
|
||||
display: inline-block;
|
||||
@include border_box_sizing;
|
||||
|
||||
|
||||
&:nth-of-type(1) {
|
||||
float: left;
|
||||
padding-right:2px;
|
||||
/**&:after {
|
||||
float:right;
|
||||
content: ',';
|
||||
padding-right:3px;
|
||||
}*/
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
padding-left:2px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -546,6 +546,8 @@ div[layout-id="ftue3"] {
|
|||
float:left;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
.ftue-inner a {
|
||||
color:#ccc;
|
||||
}
|
||||
|
|
@ -553,6 +555,7 @@ div[layout-id="ftue3"] {
|
|||
.ftue-inner a:hover {
|
||||
color:#fff;
|
||||
}
|
||||
*/
|
||||
|
||||
.ftue-instrumentlist {
|
||||
width:340px;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
#network-test-dialog {
|
||||
.buttons {
|
||||
bottom: 25px;
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
|
||||
.network-test {
|
||||
.network-test-results {
|
||||
height:268px ! important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,11 +231,11 @@
|
|||
}
|
||||
}
|
||||
.lcol {
|
||||
width: 148px;
|
||||
width: 200px;
|
||||
}
|
||||
.whitespace {
|
||||
// equal to lcol width.
|
||||
padding-left: 148px;
|
||||
padding-left: 200px;
|
||||
}
|
||||
.instruments {
|
||||
width:128px;
|
||||
|
|
|
|||
|
|
@ -201,9 +201,9 @@ small, .small {font-size:11px;}
|
|||
|
||||
.button-orange {
|
||||
margin:0px 8px 0px 8px;
|
||||
background-color: #ED3618;
|
||||
background-color: $ColorScreenPrimary;
|
||||
border: solid 1px #F27861;
|
||||
outline: solid 2px #ED3618;
|
||||
outline: solid 2px $ColorScreenPrimary;
|
||||
padding:3px 10px;
|
||||
font-size:12px;
|
||||
font-weight:300;
|
||||
|
|
@ -213,11 +213,17 @@ small, .small {font-size:11px;}
|
|||
line-height:12px;
|
||||
|
||||
&.disabled {
|
||||
@extend .button-grey;
|
||||
background-color: transparent;
|
||||
border: solid 1px #868686;
|
||||
outline: solid 2px transparent;
|
||||
color:#ccc;
|
||||
}
|
||||
|
||||
&.disabled:hover {
|
||||
@extend .button-grey:hover;
|
||||
//background-color:darken(#f16750, 20%);
|
||||
//color:#FFF;
|
||||
background-color: #515151;
|
||||
color:#ccc;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
.dialog.configure-tracks, .dialog.gear-wizard {
|
||||
|
||||
.voicechat-option {
|
||||
|
||||
position: relative;
|
||||
|
||||
div {
|
||||
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-left: 30px;
|
||||
margin-top: 14px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-left: 30px;
|
||||
margin-top: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.iradio_minimal {
|
||||
margin-top: 15px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.ftue-box {
|
||||
background-color: #222222;
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
&.chat-inputs {
|
||||
height: 230px !important;
|
||||
overflow: auto;
|
||||
color:white;
|
||||
|
||||
&.disabled {
|
||||
color:gray;
|
||||
}
|
||||
p {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
white-space: nowrap;
|
||||
|
||||
.iradio_minimal {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
.dialog {
|
||||
|
||||
.framesize {
|
||||
float:left;
|
||||
margin-right:10px;
|
||||
width:30%;
|
||||
}
|
||||
|
||||
.select-buffer-in {
|
||||
width:45%;
|
||||
}
|
||||
|
||||
.select-buffer-out {
|
||||
width:45%;
|
||||
}
|
||||
|
||||
|
||||
.buffers {
|
||||
float:left;
|
||||
width:60%;
|
||||
|
||||
h2 {
|
||||
margin-left:5px;
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
width:48px;
|
||||
}
|
||||
.easydropdown-wrapper:nth-of-type(1) {
|
||||
left:5px;
|
||||
}
|
||||
.easydropdown-wrapper:nth-of-type(2) {
|
||||
left:30px;
|
||||
}
|
||||
.easydropdown, .easydropdown-wrapper {
|
||||
width:30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
@import "client/common.css.scss";
|
||||
@charset "UTF-8";
|
||||
|
||||
.dialog {
|
||||
.ftue-box.results {
|
||||
height: 268px !important;
|
||||
padding: 0;
|
||||
@include border_box_sizing;
|
||||
|
||||
span.conditional {
|
||||
display:none;
|
||||
}
|
||||
&[data-type=automated] {
|
||||
span.conditional[data-type=automated] {
|
||||
display:inline;
|
||||
}
|
||||
}
|
||||
&[data-type=loopback] {
|
||||
span.conditional[data-type=loopback] {
|
||||
display:inline;
|
||||
}
|
||||
}
|
||||
.io, .latency {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.spinner-small {
|
||||
display:none;
|
||||
margin:auto;
|
||||
}
|
||||
|
||||
&[data-type="loopback"] .latency-score-section.starting {
|
||||
.spinner-small {
|
||||
display:block;
|
||||
}
|
||||
}
|
||||
.loopback-button-holder {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
a.loopback-test {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.unknown-text {
|
||||
display: none;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.loopback-completed {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.scoring-section {
|
||||
font-size: 15px;
|
||||
@include border_box_sizing;
|
||||
height: 64px;
|
||||
|
||||
&.good {
|
||||
background-color: #72a43b;
|
||||
}
|
||||
&.acceptable {
|
||||
background-color: #cc9900;
|
||||
}
|
||||
&.bad {
|
||||
background-color: #660000;
|
||||
}
|
||||
&.unknown, &.skip {
|
||||
background-color: #999;
|
||||
}
|
||||
&.skip {
|
||||
.io-skip-msg {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.io-countdown {
|
||||
display: none;
|
||||
padding-left: 19px;
|
||||
position: relative;
|
||||
|
||||
.secs {
|
||||
position: absolute;
|
||||
width: 19px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.io-skip-msg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.io-rate {
|
||||
display: none;
|
||||
}
|
||||
.io-var {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.results-text {
|
||||
padding: 10px 8px;
|
||||
|
||||
li {
|
||||
display: none
|
||||
}
|
||||
|
||||
&[latency-score="unknown"] {
|
||||
display: none;
|
||||
}
|
||||
&[latency-score="good"] li.latency-good {
|
||||
display: list-item;
|
||||
}
|
||||
&[latency-score="acceptable"] li.latency-acceptable {
|
||||
display: list-item;
|
||||
}
|
||||
&[latency-score="bad"] li.latency-bad {
|
||||
display: list-item;
|
||||
}
|
||||
&[io-var-score="good"] li.io-var-good {
|
||||
display: list-item;
|
||||
}
|
||||
&[io-var-score="acceptable"] li.io-var-acceptable {
|
||||
display: list-item;
|
||||
}
|
||||
&[io-var-score="bad"] li.io-var-bad {
|
||||
display: list-item;
|
||||
}
|
||||
&[io-rate-score="good"] li.io-rate-good {
|
||||
display: list-item;
|
||||
}
|
||||
&[io-rate-score="acceptable"] li.io-rate-acceptable {
|
||||
display: list-item;
|
||||
}
|
||||
&[io-rate-score="bad"] li.io-rate-bad {
|
||||
display: list-item;
|
||||
}
|
||||
&[scored="complete"] {
|
||||
li.success {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
&[io-rate-score="bad"], &[io-var-score="bad"], &[latency-score="bad"] {
|
||||
li.success {
|
||||
display: none;
|
||||
}
|
||||
li.failure {
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
|
||||
&[latency-score="unknown"] {
|
||||
li.success {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,148 @@
|
|||
@import "client/common.css.scss";
|
||||
@charset "UTF-8";
|
||||
|
||||
|
||||
.dialog.loopback-wizard {
|
||||
min-height: 500px;
|
||||
max-height: 500px;
|
||||
width:800px;
|
||||
|
||||
h2 {
|
||||
color: #FFFFFF;
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
margin-bottom: 6px;
|
||||
white-space:nowrap;
|
||||
}
|
||||
.ftue-box {
|
||||
background-color: #222222;
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ftue-inner {
|
||||
line-height: 1.3em;
|
||||
width:800px;
|
||||
padding:25px;
|
||||
font-size:15px;
|
||||
color:#aaa;
|
||||
@include border_box_sizing;
|
||||
|
||||
}
|
||||
|
||||
.wizard-step[layout-wizard-step="0"] {
|
||||
p.intro {
|
||||
margin-top:0px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin:0;
|
||||
padding:0px;
|
||||
margin-top:70px;
|
||||
}
|
||||
|
||||
li {
|
||||
text-align:center;
|
||||
width: 31%;
|
||||
height: 170px;
|
||||
margin:0px;
|
||||
/*padding:0px;*/
|
||||
list-style: none;
|
||||
float:left;
|
||||
}
|
||||
li.first {
|
||||
width: 34%;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-step[layout-wizard-step="1"] {
|
||||
|
||||
.wizard-step-content .wizard-step-column {
|
||||
|
||||
&:nth-of-type(1), &:nth-of-type(2) {
|
||||
width:31%;
|
||||
}
|
||||
|
||||
&:nth-of-type(3) {
|
||||
width:25%;
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
|
||||
a.button-orange {
|
||||
margin-left:3px;
|
||||
}
|
||||
|
||||
.ftue-controls {
|
||||
margin-top: 16px;
|
||||
position:relative;
|
||||
height: 48px;
|
||||
width: 220px;
|
||||
background-color: #222;
|
||||
}
|
||||
.ftue-vu-left {
|
||||
position:relative;
|
||||
top: 0px;
|
||||
}
|
||||
.ftue-vu-right {
|
||||
position:relative;
|
||||
top: 22px;
|
||||
}
|
||||
.ftue-fader {
|
||||
position:relative;
|
||||
top: 14px;
|
||||
left: 8px;
|
||||
}
|
||||
.gain-label {
|
||||
color: $ColorScreenPrimary;
|
||||
position:absolute;
|
||||
top: 14px;
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
.ports-header {
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
.ports {
|
||||
height:100px;
|
||||
}
|
||||
|
||||
.asio-settings-input-btn, .asio-settings-output-btn {
|
||||
position:absolute;
|
||||
right:5px;
|
||||
}
|
||||
|
||||
.resync-btn {
|
||||
margin-top:35px;
|
||||
}
|
||||
|
||||
.run-test-btn {
|
||||
text-align:center;
|
||||
display:inline-block;
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.frame-and-buffers {
|
||||
margin-top:10px;
|
||||
}
|
||||
.test-results-header {
|
||||
|
||||
}
|
||||
|
||||
.select-frame-size {
|
||||
margin-left:-2px;
|
||||
}
|
||||
|
||||
.ftue-box.results {
|
||||
margin-top:20px;
|
||||
height: 200px !important;
|
||||
padding:0;
|
||||
@include border_box_sizing;
|
||||
}
|
||||
|
||||
h2.results-text-header {
|
||||
margin-top:5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,20 @@
|
|||
}
|
||||
|
||||
.wizard-buttons-holder {
|
||||
float:right;
|
||||
position:absolute;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
position:absolute;
|
||||
left:-6px;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
position:absolute;
|
||||
right:56px;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.wizard-step {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ class ApiMusicSessionsController < ApiController
|
|||
band,
|
||||
params[:genres],
|
||||
params[:tracks],
|
||||
params[:legal_terms])
|
||||
params[:legal_terms],
|
||||
params[:audio_latency])
|
||||
|
||||
if @music_session.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
|
|
@ -227,7 +228,8 @@ class ApiMusicSessionsController < ApiController
|
|||
params[:id],
|
||||
params[:client_id],
|
||||
params[:as_musician],
|
||||
params[:tracks])
|
||||
params[:tracks],
|
||||
params[:audio_latency])
|
||||
|
||||
if @connection.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class ApiSearchController < ApiController
|
|||
conn = (clientid ? Connection.where(client_id: clientid, user_id: current_user.id).first : nil)
|
||||
# puts "================== query #{query.inspect}"
|
||||
@search = Search.musician_filter(query, current_user, conn)
|
||||
# puts "================== search #{@search.inspect}"
|
||||
else
|
||||
@search = Search.band_filter(query, current_user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class ApiUsersController < ApiController
|
|||
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
|
||||
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
|
||||
:share_session, :share_recording,
|
||||
:affiliate_report]
|
||||
:affiliate_report, :audio_latency]
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
|
@ -527,6 +527,15 @@ class ApiUsersController < ApiController
|
|||
@user = current_user
|
||||
if params[:success]
|
||||
@user.update_progression_field(:first_certified_gear_at)
|
||||
|
||||
connection = Connection.find_by_client_id(params[:client_id])
|
||||
# update last_jam location information
|
||||
@user.update_addr_loc(connection, 'g') if connection
|
||||
|
||||
if !@user.errors.any?
|
||||
# update audio gear latency information
|
||||
@user.update_audio_latency(connection, params[:audio_latency])
|
||||
end
|
||||
else
|
||||
@user.failed_qualification(params[:reason])
|
||||
end
|
||||
|
|
@ -643,10 +652,16 @@ class ApiUsersController < ApiController
|
|||
|
||||
if play.errors.any?
|
||||
render :json => { :message => "Unexpected error occurred" }, :status => 500
|
||||
return
|
||||
else
|
||||
render :json => {}, :status => 201
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# updates audio latency on the user, and associated connection
|
||||
def audio_latency
|
||||
Connection.transaction do
|
||||
@user.update_audio_latency(Connection.find_by_client_id(params[:client_id]), params[:audio_latency])
|
||||
respond_with_model(@user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ else
|
|||
|
||||
child(:connections => :participants) {
|
||||
collection @music_sessions, :object_root => false
|
||||
attributes :ip_address, :client_id, :joined_session_at
|
||||
attributes :ip_address, :client_id, :joined_session_at, :audio_latency
|
||||
|
||||
node :user do |connection|
|
||||
{ :id => connection.user.id, :photo_url => connection.user.photo_url, :name => connection.user.name, :is_friend => connection.user.friends?(current_user), :connection_state => connection.aasm_state }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
object @user
|
||||
|
||||
attributes :id
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
object @user
|
||||
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency
|
||||
|
||||
if @user.musician?
|
||||
node :location do @user.location end
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@
|
|||
<td>{{profile.id}} <span data-current="{{profile.current}}">{{profile.active_text}}</span></td>
|
||||
<td class="actions">
|
||||
<a href="#" data-id="{{profile.id}}" data-purpose="activate-audio-profile" class="button-orange">ACTIVATE</a>
|
||||
{% if(data.is_admin) { %}
|
||||
<a href="#" data-id="{{profile.id}}" data-purpose="loopback-audio-profile" class="button-orange">LOOPBACK (admin only)</a>
|
||||
{% } %}
|
||||
<a href="#" data-id="{{profile.id}}" data-purpose="configure-audio-profile" class="button-orange">CONFIGURE</a>
|
||||
<a href="#" data-id="{{profile.id}}" data-purpose="delete-audio-profile" class="button-orange">DELETE</a></td>
|
||||
</tr>
|
||||
{% } %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
.dialog.configure-tracks{ layout: 'dialog', 'layout-id' => 'configure-tracks', id: 'configure-tracks-dialog'}
|
||||
.content-head
|
||||
= image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' }
|
||||
%h1 configure tracks
|
||||
.dialog-inner
|
||||
.dialog-tabs
|
||||
%a.selected.tab-configure-audio Music Audio
|
||||
%a.tab-configure-voice Voice Chat
|
||||
|
||||
.instructions
|
||||
%span
|
||||
Choose your audio device. Drag and drop to assign input ports to tracks, and specify the instrument
|
||||
for each track. Drag and drop to assign a pair of output ports for session stereo audio monitoring.
|
||||
|
||||
.clearall
|
||||
|
||||
.tab.no-selection-range{'tab-id' => 'music-audio'}
|
||||
|
||||
.column
|
||||
.certified-audio-profile-section
|
||||
.sub-header Certified Audio Profile
|
||||
%select.certified-audio-profile
|
||||
.clearall
|
||||
|
||||
.unused-audio-inputs-section
|
||||
.sub-header Unused Input Ports
|
||||
.unassigned-input-channels.channels-holder
|
||||
|
||||
.unused-audio-outputs-section
|
||||
.sub-header Unused Output Ports
|
||||
.unassigned-output-channels.channels-holder
|
||||
|
||||
.column
|
||||
.input-tracks-section
|
||||
.sub-column
|
||||
.sub-header Track Input Port(s)
|
||||
.input-tracks.tracks
|
||||
.sub-column
|
||||
.sub-header Instrument
|
||||
.instruments
|
||||
|
||||
.output-channels-section
|
||||
.sub-header Audio Output Port
|
||||
.output-channels
|
||||
.clearall
|
||||
|
||||
|
||||
|
||||
.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
|
||||
.clearall
|
||||
|
||||
.buttons
|
||||
%a.btn-add-new-audio-gear.button-grey{'layout-link' => 'add-new-audio-gear'} ADD NEW AUDIO GEAR
|
||||
%a.button-orange.btn-update-settings{href:'#'} UPDATE SETTINGS
|
||||
%a.button-grey.btn-cancel{href:'#'} CANCEL
|
||||
|
|
@ -28,4 +28,8 @@
|
|||
|
||||
<script type="text/template" id="template-help-push-resync-when-done">
|
||||
Push 'Resync' when done modifying Framesize, Buffer In, or Buffer Out.
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-help-move-on-loopback-success">
|
||||
You can move to the next step now.
|
||||
</script>
|
||||
|
|
@ -32,12 +32,12 @@
|
|||
</div>
|
||||
<!-- todo scott vfrs-1455 i need a template tutorial to define the three variables that substitute here:
|
||||
score:
|
||||
one_way_score is joined_score / 2;
|
||||
score_color is green, yellow, red, blue, or purple (depending upon value of joined_score)
|
||||
score_color_alt is good, moderate, poor, unacceptable,or missing
|
||||
musician_one_way_score is joined_score / 2;
|
||||
musician_score_color is green, yellow, red, blue, or purple (depending upon value of joined_score)
|
||||
musician_score_color_alt is good, moderate, poor, unacceptable,or missing
|
||||
-->
|
||||
<!-- <div class="left" style="***help***">
|
||||
{one_way_score} ms <img src="../assets/content/icon_{score_color}_score.png" alt="{score_color_alt}" width="12" height="12" align="absmiddle"/>
|
||||
{musician_one_way_score} <img src="../assets/content/icon_{musician_score_color}_score.png" alt="{musician_score_color_alt}" width="12" height="12" align="absmiddle"/>
|
||||
</div> -->
|
||||
<div class="right musician-following" style="width: 120px;">
|
||||
<div class="bold">FOLLOWING:</div>
|
||||
|
|
@ -58,10 +58,11 @@
|
|||
</div>
|
||||
<div class="button-row" data-hint="button-row">
|
||||
<div class="lcol stats left">
|
||||
{friend_count} <img src="../assets/content/icon_friend.png" width="14" height="12" align="absmiddle" style="margin-right:4px;"/>
|
||||
{follow_count} <img src="../assets/content/icon_followers.png" width="22" height="12" align="absmiddle" style="margin-right:4px;"/>
|
||||
{recording_count} <img src="../assets/content/icon_recordings.png" width="12" height="13" align="absmiddle" style="margin-right:4px;"/>
|
||||
{session_count} <img src="../assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle" />
|
||||
{friend_count} <img src="../assets/content/icon_friend.png" alt="friends" width="14" height="12" align="absmiddle" style="margin-right:4px;"/>
|
||||
{follow_count} <img src="../assets/content/icon_followers.png" alt="follows" width="22" height="12" align="absmiddle" style="margin-right:4px;"/>
|
||||
{recording_count} <img src="../assets/content/icon_recordings.png" alt="recordings" width="12" height="13" align="absmiddle" style="margin-right:4px;"/>
|
||||
{session_count} <img src="../assets/content/icon_session_tiny.png" alt="sessions" width="12" height="12" align="absmiddle" style="margin-right:4px;"/>
|
||||
{musician_one_way_score} <img src="../assets/content/icon_{musician_score_color}_score.png" alt="{musician_score_color_alt} score" width="12" height="12" align="absmiddle" style="margin-right:4px;"/>
|
||||
</div>
|
||||
<div class="result-list-button-wrapper" data-musician-id={musician_id}>
|
||||
{musician_action_template}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
.dialog.network-test{ layout: 'dialog', 'layout-id' => 'network-test', id: 'network-test-dialog'}
|
||||
.content-head
|
||||
%h1 Test Router & Network
|
||||
.dialog-inner
|
||||
= render :partial => '/clients/network_test'
|
||||
.clearall
|
||||
.buttons
|
||||
%a.button-grey.btn-cancel{href:'#'} CANCEL
|
||||
%a.button-orange.btn-help{href: '#'} HELP
|
||||
%a.button-orange.btn-close{href:'#'} CLOSE
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
.network-test
|
||||
.help-text In this step, you will test your router and Internet connection to ensure that you can play in online sessions, and to see how many musicians can be in a session with you based on your internet connection.
|
||||
.wizard-step-content
|
||||
.wizard-step-column
|
||||
%h2 Instructions
|
||||
.ftue-box.instructions
|
||||
%ul
|
||||
%li Check that computer is connected to router using Ethernet cable.
|
||||
%li Click Start Network Test button.
|
||||
%li View test results.
|
||||
.center
|
||||
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
|
||||
.wizard-step-column
|
||||
.summary
|
||||
%p Ensure that your computer is connected to your home router using an Ethernet cable rather than using Wi-Fi wireless access. If necessary, find or purchase a long Ethernet cable, up to 100 ft.
|
||||
%p Then click on the Start Network Test button below.
|
||||
.center
|
||||
%a.button-orange.start-network-test{href:'#'} START NETWORK TEST
|
||||
.wizard-step-column
|
||||
%h2 Test Results
|
||||
.network-test-results.ftue-box
|
||||
.scoring-bar
|
||||
.current-score
|
||||
testing...
|
||||
.subscore
|
||||
.good-marker
|
||||
.good-line
|
||||
.network-test-score
|
||||
.scored-clients
|
||||
.network-test-text
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
<!-- my tracks -->
|
||||
<div class="session-mytracks">
|
||||
<h2>my tracks</h2>
|
||||
<div id="track-settings" class="session-add" style="display:block;" layout-link="configure-audio">
|
||||
<div id="track-settings" class="session-add" style="display:block;" layout-link="configure-tracks">
|
||||
<%= image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18} %> Settings
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
- show_back ||= local_assigns[:show_back] = local_assigns.fetch(:show_back, true)
|
||||
- show_next ||= local_assigns[:show_next] = local_assigns.fetch(:show_next, true)
|
||||
|
||||
- total_steps = 7
|
||||
|
||||
.wizard-buttons
|
||||
.wizard-buttons-holder
|
||||
%a.button-grey.btn-help{href: '#'} HELP
|
||||
- if step > 0 && step != total_steps
|
||||
%a.button-orange.btn-back{href:'#'} BACK
|
||||
- if step != total_steps
|
||||
%a.button-orange.btn-next{href:'#'} NEXT
|
||||
- if step == total_steps
|
||||
%a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE
|
||||
- if step == total_steps
|
||||
%a.button-orange.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL
|
||||
|
||||
|
||||
|
||||
|
|
@ -19,7 +19,9 @@
|
|||
<%= render "ftue" %>
|
||||
<%= render "jamServer" %>
|
||||
<%= render "iconInstrumentSelect" %>
|
||||
<%= render "clients/gear/gear_wizard" %>
|
||||
<%= render "clients/wizard/buttons" %>
|
||||
<%= render "clients/wizard/gear/gear_wizard" %>
|
||||
<%= render "clients/wizard/loopback/loopback_wizard" %>
|
||||
<%= render "terms" %>
|
||||
<%= render "leaveSessionWarning" %>
|
||||
<%= render "rateSession" %>
|
||||
|
|
@ -46,6 +48,7 @@
|
|||
<%= render "friendSelector" %>
|
||||
<%= render "account_profile_avatar" %>
|
||||
<%= render "account_audio_profile" %>
|
||||
<%= render "configure_tracks_dialog" %>
|
||||
<%= render "invitationDialog" %>
|
||||
<%= render "inviteMusicians" %>
|
||||
<%= render "hoverBand" %>
|
||||
|
|
@ -55,6 +58,7 @@
|
|||
<%= render "hoverSession" %>
|
||||
<%= render "whatsNextDialog" %>
|
||||
<%= render "shareDialog" %>
|
||||
<%= render "networkTestDialog" %>
|
||||
<%= render "recordingFinishedDialog" %>
|
||||
<%= render "localRecordingsDialog" %>
|
||||
<%= render "showServerErrorDialog" %>
|
||||
|
|
@ -247,11 +251,21 @@
|
|||
var whatsNextDialog = new JK.WhatsNextDialog(JK.app);
|
||||
whatsNextDialog.initialize(invitationDialog);
|
||||
|
||||
var ftueWizard = new JK.FtueWizard(JK.app);
|
||||
ftueWizard.initialize();
|
||||
//var ftueWizard = new JK.FtueWizard(JK.app);
|
||||
//ftueWizard.initialize();
|
||||
|
||||
|
||||
var loopbackWizard = new JK.LoopbackWizard(JK.app);
|
||||
loopbackWizard.initialize();
|
||||
|
||||
var gearWizard = new JK.GearWizard(JK.app);
|
||||
gearWizard.initialize();
|
||||
gearWizard.initialize(loopbackWizard);
|
||||
|
||||
var configureTracksDialog = new JK.ConfigureTracksDialog(JK.app);
|
||||
configureTracksDialog.initialize();
|
||||
|
||||
var networkTestDialog = new JK.NetworkTestDialog(JK.app);
|
||||
networkTestDialog.initialize();
|
||||
|
||||
var testBridgeScreen = new JK.TestBridgeScreen(JK.app);
|
||||
testBridgeScreen.initialize();
|
||||
|
|
@ -270,8 +284,8 @@
|
|||
|
||||
JK.initJamClient();
|
||||
|
||||
// latency_tester does not want to be here
|
||||
if(window.jamClient.getOperatingMode() == "server") {
|
||||
// latency_tester does not want to be here - redirect it
|
||||
if(window.jamClient.getOperatingMode && window.jamClient.getOperatingMode() == "server") {
|
||||
window.location = "/latency_tester";
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
%script{type: 'text/template', id: 'template-wizard-buttons'}
|
||||
.wizard-buttons-holder
|
||||
.left-buttons
|
||||
%a.button-grey.btn-help{href: '#'} HELP
|
||||
%a.button-grey.btn-cancel{href:'#', 'layout-action' => 'close'} CANCEL
|
||||
.right-buttons
|
||||
%a.button-grey.btn-back{href:'#'} BACK
|
||||
%a.button-orange.btn-next{href:'#'} NEXT
|
||||
%a.button-orange.btn-close{href:'#', 'layout-action' => 'close'} CLOSE
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
.frame-and-buffers
|
||||
.framesize
|
||||
%h2 Frame
|
||||
%select.select-frame-size
|
||||
%option{val:'2.5'} 2.5
|
||||
%option{val:'5'} 5
|
||||
%option{val:'10'} 10
|
||||
.buffers
|
||||
%h2 Buffer In/Out
|
||||
%select.select-buffer-in
|
||||
%option{val:'0'} 0
|
||||
%option{val:'1'} 1
|
||||
%option{val:'2'} 2
|
||||
%option{val:'3'} 3
|
||||
%option{val:'4'} 4
|
||||
%option{val:'5'} 5
|
||||
%option{val:'6'} 6
|
||||
%option{val:'7'} 7
|
||||
%option{val:'8'} 8
|
||||
%option{val:'9'} 9
|
||||
%option{val:'10'} 10
|
||||
%select.select-buffer-out
|
||||
%option{val:'0'} 0
|
||||
%option{val:'1'} 1
|
||||
%option{val:'2'} 2
|
||||
%option{val:'3'} 3
|
||||
%option{val:'4'} 4
|
||||
%option{val:'5'} 5
|
||||
%option{val:'6'} 6
|
||||
%option{val:'7'} 7
|
||||
%option{val:'8'} 8
|
||||
%option{val:'9'} 9
|
||||
%option{val:'10'} 10
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
.ftue-box.results{'data-type' => test_type}
|
||||
.left.w50.center.white.scoring-section.latency-score-section
|
||||
.p5
|
||||
.latency LATENCY
|
||||
%span.latency-score
|
||||
.spinner-small
|
||||
.left.w50.center.white.scoring-section.io-score-section
|
||||
.p5
|
||||
.io I/O
|
||||
%span.io-skip-msg
|
||||
Not Tested
|
||||
%span.io-countdown
|
||||
%span.secs
|
||||
seconds left
|
||||
%span.io-rate<
|
||||
Rate=
|
||||
%span.io-rate-score>
|
||||
%span.io-var<
|
||||
Var=
|
||||
%span.io-var-score>
|
||||
.clearall
|
||||
%ul.results-text
|
||||
%li.latency-good Your latency is good.
|
||||
%li.latency-acceptable Your latency is acceptable.
|
||||
%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-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-bad Your I/O variance is poor.
|
||||
%li.success You may proceed to the next step.
|
||||
%li.failure
|
||||
%span.conditional{'data-type' => 'automated'} We're sorry, but your audio gear has failed. Please watch video or click HELP button below.
|
||||
%span.conditional{'data-type' => 'loopback'} We're sorry, but your audio gear has failed. Please watch videos on the previous screen.
|
||||
.unknown-text
|
||||
%div We cannot accurately predict the latency of your audio gear. To proceed, you must run an audio loopback test. Click button below to do this.
|
||||
%div.loopback-button-holder
|
||||
%a.button-orange.loopback-test{href:'#'} RUN LOOPBACK TEST
|
||||
.loopback-completed
|
||||
You have completed the loopback test successfully. Click Next to continue.
|
||||
|
|
@ -54,84 +54,17 @@
|
|||
%h2.audio-channels Audio Output Ports
|
||||
.ftue-box.list.ports.output-ports
|
||||
%a.button-orange.asio-settings-output-btn ASIO SETTINGS...
|
||||
.frame-and-buffers
|
||||
.framesize
|
||||
%h2 Frame
|
||||
%select.select-frame-size
|
||||
%option{val:'2.5'} 2.5
|
||||
%option{val:'5'} 5
|
||||
%option{val:'10'} 10
|
||||
.buffers
|
||||
%h2 Buffer In/Out
|
||||
%select.select-buffer-in
|
||||
%option{val:'0'} 0
|
||||
%option{val:'1'} 1
|
||||
%option{val:'2'} 2
|
||||
%option{val:'3'} 3
|
||||
%option{val:'4'} 4
|
||||
%option{val:'5'} 5
|
||||
%option{val:'6'} 6
|
||||
%option{val:'7'} 7
|
||||
%option{val:'8'} 8
|
||||
%option{val:'9'} 9
|
||||
%option{val:'10'} 10
|
||||
%select.select-buffer-out
|
||||
%option{val:'0'} 0
|
||||
%option{val:'1'} 1
|
||||
%option{val:'2'} 2
|
||||
%option{val:'3'} 3
|
||||
%option{val:'4'} 4
|
||||
%option{val:'5'} 5
|
||||
%option{val:'6'} 6
|
||||
%option{val:'7'} 7
|
||||
%option{val:'8'} 8
|
||||
%option{val:'9'} 9
|
||||
%option{val:'10'} 10
|
||||
= render :partial => "/clients/wizard/framebuffers"
|
||||
.wizard-step-column
|
||||
%h2 Test Results
|
||||
.ftue-box.results
|
||||
.left.w50.center.white.scoring-section.latency-score-section
|
||||
.p5
|
||||
.latency LATENCY
|
||||
%span.latency-score
|
||||
.left.w50.center.white.scoring-section.io-score-section
|
||||
.p5
|
||||
.io I/O
|
||||
%span.io-skip-msg
|
||||
Not Tested
|
||||
%span.io-countdown
|
||||
%span.secs
|
||||
seconds left
|
||||
%span.io-rate<
|
||||
Rate=
|
||||
%span.io-rate-score>
|
||||
%span.io-var<
|
||||
Var=
|
||||
%span.io-var-score>
|
||||
.clearall
|
||||
%ul.results-text
|
||||
%li.latency-good Your latency is good.
|
||||
%li.latency-acceptable Your latency is acceptable.
|
||||
%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-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-bad Your I/O variance is poor.
|
||||
%li.success You may proceed to the next step.
|
||||
%li.failure We're sorry, but your audio gear has failed. Please watch video or click HELP button below.
|
||||
.unknown-text
|
||||
%div We cannot accurately predict the latency of your audio gear. To proceed, you must run an audio loopback test. Click button below to do this.
|
||||
%div.loopback-button-holder
|
||||
%a.button-orange.loopback-test{href:'#'} RUN LOOPBACK TEST
|
||||
= render :partial => "/clients/wizard/gear_test", locals: {test_type: 'automated'}
|
||||
.clearall
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "2", 'dialog-title' => "Configure Tracks", 'dialog-purpose' => "ConfigureTracks" }
|
||||
.ftuesteps
|
||||
.clearall
|
||||
.help-text In this step, you will select, configure, and test your audio gear. Please watch the video for best instructions.
|
||||
.wizard-step-content
|
||||
.wizard-step-content.no-selection-range
|
||||
.wizard-step-column
|
||||
%h2 Instructions
|
||||
.ftue-box.instructions
|
||||
|
|
@ -141,15 +74,17 @@
|
|||
%li Select the instrument for each track.
|
||||
.center
|
||||
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
|
||||
.wizard-step-column.no-selection-range
|
||||
.wizard-step-column
|
||||
%h2 Unassigned Ports
|
||||
.unassigned-channels
|
||||
.wizard-step-column.no-selection-range
|
||||
.unassigned-input-channels.channels-holder
|
||||
.wizard-step-column
|
||||
%h2 Track Input Port(s)
|
||||
.tracks
|
||||
.wizard-step-column.no-selection-range
|
||||
.wizard-step-column
|
||||
%h2 Instrument
|
||||
.instruments
|
||||
.output-channels
|
||||
.unassigned-output-channels.channels-holder
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "3", 'dialog-title' => "Configure Voice Chat", 'dialog-purpose' => "ConfigureVoiceChat" }
|
||||
.ftuesteps
|
||||
|
|
@ -165,14 +100,15 @@
|
|||
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
|
||||
.wizard-step-column
|
||||
%h2 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
|
||||
%form.voice
|
||||
.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
|
||||
.wizard-step-column
|
||||
%h2 Voice Chat Input
|
||||
.ftue-box.chat-inputs
|
||||
|
|
@ -209,39 +145,10 @@
|
|||
%span.direct-monitoring-btn Play
|
||||
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "5", 'dialog-title' => "Test Router & Network", 'dialog-purpose' => "TestRouterNetwork" }
|
||||
.wizard-step.network-test{ 'layout-wizard-step' => "5", 'dialog-title' => "Test Router & Network", 'dialog-purpose' => "TestRouterNetwork" }
|
||||
.ftuesteps
|
||||
.clearall
|
||||
.help-text In this step, you will test your router and Internet connection to ensure that you can play in online sessions, and to see how many musicians can be in a session with you based on your internet connection.
|
||||
.wizard-step-content
|
||||
.wizard-step-column
|
||||
%h2 Instructions
|
||||
.ftue-box.instructions
|
||||
%ul
|
||||
%li Check that computer is connected to router using Ethernet cable.
|
||||
%li Click Start Network Test button.
|
||||
%li View test results.
|
||||
.center
|
||||
%a.button-orange.watch-video{href:'#'} WATCH VIDEO
|
||||
.wizard-step-column
|
||||
.summary
|
||||
%p Ensure that your computer is connected to your home router using an Ethernet cable rather than using Wi-Fi wireless access. If necessary, find or purchase a long Ethernet cable, up to 100 ft.
|
||||
%p Then click on the Start Network Test button below.
|
||||
.center
|
||||
%a.button-orange.start-network-test{href:'#'} START NETWORK TEST
|
||||
.wizard-step-column
|
||||
%h2 Test Results
|
||||
.network-test-results.ftue-box
|
||||
.scoring-bar
|
||||
.current-score
|
||||
testing...
|
||||
.subscore
|
||||
.good-marker
|
||||
.good-line
|
||||
.network-test-score
|
||||
.scored-clients
|
||||
.network-test-text
|
||||
|
||||
= render :partial => '/clients/network_test'
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "6", 'dialog-title' => "Success!", 'dialog-purpose' => "Success" }
|
||||
.ftuesteps
|
||||
|
|
@ -256,23 +163,23 @@
|
|||
%h2 Tutorial Videos
|
||||
%ul
|
||||
%li
|
||||
%a Creating a Session
|
||||
%a{href: '#'} Creating a Session
|
||||
%li
|
||||
%a Finding a Session
|
||||
%a{href: '#'} Finding a Session
|
||||
%li
|
||||
%a Playing in a Session
|
||||
%a{href: '#'} Playing in a Session
|
||||
%li
|
||||
%a Connecting with Other Musicians
|
||||
%a{href: '#'} Connecting with Other Musicians
|
||||
%li
|
||||
%a Making and Sharing Recordings
|
||||
%a{href: '#'} Making and Sharing Recordings
|
||||
%li
|
||||
%a Broadcasting Your Sessions
|
||||
%a{href: '#'} Broadcasting Your Sessions
|
||||
%h2 Other Valuable Resource Links
|
||||
%ul
|
||||
%li
|
||||
%a JamKazam Support Center
|
||||
%a{href: '#'} JamKazam Support Center
|
||||
%li
|
||||
%a JamKazam Community Forum
|
||||
%a{href: '#'} JamKazam Community Forum
|
||||
.wizard-buttons
|
||||
|
||||
%script{type: 'text/template', id: 'template-ftuesteps'}
|
||||
|
|
@ -313,11 +220,19 @@
|
|||
%script{type: 'text/template', id: 'template-track-target'}
|
||||
.track{'data-num' => '{{data.num}}'}
|
||||
.num {{data.num + 1}}:
|
||||
.track-target{'data-num' => '{{data.num}}', 'track-count' => 0}
|
||||
.track-target.target{'data-num' => '{{data.num}}', 'track-count' => 0}
|
||||
%span.placeholder None
|
||||
|
||||
|
||||
%script{type: 'text/template', id: 'template-output-target'}
|
||||
.output{'data-num' => '{{data.num}}'}
|
||||
.num {{data.num + 1}}:
|
||||
.output-target.target{'data-num' => '{{data.num}}', 'output-count' => 0}
|
||||
%span.placeholder None
|
||||
|
||||
|
||||
%script{type: 'text/template', id: 'template-chat-input'}
|
||||
.chat-input
|
||||
%input{type:"radio", name: "chat-device", 'data-channel-id' => '{{data.id}}', 'data-channel-name' => '{{data.name}}'}
|
||||
%p
|
||||
= '{{data.name}}'
|
||||
%span {{data.name}}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
.dialog.loopback-wizard{ layout: 'dialog', 'layout-id' => 'loopback-wizard', id: 'loopback-wizard-dialog'}
|
||||
.content-head
|
||||
%h1 loopback latency test
|
||||
|
||||
.ftue-inner{ 'layout-wizard' => 'loopback-wizard' }
|
||||
.wizard-step{ 'layout-wizard-step' => "0", 'dialog-title' => "Welcome", 'dialog-purpose' => "Intro"}
|
||||
.help-text
|
||||
Please identify which of the three types of audio gear below you
|
||||
are going to use with the JamKazam service, and click one to
|
||||
watch a video on how to navigate this initial setup and testing
|
||||
process. After watching the video, click the 'NEXT' button to
|
||||
get started. If you don't have your audio gear handy now, click
|
||||
Cancel.
|
||||
|
||||
|
||||
%ul.device_type
|
||||
%li.ftue-video-link.first{'external-link-win'=>"http://www.youtube.com/watch?v=b1JrwGeUcOo", 'external-link-mac'=>"http://www.youtube.com/watch?v=TRzb7OTlO-Q"}
|
||||
AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS
|
||||
%br
|
||||
%p
|
||||
= image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70}
|
||||
|
||||
%li.ftue-video-link{'external-link-win'=>"http://www.youtube.com/watch?v=IDrLa8TOXwQ",'external-link-mac'=>"http://www.youtube.com/watch?v=vIs7ArrjMpE"}
|
||||
USB MICROPHONE
|
||||
%br
|
||||
%p
|
||||
= image_tag "content/microphone_ftue.png", {:width => 70, :height => 113}
|
||||
%li.ftue-video-link{'external-link-win'=>"http://www.youtube.com/watch?v=PCri4Xed4CA",'external-link-mac'=>"http://www.youtube.com/watch?v=Gatmd_ja47U"}
|
||||
COMPUTER'S BUILT-IN MIC & SPEAKERS/HEADPHONES
|
||||
%br
|
||||
%p
|
||||
= image_tag "content/computer_ftue.png", {:width => 118, :height => 105}
|
||||
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "1", 'dialog-title' => "Verify Audio", 'dialog-purpose' => "VerifyAudio" }
|
||||
.help-text
|
||||
Your currently configured audio gear is shown below. Before proceeding to the next step of the loopback test, play and speak and use the gain sliders
|
||||
so that you can clearly hear both your instrument and/or voice through your headphones. When done click Next.
|
||||
|
||||
.wizard-step-content
|
||||
.wizard-step-column
|
||||
%h2
|
||||
Audio Input
|
||||
%a.button-orange.asio-settings-input-btn ASIO SETTINGS
|
||||
.audio-device.input Unknown
|
||||
%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
|
||||
.gain-label GAIN
|
||||
.ftue-vu-right#loopback-audio-input-vu-right
|
||||
= render :partial => "/clients/wizard/framebuffers"
|
||||
.wizard-step-column
|
||||
%h2
|
||||
Audio Output
|
||||
%a.button-orange.asio-settings-output-btn ASIO SETTINGS
|
||||
.audio-device.output Unknown
|
||||
%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
|
||||
.gain-label GAIN
|
||||
.ftue-vu-right#loopback-audio-output-vu-right
|
||||
%a.button-orange.resync-btn RESYNC
|
||||
.wizard-step-column
|
||||
%h2.test-results-header
|
||||
Test & Results
|
||||
%a.button-orange.run-test-btn RUN TEST
|
||||
= render :partial => "/clients/wizard/gear_test", locals: {test_type: 'loopback'}
|
||||
.clearall
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "2", 'dialog-title' => "Congratulations", 'dialog-purpose' => "Congratulations" }
|
||||
.help-text
|
||||
Congratulations!
|
||||
%br
|
||||
%br
|
||||
You have successfully scored your audio gear using the loopback test. Close the dialog to proceed.
|
||||
|
||||
.wizard-buttons
|
||||
|
|
@ -37,6 +37,9 @@
|
|||
</ul>
|
||||
</li>
|
||||
<li class="download-app"><%= link_to "Download App", downloads_path, :rel => "external" %></li>
|
||||
<% if @nativeClient %>
|
||||
<li class="test-network"><%= link_to "Test Network", '#' %></li>
|
||||
<% end %>
|
||||
<li class="community-forum"><%= link_to "Community Forum", Rails.application.config.vanilla_url, :rel => "external" %></li>
|
||||
<li class="get-help"><%= link_to "Get Help", 'https://jamkazam.desk.com/', :rel => "external" %></li>
|
||||
<li class="sign-out"><%= link_to "Sign Out", signout_path, method: "delete" %></li>
|
||||
|
|
|
|||
|
|
@ -283,6 +283,9 @@ SampleApp::Application.routes.draw do
|
|||
match '/users/progression/certified_gear' => 'api_users#qualified_gear', :via => :post
|
||||
match '/users/progression/social_promoted' => 'api_users#social_promoted', :via => :post
|
||||
|
||||
# audio latency
|
||||
match '/users/:id/audio_latency' => 'api_users#audio_latency', :via => :post
|
||||
|
||||
# social
|
||||
match '/users/:id/share/session/:provider' => 'api_users#share_session', :via => :get
|
||||
match '/users/:id/share/recording/:provider' => 'api_users#share_recording', :via => :get
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ MusicSessionManager < BaseManager
|
|||
@log = Logging.logger[self]
|
||||
end
|
||||
|
||||
def create(music_session, user, client_id, description, musician_access, approval_required, fan_chat, fan_access, band, genres, tracks, legal_terms)
|
||||
def create(music_session, user, client_id, description, musician_access, approval_required, fan_chat, fan_access, band, genres, tracks, legal_terms, audio_latency)
|
||||
return_value = nil
|
||||
|
||||
time = Benchmark.realtime do
|
||||
|
|
@ -38,7 +38,7 @@ MusicSessionManager < BaseManager
|
|||
|
||||
# auto-join this user into the newly created session
|
||||
as_musician = true
|
||||
connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks)
|
||||
connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency)
|
||||
|
||||
unless connection.errors.any?
|
||||
user.update_progression_field(:first_music_session_at)
|
||||
|
|
@ -83,7 +83,7 @@ MusicSessionManager < BaseManager
|
|||
music_session
|
||||
end
|
||||
|
||||
def participant_create(user, music_session_id, client_id, as_musician, tracks)
|
||||
def participant_create(user, music_session_id, client_id, as_musician, tracks, audio_latency)
|
||||
connection = nil
|
||||
music_session = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
|
|
@ -92,7 +92,7 @@ MusicSessionManager < BaseManager
|
|||
|
||||
music_session.with_lock do # VRFS-1297
|
||||
music_session.tick_track_changes
|
||||
connection = ConnectionManager.new.join_music_session(user, client_id, music_session, as_musician, tracks)
|
||||
connection = ConnectionManager.new.join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency)
|
||||
|
||||
if connection.errors.any?
|
||||
# rollback the transaction to make sure nothing is disturbed in the database
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe ApiClaimedRecordingsController do
|
|||
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
|
||||
# @music_session.connections << @connection
|
||||
@music_session.save
|
||||
@connection.join_the_session(@music_session, true, nil, @user)
|
||||
@connection.join_the_session(@music_session, true, nil, @user, 10)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiUsersController do
|
||||
render_views
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let (:conn) { FactoryGirl.create(:connection, :user => user) }
|
||||
|
||||
|
||||
before(:each) do
|
||||
controller.current_user = user
|
||||
end
|
||||
|
||||
describe "audio_latency" do
|
||||
it "updates both connection and user" do
|
||||
|
||||
post :audio_latency, id: user.id, client_id: conn.client_id, audio_latency: 1.5, :format => 'json'
|
||||
response.should be_success
|
||||
|
||||
conn.reload
|
||||
conn.last_jam_audio_latency.should == 1.5
|
||||
|
||||
user.reload
|
||||
conn.user.last_jam_audio_latency.should == 1.5
|
||||
end
|
||||
|
||||
it "if connection does not exist, user is still updated" do
|
||||
|
||||
post :audio_latency, id: user.id, client_id: 'nothingness', audio_latency: 1.5, :format => 'json'
|
||||
response.should be_success
|
||||
|
||||
user.reload
|
||||
conn.user.last_jam_audio_latency.should == 1.5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -483,4 +483,25 @@ FactoryGirl.define do
|
|||
cancel_all false
|
||||
end
|
||||
|
||||
end
|
||||
factory :diagnostic, :class => JamRuby::Diagnostic do
|
||||
type JamRuby::Diagnostic::NO_HEARTBEAT_ACK
|
||||
creator JamRuby::Diagnostic::CLIENT
|
||||
data Faker::Lorem.sentence
|
||||
end
|
||||
|
||||
factory :latency_tester, :class => JamRuby::LatencyTester do
|
||||
ignore do
|
||||
connection nil
|
||||
make_connection true
|
||||
end
|
||||
|
||||
sequence(:client_id) { |n| "LatencyTesterClientId-#{n}" }
|
||||
|
||||
after(:create) do |latency_tester, evaluator|
|
||||
latency_tester.connection = evaluator.connection if evaluator.connection
|
||||
latency_tester.connection = FactoryGirl.create(:connection, client_type: Connection::TYPE_LATENCY_TESTER, client_id: latency_tester.client_id) if evaluator.make_connection
|
||||
latency_tester.save
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => true, :slow => true do
|
||||
|
||||
subject { page }
|
||||
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
emulate_client
|
||||
sign_in_poltergeist user
|
||||
end
|
||||
|
||||
it "launches from audio profile screen" do
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -7,10 +7,12 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru
|
|||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
LatencyTester.delete_all
|
||||
sign_in_poltergeist user
|
||||
end
|
||||
|
||||
it "success path" do
|
||||
FactoryGirl.create(:latency_tester)
|
||||
visit '/client#/account/audio'
|
||||
# step 1 - intro
|
||||
find("div.account-audio a[data-purpose='add-profile']").trigger(:click)
|
||||
|
|
@ -23,6 +25,12 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru
|
|||
|
||||
# step 3 - configure tracks
|
||||
find('.ftue-step-title', text: 'Configure Tracks')
|
||||
|
||||
# drag one input over to tracks area http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Element#drag_to-instance_method
|
||||
input = first('.ftue-input')
|
||||
track_slot = first('.track-target')
|
||||
input.drag_to(track_slot)
|
||||
|
||||
find('.btn-next.button-orange').trigger(:click)
|
||||
|
||||
# step 4 - configure voice chat
|
||||
|
|
@ -37,7 +45,6 @@ describe "Gear Wizard", :js => true, :type => :feature, :capybara_feature => tru
|
|||
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-grey.start-network-test').trigger(:click)
|
||||
find('.button-orange.start-network-test')
|
||||
find('.btn-next.button-orange').trigger(:click)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr
|
|||
wait_for_ajax
|
||||
expect(page).to have_selector('h1', text: 'configure tracks')
|
||||
|
||||
find('#btn-add-new-audio-gear').trigger(:click)
|
||||
find('.btn-add-new-audio-gear').trigger(:click)
|
||||
wait_for_ajax
|
||||
expect(page).to have_selector('h1', text: 'add new audio gear')
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue