* VRFS-1047 - broadcast widget almost fully done
This commit is contained in:
parent
dcc5437f3a
commit
51f955fc74
|
|
@ -33,6 +33,7 @@ gem 'carrierwave'
|
|||
gem 'aasm', '3.0.16'
|
||||
gem 'devise', '>= 1.1.2'
|
||||
gem 'postgres-copy'
|
||||
gem 'geokit'
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext'
|
||||
gem 'resque'
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ require "action_mailer"
|
|||
require "devise"
|
||||
require "sendgrid"
|
||||
require "postgres-copy"
|
||||
require "geokit"
|
||||
require "geokit-rails"
|
||||
require "postgres_ext"
|
||||
require 'builder'
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@
|
|||
|
||||
function toggleDetails() {
|
||||
if(toggledOpen) {
|
||||
|
||||
$feedItem.css('height', $feedItem.height() + 'px')
|
||||
$feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() {
|
||||
$feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
|
||||
|
||||
$musicians.hide();
|
||||
$description.css('height', $description.css('height'));
|
||||
$description.css('height', $description.data('original-height'));
|
||||
$description.dotdotdot();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,47 +5,60 @@
|
|||
context.JK = context.JK || {};
|
||||
context.JK.FeedItemSession = function($parentElement, options){
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var rest = new context.JK.Rest();
|
||||
|
||||
var $feedItem = $parentElement;
|
||||
var $description = $('.description', $feedItem)
|
||||
var $musicians = $('.musician-detail', $feedItem)
|
||||
var $controls = $('.session-controls', $feedItem);
|
||||
var $status = $('.session-status', $feedItem);
|
||||
var playing = false;
|
||||
var toggledOpen = false;
|
||||
var musicSessionId = $feedItem.attr('data-music-session');
|
||||
|
||||
if(!$feedItem.is('.feed-entry')) {
|
||||
throw "$parentElement must be a .feed-entry"
|
||||
}
|
||||
|
||||
function startPlay() {
|
||||
var img = $('.play-icon', $feedItem);
|
||||
img.attr('src', '/assets/content/icon_pausebutton.png');
|
||||
$controls.trigger('play.listenBroadcast');
|
||||
playing = true;
|
||||
}
|
||||
|
||||
function stopPlay() {
|
||||
var img = $('.play-icon', $feedItem);
|
||||
img.attr('src', '/assets/content/icon_playbutton.png');
|
||||
$controls.trigger('pause.listenBroadcast');
|
||||
playing = false;
|
||||
}
|
||||
function togglePlay() {
|
||||
if(playing) {
|
||||
var img = $('.play-icon', $(this));
|
||||
img.attr('src', '/assets/content/icon_playbutton.png');
|
||||
$controls.trigger('pause.listenBroadcast');
|
||||
$status.text('SESSION IN PROGRESS');
|
||||
stopPlay();
|
||||
}
|
||||
else {
|
||||
var img = $('.play-icon', $(this));
|
||||
img.attr('src', '/assets/content/icon_pausebutton.png');
|
||||
$controls.trigger('play.listenBroadcast');
|
||||
startPlay();
|
||||
}
|
||||
|
||||
playing = !playing;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function toggleDetails() {
|
||||
if(toggledOpen) {
|
||||
|
||||
$feedItem.css('height', $feedItem.height() + 'px')
|
||||
$feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() {
|
||||
$feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
|
||||
|
||||
$musicians.hide();
|
||||
$description.css('height', $description.data('original-height'));
|
||||
$description.dotdotdot();
|
||||
});
|
||||
}
|
||||
else {
|
||||
$description.trigger('destroy.dot');
|
||||
$description.data('original-height', $description.css('height')).css('height', 'auto');
|
||||
$musicians.show();
|
||||
$feedItem.animate({'max-height': '1000px'});
|
||||
}
|
||||
|
|
@ -55,10 +68,84 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
return rest.getSession(musicSessionId)
|
||||
.done(function(response) {
|
||||
// we only refresh for sessions that are playing (no reason to bother for ended ones).
|
||||
// so if we got a response. Then that's it. We are still OK
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status == 404 || jqXHR.status == 403) {
|
||||
$controls.trigger('destroy.listenBroadcast').removeClass('inprogress').addClass('ended')
|
||||
$status.text("SESSION ENDED");
|
||||
}
|
||||
else if(jqXHR.status >= 500 && jqXHR.status <= 599){
|
||||
$status.text('SERVER ERROR')
|
||||
stopPlay();
|
||||
}
|
||||
else{
|
||||
$status.text("NO NETWORK")
|
||||
stopPlay();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function stateChange(e, data) {
|
||||
var state = data.state;
|
||||
|
||||
if(state == 'none') {
|
||||
//$status.text('SESSION IN PROGRESS');
|
||||
}
|
||||
else if(state == 'initializing') {
|
||||
$status.text('PREPARING AUDIO');
|
||||
}
|
||||
else if(state == 'buffering') {
|
||||
|
||||
}
|
||||
else if(state == 'playing') {
|
||||
$status.text('SESSION IN PROGRESS');
|
||||
}
|
||||
else if(state == 'stalled') {
|
||||
$status.text('RECONNECTING');
|
||||
}
|
||||
else if(state == 'ended' || state == 'session_over') {
|
||||
$status.text('STREAM DISCONNECTED');
|
||||
stopPlay();
|
||||
refresh();
|
||||
}
|
||||
else if(state == 'retrying_play') {
|
||||
if(data.retryCount == 2) {
|
||||
$status.text('STILL TRYING, HANG ON');
|
||||
}
|
||||
}
|
||||
else if(state == 'failed_start') {
|
||||
$status.text('AUDIO DID NOT START');
|
||||
stopPlay();
|
||||
}
|
||||
else if(state == 'failed_playing') {
|
||||
$status.text('AUDIO FAILED');
|
||||
stopPlay();
|
||||
refresh();
|
||||
}
|
||||
else if(state == 'network_error') {
|
||||
$status.text('NO NETWORK');
|
||||
stopPlay();
|
||||
}
|
||||
else if(state == 'server_error') {
|
||||
$status.text('SERVER ERROR');
|
||||
stopPlay();
|
||||
}
|
||||
else {
|
||||
logger.error("unknown state: " + state)
|
||||
}
|
||||
}
|
||||
|
||||
function events() {
|
||||
$('.details', $feedItem).click(toggleDetails);
|
||||
$('.details-arrow', $feedItem).click(toggleDetails);
|
||||
$('.play-button', $feedItem).click(togglePlay);
|
||||
|
||||
$controls.bind('statechange.listenBroadcast', stateChange);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
|
|
|||
|
|
@ -27,22 +27,34 @@
|
|||
// this will also interact with any REST APIs needed to
|
||||
context.JK.ListenBroadcast = function($parentElement, options){
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var $parent = $parentElement;
|
||||
var $audio = null;
|
||||
var audioDomElement = null;
|
||||
var musicSessionId = null;
|
||||
var WAIT_FOR_BUFFER_TIMEOUT = 5000;
|
||||
var RETRY_ATTEMPTS = 5; // we try 4 times, so the user will wait up until RETRY_ATTEMPTS * WAIT_FOR_BUFFER_TIMEOUTS
|
||||
|
||||
var isPlaying = false;
|
||||
|
||||
var PlayStateNone = 'none';
|
||||
var PlayStateInitializing = 'initializing'; // user clicked play--nothing has happened yet
|
||||
var PlayStateBuffering = 'buffering'; // user clicked play--the stream is being read, buffer is being built
|
||||
var PlayStatePlaying = 'playing'; // we are playing
|
||||
var PlayStateStalled = 'stalled'; // we were playing, but the stream is stalled
|
||||
var PlayStateEnded = 'ended'; // we were playing, but the stream ended
|
||||
var PlayStateFailed = 'failed'; // we could not start the stream.
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var $parent = $parentElement;
|
||||
var $audio = null;
|
||||
var audioDomElement = null;
|
||||
var musicSessionId = null;
|
||||
var waitForBufferingTimeout = null;
|
||||
var retryAttempts = 0;
|
||||
|
||||
var destroyed = false;
|
||||
|
||||
var PlayStateNone = 'none';
|
||||
var PlayStateInitializing = 'initializing'; // user clicked play--nothing has happened yet
|
||||
var PlayStateBuffering = 'buffering'; // user clicked play--the stream is being read, buffer is being built
|
||||
var PlayStatePlaying = 'playing'; // we are playing
|
||||
var PlayStateStalled = 'stalled'; // we were playing, but the stream is stalled
|
||||
var PlayStateEnded = 'ended'; // we were playing, but the stream ended
|
||||
var PlayStateRetrying = 'retrying_play'; // we are retrying to play.
|
||||
var PlayStateFailedStart = 'failed_start'; // we could not start the stream. no more events are coming
|
||||
var PlayStateFailedPlaying = 'failed_playing'; // failed while playing.
|
||||
var PlayStateSessionOver = 'session_over'; // session is done
|
||||
var PlayStateNetworkError = 'network_error'; // network error
|
||||
var PlayStateServerError = 'server_error'; // network error
|
||||
|
||||
|
||||
var playState = PlayStateNone; // tracks if the stream is actually playing
|
||||
|
||||
|
|
@ -52,67 +64,189 @@
|
|||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if(destroyed) return;
|
||||
|
||||
if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a play"
|
||||
|
||||
audioDomElement.play();
|
||||
rest.getSession(musicSessionId)
|
||||
.done(function(response) {
|
||||
audioDomElement.play();
|
||||
|
||||
retryAttempts = 0;
|
||||
|
||||
transition(PlayStateInitializing);
|
||||
|
||||
// keep this after transition, because any transition clears this timer
|
||||
waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT);
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status == 404 || jqXHR.status == 403) {
|
||||
transition(PlayStateSessionOver);
|
||||
}
|
||||
else if(jqXHR.status >= 500 && jqXHR.status <= 599){
|
||||
transition(PlayStateServerError);
|
||||
}
|
||||
else {
|
||||
transition(PlayStateNetworkError);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function pause(e) {
|
||||
if(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause"
|
||||
if(destroyed) return;
|
||||
|
||||
if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause"
|
||||
|
||||
transition(PlayStateNone);
|
||||
|
||||
recreateAudioElement();
|
||||
}
|
||||
|
||||
function destroy(e) {
|
||||
if(!destroyed) {
|
||||
$audio.remove();
|
||||
$audio = null;
|
||||
audioDomElement = null;
|
||||
destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// this is the only way to make audio stop buffering after the user hits pause
|
||||
function recreateAudioElement() {
|
||||
// jeez: http://stackoverflow.com/questions/4071872/html5-video-force-abort-of-buffering/13302599#13302599
|
||||
var originalSource = $audio.html()
|
||||
audioDomElement.pause();
|
||||
audioDomElement.src = '';
|
||||
audioDomElement.load();
|
||||
var $parent = $audio.parent();
|
||||
$audio.remove();
|
||||
$parent.append('<audio preload="none"></audio>');
|
||||
$audio = $('audio', $parent);
|
||||
$audio.append(originalSource);
|
||||
audioDomElement = $audio.get(0);
|
||||
audioBind();
|
||||
}
|
||||
|
||||
function clearBufferTimeout() {
|
||||
if(waitForBufferingTimeout) {
|
||||
clearTimeout (waitForBufferingTimeout);
|
||||
waitForBufferingTimeout = null;
|
||||
}
|
||||
}
|
||||
function transition(newState) {
|
||||
logger.debug("transitioning from " + playState + " to " + newState);
|
||||
|
||||
playState = newState;
|
||||
|
||||
clearBufferTimeout();
|
||||
|
||||
$parent.triggerHandler('statechange.listenBroadcast', {state: playState, retryCount: retryAttempts});
|
||||
}
|
||||
|
||||
function noBuffer() {
|
||||
|
||||
if(retryAttempts >= RETRY_ATTEMPTS) {
|
||||
logger.debug("never received indication of buffering or playing");
|
||||
transition(PlayStateFailedStart);
|
||||
}
|
||||
else {
|
||||
retryAttempts++;
|
||||
|
||||
clearBufferTimeout();
|
||||
|
||||
// tell audio to stop/start, in attempt to retry
|
||||
//audioDomElement.stop();
|
||||
audioDomElement.load();
|
||||
audioDomElement.play();
|
||||
|
||||
transition(PlayStateRetrying);
|
||||
|
||||
waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function isNoisyEvent(eventName) {
|
||||
if(playState == PlayStateNone) {
|
||||
console.log("ignoring: " + eventName)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onPlay() {
|
||||
logger.debug("onplay", arguments);
|
||||
|
||||
isPlaying = true;
|
||||
// this just confirms that the user tried to play
|
||||
}
|
||||
|
||||
function onPlaying() {
|
||||
logger.debug("onplaying", arguments);
|
||||
if(isNoisyEvent('playing')) return;
|
||||
logger.debug("playing", arguments);
|
||||
|
||||
|
||||
isPlaying = true;
|
||||
transition(PlayStatePlaying);
|
||||
}
|
||||
|
||||
function onPause() {
|
||||
logger.debug("onpause", arguments);
|
||||
if(isNoisyEvent('pause')) return;
|
||||
logger.debug("pause", arguments);
|
||||
|
||||
isPlaying = false;
|
||||
transition(PlayStateStalled);
|
||||
}
|
||||
|
||||
function onError() {
|
||||
logger.debug("onerror", arguments);
|
||||
if(isNoisyEvent('error')) return;
|
||||
logger.debug("error", arguments);
|
||||
|
||||
if(playState == PlayStatePlaying || playState == PlayStateStalled) {
|
||||
transition(PlayStateFailedPlaying);
|
||||
}
|
||||
else {
|
||||
transition(PlayStateFailedStart);
|
||||
}
|
||||
}
|
||||
|
||||
function onEnded() {
|
||||
logger.debug("onended", arguments);
|
||||
if(isNoisyEvent('ended')) return;
|
||||
logger.debug("ended", arguments);
|
||||
|
||||
transition(PlayStateEnded);
|
||||
}
|
||||
|
||||
function onEmptied() {
|
||||
logger.debug("onemptied", arguments);
|
||||
if(isNoisyEvent('emptied')) return;
|
||||
logger.debug("emptied", arguments);
|
||||
}
|
||||
|
||||
function onAbort() {
|
||||
logger.debug("onabort", arguments);
|
||||
if(isNoisyEvent('abort')) return;
|
||||
logger.debug("abort", arguments);
|
||||
}
|
||||
|
||||
function onStalled() {
|
||||
logger.debug("onstalled", arguments);
|
||||
if(isNoisyEvent('stalled')) return;
|
||||
logger.debug("stalled", arguments);
|
||||
|
||||
// fires in Chrome on page load
|
||||
|
||||
if(playState == PlayStateBuffering || playState == PlayStatePlaying) {
|
||||
transition(PlayStateStalled);
|
||||
}
|
||||
}
|
||||
|
||||
function onSuspend() {
|
||||
if(isNoisyEvent('suspend')) return;
|
||||
logger.debug("onsuspend", arguments);
|
||||
|
||||
// fires in FF on page load
|
||||
|
||||
transition(PlayStateStalled);
|
||||
}
|
||||
|
||||
function onTimeUpdate() {
|
||||
|
|
@ -120,9 +254,27 @@
|
|||
}
|
||||
|
||||
function onProgress() {
|
||||
//logger.debug("onprogress", arguments);
|
||||
if(isNoisyEvent('progress')) return;
|
||||
|
||||
if(playState == PlayStateInitializing) {
|
||||
transition(PlayStateBuffering);
|
||||
}
|
||||
}
|
||||
|
||||
function audioBind() {
|
||||
$audio.bind('play', onPlay);
|
||||
$audio.bind('playing', onPlaying);
|
||||
$audio.bind('error', onError);
|
||||
$audio.bind('emptied', onEmptied);
|
||||
$audio.bind('abort', onAbort);
|
||||
$audio.bind('ended', onEnded);
|
||||
$audio.bind('pause', onPause);
|
||||
$audio.bind('suspend', onSuspend);
|
||||
$audio.bind('stalled', onStalled);
|
||||
$audio.bind('timeupdate', onTimeUpdate);
|
||||
$audio.bind('progress', onProgress);
|
||||
|
||||
}
|
||||
function initialize() {
|
||||
|
||||
musicSessionId = $parent.attr('data-music-session');
|
||||
|
|
@ -138,38 +290,12 @@
|
|||
throw "more than one <audio> element found";
|
||||
}
|
||||
|
||||
|
||||
audioDomElement = $audio.get(0);
|
||||
|
||||
function getAllEvents(element) {
|
||||
var result = [];
|
||||
for (var key in element) {
|
||||
if (key.indexOf('on') === 0) {
|
||||
result.push(key);
|
||||
}
|
||||
}
|
||||
return result.join(' ');
|
||||
}
|
||||
|
||||
console.log("ALL events names: " + getAllEvents(audioDomElement))
|
||||
$audio.bind(getAllEvents(audioDomElement), function(e) { console.log("ALL EVENT:", arguments) });
|
||||
|
||||
$audio.bind('*', function() {alert('works')});
|
||||
$audio.bind('play', onPlay);
|
||||
$audio.bind('playing', onPlaying);
|
||||
$audio.bind('error', onError);
|
||||
$audio.bind('emptied', onEmptied);
|
||||
$audio.bind('abort', onAbort);
|
||||
$audio.bind('ended', onEnded);
|
||||
$audio.bind('pause', onPause);
|
||||
$audio.bind('suspend', onSuspend);
|
||||
$audio.bind('stalled', onStalled);
|
||||
$audio.bind('timeupdate', onTimeUpdate);
|
||||
$audio.bind('progress', onProgress);
|
||||
$audio.bind('load', function() { console.log("load", arguments)})
|
||||
audioBind();
|
||||
|
||||
$parent.bind('play.listenBroadcast', play);
|
||||
$parent.bind('pause.listenBroadcast', pause);
|
||||
$parent.bind('destroy.listenBroadcast', destroy);
|
||||
}
|
||||
|
||||
initialize();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.feed-entry.music-session-history-entry
|
||||
.feed-entry.music-session-history-entry{'data-music-session' => feed_item.id}
|
||||
/ avatar
|
||||
.avatar-small.ib
|
||||
= session_avatar(feed_item)
|
||||
|
|
|
|||
|
|
@ -37,12 +37,14 @@ gem 'devise'
|
|||
gem 'postgres-copy'
|
||||
gem 'aws-sdk', '1.29.1'
|
||||
gem 'bugsnag'
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext'
|
||||
gem 'resque'
|
||||
gem 'resque-retry'
|
||||
gem 'resque-failed-job-mailer'
|
||||
gem 'resque-lonely_job', '~> 1.0.0'
|
||||
gem 'geokit'
|
||||
gem 'geokit-rails', '2.0.1'
|
||||
gem 'mime-types', '1.25.1'
|
||||
|
||||
group :development do
|
||||
gem 'pry'
|
||||
|
|
|
|||
Loading…
Reference in New Issue