Skip to content

Commit

Permalink
FEC-5517 FEC-5516 #comment support WebVTT format for captions (kaltur…
Browse files Browse the repository at this point in the history
  • Loading branch information
yairans authored and OrenMe committed May 10, 2016
1 parent c5201b1 commit 3bafa64
Show file tree
Hide file tree
Showing 6 changed files with 4,532 additions and 43 deletions.
3 changes: 3 additions & 0 deletions modules/EmbedPlayer/resources/mw.MediaSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@
case 'srt':
return 'text/x-srt';
break;
case 'vtt':
return 'text/vtt';
break;
case 'flv':
return 'video/x-flv';
break;
Expand Down
84 changes: 60 additions & 24 deletions modules/KalturaSupport/resources/mw.ClosedCaptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
_this.destory();
var newSources = [];
$.each( data.languages, function ( inx, src ) {
var source = new mw.TextSource( $.extend( { srclang: src.label }, src ) );
var source = new mw.TextSource( $.extend( { srclang: src.label }, src ), _this.embedPlayer );
//no need to load embedded captions
source.loaded = true;
newSources.push( source );
Expand Down Expand Up @@ -132,7 +132,16 @@
this.bind( 'playing', function(){
_this.ended = false;
});
}

this.bind('resizeEvent', function () {
// in WebVTT we have to remove the caption on resizing
// for recalculation the caption layout
if ( _this.selectedSource.mimeType === "text/vtt" ) {
mw.log( 'mw.ClosedCaptions:: resizeEvent: remove captions' );
_this.getPlayer().getInterface().find('.track').remove();
}
})
}

this.bind( 'onplay', function(){
_this.playbackStarted = true;
Expand Down Expand Up @@ -292,7 +301,7 @@
this.updateTimeOffset();
// Get from <track> elements
$.each( this.getPlayer().getTextTracks(), function( inx, textSource ){
var textSource = new mw.TextSource( textSource );
var textSource = new mw.TextSource( textSource, _this.embedPlayer );
if ( !_this.textSourcesInSources(_this.textSources, textSource) ){
_this.textSources.push( textSource );
}
Expand Down Expand Up @@ -420,6 +429,9 @@
case '2':
dbTextSource.fileExt = 'xml';
break;
case '3':
dbTextSource.fileExt = 'vtt';
break;
}
}

Expand Down Expand Up @@ -453,7 +465,7 @@
})[0]
);
// Return a "textSource" object:
return new mw.TextSource( embedSource );
return new mw.TextSource( embedSource, _this.embedPlayer );
},
forceLoadLanguage: function(){
var lang = this.getConfig('forceLoadLanguage');
Expand Down Expand Up @@ -577,64 +589,88 @@
});
},

addCaption: function( source, capId, caption ){
addCaptionAsDomElement: function ( source, capId, caption ){
var $textTarget = $('<div />')
.addClass('track')
.attr('data-capId', capId)
.html($(caption.content).addClass('caption'));

this.displayTextTarget($textTarget);

// apply custom style
$('.caption div').css(this.getCaptionCss());
},

addCaptionAsText: function ( source, capId, caption ) {
// use capId as a class instead of id for easy selections and no conflicts with
// multiple players on page.
var $textTarget = $('<div />')
.addClass( 'track' )
.attr( 'data-capId', capId )
.addClass('track')
.attr('data-capId', capId)
.hide();

// Update text ( use "html" instead of "text" so that subtitle format can
// include html formating
// TOOD we should scrub this for non-formating html
$textTarget.append(
$('<span />')
.addClass( 'ttmlStyled' )
.css( 'pointer-events', 'auto')
.css( this.getCaptionCss() )
.addClass('ttmlStyled')
.css('pointer-events', 'auto')
.css(this.getCaptionCss())
.append(
$('<span>')
// Prevent background (color) overflowing TimedText
// http://stackoverflow.com/questions/9077887/avoid-overlapping-rows-in-inline-element-with-a-background-color-applied
.css( 'position', 'relative' )
.html( caption.content )
.css('position', 'relative')
.html(caption.content)
)
);

// Add/update the lang option
$textTarget.attr( 'lang', source.srclang.toLowerCase() );
$textTarget.attr('lang', source.srclang.toLowerCase());

// Update any links to point to a new window
$textTarget.find( 'a' ).attr( 'target', '_blank' );
$textTarget.find('a').attr('target', '_blank');

// Add TTML or other complex text styles / layouts if we have ontop captions:
if( this.getConfig('layout') == 'ontop' ){
if( caption.css ){
$textTarget.css( caption.css );
if (this.getConfig('layout') == 'ontop') {
if (caption.css) {
$textTarget.css(caption.css);
} else {
$textTarget.css( this.getDefaultStyle() );
$textTarget.css(this.getDefaultStyle());
}
}
// Apply any custom style ( if we are ontop of the video )
this.displayTextTarget( $textTarget );
this.displayTextTarget($textTarget);

// apply any interface size adjustments:
$textTarget.css( this.getInterfaceSizeTextCss({
'width' : this.embedPlayer.getInterface().width(),
'height' : this.embedPlayer.getInterface().height()
$textTarget.css(this.getInterfaceSizeTextCss({
'width': this.embedPlayer.getInterface().width(),
'height': this.embedPlayer.getInterface().height()
})
);

// Update the style of the text object if set
if( caption.styleId ){
var capCss = source.getStyleCssById( caption.styleId );
if (caption.styleId) {
var capCss = source.getStyleCssById(caption.styleId);
$textTarget.find('span.ttmlStyled').css(
capCss
);
}
$textTarget.fadeIn('fast');
},

addCaption: function( source, capId, caption ){
if ( source.mimeType === "text/vtt" ) {
//in WebVTT the caption is an entire div which contains the styled caption
//so we should only hang it on the DOM
this.addCaptionAsDomElement( source, capId, caption )
} else {
// in NO WebVTT the caption is simple text
this.addCaptionAsText( source, capId, caption );
}
},

displayTextTarget: function( $textTarget ){
var embedPlayer = this.embedPlayer;
var $interface = embedPlayer.getInterface();
Expand Down
3 changes: 2 additions & 1 deletion modules/TimedText/TimedText.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"scripts": "resources/mw.TextSource.js",
"dependencies": [
"mediawiki.UtilitiesTime",
"mw.ajaxProxy"
"mw.ajaxProxy",
"vtt.js"
]
},
"mw.Language.names": {
Expand Down
80 changes: 62 additions & 18 deletions modules/TimedText/resources/mw.TextSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/
( function( mw, $ ) { "use strict";

mw.TextSource = function( source ) {
mw.TextSource = function( source, embedPlayer ) {
this.embedPlayer = embedPlayer;
return this.init( source );
};
mw.TextSource.prototype = {
Expand Down Expand Up @@ -99,37 +100,48 @@
* @param {Number} time Time in seconds
*/
getCaptionForTime: function ( time ) {
var prevCaption = this.captions[ this.prevIndex ];
var captionSet = {};

// Setup the startIndex:
if( prevCaption && time >= prevCaption.start ) {
var startIndex = this.prevIndex;
} else {
var prevIndexUpdated = false;
if( time < this.captions[this.prevIndex].start ) {
// If a backwards seek start searching at the start:
var startIndex = 0;
this.prevIndex = 0;
}
var firstCapIndex = 0;
// Start looking for the text via time, add all matches that are in range
for( var i = startIndex ; i < this.captions.length; i++ ) {
for( var i = this.prevIndex ; i < this.captions.length; i++ ) {
var caption = this.captions[ i ];
// Don't handle captions with 0 or -1 end time:
if( caption.end == 0 || caption.end == -1)
continue;

if( time >= caption.start &&
time <= caption.end ) {
// set the earliest valid time to the current start index:
if( !firstCapIndex ){
firstCapIndex = caption.start;
}

//mw.log("Start cap time: " + caption.start + ' End time: ' + caption.end );
captionSet[i] = caption ;

// Update the prevIndex:
if(!prevIndexUpdated){
this.prevIndex = i;
prevIndexUpdated = true;
}
}
}

if( this.mimeType === "text/vtt" ){
var getValues = function(obj){
// returns an array of object's values (as Object.Values() in ECMAScript 2017)
var values = [];
for( var i in obj ){
values.push(obj[i]);
}
return values;
};

WebVTT.processCues(window, getValues(captionSet), this.captionsArea[0]);

for( var j in captionSet ){
captionSet[j]['content'] = captionSet[j].displayState;
}
}
// Update the prevIndex:
this.prevIndex = firstCapIndex;

//Return the set of captions in range:
return captionSet;
},
Expand All @@ -153,6 +165,9 @@
case 'text/xml':
return this.getCaptionsFromTMML( data );
break;
case 'text/vtt':
return this.getCaptionsFromVTT( data );
break;
}
// check for other indicators ( where the caption is missing metadata )
if( this.src && (
Expand Down Expand Up @@ -526,6 +541,35 @@
mw.log( "TimedText::getCaptiosnFromMediaWikiSrt found " + captions.length + ' captions');
return captions;
},

getCaptionsFromVTT: function( data ){
var parser = new WebVTT.Parser(window, WebVTT.StringDecoder());
var cues = [];
var regions = [];

parser.oncue = function(cue) {
cues.push(cue);
};
parser.onregion = function(region) {
regions.push(region);
};
parser.onparsingerror = function(error) {
mw.log( "TextSource::getCaptionsFromVTT ", error );
};

parser.parse(data);
parser.flush();
this.captionsArea = $('<div style="visibility:hidden;"></div>');
this.embedPlayer.getVideoHolder().append(this.captionsArea);
for(var i = 0; i < cues.length; i++){
cues[i]['start'] = cues[i].startTime;
cues[i]['end'] = cues[i].endTime;
}
mw.log( "TextSource::getCaptionsFromVTT captions: ", cues );
return cues;

},

/**
* Takes a regular expresion match and converts it to a caption object
*/
Expand Down
3 changes: 3 additions & 0 deletions resources/MwEmbedSharedResources.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,8 @@
"threejs":{
"dependencies": "jquery",
"scripts": "resources/threejs/three.min.js"
},
"vtt.js": {
"scripts": "resources/vtt.js/vtt.js"
}
}
Loading

0 comments on commit 3bafa64

Please sign in to comment.