summaryrefslogtreecommitdiff
path: root/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js')
-rw-r--r--extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js491
1 files changed, 491 insertions, 0 deletions
diff --git a/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js
new file mode 100644
index 00000000..9af48998
--- /dev/null
+++ b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js
@@ -0,0 +1,491 @@
+/**
+ * A media element corresponding to a <video> element.
+ *
+ * It is implemented as a collection of mediaSource objects. The media sources
+ * will be initialized from the <video> element, its child <source> elements,
+ * and/or the ROE file referenced by the <video> element.
+ *
+ * @param {element}
+ * videoElement <video> element used for initialization.
+ * @constructor
+ */
+( function( mw, $ ) { "use strict";
+
+mw.MediaElement = function( element ) {
+ this.init( element );
+};
+
+mw.MediaElement.prototype = {
+
+ // The array of mediaSource elements.
+ sources: null,
+
+ // flag for ROE data being added.
+ addedROEData: false,
+
+ // Selected mediaSource element.
+ selectedSource: null,
+
+ /**
+ * Media Element constructor
+ *
+ * Sets up a mediaElement from a provided top level "video" element adds any
+ * child sources that are found
+ *
+ * @param {Element}
+ * videoElement Element that has src attribute or has children
+ * source elements
+ */
+ init: function( videoElement ) {
+ var _this = this;
+ mw.log( "EmbedPlayer::mediaElement:init:" + videoElement.id );
+ this.parentEmbedId = videoElement.id;
+ this.sources = new Array();
+
+ // Process the videoElement as a source element:
+ if( videoElement ){
+ if ( $( videoElement ).attr( "src" ) ) {
+ _this.tryAddSource( videoElement );
+ }
+ // Process elements source children
+ $( videoElement ).find( 'source,track' ).each( function( ) {
+ _this.tryAddSource( this );
+ } );
+ }
+ },
+
+ /**
+ * Updates the time request for all sources that have a standard time
+ * request argument (ie &t=start_time/end_time)
+ *
+ * @param {String}
+ * startNpt Start time in npt format
+ * @param {String}
+ * endNpt End time in npt format
+ */
+ updateSourceTimes: function( startNpt, endNpt ) {
+ var _this = this;
+ $.each( this.sources, function( inx, mediaSource ) {
+ mediaSource.updateSrcTime( startNpt, endNpt );
+ } );
+ },
+
+ /**
+ * Get Text tracks
+ */
+ getTextTracks: function(){
+ var textTracks = [];
+ $.each( this.sources, function(inx, source ){
+ if ( source.nodeName == 'track' || ( source.mimeType && source.mimeType.indexOf('text/') !== -1 )){
+ textTracks.push( source );
+ }
+ });
+ return textTracks;
+ },
+ /**
+ * Returns the array of mediaSources of this element.
+ *
+ * @param {String}
+ * [mimeFilter] Filter criteria for set of mediaSources to return
+ * @return {Array} mediaSource elements.
+ */
+ getSources: function( mimeFilter ) {
+ if ( !mimeFilter ) {
+ return this.sources;
+ }
+ // Apply mime filter:
+ var source_set = new Array();
+ for ( var i = 0; i < this.sources.length ; i++ ) {
+ if ( this.sources[i].mimeType &&
+ this.sources[i].mimeType.indexOf( mimeFilter ) != -1 )
+ {
+ source_set.push( this.sources[i] );
+ }
+ }
+ return source_set;
+ },
+
+ /**
+ * Selects a source by id
+ *
+ * @param {String}
+ * sourceId Id of the source to select.
+ * @return {MediaSource} The selected mediaSource or null if not found
+ */
+ getSourceById:function( sourceId ) {
+ for ( var i = 0; i < this.sources.length ; i++ ) {
+ if ( this.sources[i].id == sourceId ) {
+ return this.sources[i];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Selects a particular source for playback updating the "selectedSource"
+ *
+ * @param {Number}
+ * index Index of source element to set as selectedSource
+ */
+ setSourceByIndex: function( index ) {
+ mw.log( 'EmbedPlayer::mediaElement:selectSource: ' + index );
+ var oldSrc = this.selectedSource.getSrc();
+ var playableSources = this.getPlayableSources();
+ for ( var i = 0; i < playableSources.length; i++ ) {
+ if ( i == index ) {
+ this.selectedSource = playableSources[i];
+ break;
+ }
+ }
+ if( oldSrc != this.selectedSource.getSrc() ){
+ $( '#' + this.parentEmbedId ).trigger( 'SourceChange');
+ }
+ },
+ /**
+ * Sets a the selected source to passed in source object
+ * @param {Object} Source
+ */
+ setSource: function( source ){
+ var oldSrc = this.selectedSource.getSrc();
+ this.selectedSource = source;
+ if( oldSrc != this.selectedSource.getSrc() ){
+ $( '#' + this.parentEmbedId ).trigger( 'SourceChange');
+ }
+ },
+
+
+ /**
+ * Selects the default source via cookie preference, default marked, or by
+ * id order
+ */
+ autoSelectSource: function() {
+ mw.log( 'EmbedPlayer::mediaElement::autoSelectSource' );
+ var _this = this;
+ // Select the default source
+ var playableSources = this.getPlayableSources();
+ var flash_flag = false, ogg_flag = false;
+ // Check if there are any playableSources
+ if( playableSources.length == 0 ){
+ return false;
+ }
+ var setSelectedSource = function( source ){
+ _this.selectedSource = source;
+ return _this.selectedSource;
+ };
+
+ // Set via module driven preference:
+ $( this ).trigger( 'onSelectSource', playableSources );
+
+ if( _this.selectedSource ){
+ mw.log('MediaElement::autoSelectSource: Set via trigger::' + _this.selectedSource.getTitle() );
+ return _this.selectedSource;
+ }
+
+ // Set via marked default:
+ $.each( playableSources, function( inx, source ){
+ if ( source.markedDefault ) {
+ mw.log( 'MediaElement::autoSelectSource: Set via marked default: ' + source.markedDefault );
+ return setSelectedSource( source );;
+ }
+ });
+
+ // Set apple adaptive ( if available )
+ var vndSources = this.getPlayableSources('application/vnd.apple.mpegurl')
+ if( vndSources.length && mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( 'application/vnd.apple.mpegurl' ).length ){
+ // Check for device flags:
+ var desktopVdn, mobileVdn;
+ $.each( vndSources, function( inx, source) {
+ // Kaltura tags vdn sources with iphonenew
+ if( source.getFlavorId() && source.getFlavorId().toLowerCase() == 'iphonenew' ){
+ mobileVdn = source;
+ } else {
+ desktopVdn = source;
+ }
+ })
+ // NOTE: We really should not have two VDN sources the point of vdn is to be a set of adaptive streams.
+ // This work around is a result of Kaltura HLS stream tagging
+ if( mw.isIphone() && mobileVdn ){
+ setSelectedSource( mobileVdn );
+ } else if( desktopVdn ){
+ setSelectedSource( desktopVdn );
+ }
+ }
+ if ( this.selectedSource ) {
+ mw.log('MediaElement::autoSelectSource: Set via Adaptive HLS: source flavor id:' + _this.selectedSource.getFlavorId() + ' src: ' + _this.selectedSource.getSrc() );
+ return this.selectedSource;
+ }
+
+ // Set via user bandwidth pref will always set source to closest bandwidth allocation while not going over EmbedPlayer.UserBandwidth
+ if( $.cookie('EmbedPlayer.UserBandwidth') ){
+ var bandwidthDelta = 999999999;
+ var bandwidthTarget = $.cookie('EmbedPlayer.UserBandwidth');
+ $.each( playableSources, function(inx, source ){
+ if( source.bandwidth ){
+ // Check if a native source ( takes president over bandwidth selection )
+ var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( source.mimeType );
+ if ( !player || player.library != 'Native' ) {
+ // continue
+ return true;
+ }
+
+ if( Math.abs( source.bandwidth - bandwidthTarget ) < bandwidthDelta ){
+ bandwidthDelta = Math.abs( source.bandwidth - bandwidthTarget );
+ setSelectedSource( source );
+ }
+ }
+ });
+ }
+
+ if ( this.selectedSource ) {
+ mw.log('MediaElement::autoSelectSource: Set via bandwidth prefrence: source ' + this.selectedSource.bandwidth + ' user: ' + $.cookie('EmbedPlayer.UserBandwidth') );
+ return this.selectedSource;
+ }
+
+
+ // If we have at least one native source, throw out non-native sources
+ // for size based source selection:
+ var nativePlayableSources = [];
+ $.each( playableSources, function(inx, source ){
+ var mimeType = source.mimeType;
+ var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType );
+ if ( player && player.library == 'Native' ) {
+ nativePlayableSources.push( source );
+ }
+ });
+
+ // Prefer native playback ( and prefer WebM over ogg and h.264 )
+ var namedSourceSet = {};
+ var useBogoSlow = false; // use benchmark only for ogv.js
+ $.each( playableSources, function(inx, source ){
+ var mimeType = source.mimeType;
+ var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType );
+ if ( player && ( player.library == 'Native' || player.library == 'OgvJs' ) ) {
+ switch( player.id ){
+ case 'mp3Native':
+ var shortName = 'mp3';
+ break;
+ case 'aacNative':
+ var shortName = 'aac';
+ break;
+ case 'oggNative':
+ var shortName = 'ogg';
+ break;
+ case 'ogvJsPlayer':
+ useBogoSlow = true;
+ var shortName = 'ogg';
+ break;
+ case 'webmNative':
+ var shortName = 'webm';
+ break;
+ case 'vp9Native':
+ var shortName = 'vp9';
+ break;
+ case 'h264Native':
+ var shortName = 'h264';
+ break;
+ case 'appleVdn':
+ var shortName = 'appleVdn';
+ break;
+ }
+ if( !namedSourceSet[ shortName ] ){
+ namedSourceSet[ shortName ] = [];
+ }
+ namedSourceSet[ shortName ].push( source );
+ }
+ });
+
+ var codecPref = mw.config.get( 'EmbedPlayer.CodecPreference');
+
+ // if on android 4 use mp4 over webm
+ if( mw.isAndroid40() ){
+ if( codecPref && codecPref[0] == 'webm' ){
+ codecPref[0] = 'h264';
+ codecPref[1] = 'webm';
+ }
+ }
+
+ if( codecPref ){
+ for(var i =0; i < codecPref.length; i++){
+ var codec = codecPref[ i ];
+ if( ! namedSourceSet[ codec ] ){
+ continue;
+ }
+ if( namedSourceSet[ codec ].length == 1 ){
+ mw.log('MediaElement::autoSelectSource: Set 1 source via EmbedPlayer.CodecPreference: ' + namedSourceSet[ codec ][0].getTitle() );
+ return setSelectedSource( namedSourceSet[ codec ][0] );
+ } else if( namedSourceSet[ codec ].length > 1 ) {
+ // select based on size:
+ // Set via embed resolution closest to relative to display size
+ var minSizeDelta = null;
+
+ // unless we're really slow...
+ var isBogoSlow = useBogoSlow && OGVCompat.isSlow();
+
+ if( this.parentEmbedId ){
+ var displayWidth = $('#' + this.parentEmbedId).width();
+ $.each( namedSourceSet[ codec ], function(inx, source ){
+ if ( ( isBogoSlow && source.height > 240 )
+ || (useBogoSlow && source.height > 360 ) ) {
+ // On iOS or slow Windows devices, large videos decoded in JavaScript are a bad idea!
+ // continue
+ return true;
+ }
+ if( source.width && displayWidth ){
+ var sizeDelta = Math.abs( source.width - displayWidth );
+ mw.log('MediaElement::autoSelectSource: size delta : ' + sizeDelta + ' for s:' + source.width );
+ if( minSizeDelta == null || sizeDelta < minSizeDelta){
+ minSizeDelta = sizeDelta;
+ setSelectedSource( source );
+ }
+ }
+ });
+ }
+ // If we found a source via display size return:
+ if ( this.selectedSource ) {
+ mw.log('MediaElement::autoSelectSource: from ' + this.selectedSource.mimeType + ' because of resolution:' + this.selectedSource.width + ' close to: ' + displayWidth );
+ return this.selectedSource;
+ }
+ // if no size info is set just select the first source:
+ if( namedSourceSet[ codec ][0] ){
+ return setSelectedSource( namedSourceSet[ codec ][0] );
+ }
+ }
+ };
+ }
+
+ // Set h264 via native or flash fallback
+ $.each( playableSources, function(inx, source ){
+ var mimeType = source.mimeType;
+ var player = mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType );
+ if ( mimeType == 'video/h264'
+ && player
+ && (
+ player.library == 'Native'
+ ||
+ player.library == 'Kplayer'
+ )
+ ) {
+ if( source ){
+ mw.log('MediaElement::autoSelectSource: Set h264 via native or flash fallback:' + source.getTitle() );
+ return setSelectedSource( source );
+ }
+ }
+ });
+
+ // Else just select the first playable source
+ if ( !this.selectedSource && playableSources[0] ) {
+ mw.log( 'MediaElement::autoSelectSource: Set via first source: ' + playableSources[0].getTitle() + ' mime: ' + playableSources[0].getMIMEType() );
+ return setSelectedSource( playableSources[0] );
+ }
+ // No Source found so no source selected
+ return false;
+ },
+
+ /**
+ * check if the mime is ogg
+ */
+ isOgg: function( mimeType ){
+ if ( mimeType == 'video/ogg'
+ || mimeType == 'ogg/video'
+ || mimeType == 'video/annodex'
+ || mimeType == 'application/ogg'
+ ) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Returns the thumbnail URL for the media element.
+ *
+ * @returns {String} thumbnail URL
+ */
+ getPosterSrc: function( ) {
+ return this.poster;
+ },
+
+ /**
+ * Checks whether there is a stream of a specified MIME type.
+ *
+ * @param {String}
+ * mimeType MIME type to check.
+ * @return {Boolean} true if sources include MIME false if not.
+ */
+ hasStreamOfMIMEType: function( mimeType )
+ {
+ for ( var i = 0; i < this.sources.length; i++ )
+ {
+ if ( this.sources[i].getMIMEType() == mimeType ){
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Checks if media is a playable type
+ */
+ isPlayableType: function( mimeType ) {
+ // mw.log("isPlayableType:: " + mimeType);
+ if ( mw.EmbedTypes.getMediaPlayers().defaultPlayer( mimeType ) ) {
+ mw.log("isPlayableType:: " + mimeType);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Adds a single mediaSource using the provided element if the element has a
+ * 'src' attribute.
+ *
+ * @param {Element}
+ * element <video>, <source> or <mediaSource> <text> element.
+ */
+ tryAddSource: function( element ) {
+ //mw.log( 'mw.MediaElement::tryAddSource:' + $( element ).attr( "src" ) );
+ var newSrc = $( element ).attr( 'src' );
+ if ( newSrc ) {
+ // Make sure an existing element with the same src does not already exist:
+ for ( var i = 0; i < this.sources.length; i++ ) {
+ if ( this.sources[i].src == newSrc ) {
+ // Source already exists update any new attr:
+ this.sources[i].updateSource( element );
+ return this.sources[i];
+ }
+ }
+ }
+ // Create a new source
+ var source = new mw.MediaSource( element );
+
+ this.sources.push( source );
+ //mw.log( 'tryAddSource: added source ::' + source + 'sl:' + this.sources.length );
+ return source;
+ },
+
+ /**
+ * Get playable sources
+ *
+ *@pram mimeFilter {=string} (optional) Filter the playable sources set by mime filter
+ *
+ * @returns {Array} of playable media sources
+ */
+ getPlayableSources: function( mimeFilter ) {
+ var playableSources = [];
+ for ( var i = 0; i < this.sources.length; i++ ) {
+ if ( this.isPlayableType( this.sources[i].mimeType )
+ &&
+ ( !mimeFilter || this.sources[i].mimeType.indexOf( mimeFilter) != -1 )
+ ){
+ playableSources.push( this.sources[i] );
+ }
+ };
+ mw.log( "MediaElement::GetPlayableSources mimeFilter:" + mimeFilter + " " +
+ playableSources.length + ' sources playable out of ' + this.sources.length );
+
+ return playableSources;
+ }
+};
+
+} )( mediaWiki, jQuery );
+