merge develop

This commit is contained in:
Brian Smith 2015-04-09 20:47:01 -04:00
commit 246efbdaa8
69 changed files with 1527 additions and 916 deletions

View File

@ -265,7 +265,6 @@ connection_metronome.sql
preview_jam_track_tracks.sql
cohorts.sql
jam_track_right_admin_purchase.sql
alter_genre_player_unique_constraint.sql
jam_track_playable_plays.sql
shopping_cart_anonymous.sql
user_reuse_card_and_reedem.sql
@ -275,8 +274,10 @@ recording_client_metadata.sql
preview_support_mp3.sql
jam_track_duration.sql
sales.sql
show_whats_next_count.sql
alter_type_columns.sql
user_presences_rename.sql
add_genre_type.sql
add_description_to_perf_samples.sql
musician_search.sql
alter_genre_player_unique_constraint.sql
musician_search.sql

View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN show_whats_next_count INTEGER NOT NULL DEFAULT 0;

View File

@ -24,11 +24,12 @@ module JamRuby
validates :metronome_open, :inclusion => {:in => [true, false]}
validates :as_musician, :inclusion => {:in => [true, false, nil]}
validates :client_type, :inclusion => {:in => CLIENT_TYPES}
validates_numericality_of :last_jam_audio_latency, greater_than:0, :allow_nil => true
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
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
# this is no longer required with the new no-input profile
#after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
after_create :did_create
after_save :report_add_participant
@ -62,11 +63,11 @@ module JamRuby
def state_message
case self.aasm_state.to_sym
when CONNECT_STATE
'Connected'
when STALE_STATE
'Stale'
'Connected'
when STALE_STATE
'Stale'
else
'Idle'
'Idle'
end
end
@ -85,7 +86,7 @@ module JamRuby
def joining_session?
joining_session
end
def can_join_music_session
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?
@ -183,8 +184,8 @@ module JamRuby
end
def associate_tracks(tracks)
self.tracks.clear()
unless tracks.nil?
self.tracks.clear()
tracks.each do |track|
t = Track.new
t.instrument = Instrument.find(track["instrument_id"])

View File

@ -17,7 +17,7 @@ module JamRuby
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, as: :admin
:jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
@ -161,16 +161,21 @@ module JamRuby
query = query.where("original_artist=?", options[:artist])
end
if options[:group_artist]
query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version, MIN(genre_id) AS genre_id")
query = query.group("original_artist")
query = query.order('jam_tracks.original_artist')
else
query = query.group("jam_tracks.id")
query = query.order('jam_tracks.name')
end
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
query = query.group("jam_tracks.id")
query = query.order('jam_tracks.name')
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
if query.length == 0
[query, nil]

View File

@ -43,7 +43,7 @@ module JamRuby
session_user_history.music_session_id = music_session_id
session_user_history.user_id = user_id
session_user_history.client_id = client_id
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|")
session_user_history.instruments = tracks.map {|t| t[:instrument_id]}.join("|") if tracks
session_user_history.save
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -18,37 +18,28 @@ context.JK.AccountJamTracks = class AccountJamTracks
@screen = $('#account-jamtracks')
beforeShow:() =>
@logger.debug("beforeShow")
rest.getPurchasedJamTracks({})
.done(@populateJamTracks)
.fail(@app.ajaxError);
afterShow:() =>
@logger.debug("afterShow")
populateJamTracks:(data) =>
@logger.debug("populateJamTracks", data)
template = context._.template($('#template-account-jamtrack').html(), {jamtracks:data.jamtracks}, { variable: 'data' })
# template = context._.template($('#template-account-jamtrack').html(), {
# jamtracks: data.jamtracks
# current_user: @userId
# }, variable: 'data')
@logger.debug("TEMPLATE", template)
this.appendJamTracks template
@screen.find('.jamtrack-solo-session').on 'click', @soloSession
@screen.find('.jamtrack-group-session').on 'click', @groupSession
if (data.jamtracks? && data.jamtracks.length > 0)
@screen.find(".no-jamtracks-found").addClass("hidden")
@appendJamTracks context._.template($('#template-account-jamtrack').html(), {jamtracks:data.jamtracks}, { variable: 'data' })
@screen.find('.jamtrack-solo-session').on 'click', @soloSession
@screen.find('.jamtrack-group-session').on 'click', @groupSession
else
@screen.find(".no-jamtracks-found").removeClass("hidden")
appendJamTracks:(template) =>
$('#account-my-jamtracks table tbody').replaceWith template
soloSession:(e) =>
#context.location="client#/createSession"
@logger.debug "BLEH", e
jamRow = $(e.target).parents("tr")
@logger.debug "BLEH2", e, jamRow.data()
@createSession(jamRow.data(), true)
#@logger.debug "BLEH", $(this), $(this).data()
groupSession:(e) =>
#context.location="client#/createSession"
@ -59,7 +50,6 @@ context.JK.AccountJamTracks = class AccountJamTracks
tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient)
if (context.JK.guardAgainstBrowser(@app))
@logger.debug("CRATING SESSION", sessionData.genre, solo)
data = {}
data.client_id = @app.clientId
#data.description = $('#description').val()

View File

@ -0,0 +1,18 @@
# one time init stuff for the /client view
$ = jQuery
context = window
context.JK ||= {};
context.JK.ClientInit = class ClientInit
constructor: () ->
@logger = context.JK.logger
@gearUtils = context.JK.GearUtils
init: () =>
if context.gon.isNativeClient
this.nativeClientInit()
nativeClientInit: () =>
@gearUtils.bootstrapDefaultPlaybackProfile();

View File

@ -9,6 +9,9 @@
var $dialog = null;
var $dontShowAgain = null;
var $setupGearBtn = null;
var $browserJamTrackBtn = null;
var $jamTrackSection = null;
var $jamTracksLimitedTime = null;
function handleStartAudioQualification() {
@ -45,6 +48,12 @@
return false;
})
$browserJamTrackBtn.click(function() {
app.layout.closeDialog('getting-started')
window.location = '/client#/jamtrack'
return false;
})
$('#getting-started-dialog a.facebook-invite').on('click', function (e) {
invitationDialog.showFacebookDialog(e);
});
@ -59,13 +68,21 @@
}
function beforeShow() {
app.user().done(function(user) {
var jamtrackRule = user.free_jamtrack ? 'has-free-jamtrack' : 'no-free-jamtrack'
$jamTrackSection.removeClass('has-free-jamtrack').removeClass('no-free-jamtrack').addClass(jamtrackRule)
if(user.free_jamtrack) {
$jamTracksLimitedTime.removeClass('hidden')
}
})
}
function beforeHide() {
var showWhatsNext = !$dontShowAgain.is(':checked')
app.user().done(function(user) {
app.updateUserModel({show_whats_next: showWhatsNext, show_whats_next_count: user.show_whats_next_count + 1})
})
if ($dontShowAgain.is(':checked')) {
app.updateUserModel({show_whats_next: false})
}
}
function initializeButtons() {
@ -84,6 +101,9 @@
$dialog = $('#getting-started-dialog');
$dontShowAgain = $dialog.find('#show_getting_started');
$setupGearBtn = $dialog.find('.setup-gear-btn')
$browserJamTrackBtn = $dialog.find('.browse-jamtrack');
$jamTrackSection = $dialog.find('.get-a-free-jamtrack-section')
$jamTracksLimitedTime = $dialog.find('.jamtracks-limited-time')
registerEvents();

View File

@ -3,6 +3,7 @@
context.JK = context.JK || {};
context.JK.SessionSettingsDialog = function(app, sessionScreen) {
var logger = context.JK.logger;
var gearUtils = context.JK.GearUtilsInstance;
var $dialog;
var $screen = $('#session-settings');
var $selectedFilenames = $screen.find('#selected-filenames');
@ -15,6 +16,8 @@
function beforeShow(data) {
var canPlayWithOthers = gearUtils.canPlayWithOthers();
context.JK.GenreSelectorHelper.render('#session-settings-genre');
$dialog = $('[layout-id="session-settings"]');
@ -72,6 +75,10 @@
context.JK.dropdown($('#session-settings-language'));
context.JK.dropdown($('#session-settings-musician-access'));
context.JK.dropdown($('#session-settings-fan-access'));
var easyDropDownState = canPlayWithOthers.canPlay ? 'enable' : 'disable'
$('#session-settings-musician-access').easyDropDown(easyDropDownState)
$('#session-settings-fan-access').easyDropDown(easyDropDownState)
}
function saveSettings(evt) {

View File

@ -0,0 +1,57 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.SinglePlayerProfileGuardDialog = class SinglePlayerProfileGuardDialog
constructor: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@gearUtils = context.JK.GearUtilsInstance
@screen = null
@dialogId = 'single-player-profile-dialog';
@dialog = null;
initialize:() =>
dialogBindings = {
'beforeShow' : @beforeShow,
'afterShow' : @afterShow
}
@dialog = $('[layout-id="' + @dialogId + '"]');
@app.bindDialog(@dialogId, dialogBindings);
@content = @dialog.find(".dialog-inner")
@audioLatency = @dialog.find('.audio-latency')
@btnPrivateSession = @dialog.find('.btn-private-session')
@btnGearSetup = @dialog.find('.btn-gear-setup')
@btnPrivateSession.on('click', @onPrivateSessionChoice)
@btnGearSetup.on('click', @onGearSetupChoice)
beforeShow:() =>
@dialog.data('result', { choice: null})
afterShow:() =>
canPlayWithOthers = @gearUtils.canPlayWithOthers()
if canPlayWithOthers.isNoInputProfile
@content.removeClass('high-latency').addClass('has-no-inputs')
else
@content.removeClass('has-no-input').addClass('high-latency')
latency = '?'
if canPlayWithOthers.audioLatency?
latency = canPlayWithOthers.audioLatency
@audioLatency.text("#{latency} milliseconds.")
onPrivateSessionChoice: () =>
@dialog.data('result', { choice: 'private_session'})
@app.layout.closeDialog(@dialogId)
return false
onGearSetupChoice: () =>
@dialog.data('result', { choice: 'gear_setup'})
@app.layout.closeDialog(@dialogId)
return false

View File

@ -14,7 +14,7 @@
if (!context.jamClient || !context.jamClient.IsNativeClient()) {
$('#video-dialog-header').html($self.data('video-header') || $self.attr('data-video-header'));
$('#video-dialog-iframe').attr('src', $self.data('video-url') || $self.atr('data-video-url'));
$('#video-dialog-iframe').attr('src', $self.data('video-url') || $self.attr('data-video-url'));
app.layout.showDialog('video-dialog');
e.stopPropagation();
e.preventDefault();
@ -29,6 +29,7 @@
function events() {
$('.carousel .slides').on('click', '.slideItem', videoClick);
$('.video-slide').on('click', videoClick);
$('.video-item').on('click', videoClick);
$(dialogId + '-close').click(function (e) {
app.layout.closeDialog('video-dialog');

View File

@ -204,8 +204,7 @@
var user = app.user()
if(user) {
user.done(function(userProfile) {
console.log("app.layout.getCurrentScreen() != 'checkoutOrderScreen'", app.layout.getCurrentScreen())
if (userProfile.show_whats_next &&
if (userProfile.show_whats_next && userProfile.show_whats_next_count < 10 &&
window.location.pathname.indexOf(gon.client_path) == 0 &&
window.location.pathname.indexOf('/checkout') == -1 &&
!app.layout.isDialogShowing('getting-started'))

View File

@ -25,8 +25,9 @@
var metronomeBPM=false;
var metronomeSound=false;
var metronomeMeter=0;
var backingTrackPath="";
var backingTrackLoop=false;
var backingTrackPath = "";
var backingTrackLoop = false;
var simulateNoInputs = false;
function dbg(msg) { logger.debug('FakeJamClient: ' + msg); }
@ -47,7 +48,13 @@
function FTUEPageLeave() {}
function FTUECancel() {}
function FTUEGetMusicProfileName() {
return "FTUEAttempt-1"
if(simulateNoInputs) {
return "System Default (Playback Only)"
}
else {
return "FTUEAttempt-1"
}
}
function FTUESetMusicProfileName() {
@ -266,6 +273,10 @@
return false;
}
function FTUECreateUpdatePlayBackProfile() {
return true;
}
function RegisterVolChangeCallBack(functionName) {
dbg('RegisterVolChangeCallBack');
}
@ -444,6 +455,10 @@
];
var response = [];
for (var i=0; i<mixerIds.length; i++) {
// for testing no inputs, set simulateNoInputs = true
if(simulateNoInputs && i == 2) continue;
response.push({
client_id: clientIds[i],
group_id: groups[i],
@ -1012,6 +1027,7 @@
this.FTUELoadAudioConfiguration = FTUELoadAudioConfiguration;
this.FTUEClearChannelAssignments = FTUEClearChannelAssignments;
this.FTUEClearChatInput = FTUEClearChatInput;
this.FTUECreateUpdatePlayBackProfile = FTUECreateUpdatePlayBackProfile;
// Session
this.SessionAddTrack = SessionAddTrack;

View File

@ -20,8 +20,8 @@ context.JK.JamTrackScreen=class JamTrackScreen
@currentPage = 0
@next = null
@currentQuery = this.defaultQuery()
@expanded = false
@expanded = null
beforeShow:(data) =>
this.setFilterFromURL()
this.refresh()
@ -61,16 +61,15 @@ context.JK.JamTrackScreen=class JamTrackScreen
for v in raw_vars
[key, val] = v.split("=")
params[key] = decodeURIComponent(val)
ms
params
refresh:() =>
@currentQuery = this.buildQuery()
that = this
rest.getJamtracks(@currentQuery).done((response) ->
rest.getJamtracks(@currentQuery).done((response) =>
that.clearResults()
that.handleJamtrackResponse(response)
).fail (jqXHR) ->
).fail (jqXHR) =>
that.clearResults()
that.noMoreJamtracks.show()
that.app.notifyServerError jqXHR, 'Jamtrack Unavailable'
@ -118,7 +117,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
# if we less results than asked for, end searching
@scroller.infinitescroll 'pause'
if @currentPage == 0 and response.jamtracks.length == 0
@content.append '<div class=\'no-jamtracks-msg\'>There\'s no jamtracks.</div>'
@content.append '<div class=\'no-jamtracks-msg\'>No JamTracks found.</div>'
if @currentPage > 0
@noMoreJamtracks.show()
# there are bugs with infinitescroll not removing the 'loading'.
@ -131,6 +130,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
registerInfiniteScroll:() =>
that = this
@scroller.infinitescroll {
behavior: 'local'
navSelector: '#jamtrackScreen .btn-next-pager'
@ -143,10 +143,10 @@ context.JK.JamTrackScreen=class JamTrackScreen
loading:
msg: $('<div class="infinite-scroll-loader">Loading ...</div>')
img: '/assets/shared/spinner.gif'
path: (page) ->
'/api/jamtracks?' + $.param(this.buildQuery())
path: (page) =>
'/api/jamtracks?' + $.param(that.buildQuery())
}, (json, opts) ->
}, (json, opts) =>
this.handleJamtrackResponse(json)
@scroller.infinitescroll 'resume'
@ -156,7 +156,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
addToCartJamtrack:(e) =>
e.preventDefault()
params = id: $(e.target).attr('data-jamtrack-id')
rest.addJamtrackToShoppingCart(params).done((response) ->
rest.addJamtrackToShoppingCart(params).done((response) =>
context.location = '/client#/shoppingCart'
).fail @app.ajaxError
@ -165,33 +165,61 @@ context.JK.JamTrackScreen=class JamTrackScreen
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:() =>
@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription
#@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription
@screen.find('.play-button').on 'click', this.playJamtrack
@screen.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack
@screen.find('.license-us-why').on 'click', this.licenseUSWhy
@screen.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded
# @screen.find('.jamtrack-preview').each (index, element) =>
# new JK.JamTrackPreview(data.app, $element, jamTrack, track, {master_shows_duration: true})
rerenderJamtracks:() =>
if @currentData?
@clearResults()
@renderJamtracks(@currentData)
false
renderJamtracks:(data) =>
@currentData = data
that = this
for jamtrack in data.jamtracks
jamtrackExpanded = this.expanded==jamtrack.id
trackRow = _.clone(jamtrack)
trackRow.track_cnt = jamtrack.tracks.length
trackRow.tracks = []
for track in jamtrack.tracks
continue if track.track_type=='Master'
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument.id in instrument_logo_map
inst = instrument_logo_map[track.instrument.id].asset
track.instrument_url = inst
track.instrument_desc = track.instrument.description
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
if track.track_type != 'Master'
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in instrument_logo_map
inst = instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
options =
jamtrack: jamtrack
expanded: that.expanded
jamtrack: trackRow
expanded: jamtrackExpanded
@jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data'))
that.renderJamtrack(@jamtrackItem)
that.renderJamtrack(@jamtrackItem, jamtrack)
this.registerEvents()
renderJamtrack:(jamtrackElement, jamTrack) =>
@content.append jamtrackElement
if this.expanded==jamTrack.id
for track in jamTrack.tracks
trackRow = jamtrackElement.find("[jamtrack-track-id='#{track.id}']")
previewElement = trackRow.find(".jamtrack-preview")
new JK.JamTrackPreview(@app, previewElement, jamTrack, track, {master_shows_duration: false})
showJamtrackDescription:(e) =>
e.preventDefault()
@description = $(e.target).parent('.detail-arrow').next()
@ -200,13 +228,16 @@ context.JK.JamTrackScreen=class JamTrackScreen
else
@description.hide()
toggleExpanded:() =>
this.expanded = !this.expanded
this.refresh()
renderJamtrack:(jamtrack) =>
@content.append jamtrack
toggleExpanded:(e) =>
e.preventDefault()
jamtrackRecord = $(e.target).parents('.jamtrack-record')
jamTrackId = jamtrackRecord.attr("jamtrack-id")
if this.expanded==jamTrackId
this.expanded = null
else
this.expanded = jamTrackId
this.rerenderJamtracks()
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow

View File

@ -1,243 +0,0 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamTrackScreen=class JamTrackScreen
LIMIT = 10
instrument_logo_map = context.JK.getInstrumentIconMap24()
constructor: (@app) ->
@logger = context.JK.logger
@screen = null
@content = null
@scroller = null
@genre = null
@artist = null
@instrument = null
@availability = null
@nextPager = null
@noMoreJamtracks = null
@currentPage = 0
@next = null
@currentQuery = this.defaultQuery()
@expanded = false
beforeShow:(data) =>
this.setFilterFromURL()
this.refresh()
afterShow:(data) =>
events:() =>
@genre.on 'change', this.search
@artist.on 'change', this.search
@instrument.on 'change', this.search
@availability.on 'change', this.search
clearResults:() =>
#$logger.debug("CLEARING CONTENT")
@currentPage = 0
@content.empty()
@noMoreJamtracks.hide()
@next = null
setFilterFromURL:() =>
# Grab parms from URL for artist, instrument, and availability
parms=this.getParams()
this.logger.debug("parms", parms)
if(parms.artist?)
@artist.val(parms.artist)
if(parms.instrument?)
@instrument.val(parms.instrument)
if(parms.availability?)
@availability.val(parms.availability)
window.history.replaceState({}, "", "/client#/jamtrack")
getParams:() =>
params = {}
q = window.location.href.split("?")[1]
if q?
q = q.split('#')[0]
raw_vars = q.split("&")
for v in raw_vars
[key, val] = v.split("=")
params[key] = decodeURIComponent(val)
params
refresh:() =>
@currentQuery = this.buildQuery()
that = this
rest.getJamtracks(@currentQuery).done((response) ->
that.clearResults()
that.handleJamtrackResponse(response)
).fail (jqXHR) ->
that.clearResults()
that.noMoreJamtracks.show()
that.app.notifyServerError jqXHR, 'Jamtrack Unavailable'
search:() =>
this.refresh()
false
defaultQuery:() =>
query =
per_page: LIMIT
page: @currentPage+1
if @next
query.since = @next
query
buildQuery:() =>
@currentQuery = this.defaultQuery()
# genre filter
# var genres = @screen.find('#jamtrack_genre').val()
# if (genres !== undefined) {
# @currentQuery.genre = genres
# }
# instrument filter
instrument = @instrument.val()
if instrument?
@currentQuery.instrument = instrument
# artist filter
art = @artist.val()
if art?
@currentQuery.artist = art
# availability filter
availability = @availability.val()
if availability?
@currentQuery.availability = availability
@currentQuery
handleJamtrackResponse:(response) =>
#logger.debug("Handling response", JSON.stringify(response))
@next = response.next
this.renderJamtracks(response)
if response.next == null
# if we less results than asked for, end searching
@scroller.infinitescroll 'pause'
if @currentPage == 0 and response.jamtracks.length == 0
@content.append '<div class=\'no-jamtracks-msg\'>There\'s no jamtracks.</div>'
if @currentPage > 0
@noMoreJamtracks.show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove()
else
@currentPage++
this.buildQuery()
this.registerInfiniteScroll()
registerInfiniteScroll:() =>
@scroller.infinitescroll {
behavior: 'local'
navSelector: '#jamtrackScreen .btn-next-pager'
nextSelector: '#jamtrackScreen .btn-next-pager'
binder: @scroller
dataType: 'json'
appendCallback: false
prefill: false
bufferPx: 100
loading:
msg: $('<div class="infinite-scroll-loader">Loading ...</div>')
img: '/assets/shared/spinner.gif'
path: (page) ->
'/api/jamtracks?' + $.param(this.buildQuery())
}, (json, opts) ->
this.handleJamtrackResponse(json)
@scroller.infinitescroll 'resume'
playJamtrack:(e) =>
e.preventDefault()
addToCartJamtrack:(e) =>
e.preventDefault()
params = id: $(e.target).attr('data-jamtrack-id')
rest.addJamtrackToShoppingCart(params).done((response) ->
context.location = '/client#/shoppingCart'
).fail @app.ajaxError
licenseUSWhy:(e) =>
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:() =>
@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription
@screen.find('.play-button').on 'click', this.playJamtrack
@screen.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack
@screen.find('.license-us-why').on 'click', this.licenseUSWhy
@screen.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded
renderJamtracks:(data) =>
that = this
for jamtrack in data.jamtracks
for track in jamtrack.tracks
continue if track.track_type=='Master'
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument.id in instrument_logo_map
inst = instrument_logo_map[track.instrument.id].asset
track.instrument_url = inst
track.instrument_desc = track.instrument.description
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
options =
jamtrack: jamtrack
expanded: that.expanded
@jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data'))
that.renderJamtrack(@jamtrackItem)
this.registerEvents()
showJamtrackDescription:(e) =>
e.preventDefault()
@description = $(e.target).parent('.detail-arrow').next()
if @description.css('display') == 'none'
@description.show()
else
@description.hide()
toggleExpanded:() =>
this.expanded = !this.expanded
this.refresh()
renderJamtrack:(jamtrack) =>
@content.append jamtrack
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow
'afterShow': this.afterShow
@app.bindScreen 'jamtrack', screenBindings
@screen = $('#jamtrack-find-form')
@scroller = @screen.find('.content-body-scroller')
@content = @screen.find('.jamtrack-content')
@genre = @screen.find('#jamtrack_genre')
@artist = @screen.find('#jamtrack_artist')
@instrument = @screen.find('#jamtrack_instrument')
@availability = @screen.find('#jamtrack_availability')
@nextPager = @screen.find('a.btn-next-pager')
@noMoreJamtracks = @screen.find('.end-of-jamtrack-list')
if @screen.length == 0
throw new Error('@screen must be specified')
if @scroller.length == 0
throw new Error('@scroller must be specified')
if @content.length == 0
throw new Error('@content must be specified')
if @noMoreJamtracks.length == 0
throw new Error('@noMoreJamtracks must be specified')
#if(@genre.length == 0) throw new Error("@genre must be specified")
if @artist.length == 0
throw new Error('@artist must be specified')
if @instrument.length == 0
throw new Error('@instrument must be specified')
if @availability.length == 0
throw new Error('@availability must be specified')
this.events()

View File

@ -5,7 +5,7 @@
context.JK = context.JK || {};
context.JK.CreateScheduledSession = function(app) {
var gearUtils = context.JK.GearUtils;
var gearUtils = context.JK.GearUtilsInstance;
var sessionUtils = context.JK.SessionUtils;
var logger = context.JK.logger;
var rest = JK.Rest();
@ -597,7 +597,9 @@
if(willOptionStartSession()) {
gearUtils.guardAgainstInvalidConfiguration(app)
var shouldVerifyNetwork = createSessionSettings.musician_access.value != 'only-rsvp';
gearUtils.guardAgainstInvalidConfiguration(app, shouldVerifyNetwork)
.fail(function() {
$btn.removeClass('disabled')
app.notify(
@ -908,6 +910,13 @@
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_QUICK_START %>';
}
function optionRequiresMultiplayerProfile() {
return createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_START_SCHEDULED%>' ||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_IMMEDIATE %>' ||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_RSVP %>' ||
createSessionSettings.createType == '<%= MusicSession::CREATE_TYPE_SCHEDULE_FUTURE %>';
}
function next(event) {
if(willOptionStartSession()) {
if(!context.JK.guardAgainstBrowser(app)) {
@ -915,6 +924,12 @@
}
}
if(optionRequiresMultiplayerProfile()) {
if(context.JK.guardAgainstSinglePlayerProfile(app).canPlay == false) {
return false;
}
}
var valid = beforeMoveStep();
if (!valid) {
return false;

View File

@ -107,6 +107,8 @@
var $screen = null;
var $mixModeDropdown = null;
var $templateMixerModeChange = null;
var $myTracksNoTracks = null;
var $otherAudioContainer = null;
var $myTracksContainer = null;
var $liveTracksContainer = null;
@ -120,6 +122,9 @@
var $liveTracks = null;
var $audioTracks = null;
var $fluidTracks = null;
var $voiceChat = null;
var $openFtue = null;
var $tracksHolder = null;
var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
var muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
@ -196,53 +201,85 @@
// body-scoped drag handlers can go active
screenActive = true;
gearUtils.guardAgainstInvalidConfiguration(app)
.fail(function() {
promptLeave = false;
window.location = '/client#/home'
})
.done(function(){
var result = sessionUtils.SessionPageEnter();
rest.getSessionHistory(data.id)
.done(function(musicSession) {
gearUtils.guardAgainstActiveProfileMissing(app, result)
.fail(function(data) {
var singlePlayerCheckOK = true;
// to know whether we are allowed to be in this session, we have to check if we are the creator when checking against single player functionality
if(musicSession.user_id != context.JK.currentUserId) {
var canPlay = context.JK.guardAgainstSinglePlayerProfile(app, function () {
promptLeave = false;
if(data && data.reason == 'handled') {
if(data.nav == 'BACK') {
window.history.go(-1);
}
else {
window.location = data.nav;
}
}
else {
window.location = '/client#/home';
}
})
.done(function(){
});
sessionModel.waitForSessionPageEnterDone()
.done(function(userTracks) {
singlePlayerCheckOK = canPlay.canPlay;
}
if(singlePlayerCheckOK) {
context.JK.CurrentSessionModel.setUserTracks(userTracks);
var shouldVerifyNetwork = musicSession.musician_access;
gearUtils.guardAgainstInvalidConfiguration(app, shouldVerifyNetwork)
.fail(function() {
promptLeave = false;
window.location = '/client#/home'
})
.done(function(){
var result = sessionUtils.SessionPageEnter();
initializeSession();
})
.fail(function(data) {
if(data == "timeout") {
context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.')
}
else if(data == 'session_over') {
// do nothing; session ended before we got the user track info. just bail
}
else {
contetx.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data)
}
gearUtils.guardAgainstActiveProfileMissing(app, result)
.fail(function(data) {
promptLeave = false;
if(data && data.reason == 'handled') {
if(data.nav == 'BACK') {
window.history.go(-1);
}
else {
window.location = data.nav;
}
}
else {
window.location = '/client#/home';
}
})
.done(function(){
sessionModel.waitForSessionPageEnterDone()
.done(function(userTracks) {
context.JK.CurrentSessionModel.setUserTracks(userTracks);
initializeSession();
})
.fail(function(data) {
if(data == "timeout") {
context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.')
}
else if(data == 'session_over') {
// do nothing; session ended before we got the user track info. just bail
}
else {
context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data)
}
promptLeave = false;
window.location = '/client#/home'
});
})
})
}
else {
if(canPlay.dialog) {
canPlay.dialog.one(EVENTS.DIALOG_CLOSED, function(e, data) {
if(data.canceled) {
promptLeave = false;
window.location = '/client#/home'
});
})
}
})
}
}
})
.fail(function() {
})
}
function notifyWithUserInfo(title , text, clientId) {
@ -635,7 +672,6 @@
function renderSession() {
$myTracksContainer.empty();
$('.session-track').remove(); // Remove previous tracks
var $voiceChat = $('#voice-chat');
$voiceChat.hide();
_updateMixers();
_renderTracks();
@ -933,7 +969,6 @@
if(voiceChatMixers) {
var mixer = voiceChatMixers.mixer;
var $voiceChat = $('#voice-chat');
$voiceChat.show();
$voiceChat.attr('mixer-id', mixer.id);
var $voiceChatGain = $voiceChat.find('.voicechat-gain');
@ -1654,79 +1689,87 @@
var myTrack = app.clientId == participant.client_id;
// special case; if it's me and I have no tracks, show info about this sort of use of the app
if (myTrack && participant.tracks.length == 0) {
$tracksHolder.addClass('no-local-tracks')
}
else {
$tracksHolder.removeClass('no-local-tracks')
}
// loop through all tracks for each participant
$.each(participant.tracks, function(index, track) {
var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
$.each(participant.tracks, function (index, track) {
var instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
var photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
// Default trackData to participant + no Mixer state.
var trackData = {
trackId: track.id,
connection_id: track.connection_id,
client_track_id: track.client_track_id,
client_resource_id: track.client_resource_id,
clientId: participant.client_id,
name: name,
instrumentIcon: instrumentIcon,
avatar: photoUrl,
latency: "good",
gainPercent: 0,
muteClass: 'muted',
mixerId: "",
avatarClass: 'avatar-med',
preMasteredClass: "",
myTrack: myTrack
};
// Default trackData to participant + no Mixer state.
var trackData = {
trackId: track.id,
connection_id: track.connection_id,
client_track_id: track.client_track_id,
client_resource_id: track.client_resource_id,
clientId: participant.client_id,
name: name,
instrumentIcon: instrumentIcon,
avatar: photoUrl,
latency: "good",
gainPercent: 0,
muteClass: 'muted',
mixerId: "",
avatarClass: 'avatar-med',
preMasteredClass: "",
myTrack: myTrack
};
var mixerData = findMixerForTrack(participant.client_id, track, myTrack)
var mixer = mixerData.mixer;
var vuMixer = mixerData.vuMixer;
var muteMixer = mixerData.muteMixer;
var oppositeMixer = mixerData.oppositeMixer;
var mixerData = findMixerForTrack(participant.client_id, track, myTrack)
var mixer = mixerData.mixer;
var vuMixer = mixerData.vuMixer;
var muteMixer = mixerData.muteMixer;
var oppositeMixer = mixerData.oppositeMixer;
if (mixer && oppositeMixer) {
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
if(!myTrack) {
// it only makes sense to track 'audio established' for tracks that don't belong to you
sessionModel.setAudioEstablished(participant.client_id, true);
}
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
var muteClass = "enabled";
if (mixer.mute) {
muteClass = "muted";
}
trackData.gainPercent = gainPercent;
trackData.muteClass = muteClass;
trackData.mixerId = mixer.id;
trackData.vuMixerId = vuMixer.id;
trackData.oppositeMixer = oppositeMixer;
trackData.muteMixerId = muteMixer.id;
trackData.noaudio = false;
trackData.group_id = mixer.group_id;
context.jamClient.SessionSetUserName(participant.client_id,name);
} else { // No mixer to match, yet
lookingForMixers.push({track: track, clientId: participant.client_id})
trackData.noaudio = true;
if (!(lookingForMixersTimer)) {
logger.debug("waiting for mixer to show up for track: " + track.id)
lookingForMixersTimer = context.setInterval(lookForMixers, 500);
}
if (mixer && oppositeMixer) {
myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup);
if (!myTrack) {
// it only makes sense to track 'audio established' for tracks that don't belong to you
sessionModel.setAudioEstablished(participant.client_id, true);
}
var allowDelete = myTrack && index > 0;
_addTrack(allowDelete, trackData, mixer, oppositeMixer);
// Show settings icons only for my tracks
if (myTrack) {
myTracks.push(trackData);
var gainPercent = percentFromMixerValue(
mixer.range_low, mixer.range_high, mixer.volume_left);
var muteClass = "enabled";
if (mixer.mute) {
muteClass = "muted";
}
trackData.gainPercent = gainPercent;
trackData.muteClass = muteClass;
trackData.mixerId = mixer.id;
trackData.vuMixerId = vuMixer.id;
trackData.oppositeMixer = oppositeMixer;
trackData.muteMixerId = muteMixer.id;
trackData.noaudio = false;
trackData.group_id = mixer.group_id;
context.jamClient.SessionSetUserName(participant.client_id, name);
} else { // No mixer to match, yet
lookingForMixers.push({track: track, clientId: participant.client_id})
trackData.noaudio = true;
if (!(lookingForMixersTimer)) {
logger.debug("waiting for mixer to show up for track: " + track.id)
lookingForMixersTimer = context.setInterval(lookForMixers, 500);
}
}
var allowDelete = myTrack && index > 0;
_addTrack(allowDelete, trackData, mixer, oppositeMixer);
// Show settings icons only for my tracks
if (myTrack) {
myTracks.push(trackData);
}
});
});
configureTrackDialog = new context.JK.ConfigureTrackDialog(app, myTracks, sessionId, sessionModel);
@ -1879,14 +1922,14 @@
if (!(mixer.stereo)) { // mono track
if (mixerId.substr(-4) === "_vul") {
// Do the left
selector = $('#tracks [mixer-id="' + pureMixerId + '_vul"]');
selector = $tracksHolder.find('[mixer-id="' + pureMixerId + '_vul"]');
context.JK.VuHelpers.updateVU(selector, value);
// Do the right
selector = $('#tracks [mixer-id="' + pureMixerId + '_vur"]');
selector = $tracksHolder.find('[mixer-id="' + pureMixerId + '_vur"]');
context.JK.VuHelpers.updateVU(selector, value);
} // otherwise, it's a mono track, _vur event - ignore.
} else { // stereo track
selector = $('#tracks [mixer-id="' + mixerId + '"]');
selector = $tracksHolder.find('[mixer-id="' + mixerId + '"]');
context.JK.VuHelpers.updateVU(selector, value);
}
}
@ -3025,11 +3068,16 @@
return true;
}
function showFTUEWhenNoInputs( ) {
//app.afterFtue = function() { window.location.reload };
app.layout.startNewFtue();
}
function events() {
$('#session-leave').on('click', sessionLeave);
$('#session-resync').on('click', sessionResync);
$('#session-contents').on("click", '[action="delete"]', deleteSession);
$('#tracks').on('click', 'div[control="mute"]', toggleMute);
$tracksHolder.on('click', 'div[control="mute"]', toggleMute);
$('#recording-start-stop').on('click', startStopRecording);
$('#open-a-recording').on('click', openRecording);
$('#open-a-jamtrack').on('click', openJamTrack);
@ -3038,11 +3086,24 @@
$('#session-invite-musicians').on('click', inviteMusicians);
$('#session-invite-musicians2').on('click', inviteMusicians);
$('#track-settings').click(function() {
if(gearUtils.isNoInputProfile()) {
// show FTUE
showFTUEWhenNoInputs();
return false;
}
else {
configureTrackDialog.refresh();
configureTrackDialog.showVoiceChatPanel(true);
configureTrackDialog.showMusicAudioPanel(true);
}
});
$openFtue.click(function() {
showFTUEWhenNoInputs();
return false;
})
$closePlaybackRecording.on('click', closeOpenMedia);
$(playbackControls)
.on('pause', onPause)
@ -3083,6 +3144,8 @@
$mixModeDropdown = $screen.find('select.monitor-mode')
$templateMixerModeChange = $('#template-mixer-mode-change');
$otherAudioContainer = $('#session-recordedtracks-container');
$myTracksNoTracks = $('#session-mytracks-notracks')
$openFtue = $screen.find('.open-ftue-no-tracks')
$myTracksContainer = $('#session-mytracks-container')
$liveTracksContainer = $('#session-livetracks-container');
$closePlaybackRecording = $('#close-playback-recording')
@ -3093,7 +3156,9 @@
$myTracks = $screen.find('.session-mytracks');
$liveTracks = $screen.find('.session-livetracks');
$audioTracks = $screen.find('.session-recordings');
$fluidTracks = $screen.find('.session-fluidtracks')
$fluidTracks = $screen.find('.session-fluidtracks');
$voiceChat = $screen.find('#voice-chat');
$tracksHolder = $screen.find('#tracks')
events();

View File

@ -12,6 +12,7 @@
var ALERT_TYPES = context.JK.ALERT_TYPES;
var EVENTS = context.JK.EVENTS;
var MIX_MODES = context.JK.MIX_MODES;
var gearUtils = context.JK.GearUtilsInstance;
var userTracks = null; // comes from the backend
var clientId = client.clientID;
@ -213,7 +214,8 @@
// see if we already have tracks; if so, we need to run with these
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
if(inputTracks.length > 0) {
if(inputTracks.length > 0 || gearUtils.isNoInputProfile() ) {
logger.debug("on page enter, tracks are already available")
sessionPageEnterDeferred.resolve(inputTracks);
var deferred = sessionPageEnterDeferred;

View File

@ -136,18 +136,20 @@
return false;
}
gearUtils.guardAgainstInvalidConfiguration(app)
.fail(function() {
app.notify(
{ title: "Unable to Join Session",
text: "You can only join a session once you have working audio gear and a tested internet connection."
});
})
.done(function() {
if (successCallback) {
successCallback();
}
});
if(context.JK.guardAgainstSinglePlayerProfile(app).canPlay) {
gearUtils.guardAgainstInvalidConfiguration(app)
.fail(function() {
app.notify(
{ title: "Unable to Join Session",
text: "You can only join a session once you have working audio gear and a tested internet connection."
});
})
.done(function() {
if (successCallback) {
successCallback();
}
});
}
}
sessionUtils.joinSession = function(sessionId) {

View File

@ -1116,7 +1116,7 @@
context.JK.guardAgainstBrowser = function(app, args) {
if(!gon.isNativeClient) {
logger.debug("guarding against normal browser on screen thaht requires native client")
logger.debug("guarding against normal browser on screen that requires native client")
app.layout.showDialog('launch-app-dialog', args)
.one(EVENTS.DIALOG_CLOSED, function() {
if(args && args.goHome) {
@ -1129,6 +1129,111 @@
return true;
}
context.JK.guardAgainstSinglePlayerProfile = function(app, beforeCallback) {
var canPlayWithOthers = context.JK.GearUtilsInstance.canPlayWithOthers();
if(!canPlayWithOthers.canPlay) {
logger.debug("guarding against single player profile")
var $dialog = app.layout.showDialog('single-player-profile-dialog');
// so that callers can check dialog result
canPlayWithOthers.dialog = $dialog;
// allow callers to take action before default behavior
if(beforeCallback) {
$dialog.one(EVENTS.DIALOG_CLOSED, beforeCallback);
}
$dialog.one(EVENTS.DIALOG_CLOSED, function(e, data) {
if(!data.canceled) {
if(data.result.choice == 'private_session') {
var data = {
createType: 'quick-start',
timezone: {},
recurring_mode: {},
language: {},
band: {},
musician_access: {},
fans_access: {},
rsvp_slots: [],
open_rsvps: false
};
context.JK.privateSessionSettings(data)
context.JK.createSession(app, data)
.done(function(response) {
var sessionId = response.id;
context.JK.GA.trackSessionCount(true, true, 0);
// we redirect to the session screen, which handles the REST call to POST /participants.
logger.debug("joining session screen: " + sessionId)
context.location = '/client#/session/' + sessionId;
})
.fail(function(jqXHR) {
logger.debug("unable to schedule a private session")
app.notifyServerError(jqXHR, "Unable to schedule a private session");
})
}
else if(data.result.choice == 'gear_setup') {
window.location = '/client#/account/audio'
}
else
{
logger.error("unknown choice: " + data.result.choice)
alert("unknown choice: " + data.result.choice)
}
}
})
}
return canPlayWithOthers;
}
context.JK.createSession = function(app, data) {
// auto pick an 'other' instrument
var otherId = context.JK.server_to_client_instrument_map.Other.server_id; // get server ID
var otherInstrumentInfo = context.JK.instrument_id_to_instrument[otherId]; // get display name
var beginnerLevel = 1; // default to beginner
var instruments = [ {id: otherId, name: otherInstrumentInfo.display, level: beginnerLevel} ];
$.each(instruments, function(index, instrument) {
var slot = {};
slot.instrument_id = instrument.id;
slot.proficiency_level = instrument.level;
slot.approve = true;
data.rsvp_slots.push(slot);
});
data.isUnstructuredRsvp = true;
return rest.createScheduledSession(data)
}
context.JK.privateSessionSettings = function(createSessionSettings) {
createSessionSettings.genresValues = ['Pop'];
createSessionSettings.genres = ['pop'];
createSessionSettings.timezone = 'Central Time (US & Canada),America/Chicago'
createSessionSettings.name = "Private Test Session";
createSessionSettings.description = "Private session set up just to test things out in the session interface by myself.";
createSessionSettings.notations = [];
createSessionSettings.language = 'eng'
createSessionSettings.legal_policy = 'Standard';
createSessionSettings.musician_access = false
createSessionSettings.fan_access = false
createSessionSettings.fan_chat = false
createSessionSettings.approval_required = false
createSessionSettings.legal_terms = true
createSessionSettings.recurring_mode = 'once';
createSessionSettings.start = new Date().toDateString() + ' ' + context.JK.formatUtcTime(new Date(), false);
createSessionSettings.duration = "60";
createSessionSettings.open_rsvps = false
createSessionSettings.rsvp_slots = [];
}
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.

View File

@ -0,0 +1,18 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
var rest = context.JK.Rest();
var logger = context.JK.logger;
function initialize() {
if(gon.signed_in) {
window.location = "/client#/home"
}
}
context.JK.HomePage = initialize;
})(window, jQuery);

View File

@ -61,7 +61,7 @@
//= require web/sessions
//= require web/session_info
//= require web/recordings
//= require web/welcome
//= require web/home
//= require web/individual_jamtrack
//= require web/individual_jamtrack_band
//= require fakeJamClient

View File

@ -176,6 +176,10 @@
wizard.setBackState(enabled);
}
function moveToNext() {
wizard.moveToNext();
}
function setChosenInputs(_inputs) {
inputs = _inputs;
}
@ -222,6 +226,7 @@
this.getChosenInputs = getChosenInputs;
this.setNextState = setNextState;
this.setBackState = setBackState;
this.moveToNext = moveToNext;
this.initialize = initialize;
this.createFTUEProfile = createFTUEProfile;
this.getWizard = function() {return wizard; }

View File

@ -9,7 +9,8 @@
var logger = context.JK.logger;
var networkTest = new context.JK.NetworkTest(app);
var $step = null;
// if not null and with in say 5 seconds, then the user is 'NEXT'ing too quickly. slow them down
var clickFastTime = null;
function getLastNetworkFailAnalytics() {
return networkTest.getLastNetworkFailure();
@ -36,13 +37,29 @@
initializeBackButtonState();
}
function initializeNextButtonState() {
dialog.setNextState(networkTest.hasScoredNetworkSuccessfully() && !networkTest.isScoring());
dialog.setNextState(!networkTest.isScoring());
}
function initializeBackButtonState() {
dialog.setBackState(!networkTest.isScoring());
}
function handleNext() {
// if we don't have a valid score, and if it's been less than 5 seconds since we've shown this step, slow the user down
if (context.jamClient.GetNetworkTestScore() < 1 && userIsFastNexting()) {
context.JK.Banner.showYesNo({
html: "By clicking NEXT and skipping the test, you won't be able to play online in real-time sessions with others. Is this OK?",
yes: function() {
dialog.moveToNext();
}});
return false;
}
else {
return true;
}
}
function handleHelp() {
return "https://jamkazam.desk.com/customer/portal/articles/1716139-what-to-do-if-you-cannot-pass-the-network-test"
//return "https://jamkazam.desk.com/customer/portal/articles/1599969-first-time-setup---step-6---test-your-network";
@ -57,6 +74,16 @@
networkTest.haltScoring();
networkTest.cancel();
updateButtons();
watchForFastNexting();
}
// fast nexting is a someone hitting next very quickly
function watchForFastNexting() {
clickFastTime = new Date();
}
function userIsFastNexting() {
return new Date().getTime() - clickFastTime.getTime() < 5000
}
function beforeHide() {
@ -77,6 +104,7 @@
this.handleHelp = handleHelp;
this.newSession = newSession;
this.beforeHide = beforeHide;
this.handleNext = handleNext;
this.beforeShow = beforeShow;
this.initialize = initialize;
this.getLastNetworkFailAnalytics = getLastNetworkFailAnalytics;

View File

@ -46,6 +46,8 @@
var $templateDeviceNotValid = null;
var $resyncStatus = null;
var $resyncStatusText = null;
var $latencyScoreBox = null;
var $highLatencyNotice = null;
var operatingSystem = null;
@ -579,6 +581,11 @@
function initializeResync() {
$resyncBtn.unbind('click').click(function () {
if($highLatencyNotice) {
$highLatencyNotice.btOff()
$highLatencyNotice = null;
}
scheduleRescanSystem(function() {
if (getSelectedInputs().length > 0 && getSelectedOutputs().length == 2) {
logger.debug("after rescan, ready to attempt score")
@ -946,6 +953,21 @@
queueUpdateDeviceList = false;
updateDeviceList();
}
if(!data.validLatencyScore) {
if (selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) {
prodUserAboutHighLatency($latencyScoreBox, 'asio')
}
else if (selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) {
prodUserAboutHighLatency($latencyScoreBox, 'asio')
}
else if (selectedDeviceInfo.input.info.type == 'MacOSX_builtin' || selectedDeviceInfo.output.info.type == 'MacOSX_builtin') {
prodUserAboutHighLatency($latencyScoreBox, 'macosx-builtin')
}
else {
prodUserAboutHighLatency($latencyScoreBox, 'generic')
}
}
}
function getLastAudioTestFailAnalytics() {
@ -962,6 +984,13 @@
}
}
function prodUserAboutHighLatency($btn, additional) {
setTimeout(function() {
$highLatencyNotice = context.JK.prodBubble($btn, 'high-latency-notice', {additional: additional}, {duration: 20000, width:'400px', positions:['top']});
}, 300)
}
function prodUserToTweakASIOSettings($btn) {
setTimeout(function() {
context.JK.prodBubble($btn, 'tweak-asio-settings', {}, {positions:['top']});
@ -972,19 +1001,11 @@
renderScoringStopped();
gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true);
if(data.reason == "latency") {
if(data.reason == "io") {
console.log("selectedDeviceInfo", selectedDeviceInfo)
if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) {
prodUserToTweakASIOSettings($asioInputControlBtn)
}
else if(selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) {
prodUserToTweakASIOSettings($asioOutputControlBtn)
}
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
}
else if(data.reason = "io") {
//storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore);
if(data.ioTarget == 'bad') {
storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.ioTarget, data.ioTargetScore);
}
@ -1210,6 +1231,7 @@
$instructions = $step.find('.instructions');
$resyncStatus = $step.find('.resync-status');
$resyncStatusText = $step.find('.resynctext');
$latencyScoreBox = $step.find('.latency-score-section')
operatingSystem = context.JK.GetOSAsString();
frameBuffers.initialize($knobs);
$(frameBuffers)

View File

@ -52,7 +52,7 @@
var GEAR_TEST_INVALIDATED_ASYNC = "gear_test.async_invalidated"; // happens when backend alerts us device is invalid
function isGoodFtue() {
return validLatencyScore && validIOScore && !asynchronousInvalidDevice;
return validIOScore && !asynchronousInvalidDevice;
}
function processIOScore(io) {
@ -90,7 +90,7 @@
// 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})
$self.triggerHandler(GEAR_TEST_IO_DONE, {std:std, median:median, io:io, aggregrateIOClass: aggregrateIOClass, medianIOClass : medianIOClass, stdIOClass: stdIOClass, validLatencyScore: validLatencyScore})
//renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass);
if(aggregrateIOClass == "bad") {
@ -103,10 +103,10 @@
scoring = false;
if(isGoodFtue()) {
$self.triggerHandler(GEAR_TEST_DONE)
$self.triggerHandler(GEAR_TEST_DONE, {validLatencyScore: validLatencyScore})
}
else {
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std});
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'io', ioTarget: medianIOClass, ioTargetScore: median, ioVariance: stdIOClass, ioVarianceScore: std, validLatencyScore: validLatencyScore});
}
}
@ -182,11 +182,10 @@
updateScoreReport(latency, refocused);
// if there was a valid latency score, go on to the next step
if (validLatencyScore) {
if (true || validLatencyScore) {
$self.triggerHandler(GEAR_TEST_IO_START);
// reuse valid IO score if this is on refocus
if(refocused && validIOScore) {
if(false && (refocused && validIOScore)) {
processIOScore(ioScore);
}
else {
@ -215,12 +214,12 @@
}
else {
scoring = false;
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', latencyScore: latencyScore.latency})
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'latency', validLatencyScore: validLatencyScore, latencyScore: latencyScore.latency})
}
})
.fail(function(ftueSaveResult) {
scoring = false;
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'invalid_configuration', data: ftueSaveResult})
$self.triggerHandler(GEAR_TEST_FAIL, {reason:'invalid_configuration', validLatencyScore: validLatencyScore, data: ftueSaveResult})
})
}, 250);
}

View File

@ -14,6 +14,8 @@
var VOICE_CHAT = context.JK.VOICE_CHAT;
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
var EVENTS = context.JK.EVENTS;
var SYSTEM_DEFAULT_PLAYBACK_ONLY = 'System Default (Playback Only)';
context.JK.GearUtilsInstance = gearUtils;
@ -28,12 +30,42 @@
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
}
// to play with others, you have to have inputs,
// as well have a score below 20 ms
gearUtils.canPlayWithOthers = function(profile) {
gearUtils.createProfileName = function(deviceInfo, chatName) {
var isNoInputProfile = gearUtils.isNoInputProfile(profile);
var expectedLatency = context.jamClient.FTUEGetExpectedLatency();
var audioLatency = expectedLatency ? expectedLatency.latency : null;
var highLatency = audioLatency > 20;
var networkScore = context.jamClient.GetNetworkTestScore();
var badNetworkScore = networkScore < 2;
return {
canPlay: !isNoInputProfile && !highLatency,
isNoInputProfile: isNoInputProfile,
badNetworkScore: badNetworkScore,
highLatency: highLatency,
audioLatency: audioLatency,
networkScore: networkScore,
}
}
gearUtils.isNoInputProfile = function(profile) {
if (profile === undefined) {
profile = context.jamClient.FTUEGetMusicProfileName();
}
if(profile == SYSTEM_DEFAULT_PLAYBACK_ONLY) {
return true;
}
}
gearUtils.createProfileName = function (deviceInfo, chatName) {
var isSameInOut = deviceInfo.input.id == deviceInfo.output.id;
var name = null;
if(isSameInOut) {
if (isSameInOut) {
name = "In/Out: " + deviceInfo.input.info.displayName;
}
else {
@ -45,19 +77,19 @@
}
gearUtils.selectedDeviceInfo = function(audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
gearUtils.selectedDeviceInfo = function (audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
if(!audioInputDeviceId) {
if (!audioInputDeviceId) {
logger.debug("gearUtils.selectedDeviceInfo: no active input device");
return null;
}
if(!audioOutputDeviceId) {
if (!audioOutputDeviceId) {
logger.debug("gearUtils.selectedDeviceInfo: no active output device");
return null;
}
if(!deviceInformation) {
if (!deviceInformation) {
deviceInformation = gearUtils.loadDeviceInfo();
}
@ -81,7 +113,7 @@
}
}
gearUtils.loadDeviceInfo = function() {
gearUtils.loadDeviceInfo = function () {
var operatingSystem = context.JK.GetOSAsString();
// should return one of:
@ -128,6 +160,10 @@
return;
}
if (device.name == "JamKazam Virtual Input") {
return;
}
var deviceInfo = {};
deviceInfo.id = device.guid;
@ -145,22 +181,22 @@
return loadedDevices;
}
gearUtils.updateDefaultBuffers = function(selectedDeviceInfo, frameBuffers) {
gearUtils.updateDefaultBuffers = function (selectedDeviceInfo, frameBuffers) {
function hasWDMAssociated() {
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
}
function hasASIOAssociated() {
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio')
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio')
}
// handle specific framesize settings
if(hasWDMAssociated() || hasASIOAssociated()) {
if (hasWDMAssociated() || hasASIOAssociated()) {
var framesize = frameBuffers.selectedFramesize();
if(framesize == 2.5) {
if (framesize == 2.5) {
// if there is a WDM device, start off at 1/1 due to empirically observed issues with 0/0
if(hasWDMAssociated()) {
if (hasWDMAssociated()) {
logger.debug("setting default buffers to 1/1");
frameBuffers.setBufferIn('1');
frameBuffers.setBufferOut('1');
@ -172,7 +208,7 @@
frameBuffers.setBufferOut('0');
}
}
else if(framesize == 5) {
else if (framesize == 5) {
logger.debug("setting default buffers to 3/2");
frameBuffers.setBufferIn('3');
frameBuffers.setBufferOut('2');
@ -193,7 +229,7 @@
context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
}
gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
gearUtils.ftueSummary = function (operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
return {
os: operatingSystem,
version: context.jamClient.ClientUpdateVersion(),
@ -203,7 +239,7 @@
validLatencyScore: gearTest.isValidLatencyScore(),
validIOScore: gearTest.isValidIOScore(),
latencyScore: gearTest.getLatencyScore(),
ioScore : gearTest.getIOScore(),
ioScore: gearTest.getIOScore(),
},
audioParameters: {
frameSize: frameBuffers.selectedFramesize(),
@ -221,21 +257,21 @@
* 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() {
gearUtils.getProfiles = function () {
var all = context.jamClient.FTUEGetAllAudioConfigurations();
var good = context.jamClient.FTUEGetGoodAudioConfigurations();
var current = context.jamClient.LastUsedProfileName();
var profiles = [];
context._.each(all, function(item) {
context._.each(all, function (item) {
profiles.push({id: item, good: false, class:'bad', current: current == 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) {
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;
@ -246,21 +282,21 @@
return profiles;
}
gearUtils.postDiagnostic = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
gearUtils.postDiagnostic = function (operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
rest.createDiagnostic({
type: 'GEAR_SELECTION',
data: {
client_type: context.JK.clientType(),
client_id:
context.JK.JamServer.clientID,
summary:gearUtils.ftueSummary(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated)}
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(){
gearUtils.getChatInputs = function () {
var musicPorts = jamClient.FTUEGetChannels();
//var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true);
@ -273,11 +309,11 @@
var deDupper = {};
context._.each(musicPorts.inputs, function(input) {
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) {
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;
}
@ -295,9 +331,9 @@
}
})*/
context._.each(chatsOnOtherDevices, function(chatChannelName, chatChannelId) {
context._.each(chatsOnOtherDevices, function (chatChannelName, chatChannelId) {
var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null};
if(!deDupper[chatInput.id]) {
if (!deDupper[chatInput.id]) {
var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true);
chatInput.assignment = assignment;
@ -309,11 +345,11 @@
return chatInputs;
}
gearUtils.isChannelAvailableForChat = function(chatChannelId, musicPorts) {
gearUtils.isChannelAvailableForChat = function (chatChannelId, musicPorts) {
var result = true;
context._.each(musicPorts.inputs, function(inputChannel) {
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)) {
if (inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) {
result = false;
return false; // break
}
@ -324,13 +360,13 @@
// if the user has a good user network score, immediately returns with a resolved deferred object.
// if not, the user will have the network test dialog prompted... once it's closed, then you'll be told reject() if score is still bad, or resolve() if now good
gearUtils.guardAgainstBadNetworkScore = function(app) {
gearUtils.guardAgainstBadNetworkScore = function (app) {
var deferred = new $.Deferred();
if (!gearUtils.validNetworkScore()) {
// invalid network test score. They have to score to move on
app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, function() {
if(gearUtils.validNetworkScore()) {
app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, function () {
if (gearUtils.validNetworkScore()) {
deferred.resolve();
}
else {
@ -346,19 +382,19 @@
// XXX this isn't quite right... it needs to check if a good device is *active*
// but seen too many problems so far with the backend not reporting any profile active
gearUtils.hasGoodActiveProfile = function(verifyTracks) {
gearUtils.hasGoodActiveProfile = function (verifyTracks) {
var hasOneConfigureDevice = context.JK.hasOneConfiguredDevice();
logger.debug("hasGoodActiveProfile: " + hasOneConfigureDevice ? "devices='has at least one configured device' " : "devices='has no configured device' ")
return hasOneConfigureDevice;
}
// if the user does not have any profiles, show the FTUE
gearUtils.guardAgainstInvalidGearConfiguration = function(app) {
gearUtils.guardAgainstInvalidGearConfiguration = function (app) {
var deferred = new $.Deferred();
if (context.jamClient.FTUEGetAllAudioConfigurations().length == 0) {
app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, function() {
if(gearUtils.hasGoodActiveProfile() && gearUtils.validNetworkScore()) {
app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, function () {
if (gearUtils.hasGoodActiveProfile() && gearUtils.validNetworkScore()) {
deferred.resolve();
}
else {
@ -373,27 +409,27 @@
return deferred;
}
gearUtils.guardAgainstActiveProfileMissing = function(app, backendInfo) {
gearUtils.guardAgainstActiveProfileMissing = function (app, backendInfo) {
var deferred = new $.Deferred();
logger.debug("guardAgainstActiveProfileMissing: backendInfo %o", backendInfo);
if(backendInfo.error && backendInfo['reason'] == 'no_profile' && context.jamClient.FTUEGetAllAudioConfigurations().length > 0) {
if (backendInfo.error && backendInfo['reason'] == 'no_profile' && context.jamClient.FTUEGetAllAudioConfigurations().length > 0) {
// if the backend says we have no_profile, but we have profiles , send them to the audio profile screen
// this should be a very rare path
deferred.reject({reason:'handled', nav: '/client#/account/audio'});
deferred.reject({reason: 'handled', nav: '/client#/account/audio'});
context.JK.Banner.showAlert('No Active Profile', 'We\'ve sent you to the audio profile screen to remedy the fact that you have no active audio profile. Please select ACTIVATE on an existing profile, or select ADD NEW GEAR to add a new profile.');
}
else if (backendInfo.error && backendInfo['reason'] == 'device_failure') {
app.layout.showDialog('audio-profile-invalid-dialog')
.one(EVENTS.DIALOG_CLOSED, function(e, data) {
if(!data.result || data.result == 'cancel') {
deferred.reject({reason:'handled', nav: 'BACK'});
.one(EVENTS.DIALOG_CLOSED, function (e, data) {
if (!data.result || data.result == 'cancel') {
deferred.reject({reason: 'handled', nav: 'BACK'});
}
else if(data.result == 'configure_gear'){
deferred.reject({reason:'handled', nav: '/client#/account/audio'});
else if (data.result == 'configure_gear') {
deferred.reject({reason: 'handled', nav: '/client#/account/audio'});
}
else if(data.result == 'session') {
else if (data.result == 'session') {
deferred.resolve();
}
else {
@ -409,43 +445,49 @@
}
// tests both device config, and network score
gearUtils.guardAgainstInvalidConfiguration = function(app) {
gearUtils.guardAgainstInvalidConfiguration = function (app, verifyNetworkScore) {
var deferred = new $.Deferred();
gearUtils.guardAgainstInvalidGearConfiguration(app)
.fail(function() {
.fail(function () {
deferred.reject();
})
.done(function() {
gearUtils.guardAgainstBadNetworkScore(app)
.fail(function() {
deferred.reject();
})
.done(function() {
deferred.resolve();
})
.done(function () {
if(verifyNetworkScore) {
gearUtils.guardAgainstBadNetworkScore(app)
.fail(function () {
deferred.reject();
})
.done(function () {
deferred.resolve();
})
}
else {
deferred.resolve();
}
})
return deferred;
}
gearUtils.skipNetworkTest = function() {
gearUtils.skipNetworkTest = function () {
context.jamClient.SetNetworkTestScore(gearUtils.SKIPPED_NETWORK_TEST);
gearUtils.skippedNetworkTest = true;
}
gearUtils.isNetworkTestSkipped = function() {
gearUtils.isNetworkTestSkipped = function () {
return gearUtils.skippedNetworkTest;
}
gearUtils.validNetworkScore = function() {
gearUtils.validNetworkScore = function () {
return gearUtils.skippedNetworkTest || context.jamClient.GetNetworkTestScore() >= 2;
}
gearUtils.isRestartingAudio = function() {
gearUtils.isRestartingAudio = function () {
return !!reloadAudioTimeout;
}
gearUtils.scheduleAudioRestart = function(location, initial_delay, beforeScan, afterScan, cancelScan) {
gearUtils.scheduleAudioRestart = function (location, initial_delay, beforeScan, afterScan, cancelScan) {
logger.debug("scheduleAudioRestart: (from " + location + ")")
@ -453,40 +495,42 @@
function clearAudioReloadTimer() {
if(!cancellable) {return;}
if (!cancellable) {
return;
}
if(cancelScan) {
if (cancelScan) {
cancelScan();
}
else if(afterScan) {
else if (afterScan) {
afterScan(true);
}
clearTimeout(reloadAudioTimeout);
reloadAudioTimeout = null;
currentAudioRestartLocation = null;
currentAudioRestartLocation = null;
cancellable = false;
}
// refresh timer if outstanding
if(reloadAudioTimeout) {
if (reloadAudioTimeout) {
logger.debug("scheduleAudioRestart: clearing timeout (from " + location + ")")
clearTimeout(reloadAudioTimeout);
}
currentAudioRestartLocation = location;
if(beforeScan) {
if (beforeScan) {
beforeScan();
}
reloadAudioTimeout = setTimeout(function() {
reloadAudioTimeout = setTimeout(function () {
logger.debug("scheduleAudioRestart: rescan beginning (from " + location + ")")
reloadAudioTimeout = null;
currentAudioRestartLocation = null;
cancellable = false;
if(afterScan) {
if (afterScan) {
afterScan(false);
}
}, initial_delay ? initial_delay : 5000);
@ -494,4 +538,45 @@
return clearAudioReloadTimer;
}
gearUtils.bootstrapDefaultPlaybackProfile = function () {
var profiles = gearUtils.getProfiles();
var foundSystemDefaultPlaybackOnly = false
context._.each(profiles, function (profile) {
if (profile.id == SYSTEM_DEFAULT_PLAYBACK_ONLY) {
foundSystemDefaultPlaybackOnly = true
return false;
}
})
if (!foundSystemDefaultPlaybackOnly) {
logger.debug("creating system default profile (playback only")
if(!gearUtils.createDefaultPlaybackOnlyProfile()) {
logger.error("unable to create the default playback profile!");
}
}
}
gearUtils.createDefaultPlaybackOnlyProfile = function () {
var eMixerInputSampleRate = {
JAMKAZAM_AUTO_SR: 0,
USE_DEVICE_DEFAULT_SR: 1,
PREFER_44: 2,
PREFER_48: 3,
PREFER_96: 4
}
// null//upgrade protect
if(context.jamClient.FTUECreateUpdatePlayBackProfile) {
return context.jamClient.FTUECreateUpdatePlayBackProfile(SYSTEM_DEFAULT_PLAYBACK_ONLY,
eMixerInputSampleRate.JAMKAZAM_AUTO_SR,
0, // buffering
false); // start audio
}
else {
return false;
}
}
})(window, jQuery);

View File

@ -69,11 +69,15 @@
if(result === false) {return false;}
}
moveToNext();
return false;
}
function moveToNext() {
previousStep = step;
step = step + 1;
moveToStep();
return false;
}
function help() {
@ -238,6 +242,7 @@
this.getNextButton = getNextButton;
this.setNextState = setNextState;
this.setBackState = setBackState;
this.moveToNext = moveToNext;
this.getCurrentStep = getCurrentStep;
this.getCurrentWizardStep = getCurrentWizardStep;
this.onCloseDialog = onCloseDialog;

View File

@ -45,6 +45,10 @@ body.jam, body.web, .dialog{
}
}
.help-high-latency-notice {
width:400px;
}
.help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks {
font-size:12px;

View File

@ -1,17 +1,40 @@
@import 'common';
#jamtrackLanding {
ul {
li {
margin: 1px 4px 1px 4em;
font-size:9px;
font-family: verdana;
.two-column-list-container {
-moz-column-count: 2;
-moz-column-gap: 20px;
-webkit-column-count: 2;
-webkit-column-gap: 20px;
column-count: 2;
column-gap: 20px;
margin-left: 0px;
ul {
list-style-type: none;
li {
margin: 1px 4px 1px 0px;
font-size:11px;
}
}
}
ul {
li {
margin: 1px 4px 1px 4px;
font-size:11px;
}
}
.browse-header {
padding: 4px 0 4px 0;
}
.list-columns {
padding: 4px;
h2 {
font-size: 16pt;
font-weight:300;
font-weight:600;
font-style: bolder;
font-family: verdana;
text-transform: lowercase;
@ -23,24 +46,26 @@
padding: 3px;
@include border-radius(7px);
background-color:$ColorScreenPrimary;
color: white !important;
text-align: center;
vertical-align: center;
}
.what, .howto {
margin-bottom: 2em;
}
p {
font-size: 12pt !important;
font-weight: normal;
line-height: 16px;
color: #dddddd;
* {
font-size: 10pt !important;
font-weight: normal;
line-height: 16px;
}
}
font-family: arial;
font-size: 11pt;
font-weight: 200;
line-height: 17px;
color: #cccccc;
}
}
.details {
margin-bottom: 4px !important;
}
.about {
@include border_box_sizing;
float: left;
@ -90,13 +115,13 @@
}
.jamtrack-record {
border-bottom: 1px solid black;
//border-bottom: 1px solid black;
text-align: left;
}
.jamtrack-detail {
width: 35%;
@include border_box_sizing;
float: left;
width: 30%;
padding: 10px 0px;
.detail-label {
@ -127,22 +152,21 @@
.jamtrack-description {
display: none;
}
}
.jamtrack-detail-btn {
cursor: pointer;
margin-top: 7px;
margin-right: 5px;
padding-top: 5px;
vertical-align: bottom;
}
.jamtrack-detail-btn {
cursor: pointer;
margin-top: 7px;
margin-right: 5px;
padding-top: 5px;
vertical-align: bottom;
}
.jamtrack-tracks {
@include border_box_sizing;
float: left;
width: 50%;
padding: 10px 0px;
width: 35%;
padding: 10px 0px;
.tracks-caption {
margin-top: 5px;
margin-bottom: 10px;
@ -163,14 +187,17 @@
margin-left: 10px;
}
}
.jamtrack-track {
margin-left: 7px;
}
.jamtrack-action {
@include border_box_sizing;
float: left;
width: 20%;
padding: 10px 0px;
width: 35%;
// padding: 0px 0px;
text-align: center;
// vertical-align: top;
.play-button {
margin-top: 5px;
}

View File

@ -210,6 +210,18 @@
$otheraudio-minwidth:195px;
$otheraudio-open-minwidth:230px;
#session-mytracks-notracks {
display:none;
p {
font-size:14px;
white-space:normal;
margin:10px 10px 0 0;
line-height:125%;
}
}
.session-mytracks {
padding-left:15px;
float:left;
@ -251,6 +263,12 @@
.recording-controls {
min-width:230px;
}
#recording-start-stop {
@include border-radius(4px);
padding-left:5px;
padding-right:5px;
}
}
.session-recordings {
@ -358,6 +376,25 @@
#tracks {
margin-top:12px;
overflow:auto;
&.no-local-tracks {
#session-mytracks-notracks {
display:block;
}
#session-mytracks-container {
display:none;
}
#recording-start-stop {
display:none;
}
#session-invite-musicians {
display:none;
}
}
}
.track-empty a {

View File

@ -429,8 +429,12 @@
}
.instructions {
height: 228px !important;
}
.network-test-results {
height: 248px !important;
height: 228px !important;
@include border_box_sizing;
&.testing {

View File

@ -135,6 +135,25 @@
}
.get-a-free-jamtrack-section {
&.has-free-jamtrack {
h2.get-a-free-jamtrack {
display:block;
}
.action-button {
margin-top:-7px;
}
}
&.no-free-jamtrack {
h2.browse-jamtracks {
display:block;
}
}
}
.ftue-inner table a {
text-decoration:none;
}

View File

@ -0,0 +1,29 @@
#single-player-profile-dialog {
.dialog-inner {
&.high-latency {
.high-latency {
display:block
}
}
&.has-no-inputs {
.has-no-inputs {
display:block
}
}
}
.audio-latency {
font-weight:bold;
}
.action-buttons {
margin:20px 0;
}
p {
line-height:125%;
}
}

View File

@ -0,0 +1,135 @@
@charset "UTF-8";
@import "client/common.css.scss";
body.web.home {
.landing-tag {
margin-top:20px;
}
.logo-home {
margin:20px 0 15px;
}
.home-column {
margin-top: 20px;
width: 345px;
float: left;
margin-right: 30px;
text-align: center;
margin-bottom:35px;
&.last{
margin-right:0;
}
h3 {
text-align:left;
margin-top:12px;
margin-bottom:6px;
font-size:18px;
font-weight:700;
}
p {
color:white;
text-align:left;
line-height:120%;
font-size:13px;
margin-bottom:20px;
}
.extra-links {
width:234px;
display:inline-block;
}
.learn-more {
font-size: 12px;
margin-top: 5px;
&.shared {
float:left;
margin-left:10px;
}
}
.sign-in-holder {
font-size: 12px;
margin-top: 5px;
&.shared {
float:right;
margin-right:10px;
}
}
}
.latest-promo {
float:left;
}
.endorsement-promo {
float:right;
}
.home-buzz {
h2 {
width:100%;
text-align:center;
margin:20px 0;
}
width: 300px;
position:relative;
margin-right:20px;
.buzz-items {
.buzz-item {
padding: 12px 0;
&:last-child {
padding-bottom:0;
}
}
.buzz-item-text {
padding-left: 78px; // 58px width for image + 20px margin
}
}
}
.latest {
width: 750px;
position:relative;
top:-45px;
.home-session-list {
top:5px; // XXX remove post release
width:100%;
height:400px;
border: solid 1px #ed3618;
background-color:#353535;
float:left;
overflow:hidden;
position:relative;
}
.latest-head {
position: absolute;
padding:20px 20px 12px;
height: 53px;
width:inherit;
}
.latest-body {
width:100%;
top:65px;
bottom:0;
position:absolute;
overflow-y:scroll;
@include border_box_sizing;
.session-list-wrapper {
padding: 0 20px;
}
}
}
}

View File

@ -20,6 +20,7 @@
*= require web/footer
*= require web/recordings
*= require web/welcome
*= require web/home
#= require web/sessions
*= require web/events
*= require web/session_info

View File

@ -2,7 +2,7 @@
@import "client/common.css.scss";
body.web {
body.web.welcome {
.signin-common {
height:auto;

View File

@ -68,6 +68,7 @@ class ApiUsersController < ApiController
@user.update_genres(params[:cowriting_genres].nil? ? [] : params[:cowriting_genres], GenrePlayer::COWRITING) if params.has_key?(:cowriting_genres)
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
@user.show_whats_next_count = params[:show_whats_next_count] if params.has_key?(:show_whats_next_count)
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
@user.biography = params[:biography] if params.has_key?(:biography)

View File

@ -94,7 +94,7 @@ class LandingsController < ApplicationController
jam_track = JamTrack.first
end
gon.jam_track_plan_code = jam_track.plan_code
gon.jam_track_plan_code = jam_track.plan_code if jam_track
render 'product_jamtracks', layout: 'web'
end
end

View File

@ -215,7 +215,24 @@ class UsersController < ApplicationController
#@jamfest_2014 = Event.find_by_id('a2dfbd26-9b17-4446-8c61-b67a542ea6ee') unless @jamfest_2014 # development ID
# temporary--end
@welcome_page = true
#@welcome_page = true
render :layout => "web"
end
# DO NOT USE CURRENT_USER IN THIS ROUTINE. IT'S CACHED FOR THE WHOLE SITE
def home
@no_user_dropdown = true
@promo_buzz = PromoBuzz.active
if Rails.application.config.use_promos_on_homepage
@promo_latest = PromoLatest.active
else
@promo_latest, start = Feed.index(nil, limit: 10)
end
gon.signed_in = !current_user.nil?
render :layout => "web"
end

View File

@ -10,7 +10,7 @@ end
# give back more info if the user being fetched is yourself
if @user == current_user
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email, :auth_twitter, :new_notifications
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications
node :geoiplocation do |user|
geoiplocation = current_user.geoiplocation

View File

@ -22,7 +22,12 @@
th ORIGINAL ARTIST
th ACTIONS
tbody
tr.no-jamtracks-found.hidden
td colspan="3"
| You don't currently own any JamTracks.
a.orange href="/client#/jamtracks" Browse JamTracks Now
.right
a.button-orange href="/client#/jamtracks" JAMTRACKS
a.button-grey href="javascript:history.go(-1)" BACK
script#template-account-jamtrack type='text/template'

View File

@ -30,6 +30,29 @@ script type="text/template" id="template-help-can-move-on"
script type="text/template" id="template-help-tweak-asio-settings"
| Click here to try faster ASIO settings.
script type="text/template" id="template-help-high-latency-notice"
.help-high-latency-notice
| {% if(data.additional == 'asio') { %}
p.gear-specific-latency-notice Tip: click the ASIO SETTINGS button to try faster ASIO settings.
p
| If you are unable to get your audio gear latency below 20 milliseconds, you can click NEXT to proceed through setup with a high-latency audio profile. This will allow you to play with JamTracks and backing tracks, but not play with others.&nbsp;
p
a href="https://jamkazam.desk.com/customer/portal/articles/1520627-my-audio-gear-won-t-pass-latency-or-i-o-tests" rel="external" Click here
| &nbsp;for more troubleshooting tips to speed up your audio gear setup.
| {% } else if(data.additional == 'macosx-builtin') { %}
p.gear-specific-latency-notice Tip: Insert your headphones on a Mac to bring your latency down, and click the RESYNC button to try again.
p
| If you are unable to get your audio gear latency below 20 milliseconds, you can click NEXT to proceed through setup with a high-latency audio profile. This will allow you to play with JamTracks and backing tracks, but not play with others.&nbsp;
p
a href="https://jamkazam.desk.com/customer/portal/articles/1520627-my-audio-gear-won-t-pass-latency-or-i-o-tests" rel="external" Click here
| &nbsp;for more troubleshooting tips to speed up your audio gear setup.
| {% } else { %}
p.general-info
| Your computer and interface are processing audio too slowly to play online in real-time sessions with other musicians over the Internet. You may click NEXT to proceed through setup to play alone in sessions with JamTracks or backing tracks, or if you want to improve your speed score to play online,&nbsp;
a href="https://jamkazam.desk.com/customer/portal/articles/1520627-my-audio-gear-won-t-pass-latency-or-i-o-tests" rel="external" click here
| &nbsp;for a troubleshooting article.
| {% } %}
script type="text/template" id="template-help-session-plus-musicians"
| Plus any interested JamKazam musicians that I approve.

View File

@ -9,30 +9,24 @@
=render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_JAMTRACK})
.filter-body
.content-body-scroller
.profile-wrapper
.jamtrack-content
.profile-wrapper
table.generaltable
thead
tr
th JAMTRACK
th TRACKS INCLUDED/PREVIEW
th SHOP
tbody.jamtrack-content
a.btn-next-pager href="/api/jamtracks?page=1" Next
.end-of-jamtrack-list.end-of-list="No more Jamtracks"
script#template-jamtrack type='text/template'
.jamtrack-record jamtrack-id="{{data.jamtrack.id}}"
.top_bar
.jamtrack-detail.jamtrack-header JAMTRACK
.jamtrack-tracks.jamtrack-header TRACKS INCLUDED/PREVIEW
.jamtrack-action.jamtrack-header SHOP
.jamtrack-detail
tr.jamtrack-record jamtrack-id="{{data.jamtrack.id}}"
td.jamtrack-detail
.detail-label
| Title:
.detail-value
| {{data.jamtrack.name}}
/ .clearall.detail-label
/ | Type:
/ .detail-value
/ | {{data.jamtrack.recording_type}}
/ .clearall.detail-label
/ | Original Artist:
/ .detail-value
/ | {{data.jamtrack.original_artist}}
.clearall.detail-label
| Original Artist:
.detail-value
@ -55,46 +49,27 @@ script#template-jamtrack type='text/template'
.detail-value
| {{data.jamtrack.description}}
="{% } %}"
/ / / .clearall.detail-label
/ | Copyright:
/ .copyright-value
/ ="{% if (data.jamtrack.licensor !=null) { %}"
/ | {{data.jamtrack.licensor.name}}
/ ="{% }; %}"
/ .clearall.jamtrack-description
/ .detail-label
/ | Description
/ .detail-value
/ | {{data.jamtrack.description}}
/ .clearall
.jamtrack-tracks
/ .tracks-caption
/ | Tracks in This Recording:
="{% counter = 0 %}"
="{% _.each(data.jamtrack.tracks, function(track) { %}"
="{% if(track.track_type == JK.MASTER_TRACK) return; %}"
.track-instrument href="{{track.url_44}}"
.instrument-image
img src="/assets/shared/play_button.png" width=24 height=24
.instrument-image
img src="{{track.instrument_url}}" width=24 height=24
.instrument-desc
| {{track.instrument_desc}}
.clearall
="{% }); %}"
td.jamtrack-tracks
.detail-arrow
.jamtrack-detail-btn
.jamtrack-detail-btn.orange
="{% if (data.expanded) { %}"
| hide tracks
=image_tag("up_arrow.png")
| hide tracks ({{data.jamtrack.tracks.length}})
a.details-arrow.arrow-up-orange
="{% } else { %}"
| preview all tracks
=image_tag("down_arrow.png")
| show tracks ({{data.jamtrack.tracks.length}})
a.details-arrow.arrow-down-orange
="{% } %}"
.jamtrack-action
="{% if (data.expanded) { %}"
="{% _.each(data.jamtrack.tracks, function(track) { %}"
.jamtrack-track jamtrack-track-id="{{track.id}}"
/ .instrument-desc
/ | {{track.instrument_desc}}
/.track-instrument
.jamtrack-preview
.clearall
="{% }); %}"
="{% } %}"
td.jamtrack-action
/ a.play-button href="#" data-jamtrack-id="{{data.jamtrack.id}}"
/ =image_tag "shared/play_button.png"
.jamtrack-price
@ -108,7 +83,6 @@ script#template-jamtrack type='text/template'
="{% }; %}"
="{% if (data.jamtrack.sales_region==JK.AVAILABILITY_US) { %}"
.jamtrack-license
| This JamTrack available only to US customers.
a.license-us-why href="#", why?
| This JamTrack available only to US customers. &nbsp;&nbsp;&nbsp;&nbsp;
a.license-us-why.orange href="#" why?
="{% }; %}"
.clearall

View File

@ -8,7 +8,7 @@
.list-columns
.about
h2 what are jamtracks?
p.what
.what
.details JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each and every part of the master mix. Used with the free JamKazam app & Internet service, you can:
ul
li Solo just the part you want to play in order to hear and learn it
@ -19,17 +19,17 @@
/ TODO: put in video thumbnail when available:
.browse
h2 my jamtracks
p.howto
.howto
.details
span="To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or "
a href="client#/jamtrack" visit the JamTracks Section of your account.
.free-jamtrack.orange-fill
.free-jamtrack.orange-fill.details
| For a limited time, get one JamTrack free. Browse JamTracks below, add one to your shopping cart, and we'll make it free during the checkout process.
h2 browse jamtracks
.browse-header
span="browse by band "
| browse by band &nbsp;&nbsp;
a href="client#/jamtrack" or browse all jamtracks
.band-browse
.band-browse.two-column-list-container
ul#band_list
li#no_bands_found.hidden No bands found

View File

@ -1,5 +1,5 @@
.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.
.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. If you don't want to play online in real-time sessions, you can click NEXT to skip this step.
.wizard-step-content
.wizard-step-column
%h2 Instructions

View File

@ -40,6 +40,12 @@
span
| Settings
.session-tracks-scroller
#session-mytracks-notracks
p.notice
| You have not set up any inputs for your instrument or vocals.&nbsp;
| If you want to hear yourself play through the JamKazam app,&nbsp;
| and let the app mix your live playing with JamTracks, or with other musicians in online sessions,&nbsp;
a.open-ftue-no-tracks href='#' click here now.
#session-mytracks-container
#voice-chat.voicechat[style="display:none;" mixer-id=""]
.voicechat-label

View File

@ -264,8 +264,6 @@
var jamtrackScreen = new JK.JamTrackScreen(JK.app);
jamtrackScreen.initialize();
var jamtrackLanding = new JK.JamTrackLanding(JK.app);
jamtrackLanding.initialize();
@ -316,6 +314,9 @@
var allSyncsDialog = new JK.AllSyncsDialog(JK.app);
allSyncsDialog.initialize();
var singlePlayerProfileGuardDialog = new JK.SinglePlayerProfileGuardDialog(JK.app);
singlePlayerProfileGuardDialog.initialize();
// do a client update early check upon initialization
JK.ClientUpdateInstance.check()
@ -329,6 +330,9 @@
var jamServer = new JK.JamServer(JK.app, function(event_type) {JK.app.activeElementEvent(event_type)});
jamServer.initialize();
var clientInit = new JK.ClientInit();
clientInit.init();
// latency_tester does not want to be here - redirect it
if(window.jamClient.getOperatingMode && window.jamClient.getOperatingMode() == "server") {
window.location = "/latency_tester";

View File

@ -37,5 +37,6 @@
= render 'dialogs/openBackingTrackDialog'
= render 'dialogs/loginRequiredDialog'
= render 'dialogs/jamtrackPaymentHistoryDialog'
= render 'dialogs/singlePlayerProfileGuard'
= render 'dialogs/genreSelectorDialog'
= render 'dialogs/recordingSelectorDialog'
= render 'dialogs/recordingSelectorDialog'

View File

@ -36,16 +36,24 @@
a href="#" class="google-invite"
= image_tag "content/icon_google.png", {:align=>"absmiddle", :height => 26, :width => 26 }
span Google+
.column.get-a-free-jamtrack-section
h2.get-a-free-jamtrack.hidden GET A FREE JAMTRACK
h2.browse-jamtracks.hidden CHECK OUT JAMTRACKS
.blurb
| JamTracks are the best way to play with your favorite music. Unlike traditional backing tracks,&nbsp;
| they are complete multitrack recordings, with fully isolated tracks for each part.&nbsp
span.jamtracks-limited-time.hidden For a limited time, you can get your first JamTrack free.
| Check it out!
.action-button
a.button-orange.browse-jamtrack rel="external" href="#" LEARN MORE
br clear="both"
.row.find-connect
.column
h2 CREATE A "REAL" SESSION
.blurb
| You can create a session to start immediately and hope others join, but this doesnt work well. Its better
to schedule a session and invite friends or the community to join you. Watch a video to learn how, then
schedule your first session!
| You can create sessions that start immediately and see who joins, or you can schedule sessions, invite friends, and others from the community, and manage RSVPs. Learn how.
.action-button
a.button-orange rel="external" href="https://www.youtube.com/watch?v=EZZuGcDUoWk" WATCH VIDEO
br clear="both"
.row.find-connect
.column
h2 FIND SESSIONS TO JOIN
.blurb
@ -53,14 +61,6 @@
to learn about how to find and select good sessions to join.
.action-button
a.button-orange.setup-gear rel="external" href="https://www.youtube.com/watch?v=xWponSJo-GU" WATCH VIDEO
.column
h2 CONNECT WITH MUSICIANS
.blurb
| To play more music, tap into our growing
community to connect with other musicians. Watch this video for tips on how to do this.
.action-button
a.button-orange rel="external" href="https://www.youtube.com/watch?v=4KWklSZZxRc" WATCH VIDEO
br clear="both"
.row.full.learn-more
.column

View File

@ -0,0 +1,22 @@
.dialog.dialog-overlay-sm layout='dialog' layout-id='single-player-profile-dialog' id='single-player-profile-dialog'
.content-head
= image_tag "content/icon_alert.png", {:width => 24, :height => 24, :class => 'content-icon' }
h1 Application Notice
.dialog-inner
p.high-latency.hidden
| Your audio profile has a latency score of&nbsp;
span.audio-latency
br
br
| This is too high to play with others in real-time. However, you can play with JamTracks and backing tracks by yourself in a private session, or go to the gear setup wizard and add a new audio profile with lower latency.
p.has-no-inputs.hidden
| You are currently using the default system profile, which has no audio inputs.
br
br
| With this profile, you can't play with others in real-time. However, you can play with JamTracks and backing tracks by yourself in a private session, or go to the gear setup wizard and add a new audio profile that uses your gear.
.right.action-buttons
a.button-grey.btn-cancel href='#' layout-action="cancel" CANCEL
a.button-grey.btn-gear-setup href="/client#/account/audio" GO TO GEAR SETUP
a.button-orange.btn-private-session href="#" PRIVATE SESSION

View File

@ -0,0 +1,63 @@
- provide(:page_name, 'home')
.home-column
= link_to image_tag("web/thumbnail_jamtracks.jpg", :alt => "JamTracks explanatory video"), '#', class: "jamtracks-video video-item", 'data-video-header' => 'JamTracks', 'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'
h3 Complete, Multi-Track Backing Tracks
p
strong JamTracks
| &nbsp;are the best way to play with your favorite music. Unlike traditional backing tracks, JamTracks are complete multitrack recordings, with fully isolated tracks for each part.
= link_to image_tag("web/button_cta_jamtrack.png", width: 234, height:57), '/client#/jamtrack', class: 'cta-button jamtracks'
br clear="all"
.extra-links
.learn-more
a.learn-more-jamtracks href='/products/jamtracks' learn more
.home-column
= link_to image_tag("web/thumbnail_platform.jpg", :alt => "JamKazam explanatory video!"), '#', class: "platform-video video-item", 'data-video-header' => 'JamKazam Platform', 'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'
h3 Online Music Collaboration Platform
p
strong JamKazam
| &nbsp;is an innovative live music platform and social network, enabling musicians to play music together in real time from different locations over the Internet as if they are sitting in the same room.
= link_to image_tag("web/button_cta_platform.png", width: 234, height: 57), '/signup', class: 'cta-button platform'
.extra-links
span.learn-more.shared
a.learn-more-platform href='/products/platform' learn more
span.sign-in-holder.shared
a.sign-in href='/signin' sign in
br clear="all"
.home-column.last
= link_to image_tag("web/thumbnail_jamblaster.jpg", :alt => "JamBlaster explanatory video!"), '#', class: "jamblaster-video video-item", 'data-video-header' => 'JamBlaster', 'data-video-url' => 'http://www.youtube.com/embed/gAJAIHMyois?autoplay=1'
h3 Ultra Low-Latency Audio Interface
p
| The&nbsp;
strong JamBlaster
| &nbsp;is a device designed from the ground up to meet the requirements of online music play, vastly extending the range over which musicians can play together across the Internet.
= link_to image_tag("web/button_cta_jamblaster.png", width: 234, height: 57), '/products/jamblaster', class: 'cta-button jamblaster'
.extra-links
.learn-more
a.learn-more-jamblaster href='/products/jamblaster' learn more
br clear="all"
br clear="all"
- content_for :after_black_bar do
.latest-promo
= render :partial => "latest"
.endorsement-promo
.home-buzz
h2 What Musicians in the JamKazam Community Are Saying
= link_to image_tag("web/thumbnail_buzz.jpg", :alt => "JamKazam Endorsements!", width:300), '#', class: "endorsements-video video-item", 'data-video-header' => 'JamKazam Community', 'data-video-url' => 'http://www.youtube.com/embed/_7qj5RXyHCo?autoplay=1'
br clear="all"
javascript:
window.JK.HomePage();

View File

@ -8,7 +8,7 @@ SampleApp::Application.routes.draw do
resources :users
resources :sessions, only: [:new, :create, :destroy]
root to: 'users#welcome'
root to: 'users#home'
# signup, and signup completed, related pages
match '/signup', to: 'users#new', :via => 'get'
@ -213,10 +213,10 @@ SampleApp::Application.routes.draw do
match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'
# Jamtracks
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
match '/jamtracks/:plan_code' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show'
match '/jamtracks/band/:plan_code' => 'api_jam_tracks#show_with_artist_info', :via => :get, :as => 'api_jam_tracks_show_with_artist_info'
match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list'
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download'
match '/jamtracks/played/:id' => 'api_jam_tracks#played', :via => :post, :as => 'api_jam_tracks_played'
match '/jamtracks/enqueue/:id' => 'api_jam_tracks#enqueue', :via => :post, :as => 'api_jam_tracks_enqueue'

View File

@ -10,8 +10,7 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
Capybara.default_wait_time = 10
end
let(:user) { FactoryGirl.create(:user, :show_whats_next => true) }
let(:user) { FactoryGirl.create(:user, show_whats_next: true, has_redeemable_jamtrack: true) }
describe "in normal browser" do
before(:each) do
@ -24,6 +23,36 @@ describe "Home Screen", :js => true, :type => :feature, :capybara_feature => tru
find('#getting-started-dialog .setup-gear-btn').trigger('click')
should have_selector('p', text: 'To configure your audio gear, you must use the JamKazam application.')
end
it "should show jam track browsing page" do
find('#getting-started-dialog span.jamtracks-limited-time')
find('#getting-started-dialog h2.get-a-free-jamtrack')
expect(page).to have_selector('#getting-started-dialog .browse-jamtracks', visible: false)
find('#getting-started-dialog a.browse-jamtrack').trigger('click')
should have_selector('h1', text: 'jamtracks')
end
end
describe "in normal browser with redeemed jamtrack" do
let(:redeemed_user) { FactoryGirl.create(:user, show_whats_next: true, has_redeemable_jamtrack: false) }
before(:each) do
sign_in_poltergeist redeemed_user
visit "/client"
should have_selector('h1', text: 'getting started')
end
it "should show jam track browsing page" do
find('#getting-started-dialog h2.browse-jamtracks')
expect(page).to have_selector('#getting-started-dialog h2.get-a-free-jamtrack', visible: false)
expect(page).to have_selector('#getting-started-dialog span.jamtracks-limited-time', visible: false)
find('#getting-started-dialog .browse-jamtrack').trigger('click')
should have_selector('h1', text: 'jamtracks')
end
end
describe "in native client" do

View File

@ -0,0 +1,158 @@
require 'spec_helper'
describe "Home Page", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
end
before(:each) do
Feed.delete_all
MusicSessionUserHistory.delete_all
MusicSession.delete_all
Recording.delete_all
emulate_client
visit "/"
find('h1', text: 'Live music platform & social network for musicians')
end
let(:user) { FactoryGirl.create(:user) }
let(:fb_auth) {
{ :provider => "facebook",
:uid => "1234",
:info => {:name => "John Doe",
:email => "johndoe@email.com"},
:credentials => {:token => "testtoken234tsdf", :expires_at => 2391456019},
:extra => { :raw_info => {:first_name => 'John', :last_name => 'Doe', :email => 'facebook@jamkazam.com', :gender => 'male'}} }
}
it "links work" do
# learn more about JamTracks
find('.learn-more-jamtracks').trigger(:click)
find('h1.product-headline', text: 'JamTracks by JamKazam')
visit '/'
# learn more about the platform
find('.learn-more-platform').trigger(:click)
find('h1.product-headline', text: 'The JamKazam Platform')
visit '/'
# learn more about the platform
find('.learn-more-jamblaster').trigger(:click)
find('h1.product-headline', text: 'The JamBlaster by JamKazam')
visit '/'
# try to sign in
find('a.sign-in').trigger(:click)
find('h1', text: 'sign in or register')
visit '/'
# try to click jamtrack CTA button
find('a.cta-button.jamtracks').trigger(:click)
find('h1', text: 'jamtracks')
visit '/'
# try to click platform CTA button
find('a.cta-button.platform').trigger(:click)
find('h2', text: '1Create your free JamKazam account')
visit '/'
# try to click jamblaster CTA button
find('a.cta-button.jamblaster').trigger(:click)
find('h1.product-headline', text: 'The JamBlaster by JamKazam')
end
it "signed in user gets redirected to app home page" do
fast_signin(user,'/')
find('h2', text: 'create session')
end
describe "feed" do
it "data" do
claimedRecording1 = FactoryGirl.create(:claimed_recording)
MusicSession1 = claimedRecording1.recording.music_session.music_session
visit "/"
find('h1', text: 'Live music platform & social network for musicians')
find('.feed-entry.music-session-history-entry .description', text: MusicSession1.description)
find('.feed-entry.music-session-history-entry .session-status', text: 'BROADCASTING OFFLINE')
find('.feed-entry.music-session-history-entry .session-controls.inprogress', text: 'BROADCASTING OFFLINE')
find('.feed-entry.music-session-history-entry .artist', text: MusicSession1.creator.name)
should_not have_selector('.feed-entry.music-session-history-entry .musician-detail')
find('.feed-entry.recording-entry .name', text: claimedRecording1.name)
find('.feed-entry.recording-entry .description', text: claimedRecording1.description)
find('.feed-entry.recording-entry .title', text: 'RECORDING')
find('.feed-entry.recording-entry .artist', text: claimedRecording1.user.name)
should_not have_selector('.feed-entry.recording-entry .musician-detail')
# try to hide the recording
claimedRecording1.is_public = false
claimedRecording1.save!
visit "/"
find('h1', text: 'Live music platform & social network for musicians')
find('.feed-entry.music-session-history-entry .description', text: MusicSession1.description)
should_not have_selector('.feed-entry.recording-entry')
# try to hide the music session
MusicSession1.fan_access = false
MusicSession1.save!
visit "/"
find('h1', text: 'Live music platform & social network for musicians')
should have_selector('.feed-entry.music-session-history-entry')
# try to mess with the music session history by removing all user histories (which makes it a bit invalid)
# but we really don't want the front page to ever crash if we can help it
MusicSession1.fan_access = true
MusicSession1.music_session_user_histories.delete_all
MusicSession1.reload
MusicSession1.music_session_user_histories.length.should == 0
visit "/"
find('h1', text: 'Live music platform & social network for musicians')
should_not have_selector('.feed-entry.music-session-history-entry')
end
end
=begin
describe "signin with facebook" do
before(:each) do
user.user_authorizations.build provider: 'facebook', uid: '1234', token: 'abc', token_expiration: 1.days.from_now
user.save!
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new(fb_auth)
end
it "click will redirect to facebook for authorization" do
pending "move elsewhere"
find('.signin-facebook').trigger(:click)
wait_until_curtain_gone
find('h2', text: 'musicians')
end
end
=end
end

View File

@ -49,15 +49,12 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
find('#jamtrack-availability-dialog #dialog-close-button').trigger(:click)
end
jamtrack.jam_track_tracks.each do |track|
jamtrack_record.find('.instrument-desc', "#{track.instrument.description} ( #{track.part} }")
end
if options[:added_cart]
jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Already In Cart')
else
jamtrack_record.find('a.jamtrack-add-cart.button-orange', text: 'Add to Cart')
end
jamtrack_record
end
def not_find_jamtrack jamtrack
@ -144,5 +141,16 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
find('.shopping-sub-total', text: "Subtotal: $ #{jt_us.price + jt_ww.price}")
end
it "can expand" do
jamtrack = find_jamtrack(jt_us)
jamtrack.find('.jamtrack-detail-btn').trigger(:click)
jt_us.jam_track_tracks.each do |track|
jamtrack.find('.instrument-name', track.instrument.description)
jamtrack.find('.part', "(#{track.part}}")
end
end
end
end

View File

@ -25,7 +25,7 @@ describe "signin" do
click_button "SIGN IN"
end
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
end
# proves that redirect-to is preserved between failure
@ -47,7 +47,7 @@ describe "signin" do
click_button "SIGN IN"
end
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
end
it "success with forum sso" do

View File

@ -80,7 +80,7 @@ describe "Text Message", :js => true, :type => :feature, :capybara_feature => tr
it "can load directly into chat session from url" do
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
visit "/client#/home/text-message/d1=#{@user2.id}"
find('h1', text: 'conversation with ' + @user2.name)
end
@ -125,7 +125,7 @@ describe "Text Message", :js => true, :type => :feature, :capybara_feature => tr
it "shows error with a notify" do
visit '/'
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
visit "/client#/home/text-message/d1=#{@user2.id}"
find('h1', text: 'conversation with ' + @user2.name)
send_text_message('ass', should_fail:'profanity')

View File

@ -28,12 +28,12 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d
emulate_client
sign_in_poltergeist user
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
end
it "redirects back when done, and updates user_auth" do
visit '/auth/twitter'
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
sleep 1
user.reload
auth = user.user_authorization('twitter')
@ -43,7 +43,7 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d
auth.secret.should == 'twitter_secret'
visit '/auth/twitter'
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
user.reload
auth = user.user_authorization('twitter')
auth.uid.should == '1234'
@ -53,7 +53,7 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d
it "shows error when two users try to auth same twitter account" do
visit '/auth/twitter'
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
sleep 1
user.reload
auth = user.user_authorization('twitter')
@ -63,7 +63,7 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d
sign_in_poltergeist user2
visit '/'
find('h1', text: 'Play music together over the Internet as if in the same room')
should_be_at_root
visit '/auth/twitter'
find('li', text: 'This twitter account is already associated with someone else')
end

View File

@ -1,235 +0,0 @@
require 'spec_helper'
describe "Welcome", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 10
end
before(:each) do
Feed.delete_all
MusicSessionUserHistory.delete_all
MusicSession.delete_all
Recording.delete_all
emulate_client
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
end
let(:user) { FactoryGirl.create(:user) }
let(:fb_auth) {
{ :provider => "facebook",
:uid => "1234",
:info => {:name => "John Doe",
:email => "johndoe@email.com"},
:credentials => {:token => "testtoken234tsdf", :expires_at => 2391456019},
:extra => { :raw_info => {:first_name => 'John', :last_name => 'Doe', :email => 'facebook@jamkazam.com', :gender => 'male'}} }
}
describe "signin" do
before(:each) do
find('#signin').trigger(:click)
end
it "show dialog" do
should have_selector('h1', text: 'sign in')
end
it "shows signup dialog if selected" do
find('.show-signup-dialog').trigger(:click)
find('h1', text: 'sign up for jamkazam')
end
it "forgot password" do
find('a.forgot-password').trigger(:click)
find('h1', text: 'reset your password')
end
it "closes if cancelled" do
find('a.signin-cancel').trigger(:click)
should_not have_selector('h1', text: 'sign in')
end
describe "signin natively" do
it "redirects to client on login" do
within('.signin-form') do
fill_in "Email Address:", with: user.email
fill_in "Password:", with: user.password
click_button "SIGN IN"
end
wait_until_curtain_gone
find('h2', text: 'musicians')
end
it "shows error if bad login" do
within('.signin-form') do
fill_in "Email Address:", with: "junk"
fill_in "Password:", with: user.password
click_button "SIGN IN"
end
should have_selector('h1', text: 'sign in')
find('div.login-error-msg', text: 'Invalid login')
end
end
describe "redirect-to" do
it "redirect on login" do
visit "/client#/account"
find('h1', text: 'sign in or register')
within('.signin-form') do
fill_in "Email Address:", with: user.email
fill_in "Password:", with: user.password
click_button "SIGN IN"
end
wait_until_curtain_gone
find('h2', text: 'identity:')
end
it "redirect if already logged in" do
# this is a rare case
sign_in_poltergeist(user)
visit "/?redirect-to=" + ERB::Util.url_encode("/client#/account")
find('h1', text: 'Play music together over the Internet as if in the same room')
find('#signin').trigger(:click)
wait_until_curtain_gone
find('h2', text: 'identity:')
end
end
describe "signin with facebook" do
before(:each) do
user.user_authorizations.build provider: 'facebook', uid: '1234', token: 'abc', token_expiration: 1.days.from_now
user.save!
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new(fb_auth)
end
it "click will redirect to facebook for authorization" do
find('.signin-facebook').trigger(:click)
wait_until_curtain_gone
find('h2', text: 'musicians')
end
end
end
describe "signup" do
before(:each) do
find('#signup').trigger(:click)
end
it "show dialog" do
should have_selector('h1', text: 'sign up for jamkazam')
end
it "shows signin dialog if selected" do
find('.show-signin-dialog').trigger(:click)
find('h1', text: 'sign in')
end
it "closes if cancelled" do
find('a.signup-cancel').trigger(:click)
should_not have_selector('h1', text: 'sign in')
end
describe "signup with email" do
it "click will redirect to signup page" do
find('.signup-email').trigger(:click)
find('h2.create-account-header', text: '1Create your free JamKazam account')
end
end
describe "signup with facebook" do
before(:each) do
fb_auth[:uid] = '12345'
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new(fb_auth)
end
it "click will redirect to facebook for authorization" do
find('.signup-facebook').trigger(:click)
find('h2.create-account-header', text: '1Create your free JamKazam account')
find_field('jam_ruby_user[first_name]').value.should eq 'John'
find_field('jam_ruby_user[last_name]').value.should eq 'Doe'
find_field('jam_ruby_user[email]').value.should eq 'facebook@jamkazam.com'
end
end
end
describe "feed" do
it "data" do
claimedRecording1 = FactoryGirl.create(:claimed_recording)
MusicSession1 = claimedRecording1.recording.music_session.music_session
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
find('.feed-entry.music-session-history-entry .description', text: MusicSession1.description)
find('.feed-entry.music-session-history-entry .session-status', text: 'BROADCASTING OFFLINE')
find('.feed-entry.music-session-history-entry .session-controls.inprogress', text: 'BROADCASTING OFFLINE')
find('.feed-entry.music-session-history-entry .artist', text: MusicSession1.creator.name)
should_not have_selector('.feed-entry.music-session-history-entry .musician-detail')
find('.feed-entry.recording-entry .name', text: claimedRecording1.name)
find('.feed-entry.recording-entry .description', text: claimedRecording1.description)
find('.feed-entry.recording-entry .title', text: 'RECORDING')
find('.feed-entry.recording-entry .artist', text: claimedRecording1.user.name)
should_not have_selector('.feed-entry.recording-entry .musician-detail')
# try to hide the recording
claimedRecording1.is_public = false
claimedRecording1.save!
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
find('.feed-entry.music-session-history-entry .description', text: MusicSession1.description)
should_not have_selector('.feed-entry.recording-entry')
# try to hide the music session
MusicSession1.fan_access = false
MusicSession1.save!
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
should have_selector('.feed-entry.music-session-history-entry')
# try to mess with the music session history by removing all user histories (which makes it a bit invalid)
# but we really don't want the front page to ever crash if we can help it
MusicSession1.fan_access = true
MusicSession1.music_session_user_histories.delete_all
MusicSession1.reload
MusicSession1.music_session_user_histories.length.should == 0
visit "/"
find('h1', text: 'Play music together over the Internet as if in the same room')
should_not have_selector('.feed-entry.music-session-history-entry')
end
end
end

View File

@ -310,7 +310,7 @@ describe "Active Music Session API ", :type => :api do
JSON.parse(last_response.body)["errors"]["genre"].should == ["can't be blank"]
end
it "should error with no track specified" do
it "should not error with no track specified" do
original_count = ActiveMusicSession.all().length
client = FactoryGirl.create(:connection, :user => user, :ip_address => "1.1.1.1")
@ -320,10 +320,7 @@ describe "Active Music Session API ", :type => :api do
music_session = JSON.parse(last_response.body)
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client.client_id, :as_musician => true, :tracks => []}.to_json, "CONTENT_TYPE" => 'application/json'
JSON.parse(last_response.body)["errors"]["tracks"].should == [ValidationMessages::SELECT_AT_LEAST_ONE]
# check that the transaction was rolled back
ActiveMusicSession.all().length.should == original_count
last_response.status.should eq(201)
end
it "should error with invalid track specified" do

View File

@ -213,8 +213,35 @@ def go_to_root
should_be_at_root
end
def should_be_at_root
find('h1', text: 'Play music together over the Internet as if in the same room')
def should_be_at_root(options={signed_in:nil})
#if options[:signed_in].nil?
case Capybara.current_session.driver
when Capybara::Poltergeist::Driver
signed_in = !page.driver.cookies['remember_token'].nil?
if signed_in
find('h2', text: 'create session')
else
find('h1', text: 'Live music platform & social network for musicians')
end
when Capybara::RackTest::Driver
signed_in = false # actually, the user may be signed in, but, we only redirect to /client in javascript, so RackTest won't do that
if signed_in
find('h2', text: 'create session')
else
find('h1', text: 'Live music platform & social network for musicians')
end
else
raise "no cookie-setter implemented for driver #{Capybara.current_session.driver.class.name}"
end
#if Capybara.javascript_driver == :poltergeist
#signed_in = !cookie_jar['remember_me'].nil? # !page.driver.cookies['remember_token'].nil?
#else
#signed_in = false # actually, the user may be signed in, but, we only redirect to /client in javascript, so RackTest won't do that
#end
#else
# signed_in = options[:signed_in]
#end
end
def should_be_at_signin