diff --git a/db/manifest b/db/manifest index 5a4fa59d1..1c52fc6c1 100755 --- a/db/manifest +++ b/db/manifest @@ -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 \ No newline at end of file diff --git a/db/up/show_whats_next_count.sql b/db/up/show_whats_next_count.sql new file mode 100644 index 000000000..82e50d469 --- /dev/null +++ b/db/up/show_whats_next_count.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN show_whats_next_count INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/connection.rb b/ruby/lib/jam_ruby/models/connection.rb index 26dd417fe..5a5378b91 100644 --- a/ruby/lib/jam_ruby/models/connection.rb +++ b/ruby/lib/jam_ruby/models/connection.rb @@ -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"]) diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index a4d293276..5c8eb1d3a 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -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] diff --git a/ruby/lib/jam_ruby/models/music_session_user_history.rb b/ruby/lib/jam_ruby/models/music_session_user_history.rb index 430c897a1..bca0ecccc 100644 --- a/ruby/lib/jam_ruby/models/music_session_user_history.rb +++ b/ruby/lib/jam_ruby/models/music_session_user_history.rb @@ -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 diff --git a/web/app/assets/images/web/button_cta_jamblaster.png b/web/app/assets/images/web/button_cta_jamblaster.png new file mode 100644 index 000000000..8650fe322 Binary files /dev/null and b/web/app/assets/images/web/button_cta_jamblaster.png differ diff --git a/web/app/assets/images/web/button_cta_jamtrack.png b/web/app/assets/images/web/button_cta_jamtrack.png new file mode 100644 index 000000000..998d89b75 Binary files /dev/null and b/web/app/assets/images/web/button_cta_jamtrack.png differ diff --git a/web/app/assets/images/web/button_cta_platform.png b/web/app/assets/images/web/button_cta_platform.png new file mode 100644 index 000000000..dba8d499b Binary files /dev/null and b/web/app/assets/images/web/button_cta_platform.png differ diff --git a/web/app/assets/images/web/thumbnail_buzz.jpg b/web/app/assets/images/web/thumbnail_buzz.jpg new file mode 100644 index 000000000..737a17873 Binary files /dev/null and b/web/app/assets/images/web/thumbnail_buzz.jpg differ diff --git a/web/app/assets/images/web/thumbnail_jamblaster.jpg b/web/app/assets/images/web/thumbnail_jamblaster.jpg new file mode 100644 index 000000000..7eef7d267 Binary files /dev/null and b/web/app/assets/images/web/thumbnail_jamblaster.jpg differ diff --git a/web/app/assets/images/web/thumbnail_jamtracks.jpg b/web/app/assets/images/web/thumbnail_jamtracks.jpg new file mode 100644 index 000000000..98bb27db5 Binary files /dev/null and b/web/app/assets/images/web/thumbnail_jamtracks.jpg differ diff --git a/web/app/assets/images/web/thumbnail_platform.jpg b/web/app/assets/images/web/thumbnail_platform.jpg new file mode 100644 index 000000000..8adacef59 Binary files /dev/null and b/web/app/assets/images/web/thumbnail_platform.jpg differ diff --git a/web/app/assets/javascripts/accounts_jamtracks.js.coffee b/web/app/assets/javascripts/accounts_jamtracks.js.coffee index 43e1e51b9..f7bdef10f 100644 --- a/web/app/assets/javascripts/accounts_jamtracks.js.coffee +++ b/web/app/assets/javascripts/accounts_jamtracks.js.coffee @@ -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() diff --git a/web/app/assets/javascripts/client_init.js.coffee b/web/app/assets/javascripts/client_init.js.coffee new file mode 100644 index 000000000..20d0e76cc --- /dev/null +++ b/web/app/assets/javascripts/client_init.js.coffee @@ -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(); diff --git a/web/app/assets/javascripts/dialog/gettingStartedDialog.js b/web/app/assets/javascripts/dialog/gettingStartedDialog.js index 6fc114f74..db80c15ad 100644 --- a/web/app/assets/javascripts/dialog/gettingStartedDialog.js +++ b/web/app/assets/javascripts/dialog/gettingStartedDialog.js @@ -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(); diff --git a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js index ea18fcd01..0c47eb20e 100644 --- a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js +++ b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js @@ -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) { diff --git a/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee b/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee new file mode 100644 index 000000000..d94a21d6a --- /dev/null +++ b/web/app/assets/javascripts/dialog/singlePlayerProfileGuard.js.coffee @@ -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 \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/videoDialog.js b/web/app/assets/javascripts/dialog/videoDialog.js index 3d5a41707..01ab3dc6b 100644 --- a/web/app/assets/javascripts/dialog/videoDialog.js +++ b/web/app/assets/javascripts/dialog/videoDialog.js @@ -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'); diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js index 16adb8b3f..2c010bac2 100644 --- a/web/app/assets/javascripts/everywhere/everywhere.js +++ b/web/app/assets/javascripts/everywhere/everywhere.js @@ -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')) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index d0b640b8d..732547d19 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -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 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 '
There\'s no jamtracks.
' + @content.append '
No JamTracks found.
' 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: $('
Loading ...
') 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 diff --git a/web/app/assets/javascripts/jamtrack.js.coffee b/web/app/assets/javascripts/jamtrack.js.coffee deleted file mode 100644 index 95a1c89f0..000000000 --- a/web/app/assets/javascripts/jamtrack.js.coffee +++ /dev/null @@ -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 '
There\'s no jamtracks.
' - 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: $('
Loading ...
') - 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() - - diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index cf75275e1..a9ab74fa2 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -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; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 2b6ea1e80..e1b191104 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -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(); diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 05f726557..3c0afa636 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -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; diff --git a/web/app/assets/javascripts/session_utils.js b/web/app/assets/javascripts/session_utils.js index d1e2358f7..4e3b13594 100644 --- a/web/app/assets/javascripts/session_utils.js +++ b/web/app/assets/javascripts/session_utils.js @@ -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) { diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 52d47094a..f6a47d982 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -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. diff --git a/web/app/assets/javascripts/web/home.js b/web/app/assets/javascripts/web/home.js new file mode 100644 index 000000000..5eb7c1ed1 --- /dev/null +++ b/web/app/assets/javascripts/web/home.js @@ -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); \ No newline at end of file diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js index a36ad12e2..7f128d092 100644 --- a/web/app/assets/javascripts/web/web.js +++ b/web/app/assets/javascripts/web/web.js @@ -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 diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js index cb0cd4317..218ad8b8f 100644 --- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js +++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js @@ -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; } diff --git a/web/app/assets/javascripts/wizard/gear/step_network_test.js b/web/app/assets/javascripts/wizard/gear/step_network_test.js index 008c2409a..f2b019b14 100644 --- a/web/app/assets/javascripts/wizard/gear/step_network_test.js +++ b/web/app/assets/javascripts/wizard/gear/step_network_test.js @@ -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; diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js index 9479f86c9..bd67be896 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -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) diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js index fc397c2f9..7e7f2b352 100644 --- a/web/app/assets/javascripts/wizard/gear_test.js +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -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); } diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index c9d67f533..04716dde1 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -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); \ No newline at end of file diff --git a/web/app/assets/javascripts/wizard/wizard.js b/web/app/assets/javascripts/wizard/wizard.js index 6d8600e3e..d0b1d45fc 100644 --- a/web/app/assets/javascripts/wizard/wizard.js +++ b/web/app/assets/javascripts/wizard/wizard.js @@ -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; diff --git a/web/app/assets/stylesheets/client/help.css.scss b/web/app/assets/stylesheets/client/help.css.scss index 512988936..6dbfa7149 100644 --- a/web/app/assets/stylesheets/client/help.css.scss +++ b/web/app/assets/stylesheets/client/help.css.scss @@ -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; diff --git a/web/app/assets/stylesheets/client/jamtrack.css.scss b/web/app/assets/stylesheets/client/jamtrack.css.scss index d63839387..b9ed8314b 100644 --- a/web/app/assets/stylesheets/client/jamtrack.css.scss +++ b/web/app/assets/stylesheets/client/jamtrack.css.scss @@ -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; } diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 00fde3ac1..700758035 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -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 { diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 6916f7c60..5692b085b 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -429,8 +429,12 @@ } + .instructions { + height: 228px !important; + } + .network-test-results { - height: 248px !important; + height: 228px !important; @include border_box_sizing; &.testing { diff --git a/web/app/assets/stylesheets/dialogs/gettingStartDialog.css.scss b/web/app/assets/stylesheets/dialogs/gettingStartDialog.css.scss index a3ada5d95..401eea8a9 100644 --- a/web/app/assets/stylesheets/dialogs/gettingStartDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/gettingStartDialog.css.scss @@ -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; } diff --git a/web/app/assets/stylesheets/dialogs/singlePlayerProfileGuard.css.scss b/web/app/assets/stylesheets/dialogs/singlePlayerProfileGuard.css.scss new file mode 100644 index 000000000..89eedac9a --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/singlePlayerProfileGuard.css.scss @@ -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%; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/web/home.css.scss b/web/app/assets/stylesheets/web/home.css.scss new file mode 100644 index 000000000..4a82efe37 --- /dev/null +++ b/web/app/assets/stylesheets/web/home.css.scss @@ -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; + } + } + } +} diff --git a/web/app/assets/stylesheets/web/web.css b/web/app/assets/stylesheets/web/web.css index ac4463b15..5761892c5 100644 --- a/web/app/assets/stylesheets/web/web.css +++ b/web/app/assets/stylesheets/web/web.css @@ -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 diff --git a/web/app/assets/stylesheets/web/welcome.css.scss b/web/app/assets/stylesheets/web/welcome.css.scss index 2171789eb..8417ca9a2 100644 --- a/web/app/assets/stylesheets/web/welcome.css.scss +++ b/web/app/assets/stylesheets/web/welcome.css.scss @@ -2,7 +2,7 @@ @import "client/common.css.scss"; -body.web { +body.web.welcome { .signin-common { height:auto; diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 30a8a7ebf..fcb03267d 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -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) diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb index e2df670cc..cd1038904 100644 --- a/web/app/controllers/landings_controller.rb +++ b/web/app/controllers/landings_controller.rb @@ -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 diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index bcac17d3e..fbd431bc6 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -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 diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index 26dc5df11..18dee81ff 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -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 diff --git a/web/app/views/clients/_account_jamtracks.html.slim b/web/app/views/clients/_account_jamtracks.html.slim index 966523768..c04de8d3c 100644 --- a/web/app/views/clients/_account_jamtracks.html.slim +++ b/web/app/views/clients/_account_jamtracks.html.slim @@ -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' diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index 38fa7c4dd..235c23381 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -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.  + 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 + |  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.  + 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 + |  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,  + 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 + |  for a troubleshooting article. + | {% } %} + script type="text/template" id="template-help-session-plus-musicians" | Plus any interested JamKazam musicians that I approve. diff --git a/web/app/views/clients/_jamtrack.html.slim b/web/app/views/clients/_jamtrack.html.slim index 79e2cf17e..6cf0504e2 100644 --- a/web/app/views/clients/_jamtrack.html.slim +++ b/web/app/views/clients/_jamtrack.html.slim @@ -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.      + a.license-us-why.orange href="#" why? ="{% }; %}" - .clearall diff --git a/web/app/views/clients/_jamtrack_landing.html.slim b/web/app/views/clients/_jamtrack_landing.html.slim index 06e3efc2f..3096da3c8 100644 --- a/web/app/views/clients/_jamtrack_landing.html.slim +++ b/web/app/views/clients/_jamtrack_landing.html.slim @@ -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    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 diff --git a/web/app/views/clients/_network_test.html.haml b/web/app/views/clients/_network_test.html.haml index 0a2e34d64..db61e567a 100644 --- a/web/app/views/clients/_network_test.html.haml +++ b/web/app/views/clients/_network_test.html.haml @@ -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 diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index ae422b6b7..3ffaa3b68 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -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.  + | If you want to hear yourself play through the JamKazam app,  + | and let the app mix your live playing with JamTracks, or with other musicians in online sessions,  + a.open-ftue-no-tracks href='#' click here now. #session-mytracks-container #voice-chat.voicechat[style="display:none;" mixer-id=""] .voicechat-label diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 49bcfa5f9..b71e72d07 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -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"; diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 990240721..76d29970a 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -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' \ No newline at end of file diff --git a/web/app/views/dialogs/_gettingStartedDialog.html.slim b/web/app/views/dialogs/_gettingStartedDialog.html.slim index 88e674e8c..df934ab25 100644 --- a/web/app/views/dialogs/_gettingStartedDialog.html.slim +++ b/web/app/views/dialogs/_gettingStartedDialog.html.slim @@ -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,  + | they are complete multitrack recordings, with fully isolated tracks for each part.  + 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 doesn’t work well. It’s 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 diff --git a/web/app/views/dialogs/_singlePlayerProfileGuard.html.slim b/web/app/views/dialogs/_singlePlayerProfileGuard.html.slim new file mode 100644 index 000000000..d7c1628d3 --- /dev/null +++ b/web/app/views/dialogs/_singlePlayerProfileGuard.html.slim @@ -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  + 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 \ No newline at end of file diff --git a/web/app/views/users/home.html.slim b/web/app/views/users/home.html.slim new file mode 100644 index 000000000..57a635237 --- /dev/null +++ b/web/app/views/users/home.html.slim @@ -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 + |  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 + |  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  + strong JamBlaster + |  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(); diff --git a/web/config/routes.rb b/web/config/routes.rb index 88d621376..45a423d25 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -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' diff --git a/web/spec/features/getting_started_dialog_spec.rb b/web/spec/features/getting_started_dialog_spec.rb index 1c973f6cb..d7186cfe3 100644 --- a/web/spec/features/getting_started_dialog_spec.rb +++ b/web/spec/features/getting_started_dialog_spec.rb @@ -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 diff --git a/web/spec/features/home_page_spec.rb b/web/spec/features/home_page_spec.rb new file mode 100644 index 000000000..96ed58527 --- /dev/null +++ b/web/spec/features/home_page_spec.rb @@ -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 + diff --git a/web/spec/features/jamtrack_shopping_spec.rb b/web/spec/features/jamtrack_shopping_spec.rb index daeb34152..95f887979 100644 --- a/web/spec/features/jamtrack_shopping_spec.rb +++ b/web/spec/features/jamtrack_shopping_spec.rb @@ -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 diff --git a/web/spec/features/signin_spec.rb b/web/spec/features/signin_spec.rb index ad4f7dfec..699023a31 100644 --- a/web/spec/features/signin_spec.rb +++ b/web/spec/features/signin_spec.rb @@ -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 diff --git a/web/spec/features/text_message_spec.rb b/web/spec/features/text_message_spec.rb index 81bebc17e..45e4e77dc 100644 --- a/web/spec/features/text_message_spec.rb +++ b/web/spec/features/text_message_spec.rb @@ -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') diff --git a/web/spec/features/twitter_auth_spec.rb b/web/spec/features/twitter_auth_spec.rb index 40962ef94..ce28db090 100644 --- a/web/spec/features/twitter_auth_spec.rb +++ b/web/spec/features/twitter_auth_spec.rb @@ -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 diff --git a/web/spec/features/welcome_spec.rb b/web/spec/features/welcome_spec.rb deleted file mode 100644 index 4a2cd9e8f..000000000 --- a/web/spec/features/welcome_spec.rb +++ /dev/null @@ -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 - diff --git a/web/spec/requests/active_music_sessions_api_spec.rb b/web/spec/requests/active_music_sessions_api_spec.rb index de4fae3ed..61836348e 100755 --- a/web/spec/requests/active_music_sessions_api_spec.rb +++ b/web/spec/requests/active_music_sessions_api_spec.rb @@ -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 diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 7a89d9b23..8248cfbae 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -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