jquery.pandoravideo.js/padmavideo.js
2012-05-07 09:48:19 +05:30

328 lines
12 KiB
JavaScript

'use strict';
(function($) {
$.fn.padmavideo = function(options) {
//get options, giving preference, in order, to data- attributes defined on the html element, options passed when instantiatiing $(element).padmavideo(options), defaults.
var options = options || {},
namespace = options.namespace || "padma",
$this = this;
var optionTypes = {
'strings': ['api', 'base', 'resolution', 'action', 'id'],
'integers': ['in', 'out', 'width', 'interval'],
'arrays': ['layers', 'keys'],
'booleans': []
//'functions': ['callback'] FIXME: lets not.
};
var dataOptions = $.extend(options, $this.getDataOptions(optionTypes, namespace));
var opts = $.extend({
'id': 'ABC', //FIXME: throw an error if id is undefined at this point
'layers': ['transcripts'],
'keys': ['layers'], //FIXME: add more apt keys
'api': "http://pad.ma/api/",
'in': 0,
'out': 0,
'padma_base': 'http://pad.ma/',
'resolution': '480p',
'width': '640',
'interval': 400,
'action': 'get', //action POST param to send to url
'callback': function() { $.noop(); } //function to call after done instantiating PadmaVideo object, called with the object.
}, dataOptions);
//get the padma id and instantiate a PadmaVideo object with the current element and render it and execute optional callback
var id = opts.id,
$loading = $('<div />').addClass("padmaLoading").text("Loading video...").appendTo($this),
sendData = JSON.stringify({'id': id, 'keys': opts.keys});
var deferred = $.post(opts.api, {'action': opts.action, 'data': sendData}, function(response) {
$loading.hide().remove();
var padma = new PadmaVideo(id, response.data, $this, opts);
padma.render();
opts.callback(padma);
}, "json");
deferred.error(function(data) {
alert("failed to load video data");
});
};
/*
PadmaVideo class
Parameters:
id: <string> pandora video id
data: <object> data for the video object
$el: <jQuery element>
opts: <object> options object
*/
var PadmaVideo = function(id, data, $el, opts) {
this.id = id;
this.data = data;
this.$el = $el;
this.o = opts;
this.getVideoURL = function() {
return opts.padma_base + id + "/" + opts.resolution + ".webm";
};
//empties element, appends video widget
this.render = function() {
var that = this;
this.$el.empty();
this.$el.append(that.getWidget());
};
/*
Use this to set options on the player from the outside.
Example:
var padmaVideoObject = $('#video').data("padmavideo");
padmaVideoObject.setOption("width", 250);
*/
this.setOption = function(key, val) {
var that = this;
this.o[key] = val;
if ($.inArray(key, ['resolution']) != -1) {
this.destroy();
this.render();
return this;
}
if ($.inArray(key, ['in', 'out', 'layers']) != -1) {
this.updatePlayer();
return this;
}
if ($.inArray(key, ['width']) != -1) {
this.$video.animate({'width': that.o.width + "px"});
return this;
}
console.log("attempt to set invalid option or option which will make no difference to player state");
};
/*
Returns currently defined option for key specified
Parameters:
key: <string> eg. 'width'
*/
this.getOption = function(key) {
return this.o[key] || "invalid option";
};
//returns <jQuery element> widget for this PadmaVideo object
this.getWidget = function() {
var that = this;
var $container = this.$container = $('<div />').addClass("padmaContainer");
var $video = this.$video = $('<video />')
.appendTo($container)
.attr("src", that.getVideoURL())
.attr("controls", "controls")
.addClass("padmaVideo")
.animate({'width': that.o.width})
.load()
.bind("loadedmetadata", function() {
that.updatePlayer();
})
.bind("play", function() {
that.interval = setInterval(function() {
that.updatePlayer();
}, that.o.interval)
})
.bind("pause", function() {
clearInterval(that.interval);
})
.bind("ended", function() {
clearInterval(that.interval);
})
.bind("seeked", function() {
that.updatePlayer();
});
var $annotations = this.$annotations = $('<div />')
.addClass("padmaAnnotations")
.appendTo($container);
return $container;
};
//update annotations, etc. based on video currentTime
this.updatePlayer = function() {
var that = this;
var currentTime = this.$video[0].currentTime;
//first, handle if video has crossed out-point or is before in-point
if (that.o.out != 0) {
if (currentTime >= that.o.out) {
that.$video[0].currentTime = that.o.out;
that.$video[0].pause();
}
if (currentTime <= that.o['in']) {
that.$video[0].currentTime = that.o['in'];
that.$video[0].pause();
}
}
//now get all matching layers at current time code
var layerNames = this.o.layers,
matchedLayers = {};
$.each(layerNames, function(i, layerName) {
matchedLayers[layerName] = that.getLayersAtTimecode(layerName, currentTime)
});
//if layers are the same as last update, return
if (isSameLayers(matchedLayers, that.currentLayers)) return false;
//else, construct DOM elements for currently matched layers, etc.
that.currentLayers = matchedLayers;
that.$annotations.empty();
for (var layer in matchedLayers) {
if (matchedLayers.hasOwnProperty(layer)) {
var theseLayers = matchedLayers[layer];
//console.log(theseLayers);
if (theseLayers.length > 0) {
var $annotsForLayer = getElemForLayer(layer, theseLayers);
$annotsForLayer.appendTo(that.$annotations);
}
}
}
};
/*
Parameters:
layerName: <string> eg. 'transcripts'
currentTime: <float> in seconds
Returns <array> of matched layer objects
*/
this.getLayersAtTimecode = function(layerName, currentTime) {
var ret = [];
var theseLayers = this.data.layers[layerName];
$.each(theseLayers, function(i,layer) {
if (layer['in'] < currentTime && layer.out > currentTime) {
ret.push(layer);
}
});
return ret;
};
this.destroy = function() {
this.$video[0].pause();
this.$video.remove();
this.$el.empty();
};
};
/*
Parameters:
layerName: <string> eg. 'transcripts'
layers: <array> layer objects to render
Returns <jQuery element> for an annotation type - i.e. all 'transcripts', or all 'descriptions'
*/
function getElemForLayer(layerName, layers) {
if (layers.length === 0) {
return $('<div />'); //FIXME
}
var $elem = $('<div />').addClass("padmaLayer").addClass(layerName);
var title = layerName.substr(0,1).toUpperCase() + layerName.substr(1, layerName.length);
var $title = $('<div />').addClass("padmaLayerTitle").text(title).appendTo($elem);
$.each(layers, function(i,v) {
var $annot = $('<div />').addClass("padmaAnnot");
//TODO: add time-code div
var $annotText = $('<div />')
.addClass("padmaAnnotText")
.html(v.value)
.appendTo($annot);
$annot.appendTo($elem);
});
return $elem;
}
/*
Silly function to check if two layer arrays are the same (i.e. to check if matchedLayers have changed)
Parameters:
layers1: <array> of layer objects
layers2: <array> of layer objects to compare with
Returns <boolean> true if layer arrays are the same, false if different
FIXME: this id concatenation string comparison is a bit weird, but it seemed like a non-expensive simple way to do it
*/
function isSameLayers(layers1, layers2) {
var idString1 = '',
idString2 = '';
for (var l in layers1) {
if (layers1.hasOwnProperty(l)) {
$.each(layers1[l], function(i,v) {
idString1 += v.id;
});
}
}
for (var l in layers2) {
if (layers2.hasOwnProperty(l)) {
$.each(layers2[l], function(i,v) {
idString2 += v.id;
});
}
}
return idString1 == idString2
}
//Returns <boolean> true or false based on whether the browser can play padma video
//FIXME: actually implement this function
function canPlayVideo() {
return true;
}
/*
Get options from data- attributes
Parameters:
optionTypes: <object>
example: {
'strings': ['option1', 'option2', 'option3'],
'integers': ['fooint', 'barint'],
'arrays': ['list1', 'list2'],
'booleans': ['bool1']
}
namespace: <string>
example: 'padma'
namespace for data- attributes
example html:
<div id="blah" data-padma-option1="foobar" data-padma-fooint="23" data-padma-list2="apples, oranges" data-padma-bool1="true">
usage:
var dataOptions = $('#blah').getDataOptions(optionTypes, namespace);
*/
$.fn.getDataOptions = function(optionTypes, namespace) {
var $element = this;
var prefix = "data-" + namespace + "-",
options = {};
$.each(optionTypes['strings'], function(i,v) {
var attr = prefix + v;
options[v] = $element.hasAttr(attr) ? $element.attr(attr) : undefined;
});
$.each(optionTypes['integers'], function(i,v) {
var attr = prefix + v;
options[v] = $element.hasAttr(attr) ? parseInt($element.attr(attr)) : undefined;
});
$.each(optionTypes['arrays'], function(i,v) {
var attr = prefix + v;
options[v] = $element.hasAttr(attr) ? $.map($element.attr(attr).split(","), $.trim) : undefined;
});
$.each(optionTypes['booleans'], function(i,v) {
var attr = prefix + v;
options[v] = $element.hasAttr(attr) ? $element.attr(attr) == 'true' : false;
});
return options;
}
/*
FIXME: possibly improve - http://stackoverflow.com/questions/1318076/jquery-hasattr-checking-to-see-if-there-is-an-attribute-on-an-element#1318091
*/
$.fn.hasAttr = function(attr) {
return this.attr(attr) != undefined;
};
})(jQuery);