From 7d0ca26bdaf9bb09c7cd2bb549dee31e6522f70a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 4 Mar 2014 11:06:53 -0600 Subject: [PATCH 1/4] * restrict not null --- ruby/lib/jam_ruby/models/promotional.rb | 2 +- .../javascripts/jquery.listenbroadcast.js | 56 +++++++++++++------ .../stylesheets/web/audioWidgets.css.scss | 6 ++ .../views/users/_feed_music_session.html.haml | 2 +- web/config/application.rb | 2 +- 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/ruby/lib/jam_ruby/models/promotional.rb b/ruby/lib/jam_ruby/models/promotional.rb index 407a30c1e..67da84900 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..ac9b316e0 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -132,7 +132,7 @@ $audio.append(originalSource); audioDomElement = $audio.get(0); audioBind(); - logger.debug("recreated audio element ") + logger.log("recreated audio element ") } function clearBufferTimeout() { @@ -143,7 +143,7 @@ } function transition(newState) { - logger.debug("transitioning from " + playState + " to " + newState); + logger.log("transitioning from " + playState + " to " + newState); playState = newState; @@ -156,6 +156,7 @@ playState == PlayStateSessionOver || playState == PlayStateNetworkError || playState == PlayStateServerError ) { + $audio.unbind('canplay'); context.JK.ListenBroadcastCurrentlyPlaying = null; } @@ -165,7 +166,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 { @@ -176,7 +177,14 @@ // tell audio to stop/start, in attempt to retry //audioDomElement.pause(); audioDomElement.load(); - audioDomElement.play(); + if(isDesktopSafari()) { + $audio.bind('canplay', function() { + audioDomElement.play(); + }) + } + else { + audioDomElement.play(); + } transition(PlayStateRetrying); @@ -199,21 +207,21 @@ 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); transition(PlayStateStalled); } function onError() { if(isNoisyEvent('error')) return; - logger.debug("error", arguments); + logger.log("error", arguments); if(playState == PlayStatePlaying || playState == PlayStateStalled) { transition(PlayStateFailedPlaying); @@ -225,27 +233,27 @@ 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; } // fires in Chrome on page load @@ -257,7 +265,7 @@ function onSuspend() { if(isNoisyEvent('suspend')) return; - logger.debug("onsuspend", arguments); + logger.log("onsuspend", arguments); // fires in FF on page load @@ -265,7 +273,7 @@ } function onTimeUpdate() { - //logger.debug("ontimeupdate", arguments); + //logger.log("ontimeupdate", arguments); } function onProgress() { @@ -368,6 +376,22 @@ } } + + function isDesktopSafari() { + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf('safari') != -1) { + if (ua.indexOf('chrome') > -1) { + } else { + if (ua.match(/iPad/i) || ua.match(/iPhone/i)) { + return true; // not sure yet which way this should go + } + return true; + } + } + return false; + } + + function audioBind() { $audio.bind('play', onPlay); $audio.bind('playing', onPlaying); @@ -380,8 +404,8 @@ $audio.bind('stalled', onStalled); //$audio.bind('timeupdate', onTimeUpdate); $audio.bind('progress', onProgress); - } + function initialize() { musicSessionId = $parent.attr('data-music-session'); @@ -394,7 +418,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/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/application.rb b/web/config/application.rb index e8340ef5b..5d46abe26 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -213,6 +213,6 @@ if defined?(Bundler) config.send_join_session_email_notifications = true - config.use_promos_on_homepage = false + config.use_promos_on_homepage = true end end From 3afdd55a15e9cb045c12453ec4223f951c6ec8a9 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 4 Mar 2014 11:56:42 -0600 Subject: [PATCH 2/4] * fix IE auth issues (and other browsers)... chehck if session is over between every retry play attempt --- .../javascripts/jquery.listenbroadcast.js | 54 ++++++++++++------- web/app/controllers/api_icecast_controller.rb | 9 ++-- web/app/views/api_music_sessions/show.rabl | 8 +++ web/config/application.rb | 2 +- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index ac9b316e0..f8670f979 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); + } }) } @@ -174,21 +181,32 @@ clearBufferTimeout(); - // tell audio to stop/start, in attempt to retry - //audioDomElement.pause(); - audioDomElement.load(); - if(isDesktopSafari()) { - $audio.bind('canplay', function() { - audioDomElement.play(); + checkServer() + .done(function(response) { + + if(!response.mount) { + transition(PlayStateSessionOver); + destroy(); + } + else { + // tell audio to stop/start, in attempt to retry + //audioDomElement.pause(); + audioDomElement.load(); + if(isDesktopSafari()) { + $audio.bind('canplay', function() { + audioDomElement.play(); + }) + } + else { + audioDomElement.play(); + } + + transition(PlayStateRetrying); + + waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); + } }) - } - else { - audioDomElement.play(); - } - transition(PlayStateRetrying); - - waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); } } 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/config/application.rb b/web/config/application.rb index 5d46abe26..e8340ef5b 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -213,6 +213,6 @@ if defined?(Bundler) config.send_join_session_email_notifications = true - config.use_promos_on_homepage = true + config.use_promos_on_homepage = false end end From 314d3c9dac818119133e26ca4f1e2a64f2255fe4 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 4 Mar 2014 12:22:42 -0600 Subject: [PATCH 3/4] * supporting session widget in rich client --- .../javascripts/jquery.listenbroadcast.js | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index f8670f979..440c0a1a8 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -192,7 +192,7 @@ // tell audio to stop/start, in attempt to retry //audioDomElement.pause(); audioDomElement.load(); - if(isDesktopSafari()) { + if(needsCanPlayGuard()) { $audio.bind('canplay', function() { audioDomElement.play(); }) @@ -212,7 +212,7 @@ function isNoisyEvent(eventName) { if(playState == PlayStateNone) { - console.log("ignoring: " + eventName) + logger.log("ignoring: " + eventName) return true; } @@ -234,6 +234,11 @@ if(isNoisyEvent('pause')) return; logger.log("pause", arguments); + if(treatPauseLikeEnd()) { + transition(PlayStateEnded); + return; + }; + transition(PlayStateStalled); } @@ -274,6 +279,8 @@ 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) { @@ -287,6 +294,8 @@ // fires in FF on page load + if(ignoreSuspend()) return; + transition(PlayStateStalled); } @@ -394,18 +403,34 @@ } } + 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 isDesktopSafari() { + function ignoreStalls() { + return false; + } + + function needsCanPlayGuard() { var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf('safari') != -1) { if (ua.indexOf('chrome') > -1) { } else { - if (ua.match(/iPad/i) || ua.match(/iPhone/i)) { - return true; // not sure yet which way this should go + 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; } From 5f67ad3290857b85a3535b4bebd6020f4cde51a0 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 4 Mar 2014 15:51:49 -0600 Subject: [PATCH 4/4] * make rabbitmq_host and rabbitmq_port configurable for websocket-gateway --- web/config/initializers/eventmachine.rb | 4 +++- websocket-gateway/bin/websocket_gateway | 4 +++- websocket-gateway/config/application.yml | 8 +++++++- websocket-gateway/lib/jam_websockets/server.rb | 6 ++++-- 4 files changed, 17 insertions(+), 5 deletions(-) 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