diff --git a/ruby/lib/jam_ruby/models/promotional.rb b/ruby/lib/jam_ruby/models/promotional.rb index 109b8bccd..19b66f63d 100644 --- a/ruby/lib/jam_ruby/models/promotional.rb +++ b/ruby/lib/jam_ruby/models/promotional.rb @@ -35,7 +35,7 @@ class JamRuby::Promotional < ActiveRecord::Base end def self.active(max_count=10) - rel = self.where(:aasm_state => ACTIVE_STATE) + rel = self.where(:aasm_state => ACTIVE_STATE).where('latest_id IS NOT NULL') if 0 < (mc = max_count.to_i) rel = rel.limit(mc) end diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index f447b6d19..440c0a1a8 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -78,14 +78,21 @@ checkServer() .done(function(response) { - audioDomElement.play(); - retryAttempts = 0; + if(!response.mount) { + transition(PlayStateSessionOver); + destroy(); + } + else { + audioDomElement.play(); - transition(PlayStateInitializing); + retryAttempts = 0; - // keep this after transition, because any transition clears this timer - waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); + transition(PlayStateInitializing); + + // keep this after transition, because any transition clears this timer + waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); + } }) } @@ -132,7 +139,7 @@ $audio.append(originalSource); audioDomElement = $audio.get(0); audioBind(); - logger.debug("recreated audio element ") + logger.log("recreated audio element ") } function clearBufferTimeout() { @@ -143,7 +150,7 @@ } function transition(newState) { - logger.debug("transitioning from " + playState + " to " + newState); + logger.log("transitioning from " + playState + " to " + newState); playState = newState; @@ -156,6 +163,7 @@ playState == PlayStateSessionOver || playState == PlayStateNetworkError || playState == PlayStateServerError ) { + $audio.unbind('canplay'); context.JK.ListenBroadcastCurrentlyPlaying = null; } @@ -165,7 +173,7 @@ function noBuffer() { if(retryAttempts >= RETRY_ATTEMPTS) { - logger.debug("never received indication of buffering or playing"); + logger.log("never received indication of buffering or playing"); transition(PlayStateFailedStart); } else { @@ -173,20 +181,38 @@ clearBufferTimeout(); - // tell audio to stop/start, in attempt to retry - //audioDomElement.pause(); - audioDomElement.load(); - audioDomElement.play(); + checkServer() + .done(function(response) { - transition(PlayStateRetrying); + if(!response.mount) { + transition(PlayStateSessionOver); + destroy(); + } + else { + // tell audio to stop/start, in attempt to retry + //audioDomElement.pause(); + audioDomElement.load(); + if(needsCanPlayGuard()) { + $audio.bind('canplay', function() { + audioDomElement.play(); + }) + } + else { + audioDomElement.play(); + } + + transition(PlayStateRetrying); + + waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); + } + }) - waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); } } function isNoisyEvent(eventName) { if(playState == PlayStateNone) { - console.log("ignoring: " + eventName) + logger.log("ignoring: " + eventName) return true; } @@ -199,21 +225,26 @@ function onPlaying() { if(isNoisyEvent('playing')) return; - logger.debug("playing", arguments); + logger.log("playing", arguments); transition(PlayStatePlaying); } function onPause() { if(isNoisyEvent('pause')) return; - logger.debug("pause", arguments); + logger.log("pause", arguments); + + if(treatPauseLikeEnd()) { + transition(PlayStateEnded); + return; + }; transition(PlayStateStalled); } function onError() { if(isNoisyEvent('error')) return; - logger.debug("error", arguments); + logger.log("error", arguments); if(playState == PlayStatePlaying || playState == PlayStateStalled) { transition(PlayStateFailedPlaying); @@ -225,29 +256,31 @@ function onEnded() { if(isNoisyEvent('ended')) return; - logger.debug("ended", arguments); + logger.log("ended", arguments); transition(PlayStateEnded); } function onEmptied() { if(isNoisyEvent('emptied')) return; - logger.debug("emptied", arguments); + logger.log("emptied", arguments); } function onAbort() { if(isNoisyEvent('abort')) return; - logger.debug("abort", arguments); + logger.log("abort", arguments); } function onStalled() { if(isNoisyEvent('stalled')) return; - logger.debug("stalled", arguments); + logger.log("stalled", arguments); if(arguments[0].target !== audioDomElement) { - logger.debug("ignoring stalled msg for non-active audio element") + logger.log("ignoring stalled msg for non-active audio element") return; } + + if(ignoreStalls()) return; // fires in Chrome on page load if(playState == PlayStateBuffering || playState == PlayStatePlaying) { @@ -257,15 +290,17 @@ function onSuspend() { if(isNoisyEvent('suspend')) return; - logger.debug("onsuspend", arguments); + logger.log("onsuspend", arguments); // fires in FF on page load + if(ignoreSuspend()) return; + transition(PlayStateStalled); } function onTimeUpdate() { - //logger.debug("ontimeupdate", arguments); + //logger.log("ontimeupdate", arguments); } function onProgress() { @@ -368,6 +403,38 @@ } } + function treatPauseLikeEnd() { + // never see a pause event until the session is over, in the rich client + return playState == PlayStatePlaying && navigator.userAgent.toLowerCase().indexOf('jamkazam') > -1; + } + function ignoreSuspend() { + // client always does a 'suspend' after playing. bleh + return playState == PlayStatePlaying && navigator.userAgent.toLowerCase().indexOf('jamkazam') > -1; + } + + function ignoreStalls() { + return false; + } + + function needsCanPlayGuard() { + var ua = navigator.userAgent.toLowerCase(); + + if (ua.indexOf('safari') != -1) { + if (ua.indexOf('chrome') > -1) { + } else { + if (ua.indexOf('ipad') > -1 || ua.indexOf('iphone') > -1) { + return false; // not sure yet which way this should go + } + return true; + } + } + else if(ua.indexOf('jamkazam') > -1) { + return true; + } + return false; + } + + function audioBind() { $audio.bind('play', onPlay); $audio.bind('playing', onPlaying); @@ -380,8 +447,8 @@ $audio.bind('stalled', onStalled); //$audio.bind('timeupdate', onTimeUpdate); $audio.bind('progress', onProgress); - } + function initialize() { musicSessionId = $parent.attr('data-music-session'); @@ -394,7 +461,7 @@ $audio = $('audio', $parent); if($audio.length == 0) { - logger.debug("listen_broadcast: no audio element. deactivating") + logger.log("listen_broadcast: no audio element. deactivating") return; } if($audio.length > 1) { diff --git a/web/app/assets/stylesheets/web/audioWidgets.css.scss b/web/app/assets/stylesheets/web/audioWidgets.css.scss index e72005acb..375575487 100644 --- a/web/app/assets/stylesheets/web/audioWidgets.css.scss +++ b/web/app/assets/stylesheets/web/audioWidgets.css.scss @@ -11,8 +11,10 @@ text-align: center; @include border_box_sizing; height: 36px; + } + .recording-controls { margin-top: 15px; padding: 3px 5px 3px 10px; @@ -35,6 +37,10 @@ .play-button { outline: 0; + // the following 3 properties were to make safari on iOS not wrap text after the .play-button float:left was preferred + position:absolute; + left:5px; + top:7px; } &.no-mount{ diff --git a/web/app/controllers/api_icecast_controller.rb b/web/app/controllers/api_icecast_controller.rb index 2340575b0..1c1fab4c1 100644 --- a/web/app/controllers/api_icecast_controller.rb +++ b/web/app/controllers/api_icecast_controller.rb @@ -27,8 +27,9 @@ class ApiIcecastController < ApiController remote_ip = params[:ip] remote_user_agent = params[:agent] - mount = IcecastMount.find_by_name!(@mount_id) - mount.listener_add + mount = IcecastMount.find_by_name(@mount_id) + + mount.listener_add if mount render text: '', :status => :ok end @@ -39,8 +40,8 @@ class ApiIcecastController < ApiController pass = params[:pass] duration = params[:duration] # seconds connected to the listen stream - mount = IcecastMount.find_by_name!(@mount_id) - mount.listener_remove + mount = IcecastMount.find_by_name(@mount_id) + mount.listener_remove if mount render text: '', :status => :ok end diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index f205a5e5b..2eec273b6 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -3,6 +3,14 @@ object @music_session if !current_user # there should be more data returned, but we need to think very carefully about what data is public for a music session attributes :id + + # only show mount info if fan_access is public. Eventually we'll also need to show this in other scenarios, like if invited + child({:mount => :mount}, :if => lambda { |music_session| music_session.fan_access}) { + attributes :id, :name, :sourced, :listeners, :bitrate, :subtype, :url + node(:mime_type) { |mount| mount.resolve_string(:mime_type) } + node(:bitrate) { |mount| mount.resolve_string(:bitrate) } + node(:subtype) { |mount| mount.resolve_string(:subtype) } + } else attributes :id, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter diff --git a/web/app/views/users/_feed_music_session.html.haml b/web/app/views/users/_feed_music_session.html.haml index 98c911ed2..706e85da1 100644 --- a/web/app/views/users/_feed_music_session.html.haml +++ b/web/app/views/users/_feed_music_session.html.haml @@ -22,7 +22,7 @@ - if feed_item.has_mount? %audio{preload: 'none'} %source{src: feed_item.music_session.mount.url, type: feed_item.music_session.mount.resolve_string(:mime_type)} - .session-status + %span.session-status = session_text(feed_item) / current playback time = session_duration(feed_item, class: 'session-duration tick-duration recording-current', 'data-created-at' => feed_item.created_at.to_i) diff --git a/web/config/initializers/eventmachine.rb b/web/config/initializers/eventmachine.rb index 15d4c627d..679b6c775 100644 --- a/web/config/initializers/eventmachine.rb +++ b/web/config/initializers/eventmachine.rb @@ -10,7 +10,9 @@ unless $rails_rake_task :port => APP_CONFIG.websocket_gateway_port, :emwebsocket_debug => APP_CONFIG.websocket_gateway_internal_debug, :connect_time_stale => APP_CONFIG.websocket_gateway_connect_time_stale, - :connect_time_expire => APP_CONFIG.websocket_gateway_connect_time_expire) + :connect_time_expire => APP_CONFIG.websocket_gateway_connect_time_expire, + :rabbitmq_host => APP_CONFIG.rabbitmq_host, + :rabbitmq_port => APP_CONFIG.rabbitmq_port) end end end diff --git a/websocket-gateway/bin/websocket_gateway b/websocket-gateway/bin/websocket_gateway index 067d12d93..343f589dc 100755 --- a/websocket-gateway/bin/websocket_gateway +++ b/websocket-gateway/bin/websocket_gateway @@ -48,4 +48,6 @@ Object.send(:remove_const, :Rails) # this is to 'fool' new relic into not thinki Server.new.run(:port => config["port"], :emwebsocket_debug => config["emwebsocket_debug"], :connect_time_stale => config["connect_time_stale"], - :connect_time_expire => config["connect_time_expire"]) + :connect_time_expire => config["connect_time_expire"], + :rabbitmq_host => config['rabbitmq_host'], + :rabbitmq_port => config['rabbitmq_port']) diff --git a/websocket-gateway/config/application.yml b/websocket-gateway/config/application.yml index 63da2c18b..7647b2df8 100644 --- a/websocket-gateway/config/application.yml +++ b/websocket-gateway/config/application.yml @@ -6,14 +6,20 @@ development: port: 6767 verbose: true emwebsocket_debug: false + rabbitmq_host: localhost + rabbitmq_port: 5672 <<: *defaults test: port: 6769 verbose: true + rabbitmq_host: localhost + rabbitmq_port: 5672 <<: *defaults production: port: 6767 - verbose: false + verbose: false + rabbitmq_host: localhost + rabbitmq_port: 5672 <<: *defaults diff --git a/websocket-gateway/lib/jam_websockets/server.rb b/websocket-gateway/lib/jam_websockets/server.rb index 30cac4ac5..a82875450 100644 --- a/websocket-gateway/lib/jam_websockets/server.rb +++ b/websocket-gateway/lib/jam_websockets/server.rb @@ -17,8 +17,10 @@ module JamWebsockets port = options[:port] connect_time_stale = options[:connect_time_stale].to_i connect_time_expire = options[:connect_time_expire].to_i + rabbitmq_host = options[:rabbitmq_host] + rabbitmq_port = options[:rabbitmq_port].to_i - @log.info "starting server #{host}:#{port} with staleness_time=#{connect_time_stale}; reconnect time = #{connect_time_expire}" + @log.info "starting server #{host}:#{port} staleness_time=#{connect_time_stale}; reconnect time = #{connect_time_expire}, rabbitmq=#{rabbitmq_host}:#{rabbitmq_port}" EventMachine.error_handler{|e| @log.error "unhandled error #{e}" @@ -26,7 +28,7 @@ module JamWebsockets } EventMachine.run do - @router.start(connect_time_stale) do + @router.start(connect_time_stale, host: rabbitmq_host, port: rabbitmq_port) do # take stale off the expire limit because the call to stale will # touch the updated_at column, adding an extra stale limit to the expire time limit # expire_time = connect_time_expire > connect_time_stale ? connect_time_expire - connect_time_stale : connect_time_expire