Skip to content

Instantly share code, notes, and snippets.

@ryan1234
Created September 24, 2014 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryan1234/db1de94d84a9a997b198 to your computer and use it in GitHub Desktop.
Save ryan1234/db1de94d84a9a997b198 to your computer and use it in GitHub Desktop.
/* TODO - This seriously needs to be refactored with the knockout mapping plugin in the future */
(function ()
{
if (!window.SB.VideoStream)
{
window.SB.VideoStream = {};
}
var vm = function ()
{
var self = this;
self.currentService = ko.mapping.fromJS({}); //currently available/playing service
self.nextService = ko.mapping.fromJS({}); //available if within the last 30-45 minutes of the currentService
self.onDemandService = ko.mapping.fromJS({});
self.availableService = ko.mapping.fromJS({}); //use this service for templates to get service properties. defaults to currentService, uses nextService f current is unavailable
self.streamAvailable = ko.observable(false);
self.liveNow = ko.observable(false);
self.nextDeadline = ko.observable(''); //starting time of the next service, if available
self.timeToNext = ko.observable(); //time remaining until nextService switches over
self.serviceNow = ko.observable(false); //true if service should start playing
self.serviceTitle = ko.observable(''); //service title - keep logic here, not in the view
self.timer = null; //reference to the countdown function tracking time until nextService
self.notes = ko.observable(); //SermonNotes object
self.hasCountdown = ko.observable(false); //true if the widget has a countdown
self.playType = ko.observable('live'); //type of the player we need to use (live or on-demand)
self.img = ko.observable();
self.title = ko.observable();
self.presenterImg = ko.observable();
self.presenterName = ko.observable();
self.link = ko.observable(""); //link to an external page to play on if necessary
self.videoUrl = ko.observable(null); //set if there is an on-demand version of the currentService
self.vidType = ko.observable('mp4');
self.vidMediaLibraryId = ko.observable();
self.mediaSeriesId = ko.observable();
if ("function" === typeof ko.subscribable.fn.publishOn)
self.mediaSeriesId.publishOn("updateMediaSeriesId");
self.vidAlternateFormats = ko.observableArray([]);
self.shouldDisplaySkipButton = ko.observable(false); // true if the skip to message button should be displayed
self.enableExtras = ko.observable(true);
self.sought = false;
self.inPrePeriod = ko.observable(false);
self.initFinished = $.Deferred();
self.init = function (element, params)
{
try {
if (typeof params === "undefined")
{
self.error = true;
return;
}
self.initParameters(params);
self.setSubscribers();
//bind called in loadData
self.loadData(element, params);
}
catch (e) {
SB.util.logError(e);
}
};
self.initParameters = function() {
self.link(SB.util.safeGetWidgetParameter('link', params, null));
self.nextDeadline(SB.util.safeGetWidgetParameter('nextDeadline', params, ""));
self.enableExtras(SB.util.safeGetWidgetParameter('enableExtras', params, false));
self.videoUrl(SB.util.safeGetWidgetParameter('videoUrl', params, null));
self.img(SB.util.safeGetWidgetParameter('img', params, "")); //put a default image here
self.vidType(SB.util.safeGetWidgetParameter('vidType', params, "mp4"));
self.vidMediaLibraryId(SB.util.safeGetWidgetParameter('vidMediaLibraryId', params, null));
}
self.bind = function (element, params)
{
window.SB.VideoStream = $.extend({}, self, window.SB.VideoStream);
//if (typeof params.bindKODuringInit != 'undefined' && !self.koBound())
if (typeof params.bindKODuringInit != 'undefined')
{
ko.cleanNode(element);
ko.applyBindings(self, element);
}
};
self.setSubscribers = function()
{
self.inPrePeriod.subscribe(self.inPrePeriodChanged);
self.nextDeadline.subscribe(setDeadlineInterval);
};
self.loadData = function (element, params)
{
SB.api.Get('LiveService?campus=1', null, function (data, status)
{
if (status == "success")
{
if (data.Current !== null)
ko.mapping.fromJS(data.Current, {}, self.currentService);
else
{
self.currentService = null;
self.nextDeadline(moment(self.nextService.StartTime()));
self.timeToNext(moment(self.nextService.StartTime()) - moment(Date.now()));
self.hasCountdown(true);
}
if (data.Next !== null)
ko.mapping.fromJS(data.Next, {}, self.nextService);
else
self.nextService = null;
if (data.OnDemand !== null)
ko.mapping.fromJS(data.OnDemand, {}, self.onDemandService);
else
self.onDemandService = null;
self.availableService = self.currentService || self.onDemandService || self.nextService;
// If there's no service, quit without throwing
if (!self.availableService) { return false; }
self.serviceUpdated(); // we can't subscribe to mapping objects
if (typeof(self.availableService.MediaItem) === 'object')
{
var mediaItem = ko.mapping.toJS(self.availableService.MediaItem);
self.mediaSeriesId(mediaItem.MediaSeriesID);
self.img(mediaItem.VideoThumbUrl);
self.presenterName(mediaItem.MediaPresenterName);
self.videoUrl(mediaItem.VideoUrl);
self.vidType(mediaItem.VideoFileType);
self.vidMediaLibraryId(mediaItem.VideoMediaLibraryID);
self.vidAlternateFormats(mediaItem.VideoAlternateFormats);
// Show the Skip to Message button only if the Video URL is present
self.shouldDisplaySkipButton(typeof mediaItem.VideoUrl !== "undefined" && mediaItem.VideoUrl.length > 0);
}
else
{
self.img('http://mediacenter.saddleback.com/mc/images/default_videostream.png');
}
self.setServiceTitle();
//load initial notes
if(self.enableExtras())
self.loadNotes();
self.bind(element, params);
$('.serviceCurrentMessageTitle').fitText(1);
self.checkForUrlParams();
self.initFinished.resolve();
}
}, SB.i18n.getTimezoneHeader());
};
self.checkForUrlParams = function() {
self.checkForAutoPlay();
};
self.checkForAutoPlay = function() {
if (document.location.href.toLowerCase().indexOf('autoplay=true') !== -1)
self.initFinished.done(self.pressPlay);
};
self.inPrePeriodChanged = function(newValue)
{
if (!newValue) { return; }
self.initFinished.done(function() { // don't run until data has loaded
self.setServiceTitle(self.nextService.Title());
self.serviceUpdated();
});
};
self.serviceUpdated = function()
{
self.streamAvailable(null !== self.availableService.StreamUrl());
self.liveNow(self.currentService !== null && self.currentService.StreamUrl() !== null);
self.updateChat();
};
self.mediaLibraryId = function ()
{
if (self.availableService)
return self.currentService ? 3672 : 7474;
else
return null;
};
self.inFirstHourLive = function() {
return 36e5 > moment() - moment(self.currentService && self.currentService.StartTime());
};
self.liveAndOnDemandSameStream = function() {
return self.currentService && self.currentService.StreamUrl() === self.onDemandService.StreamUrl();
};
self.updateChat = function()
{
if ('function' === typeof(window.disableChat) && 'function' === typeof(window.enableChat))
{
if (self.currentService || self.inPrePeriod())
window.enableChat();
else
window.disableChat();
}
};
self.setServiceTitle = function(custom)
{
self.serviceTitle(custom || self.availableService.Title());
};
//user has clicked on the widget- navigate to link begin playing video
self.pressPlay = function (data, event, callback)
{
var dest = self.link();
if (dest)
{
document.location.href = dest;
return;
}
if(self.serviceNow || self.inPrePeriod) { return; }
//IGNORE JW EVENTS - look into event bubbling at some point
//if (event.target.parentElement.className.search('jw') > -1)
if (event && self.IsJwPlayerControlChildElement(event.target))
return false;
if (self.currentService && self.currentService.IsLive())
self.playType("live");
else
self.playType("ondemand");
self.playVideo();
if ("function" === typeof callback)
callback();
};
self.IsJwPlayerControlChildElement = function (element)
{
var parent = element.parentElement;
try
{
while (true)
{
if (typeof parent == "undefined")
return false;
else if (parent.className === "" || parent.className.search('jw') === -1)
parent = parent.parentElement;
else
{
return true;
}
}
}
catch (e)
{
return false;
}
return false;
};
self.pressBack = function (data, event, callback)
{
jwplayer($(event.target).parent().find('object').id).stop().remove();
$('.backToggle, .viewToggleContainer').hide();
$(".serviceImageContainer, .serviceCountdownContainer").show();
//service is false
self.serviceNow(false);
//reset skip to message to currentServiceMedia
if (self.availableService)
{
self.videoUrl(self.availableService.MediaItem.VideoUrl());
self.img(self.availableService.MediaItem.VideoThumbUrl());
self.vidType(self.availableService.MediaItem.VideoFileType());
self.vidMediaLibraryId(self.availableService.MediaItem.VideoMediaLibraryID());
self.vidAlternateFormats(self.availableService.MediaItem.VideoAlternateFormats());
}
//"change" the deadline to cause the countdown to restart
if (self.nextService !== null)
self.nextDeadline(moment(self.nextService.StartTime()));
if ("function" === typeof callback)
callback();
self.serviceUpdated();
event.stopPropagation();
};
self.playArchive = function (data, event, callback)
{
try
{
jwplayer().stop();
}
catch (error)
{ }
self.playType("archive");
self.videoUrl(data.VideoUrl());
self.img(data.VideoThumbUrl());
self.vidType(data.VideoFileType());
self.vidMediaLibraryId(data.VideoMediaLibraryID());
self.vidAlternateFormats(data.VideoAlternateFormats());
self.loadNotes(data);
self.playVideo();
if ("function" === typeof callback)
callback();
};
self.playOnDemand = function ()
{
try
{
jwplayer().stop();
}
catch (error)
{ }
self.playType("ondemand");
self.loadNotes();
self.playVideo();
};
//called every second that the timer counts down toward the deadline.
//when the timer hits 0, the widget autoplays the service
self.tick = function (timestamp)
{
self.setTimeToNext(timestamp);
self.updateTimeSensitiveVariables(timestamp);
};
self.updateTimeSensitiveVariables = function(timestamp)
{
self.inPrePeriod(timestamp.value < 36e5 && timestamp.value > 0);
if (timestamp.value <= 0 && !self.serviceNow())
self.countdownFinished();
};
self.countdownFinished = function()
{
self.availableService = self.nextService;
self.currentService = self.availableService;
self.serviceUpdated();
self.playType("live");
self.hasCountdown(false);
window.clearInterval(self.timer);
if (!self.link()) {
self.playVideo();
}
};
self.setTimeToNext = function(timestamp)
{
var timerString = '';
var showMinute = true;
var showSecond = true;
if (timestamp.days > 0)
{
timerString += '<span class="serviceCountdownMinutes">' + timestamp.days + '</span><span class="serviceCountdownUnit"> day';
if (timestamp.days > 1)
timerString += 's';
timerString += '</span>&nbsp;&nbsp;';
showMinute = false;
}
if (timestamp.hours > 0)
{
timerString += '<span class="serviceCountdownMinutes">' + timestamp.hours + '</span><span class="serviceCountdownUnit"> hour';
if (timestamp.hours > 1)
timerString += 's';
timerString += '</span>&nbsp;&nbsp;';
showSecond = false;
}
if (timestamp.minutes > 0 && showMinute)
{
timerString += '<span class="serviceCountdownMinutes">' + timestamp.minutes + '</span><span class="serviceCountdownUnit"> min</span>&nbsp;&nbsp;';
}
if (timestamp.seconds >= 0 && showSecond)
{
timerString += '<span class="serviceCountdownSeconds">' + timestamp.seconds + '</span><span class="serviceCountdownUnit"> sec</span>';
}
self.timeToNext(timerString);
};
// Subscribe to the KO observable to setup deadline changes
// if the deadline is a string (initial call), do an ajax call to get service info
// the service call handler will set the deadline to be a Date()
// and this fn will be called again
function setDeadlineInterval(newDeadline)
{
if (self.timer)
{
window.clearInterval(self.timer);
}
self.serviceNow(false);
var tempDeadline = self.nextDeadline();
if (tempDeadline != 'data-not-loaded-yet')
{
//Need countdown JS to support the timespan argument to the self.tick function
self.timer = countdown(self.tick, tempDeadline, countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS);
}
}
// vodplayer binding - e.g.: <div data-bind="vodplayer: {stream: "simlive", thumb:"mymoviethumb.jpg", start:true }"></div>
self.playVideo = function () {
window.clearInterval(self.timer);
self.serviceNow(true);
var w = "100%";
var h = "auto";
if(document.location.href.indexOf('forcelive=true') > 0)
{
if (self.currentService && typeof self.currentService.IsLive !== "undefined")
self.currentService.IsLive(true);
else
self.currentService.IsLive = ko.observable(true);
}
if (self.playType() === "archive")
{
videoUrl = GetVideoUrl(self.videoUrl(), self.vidAlternateFormats());
if (userAgentAndroid()) {
window.location = videoUrl;
}
else {
// "skip to message" - play the archived service
try
{
jwPlayerInstance = jwplayer("sbvid-1").setup({
file: videoUrl,
image: self.img(),
type: 'mp4',
height: "auto",
width: "100%",
primary: "html5",
aspectratio: "16:9",
});
}
catch (e)
{
SB.util.logError(e);
alert("An error occurred while trying to set up the archive player. Please refresh the page and try again or try again later.");
return;
}
jwPlayerInstance.play();
}
var currentServiceId = "";
if (self.availableService && self.availableService.MediaItem)
if (typeof self.availableService.MediaItem.VideoMediaLibraryID != "undefined")
currentServiceId = self.availableService.MediaItem.VideoMediaLibraryID();
if (typeof self.vidMediaLibraryId != "undefined")
{
var trackType = "archive";
if (self.vidMediaLibraryId() == currentServiceId)
trackType = "skiptomessage";
$('.backToggle, .viewToggleContainer').show();
$(".serviceImageContainer, .serviceCountdownContainer").hide();
InitSendStatsAt5MinIntervals(false, self.vidMediaLibraryId(), trackType);
}
$('.backToggle, .viewToggleContainer').show();
$(".serviceImageContainer, .serviceCountdownContainer").hide();
}
else
{
// TODO: Refactor: This logic is very specific to the online campus
CreatePlayerAsync("sbvid-1", w, h, self.img());
$('.backToggle').show();
if ($(window).width() > 960)
{
$('.viewToggleContainer').show();
}
$(".serviceImageContainer, .serviceCountdownContainer").hide();
}
};
self.loadNotes = function (mediaObject)
{
checkNediaObject(mediaObject);
var qp = {};
qp = { mediaId: mediaObject.MediaID() };
SB.api.Query('SermonNotes', qp, function (data, status)
{
self.notes(data.Html);
bindRedactor();
setStuff();
});
return queryParameters;
};
function GetDefaultBitRate()
{
if (userAgentiPodIPhone())
return 400;
else if (userAgentiPad())
return 700;
else if (userAgentAndroid())
return 400;
else
{
// otherwise, default to this
return 1500;
}
}
function GetVideoUrl(defaultVideoUrl, videoAlternateFormats)
{
var videoUrl = defaultVideoUrl;
var bitRate = GetDefaultBitRate();
if (bitRate && videoAlternateFormats)
{
$.each(videoAlternateFormats, function (index, element)
{
if (typeof element.AlternateBitrate === "function")
{
if (bitRate == element.AlternateBitrate())
{
videoUrl = element.VideoUrl();
}
}
else
{
if (bitRate == element.AlternateBitrate)
{
videoUrl = element.VideoUrl;
}
}
});
}
return videoUrl;
}
function getAkamaiUrls() {
return {
RTMP: self.currentService.StreamUrl(),
HLS: self.currentService.StreamUrlMobile()
};
}
function getAkamaiConferenceUrls(){
return {
RTMP: 'http://257929-lh.akamaihd.net/z/saddleback_hdsconf@153536/manifest.f4m',
HLS: 'http://saddleback-lh.akamaihd.net/i/saddleback_hlsconf@153537/master.m3u8'
};
}
function getWowzaUrls(server, app, stream) {
return {
RTMP: 'http://' + server + ':1935/' + app + '/smil:' + stream + '.smil/manifest.f4m' ,
HLS: 'http://' + server + ':1935/' + app + '/smil:' + stream + '.smil/playlist.m3u8',
RTMPBackup: 'rtmp://' + server + '/' + app + '/' + stream + '_360p',
};
}
// fixme - DRY - move this to a central library
function userAgentAndroid()
{
var deviceAgent = navigator.userAgent.toLowerCase();
var androidUserAgents = /(android|silk)/;
return (deviceAgent.match(androidUserAgents) !== null);
}
function userAgentiPodIPhone() {
var deviceAgent = navigator.userAgent.toLowerCase();
var iOSUserAgents = /(iphone|ipod)/;
return (deviceAgent.match(iOSUserAgents) !== null);
}
function userAgentiPad() {
var deviceAgent = navigator.userAgent.toLowerCase();
var iOSUserAgents = /(ipad)/;
return (deviceAgent.match(iOSUserAgents) !== null);
}
function userAgentiOS() {
return userAgentiPad() || userAgentiPodIPhone();
}
function userAgentSupportsHTML5() {
return userAgentiOS() || userAgentAndroid();
}
function renderHtml5Player(domTarget, width, height, src) {
var id = 'html5';
var player = '<video id="' + id + '" controls height="' + height + '" width="' + width + '" src="' + src + '"></video>';
$('#' + domTarget).html(player);
var myVideo=document.getElementById(id);
setTimeout(function()
{
$(window).resize();
}, 500);
myVideo.play();
return id;
}
// FIXME - cross domain issue - need to proxy this service in REST service
// Valid modes: stream, skiptomessage, archive
function MediaLibraryStatUpdate(mediaLibraryID, cookieID, mode, isInitialCall) {
if (typeof isInitialCall == "undefined")
isInitialCall = false;
if (typeof mode == "undefined" || !mode)
mode = "stream";
var url = "http://" + window.location.host + "/mc/player/stattracking.aspx";
if (document.URL.toLowerCase().indexOf("//conf.") >= 0 || document.URL.toLowerCase().indexOf("//conference-") >= 0 || document.URL.toLowerCase().indexOf("//conferencelocal") >= 0) {
//Conference Tracking using Image
url = "http://" + window.location.host + url;
url = url.replace("stattracking.aspx", "stattrackingimage.aspx");
url = url + "?m=" + mediaLibraryID + "&c=" + cookieID + "&mode=" + mode + "&_tt=" + (new Date()).getTime();
if (isInitialCall)
url = url + "&ft=true";
$("#videoTracking").html('<img src="' + url + '" height="1" width="1" />');
}
else {
// add caching busting
url = url + "?m=" + mediaLibraryID + "&c=" + cookieID + "&mode=" + mode + "&tm=true" + "&_tt=" + (new Date()).getTime();
if (isInitialCall)
url = url + "&ft=true";
$.ajax({
url: url,
cache: false
});
}
}
// Valid modes: stream, skiptomessage, archive
function InitSendStatsAt5MinIntervals(live,mediaLibraryId,mode) {
var cookieKey = live ? 'SaddlebackLiveStream' : 'SaddlebackSimLiveStream';
var cookieId = $.cookie(cookieKey);
var timerId = -1;
if (cookieId === null) {
cookieId = 1;
cookieId = Math.random().toString(16).substring(2);
$.cookie(cookieKey, cookieId);
}
MediaLibraryStatUpdate(mediaLibraryId, cookieId, mode, true);
if (window.SBSTimerId > 0) {
window.clearInterval(window.SBSTimerId);
}
window.SBSTimerId = window.setInterval(function () {
MediaLibraryStatUpdate(mediaLibraryId, cookieId, mode, false);
}, 300000); //send stats every 5 min
}
function rotateBetweenValues(values) {
return values[Math.floor(Math.random() * values.length)];
}
function CreatePlayerAsync(domTarget, playerWidth, playerHeight, imageUrl, callback) {
var urls, newCallback;
if (self.playType() == "ondemand")
{
callback = (function(oldcb) {
return function() {
if ('function' === typeof(oldcb)) { oldcb.apply(arguments); }
if (self.currentService && self.currentService.StreamUrl() === self.onDemandService.StreamUrl()) {
jwplayer().onPlay(function() {
if(self.sought === false) {
setTimeout((function(player) {
return function() { player.seek(0); };
})(this), 0);
self.sought = true;
}
});
}
};
})(callback);
urls = { RTMP: self.onDemandService.StreamUrl(), HLS: self.onDemandService.StreamUrlMobile() };
}
else
{
urls = { RTMP: self.currentService.StreamUrl(), HLS: self.currentService.StreamUrlMobile() };
}
CreatePlayer(domTarget, playerWidth, playerHeight, imageUrl, urls, callback);
}
function CreatePlayer(domTarget, playerWidth, playerHeight, imageUrl, urls, readyCallback) {
var file = urls.HLS;
if (userAgentAndroid() || userAgentiOS()) {
file = urls.HLS;
renderHtml5Player(domTarget, playerWidth, playerHeight, file);
} else {
var playlist = [
{ file: file}
];
//add the Akamai HD JWPlayer plugin
if (urls.RTMP && urls.RTMP.indexOf('akamaihd.net') > 0) {
playlist[0].file = urls.RTMP;
playlist[0].provider = 'http://players.edgesuite.net/flash/plugins/jw/v3.2.0.1/AkamaiAdvancedJWStreamProvider.swf';
playlist[0].type = 'mp4';
}
try
{
var vidPlayer = jwplayer(domTarget).setup({
playlist: playlist,
image: imageUrl,
height: playerHeight,
width: playerWidth,
primary: "flash",
aspectratio: "16:9",
logo: {
hide: true
}
}).setFullscreen(false).onReady(function() {
var counter = 0;
var interval = setInterval(jwplayer().play, 10);
jwplayer().onPlay(function() { clearInterval(interval); });
if ('function' === typeof(readyCallback)) { readyCallback(); }
});
}
catch (e)
{
SB.util.logError(e);
alert("An error occurred while trying to set up the streaming player. Please refresh the page and try again or try again later.");
return;
}
InitSendStatsAt5MinIntervals(self.availableService.InProgress(), self.mediaLibraryId(), "stream");
}
}
};
SB.widgets.registerVM("VideoStreamViewModel", vm);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment