2012-05-06 22:39:24 +00:00
'use strict' ;
( function ( $ ) {
2012-05-07 02:14:21 +00:00
$ . 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' ] ,
2012-05-07 02:51:08 +00:00
'arrays' : [ 'layers' , 'keys' ] ,
'booleans' : [ ]
2012-05-07 02:14:21 +00:00
//'functions': ['callback'] FIXME: lets not.
} ;
var dataOptions = $ . extend ( options , $this . getDataOptions ( optionTypes , namespace ) ) ;
2012-05-06 22:39:24 +00:00
var opts = $ . extend ( {
2012-05-07 02:14:21 +00:00
'id' : 'ABC' , //FIXME: throw an error if id is undefined at this point
2012-05-06 22:39:24 +00:00
'layers' : [ 'transcripts' ] ,
2012-05-07 02:14:21 +00:00
'keys' : [ 'layers' ] , //FIXME: add more apt keys
'api' : "http://pad.ma/api/" ,
'in' : 0 ,
'out' : 0 ,
2012-05-06 22:39:24 +00:00
'padma_base' : 'http://pad.ma/' ,
'resolution' : '480p' ,
'width' : '640' ,
'interval' : 400 ,
2012-05-07 02:14:21 +00:00
'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 ) {
2012-05-06 22:39:24 +00:00
$loading . hide ( ) . remove ( ) ;
var padma = new PadmaVideo ( id , response . data , $this , opts ) ;
padma . render ( ) ;
2012-05-07 02:14:21 +00:00
opts . callback ( padma ) ;
2012-05-06 22:39:24 +00:00
} , "json" ) ;
deferred . error ( function ( data ) {
alert ( "failed to load video data" ) ;
} ) ;
} ;
2012-05-07 02:14:21 +00:00
/ *
PadmaVideo class
Parameters :
id : < string > pandora video id
data : < object > data for the video object
$el : < jQuery element >
opts : < object > options object
* /
2012-05-06 22:39:24 +00:00
var PadmaVideo = function ( id , data , $el , opts ) {
this . id = id ;
this . data = data ;
this . $el = $el ;
this . o = opts ;
2012-05-07 02:14:21 +00:00
this . getVideoURL = function ( ) {
return opts . padma _base + id + "/" + opts . resolution + ".webm" ;
} ;
//empties element, appends video widget
2012-05-06 22:39:24 +00:00
this . render = function ( ) {
var that = this ;
this . $el . empty ( ) ;
this . $el . append ( that . getWidget ( ) ) ;
} ;
2012-05-07 02:14:21 +00:00
/ *
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" ) ;
} ;
2012-05-06 22:39:24 +00:00
2012-05-07 02:14:21 +00:00
/ *
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
2012-05-06 22:39:24 +00:00
this . getWidget = function ( ) {
var that = this ;
var $container = this . $container = $ ( '<div />' ) . addClass ( "padmaContainer" ) ;
var $video = this . $video = $ ( '<video />' )
. appendTo ( $container )
2012-05-07 02:14:21 +00:00
. attr ( "src" , that . getVideoURL ( ) )
2012-05-06 22:39:24 +00:00
. attr ( "controls" , "controls" )
. addClass ( "padmaVideo" )
. animate ( { 'width' : that . o . width } )
. load ( )
2012-05-07 02:14:21 +00:00
. bind ( "loadedmetadata" , function ( ) {
that . updatePlayer ( ) ;
} )
2012-05-06 22:39:24 +00:00
. bind ( "play" , function ( ) {
that . interval = setInterval ( function ( ) {
that . updatePlayer ( ) ;
2012-05-07 02:51:08 +00:00
} , that . o . interval )
} )
2012-05-06 22:39:24 +00:00
. 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 ;
} ;
2012-05-07 02:14:21 +00:00
//update annotations, etc. based on video currentTime
2012-05-06 22:39:24 +00:00
this . updatePlayer = function ( ) {
var that = this ;
var currentTime = this . $video [ 0 ] . currentTime ;
2012-05-07 02:14:21 +00:00
//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 ;
2012-05-06 22:39:24 +00:00
that . $video [ 0 ] . pause ( ) ;
}
2012-05-07 02:14:21 +00:00
if ( currentTime <= that . o [ 'in' ] ) {
that . $video [ 0 ] . currentTime = that . o [ 'in' ] ;
2012-05-06 22:39:24 +00:00
that . $video [ 0 ] . pause ( ) ;
}
}
//now get all matching layers at current time code
var layerNames = this . o . layers ,
matchedLayers = { } ;
$ . each ( layerNames , function ( i , layerName ) {
2012-05-07 02:51:08 +00:00
2012-05-06 22:39:24 +00:00
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 ] ;
2012-05-07 04:18:19 +00:00
//console.log(theseLayers);
2012-05-06 22:39:24 +00:00
if ( theseLayers . length > 0 ) {
var $annotsForLayer = getElemForLayer ( layer , theseLayers ) ;
$annotsForLayer . appendTo ( that . $annotations ) ;
}
}
}
} ;
2012-05-07 02:14:21 +00:00
/ *
Parameters :
layerName : < string > eg . 'transcripts'
currentTime : < float > in seconds
Returns < array > of matched layer objects
* /
2012-05-06 22:39:24 +00:00
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 ;
} ;
2012-05-07 02:14:21 +00:00
this . destroy = function ( ) {
this . $video [ 0 ] . pause ( ) ;
this . $video . remove ( ) ;
this . $el . empty ( ) ;
} ;
2012-05-06 22:39:24 +00:00
} ;
2012-05-07 02:14:21 +00:00
/ *
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'
* /
2012-05-06 22:39:24 +00:00
function getElemForLayer ( layerName , layers ) {
2012-05-07 02:51:08 +00:00
if ( layers . length === 0 ) {
return $ ( '<div />' ) ; //FIXME
}
var $elem = $ ( '<div />' ) . addClass ( "padmaLayer" ) . addClass ( layerName ) ;
2012-05-06 22:39:24 +00:00
var title = layerName . substr ( 0 , 1 ) . toUpperCase ( ) + layerName . substr ( 1 , layerName . length ) ;
var $title = $ ( '<div />' ) . addClass ( "padmaLayerTitle" ) . text ( title ) . appendTo ( $elem ) ;
2012-05-07 02:51:08 +00:00
2012-05-06 22:39:24 +00:00
$ . 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 ;
}
2012-05-07 02:14:21 +00:00
/ *
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
* /
2012-05-06 22:39:24 +00:00
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
}
2012-05-07 02:14:21 +00:00
//Returns <boolean> true or false based on whether the browser can play padma video
//FIXME: actually implement this function
2012-05-06 22:39:24 +00:00
function canPlayVideo ( ) {
return true ;
}
2012-05-07 02:14:21 +00:00
/ *
Get options from data - attributes
Parameters :
optionTypes : < object >
example : {
'strings' : [ 'option1' , 'option2' , 'option3' ] ,
'integers' : [ 'fooint' , 'barint' ] ,
2012-05-07 02:51:08 +00:00
'arrays' : [ 'list1' , 'list2' ] ,
'booleans' : [ 'bool1' ]
2012-05-07 02:14:21 +00:00
}
2012-05-06 22:39:24 +00:00
2012-05-07 02:14:21 +00:00
namespace : < string >
example : 'padma'
namespace for data - attributes
example html :
2012-05-07 02:51:08 +00:00
< div id = "blah" data - padma - option1 = "foobar" data - padma - fooint = "23" data - padma - list2 = "apples, oranges" data - padma - bool1 = "true" >
2012-05-06 22:39:24 +00:00
2012-05-07 02:14:21 +00:00
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 ;
2012-05-06 22:39:24 +00:00
} ) ;
2012-05-07 02:14:21 +00:00
$ . each ( optionTypes [ 'arrays' ] , function ( i , v ) {
var attr = prefix + v ;
2012-05-07 02:51:08 +00:00
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 ;
} ) ;
2012-05-07 02:14:21 +00:00
return options ;
}
2012-05-06 22:39:24 +00:00
2012-05-07 02:14:21 +00:00
/ *
FIXME : possibly improve - http : //stackoverflow.com/questions/1318076/jquery-hasattr-checking-to-see-if-there-is-an-attribute-on-an-element#1318091
* /
2012-05-06 22:39:24 +00:00
$ . fn . hasAttr = function ( attr ) {
return this . attr ( attr ) != undefined ;
} ;
} ) ( jQuery ) ;