3 "mwe-select_transcript_set" : "Select layers",
4 "mwe-auto_scroll" : "auto scroll",
6 "mwe-improve_transcript" : "Improve",
7 "mwe-no_text_tracks_found" : "No text tracks were found"
9 // text interface object (for inline display captions)
10 var mvTextInterface = function( parentEmbed ){
11 return this.init( parentEmbed );
13 mvTextInterface.prototype = {
14 text_lookahead_time:0,
16 default_time_range: "source", //by default just use the source don't get a time-range
19 add_to_end_on_this_pass:false,
21 init:function( parentEmbed ){
22 //init a new availableTracks obj:
23 this.availableTracks={};
24 //set the parent embed object:
26 //parse roe if not already done:
27 this.getTimedTextTracks();
29 //@@todo separate out data loader & data display
30 getTimedTextTracks:function(){
31 js_log("load timed text from roe: "+ this.pe.roe);
33 //if roe not yet loaded do load it:
35 if(!this.pe.media_element.addedROEData){
36 js_log("load roe data!");
37 $j('#mv_txt_load_'+_this.pe.id).show(); //show the loading icon
38 do_request( _this.pe.roe, function(data)
41 _this.pe.media_element.addROE(data);
42 _this.getParseTimedText_rowReady();
45 js_log('row data ready (no roe request)');
46 _this.getParseTimedText_rowReady();
49 if( this.pe.media_element.timedTextSources() ){
50 _this.getParseTimedText_rowReady();
52 js_log('no roe data or timed text sources');
56 getParseTimedText_rowReady: function (){
59 var default_found=false;
60 js_log("mv_txt_load_:SHOW mv_txt_load_");
61 $j('#mv_txt_load_'+_this.pe.id).show(); //show the loading icon
63 $j.each( this.pe.media_element.sources, function(inx, source){
65 if( typeof source.id == 'undefined' || source.id == null ){
66 source.id = 'tt_' + inx;
68 var tObj = new timedTextObj( source );
69 //make sure its a valid timed text format (we have not loaded or parsed yet) : (
70 if( tObj.lib != null ){
71 js_log('adding Track: ' + source.id + ' to ' + _this.pe.id);
72 _this.availableTracks[ source.id ] = tObj;
73 //js_log( 'is : ' + source.id + ' default: ' + source.default );
74 //display if requested:
75 if( source['default'] == "true" ){
76 //we did set at least one track by default tag
78 js_log('do load timed text: ' + source.id );
79 _this.loadAndDisplay( source.id );
81 //don't load the track and don't display
86 //no default clip found take the first_id
88 $j.each( _this.availableTracks, function(inx, sourceTrack){
89 _this.loadAndDisplay( sourceTrack.id );
91 //retun after loading first available
96 //if nothing found anywhere update the loading icon to say no tracks found
98 $j('#mv_txt_load_'+_this.pe.id).html( gM('mwe-no_text_tracks_found') );
102 loadAndDisplay: function ( track_id){
104 $j('#mv_txt_load_'+_this.pe.id).show();//show the loading icon
105 _this.availableTracks[ track_id ].load(_this.default_time_range, function(){
106 $j('#mv_txt_load_'+_this.pe.id).hide();
107 _this.addTrack( track_id );
110 addTrack: function( track_id ){
111 js_log('f:displayTrack:'+ track_id);
113 //set the display flag to true:
114 _this.availableTracks[ track_id ].display=true;
117 js_log("SHOULD ADD: "+ track_id + ' count:' + _this.availableTracks[ track_id ].textNodes.length);
119 //a flag to avoid checking all clips if we know we are adding to the end:
120 _this.add_to_end_on_this_pass = false;
122 //run clip adding on a timed interval to not lock the browser on large srt file merges (should use worker threads)
124 var track_id = track_id;
125 var addNextClip = function(){
126 var text_clip = _this.availableTracks[ track_id ].textNodes[i];
127 _this.add_merge_text_clip(text_clip);
129 if(i < _this.availableTracks[ track_id ].textNodes.length){
130 setTimeout(addNextClip, 1);
135 add_merge_text_clip: function( text_clip ){
137 //make sure the clip does not already exist:
138 if($j('#tc_'+text_clip.id).length==0){
139 var inserted = false;
140 var text_clip_start_time = npt2seconds( text_clip.start );
142 var insertHTML = '<div id="tc_'+text_clip.id+'" ' +
143 'start_sec="' + text_clip_start_time + '" ' +
144 'start="'+text_clip.start+'" end="'+text_clip.end+'" class="mvtt tt_'+text_clip.type_id+'">' +
145 '<div class="mvttseek" style="top:0px;left:0px;right:0px;height:20px;font-size:small">'+
146 text_clip.start + ' to ' +text_clip.end+
150 //js_log("ADDING CLIP: " + text_clip_start_time + ' html: ' + insertHTML);
151 if(!_this.add_to_end_on_this_pass){
152 $j('#mmbody_'+this.pe.id +' .mvtt').each(function(){
154 //js_log( npt2seconds($j(this).attr('start')) + ' > ' + text_clip_start_time);
155 if( $j(this).attr('start_sec') > text_clip_start_time){
157 $j(this).before(insertHTML);
160 _this.add_to_end = true;
164 //js_log('should just add to end: '+insertHTML);
166 $j('#mmbody_'+this.pe.id ).append(insertHTML);
169 //apply the mouse over transcript seek/click functions:
170 $j(".mvttseek").click( function() {
171 _this.pe.doSeek( $j(this).parent().attr("start_sec") / _this.pe.getDuration() );
173 $j(".mvttseek").hoverIntent({
174 interval:200, //polling interval
175 timeout:200, //delay before onMouseOut
177 js_log('mvttseek: over');
178 $j(this).parent().addClass('tt_highlight');
179 //do section highlight
180 _this.pe.highlightPlaySection( {
181 'start' : $j(this).parent().attr("start"),
182 'end' : $j(this).parent().attr("end")
186 js_log('mvttseek: out');
187 $j(this).parent().removeClass('tt_highlight');
188 //de highlight section
189 _this.pe.hideHighlight();
195 setup_layout:function(){
196 //check if we have already loaded the menu/body:
197 if($j('#tt_mmenu_'+this.pe.id).length==0){
198 $j('#metaBox_'+this.pe.id).html(
202 this.doMenuBindings();
206 //setup layout if not already done:
208 //display the interface if not already displayed:
209 $j('#metaBox_'+this.pe.id).fadeIn("fast");
210 //start the autoscroll timer:
211 if( this.autoscroll )
212 this.setAutoScroll();
216 $j('#metaBox_'+this.pe.id).fadeOut('fast');
218 $j('#metaButton_'+this.pe.id).fadeIn('fast');
221 return '<div id="mmbody_'+this.pe.id+'" ' +
222 'style="position:absolute;top:30px;left:0px;' +
223 'right:0px;bottom:0px;' +
224 'height:'+(this.pe.height-30)+
225 'px;overflow:auto;"><span style="display:none;" id="mv_txt_load_' + this.pe.id + '">'+
226 gM('mwe-loading_txt')+'</span>' +
229 getTsSelect:function(){
231 js_log('getTsSelect');
232 var selHTML = '<div id="mvtsel_' + this.pe.id + '" style="position:absolute;background:#FFF;top:30px;left:0px;right:0px;bottom:0px;overflow:auto;">';
233 selHTML+='<b>' + gM('mwe-select_transcript_set') + '</b><ul>';
235 for(var i in _this.availableTracks){ //for in loop ok on object
236 var checked = ( _this.availableTracks[i].display ) ? 'checked' : '';
237 selHTML+='<li><input name="'+i+'" class="mvTsSelect" type="checkbox" ' + checked + '>'+
238 _this.availableTracks[i].getTitle() + '</li>';
241 '<a href="#" onClick="document.getElementById(\'' + this.pe.id + '\').textInterface.applyTsSelect();return false;">'+gM('mwe-close')+'</a>'+
243 $j('#metaBox_'+_this.pe.id).append( selHTML );
245 applyTsSelect:function(){
247 //update availableTracks
248 $j('#mvtsel_'+this.pe.id+' .mvTsSelect').each(function(){
250 var track_id = this.name;
251 //if not yet loaded now would be a good time
252 if(! _this.availableTracks[ track_id ].loaded ){
253 _this.loadAndDisplay( track_id);
255 _this.availableTracks[this.name].display=true;
256 //display the named class:
257 $j('#mmbody_'+_this.pe.id +' .tt_'+this.name ).fadeIn("fast");
260 if(_this.availableTracks[this.name].display){
261 _this.availableTracks[this.name].display=false;
263 $j('#mmbody_'+_this.pe.id +' .tt_'+this.name ).fadeOut("fast");
267 $j('#mvtsel_'+_this.pe.id).fadeOut("fast").remove();
271 //grab the time from the video object
272 var cur_time = this.pe.currentTime ;
274 var search_for_range = true;
275 //check if the current transcript is already where we want:
276 if($j('#mmbody_'+this.pe.id +' .tt_scroll_highlight').length != 0){
277 var curhl = $j('#mmbody_'+this.pe.id +' .tt_scroll_highlight').get(0);
278 if(npt2seconds($j(curhl).attr('start') ) < cur_time &&
279 npt2seconds($j(curhl).attr('end') ) > cur_time){
280 /*js_log('in range of current hl: ' +
281 npt2seconds($j(curhl).attr('start')) + ' to ' + npt2seconds($j(curhl).attr('end')));
283 search_for_range = false;
285 search_for_range = true;
286 //remove the highlight from all:
287 $j('#mmbody_'+this.pe.id +' .tt_scroll_highlight').removeClass('tt_scroll_highlight');
290 /*js_log('search_for_range:'+search_for_range + ' for: '+ cur_time);*/
291 if( search_for_range ){
292 //search for current time: add tt_scroll_highlight to clip
294 // should do binnary search not iterative
295 // avoid jquery function calls do native loops
296 $j('#mmbody_'+this.pe.id +' .mvtt').each(function(){
297 if(npt2seconds($j(this).attr('start') ) < cur_time &&
298 npt2seconds($j(this).attr('end') ) > cur_time){
299 _this.prevTimeScroll=cur_time;
300 $j('#mmbody_'+_this.pe.id).animate({
301 scrollTop: $j(this).get(0).offsetTop
303 $j(this).addClass('tt_scroll_highlight');
304 //js_log('should add class to: ' + $j(this).attr('id'));
312 setAutoScroll:function( timer ){
314 this.autoscroll = ( typeof timer=='undefined' )?this.autoscroll:timer;
316 //start the timer if its not already running
317 if(!this.scrollTimerId){
318 this.scrollTimerId = setInterval('$j(\'#'+_this.pe.id+'\').get(0).textInterface.monitor()', 500);
320 //jump to the current position:
321 var cur_time = parseInt (this.pe.currentTime );
322 js_log('cur time: '+ cur_time);
326 $j('#mmbody_'+this.pe.id +' .mvtt').each(function(){
327 if(cur_time > npt2seconds($j(this).attr('start')) ){
328 _this.prevTimeScroll=cur_time;
329 if( $j(this).attr('id') )
330 scroll_to_id = $j(this).attr('id');
333 if(scroll_to_id != '')
334 $j( '#mmbody_' + _this.pe.id ).animate( { scrollTop: $j('#'+scroll_to_id).position().top } , 'slow' );
337 clearInterval(this.scrollTimerId);
338 this.scrollTimerId=0;
343 //add in loading icon:
344 var as_checked = (this.autoscroll)?'checked':'';
345 out+= '<div id="tt_mmenu_'+this.pe.id+'" class="ui-widget-header" style="font-size:.6em;position:absolute;top:0;height:30px;left:0px;right:0px;">' +
346 $j.btnHtml(gM('mwe-select_transcript_set'), 'tt-select', 'shuffle');
347 if(this.pe.media_element.linkback){
348 out+=' ' + $j.btnHtml(gM('mwe-improve_transcript'), 'tt-improve', 'document', {href:this.pe.media_element.linkback, target:'_new'});
350 out+='<input onClick="document.getElementById(\''+this.pe.id+'\').textInterface.setAutoScroll(this.checked);return false;" ' +
351 'type="checkbox" '+as_checked +'>'+gM('mwe-auto_scroll') + ' ' +
352 $j.btnHtml(gM('mwe-close'), 'tt-close', 'circle-close');
356 doMenuBindings:function(){
358 var mt = '#tt_mmenu_'+ _this.pe.id;
359 $j(mt + ' .tt-close').unbind().btnBind().click(function(){
360 $j( '#' + _this.pe.id).get(0).closeTextInterface();
363 $j(mt + ' .tt-select').unbind().btnBind().click(function(){
364 $j( '#' + _this.pe.id).get(0).textInterface.getTsSelect();
367 //use hard-coded link:
368 $j(mt + ' .tt-improve').btnBind();
372 /* text format objects
373 * @@todo allow loading from external lib set
375 var timedTextObj = function( source ){
376 //@@todo in the future we could support timed text in oggs if they can be accessed via javascript
377 //we should be able to do a HEAD request to see if we can read transcripts from the file.
378 switch( source.mime_type ){
387 js_log( source.mime_type + ' is not suported timed text fromat');
391 //extend with the per-mime type lib:
392 eval('var tObj = timedText' + this.lib + ';');
393 for( var i in tObj ){
396 return this.init( source );
399 //base timedText object
400 timedTextObj.prototype = {
404 textNodes:new Array(),
405 init: function( source ){
406 //copy source properties
407 this.source = source;
411 return this.source.title;
414 return this.source.src;
418 // Specific Timed Text formats:
421 load: function( range, callback ){
423 js_log('textCMML: loading track: '+ this.src);
425 //:: Load transcript range ::
427 var pcurl = parseUri( _this.getSRC() );
428 //check for urls without time keys:
429 if( typeof pcurl.queryKey['t'] == 'undefined'){
430 //in which case just get the full time req:
431 do_request( this.getSRC(), function(data){
432 _this.doParse( data );
438 var req_time = pcurl.queryKey['t'].split('/');
439 req_time[0]=npt2seconds(req_time[0]);
440 req_time[1]=npt2seconds(req_time[1]);
441 if(req_time[1]-req_time[0]> _this.request_length){
442 //longer than 5 min will only issue a (request 5 min)
443 req_time[1] = req_time[0]+_this.request_length;
445 //set up request url:
446 url = pcurl.protocol + '://' + pcurl.authority + pcurl.path +'?';
447 $j.each(pcurl.queryKey, function(key, val){
449 url+=key+'='+val+'&';
451 url+= 't=' + seconds2npt(req_time[0]) + '/' + seconds2npt(req_time[1]) + '&';
454 do_request( url, function(data){
455 js_log("load track clip count:" + data.getElementsByTagName('clip').length );
456 _this.doParse( data );
461 doParse: function(data){
463 $j.each(data.getElementsByTagName('clip'), function(inx, clip){
464 //js_log(' on clip ' + clip.id);
466 start: $j(clip).attr('start').replace('npt:', ''),
467 end: $j(clip).attr('end').replace('npt:', ''),
469 id: $j(clip).attr('id')
471 $j.each( clip.getElementsByTagName('body'), function(binx, bn ){
473 text_clip.body = bn.textContent;
475 text_clip.body = bn.text;
478 _this.textNodes.push( text_clip );
483 load: function( range, callback ){
485 js_log('textSRT: loading : '+ _this.getSRC() );
486 do_request( _this.getSRC() , function(data){
487 _this.doParse( data );
492 doParse:function( data ){
493 //split up the transcript chunks:
495 var tc = data.split(/[\r]?\n[\r]?\n/);
496 //pushing can take time
497 for(var s=0; s < tc.length ; s++) {
498 var st = tc[s].split('\n');
501 var i = st[1].split(' --> ')[0].replace(/^\s+|\s+$/g,"");
502 var o = st[1].split(' --> ')[1].replace(/^\s+|\s+$/g,"");
505 for(j=3; j<st.length;j++)
512 "id": this.id + '_' + n,
515 this.textNodes.push( text_clip );