a74cbf49fc951a47f6e18c332e62d846346be946
4 "select_transcript_set" : "Select layers",
5 "auto_scroll" : "auto scroll",
7 "improve_transcript" : "Improve"
10 // text interface object (for inline display captions)
11 var mvTextInterface = function( parentEmbed
){
12 return this.init( parentEmbed
);
14 mvTextInterface
.prototype = {
15 text_lookahead_time
:0,
17 default_time_range
: "source", //by default just use the source don't get a time-range
20 add_to_end_on_this_pass
:false,
22 init:function( parentEmbed
){
23 //init a new availableTracks obj:
24 this.availableTracks
={};
25 //set the parent embed object:
27 //parse roe if not already done:
28 this.getTimedTextTracks();
30 //@@todo separate out data loader & data display
31 getTimedTextTracks:function(){
32 js_log("load timed text from roe: "+ this.pe
.roe
);
34 //if roe not yet loaded do load it:
36 if(!this.pe
.media_element
.addedROEData
){
37 js_log("load roe data!");
38 $j('#mv_txt_load_'+_this
.pe
.id
).show(); //show the loading icon
39 do_request( _this
.pe
.roe
, function(data
)
42 _this
.pe
.media_element
.addROE(data
);
43 _this
.getParseTimedText_rowReady();
46 js_log('row data ready (no roe request)');
47 _this
.getParseTimedText_rowReady();
50 if( this.pe
.media_element
.timedTextSources() ){
51 _this
.getParseTimedText_rowReady();
53 js_log('no roe data or timed text sources');
57 getParseTimedText_rowReady: function (){
60 var default_found
=false;
61 js_log("mv_txt_load_:SHOW mv_txt_load_");
62 $j('#mv_txt_load_'+_this
.pe
.id
).show(); //show the loading icon
64 $j
.each( this.pe
.media_element
.sources
, function(inx
, source
){
66 if( typeof source
.id
== 'undefined' || source
.id
== null ){
67 source
.id
= 'tt_' + inx
;
69 var tObj
= new timedTextObj( source
);
70 //make sure its a valid timed text format (we have not loaded or parsed yet) : (
71 if( tObj
.lib
!= null ){
72 js_log('adding Track: ' + source
.id
+ ' to ' + _this
.pe
.id
);
73 _this
.availableTracks
[ source
.id
] = tObj
;
74 //js_log( 'is : ' + source.id + ' default: ' + source.default );
75 //display if requested:
76 if( source
['default'] == "true" ){
77 //we did set at least one track by default tag
79 js_log('do load timed text: ' + source
.id
);
80 _this
.loadAndDisplay( source
.id
);
82 //don't load the track and don't display
87 //no default clip found take the first_id
89 $j
.each( _this
.availableTracks
, function(inx
, sourceTrack
){
90 _this
.loadAndDisplay( sourceTrack
.id
);
92 //retun after loading first available
97 //if nothing found anywhere update the loading icon to say no tracks found
99 $j('#mv_txt_load_'+_this
.pe
.id
).html( gM('no_text_tracks_found') );
103 loadAndDisplay: function ( track_id
){
105 $j('#mv_txt_load_'+_this
.pe
.id
).show();//show the loading icon
106 _this
.availableTracks
[ track_id
].load(_this
.default_time_range
, function(){
107 $j('#mv_txt_load_'+_this
.pe
.id
).hide();
108 _this
.addTrack( track_id
);
111 addTrack: function( track_id
){
112 js_log('f:displayTrack:'+ track_id
);
114 //set the display flag to true:
115 _this
.availableTracks
[ track_id
].display
=true;
118 js_log("SHOULD ADD: "+ track_id
+ ' count:' + _this
.availableTracks
[ track_id
].textNodes
.length
);
120 //a flag to avoid checking all clips if we know we are adding to the end:
121 _this
.add_to_end_on_this_pass
= false;
123 //run clip adding on a timed interval to not lock the browser on large srt file merges (should use worker threads)
125 var track_id
= track_id
;
126 var addNextClip = function(){
127 var text_clip
= _this
.availableTracks
[ track_id
].textNodes
[i
];
128 _this
.add_merge_text_clip(text_clip
);
130 if(i
< _this
.availableTracks
[ track_id
].textNodes
.length
){
131 setTimeout(addNextClip
, 1);
136 add_merge_text_clip: function( text_clip
){
138 //make sure the clip does not already exist:
139 if($j('#tc_'+text_clip
.id
).length
==0){
140 var inserted
= false;
141 var text_clip_start_time
= npt2seconds( text_clip
.start
);
143 var insertHTML
= '<div id="tc_'+text_clip
.id
+'" ' +
144 'start_sec="' + text_clip_start_time
+ '" ' +
145 'start="'+text_clip
.start
+'" end="'+text_clip
.end
+'" class="mvtt tt_'+text_clip
.type_id
+'">' +
146 '<div class="mvttseek" style="top:0px;left:0px;right:0px;height:20px;font-size:small">'+
147 text_clip
.start
+ ' to ' +text_clip
.end
+
151 //js_log("ADDING CLIP: " + text_clip_start_time + ' html: ' + insertHTML);
152 if(!_this
.add_to_end_on_this_pass
){
153 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
155 //js_log( npt2seconds($j(this).attr('start')) + ' > ' + text_clip_start_time);
156 if( $j(this).attr('start_sec') > text_clip_start_time
){
158 $j(this).before(insertHTML
);
161 _this
.add_to_end
= true;
165 //js_log('should just add to end: '+insertHTML);
167 $j('#mmbody_'+this.pe
.id
).append(insertHTML
);
170 //apply the mouse over transcript seek/click functions:
171 $j(".mvttseek").click( function() {
172 _this
.pe
.doSeek( $j(this).parent().attr("start_sec") / _this
.pe
.getDuration() );
174 $j(".mvttseek").hoverIntent({
175 interval
:200, //polling interval
176 timeout
:200, //delay before onMouseOut
178 js_log('mvttseek: over');
179 $j(this).parent().addClass('tt_highlight');
180 //do section highlight
181 _this
.pe
.highlightPlaySection( {
182 'start' : $j(this).parent().attr("start"),
183 'end' : $j(this).parent().attr("end")
187 js_log('mvttseek: out');
188 $j(this).parent().removeClass('tt_highlight');
189 //de highlight section
190 _this
.pe
.hideHighlight();
196 setup_layout:function(){
197 //check if we have already loaded the menu/body:
198 if($j('#tt_mmenu_'+this.pe
.id
).length
==0){
199 $j('#metaBox_'+this.pe
.id
).html(
203 this.doMenuBindings();
207 //setup layout if not already done:
209 //display the interface if not already displayed:
210 $j('#metaBox_'+this.pe
.id
).fadeIn("fast");
211 //start the autoscroll timer:
212 if( this.autoscroll
)
213 this.setAutoScroll();
217 $j('#metaBox_'+this.pe
.id
).fadeOut('fast');
219 $j('#metaButton_'+this.pe
.id
).fadeIn('fast');
222 return '<div id="mmbody_'+this.pe
.id
+'" ' +
223 'style="position:absolute;top:30px;left:0px;' +
224 'right:0px;bottom:0px;' +
225 'height:'+(this.pe
.height
-30)+
226 'px;overflow:auto;"><span style="display:none;" id="mv_txt_load_' + this.pe
.id
+ '">'+
227 gM('loading_txt')+'</span>' +
230 getTsSelect:function(){
232 js_log('getTsSelect');
233 var selHTML
= '<div id="mvtsel_' + this.pe
.id
+ '" style="position:absolute;background:#FFF;top:30px;left:0px;right:0px;bottom:0px;overflow:auto;">';
234 selHTML
+='<b>' + gM('select_transcript_set') + '</b><ul>';
236 for(var i
in _this
.availableTracks
){ //for in loop ok on object
237 var checked
= ( _this
.availableTracks
[i
].display
) ? 'checked' : '';
238 selHTML
+='<li><input name="'+i
+'" class="mvTsSelect" type="checkbox" ' + checked
+ '>'+
239 _this
.availableTracks
[i
].getTitle() + '</li>';
242 '<a href="#" onClick="document.getElementById(\'' + this.pe
.id
+ '\').textInterface.applyTsSelect();return false;">'+gM('close')+'</a>'+
244 $j('#metaBox_'+_this
.pe
.id
).append( selHTML
);
246 applyTsSelect:function(){
248 //update availableTracks
249 $j('#mvtsel_'+this.pe
.id
+' .mvTsSelect').each(function(){
251 var track_id
= this.name
;
252 //if not yet loaded now would be a good time
253 if(! _this
.availableTracks
[ track_id
].loaded
){
254 _this
.loadAndDisplay( track_id
);
256 _this
.availableTracks
[this.name
].display
=true;
257 //display the named class:
258 $j('#mmbody_'+_this
.pe
.id
+' .tt_'+this.name
).fadeIn("fast");
261 if(_this
.availableTracks
[this.name
].display
){
262 _this
.availableTracks
[this.name
].display
=false;
264 $j('#mmbody_'+_this
.pe
.id
+' .tt_'+this.name
).fadeOut("fast");
268 $j('#mvtsel_'+_this
.pe
.id
).fadeOut("fast").remove();
272 //grab the time from the video object
273 var cur_time
= this.pe
.currentTime
;
275 var search_for_range
= true;
276 //check if the current transcript is already where we want:
277 if($j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').length
!= 0){
278 var curhl
= $j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').get(0);
279 if(npt2seconds($j(curhl
).attr('start') ) < cur_time
&&
280 npt2seconds($j(curhl
).attr('end') ) > cur_time
){
281 /*js_log('in range of current hl: ' +
282 npt2seconds($j(curhl).attr('start')) + ' to ' + npt2seconds($j(curhl).attr('end')));
284 search_for_range
= false;
286 search_for_range
= true;
287 //remove the highlight from all:
288 $j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').removeClass('tt_scroll_highlight');
291 /*js_log('search_for_range:'+search_for_range + ' for: '+ cur_time);*/
292 if( search_for_range
){
293 //search for current time: add tt_scroll_highlight to clip
295 // should do binnary search not iterative
296 // avoid jquery function calls do native loops
297 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
298 if(npt2seconds($j(this).attr('start') ) < cur_time
&&
299 npt2seconds($j(this).attr('end') ) > cur_time
){
300 _this
.prevTimeScroll
=cur_time
;
301 $j('#mmbody_'+_this
.pe
.id
).animate({
302 scrollTop
: $j(this).get(0).offsetTop
304 $j(this).addClass('tt_scroll_highlight');
305 //js_log('should add class to: ' + $j(this).attr('id'));
313 setAutoScroll:function( timer
){
315 this.autoscroll
= ( typeof timer
=='undefined' )?this.autoscroll
:timer
;
317 //start the timer if its not already running
318 if(!this.scrollTimerId
){
319 this.scrollTimerId
= setInterval('$j(\'#'+_this
.pe
.id
+'\').get(0).textInterface.monitor()', 500);
321 //jump to the current position:
322 var cur_time
= parseInt (this.pe
.currentTime
);
323 js_log('cur time: '+ cur_time
);
327 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
328 if(cur_time
> npt2seconds($j(this).attr('start')) ){
329 _this
.prevTimeScroll
=cur_time
;
330 if( $j(this).attr('id') )
331 scroll_to_id
= $j(this).attr('id');
334 if(scroll_to_id
!= '')
335 $j( '#mmbody_' + _this
.pe
.id
).animate( { scrollTop
: $j('#'+scroll_to_id
).position().top
} , 'slow' );
338 clearInterval(this.scrollTimerId
);
339 this.scrollTimerId
=0;
344 //add in loading icon:
345 var as_checked
= (this.autoscroll
)?'checked':'';
346 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;">' +
347 $j
.btnHtml(gM('select_transcript_set'), 'tt-select', 'shuffle');
348 if(this.pe
.media_element
.linkback
){
349 out
+=' ' + $j
.btnHtml(gM('improve_transcript'), 'tt-improve', 'document', {href
:this.pe
.media_element
.linkback
, target
:'_new'});
351 out
+='<input onClick="document.getElementById(\''+this.pe
.id
+'\').textInterface.setAutoScroll(this.checked);return false;" ' +
352 'type="checkbox" '+as_checked
+'>'+gM('auto_scroll') + ' ' +
353 $j
.btnHtml(gM('close'), 'tt-close', 'circle-close');
357 doMenuBindings:function(){
359 var mt
= '#tt_mmenu_'+ _this
.pe
.id
;
360 $j(mt
+ ' .tt-close').unbind().btnBind().click(function(){
361 $j( '#' + _this
.pe
.id
).get(0).closeTextInterface();
364 $j(mt
+ ' .tt-select').unbind().btnBind().click(function(){
365 $j( '#' + _this
.pe
.id
).get(0).textInterface
.getTsSelect();
368 //use hard-coded link:
369 $j(mt
+ ' .tt-improve').btnBind();
373 /* text format objects
374 * @@todo allow loading from external lib set
376 var timedTextObj = function( source
){
377 //@@todo in the future we could support timed text in oggs if they can be accessed via javascript
378 //we should be able to do a HEAD request to see if we can read transcripts from the file.
379 switch( source
.mime_type
){
388 js_log( source
.mime_type
+ ' is not suported timed text fromat');
392 //extend with the per-mime type lib:
393 eval('var tObj = timedText' + this.lib
+ ';');
394 for( var i
in tObj
){
397 return this.init( source
);
400 //base timedText object
401 timedTextObj
.prototype = {
405 textNodes
:new Array(),
406 init: function( source
){
407 //copy source properties
408 this.source
= source
;
412 return this.source
.title
;
415 return this.source
.src
;
419 // Specific Timed Text formats:
422 load: function( range
, callback
){
424 js_log('textCMML: loading track: '+ this.src
);
426 //:: Load transcript range ::
428 var pcurl
= parseUri( _this
.getSRC() );
429 //check for urls without time keys:
430 if( typeof pcurl
.queryKey
['t'] == 'undefined'){
431 //in which case just get the full time req:
432 do_request( this.getSRC(), function(data
){
433 _this
.doParse( data
);
439 var req_time
= pcurl
.queryKey
['t'].split('/');
440 req_time
[0]=npt2seconds(req_time
[0]);
441 req_time
[1]=npt2seconds(req_time
[1]);
442 if(req_time
[1]-req_time
[0]> _this
.request_length
){
443 //longer than 5 min will only issue a (request 5 min)
444 req_time
[1] = req_time
[0]+_this
.request_length
;
446 //set up request url:
447 url
= pcurl
.protocol
+ '://' + pcurl
.authority
+ pcurl
.path
+'?';
448 $j
.each(pcurl
.queryKey
, function(key
, val
){
450 url
+=key
+'='+val
+'&';
452 url
+= 't=' + seconds2npt(req_time
[0]) + '/' + seconds2npt(req_time
[1]) + '&';
455 do_request( url
, function(data
){
456 js_log("load track clip count:" + data
.getElementsByTagName('clip').length
);
457 _this
.doParse( data
);
462 doParse: function(data
){
464 $j
.each(data
.getElementsByTagName('clip'), function(inx
, clip
){
465 //js_log(' on clip ' + clip.id);
467 start
: $j(clip
).attr('start').replace('npt:', ''),
468 end
: $j(clip
).attr('end').replace('npt:', ''),
470 id
: $j(clip
).attr('id')
472 $j
.each( clip
.getElementsByTagName('body'), function(binx
, bn
){
474 text_clip
.body
= bn
.textContent
;
476 text_clip
.body
= bn
.text
;
479 _this
.textNodes
.push( text_clip
);
484 load: function( range
, callback
){
486 js_log('textSRT: loading : '+ _this
.getSRC() );
487 do_request( _this
.getSRC() , function(data
){
488 _this
.doParse( data
);
493 doParse:function( data
){
494 //split up the transcript chunks:
496 var tc
= data
.split(/[\r]?\n[\r]?\n/);
497 //pushing can take time
498 for(var s
=0; s
< tc
.length
; s
++) {
499 var st
= tc
[s
].split('\n');
502 var i
= st
[1].split(' --> ')[0].replace(/^\s+|\s+$/g,"");
503 var o
= st
[1].split(' --> ')[1].replace(/^\s+|\s+$/g,"");
506 for(j
=3; j
<st
.length
;j
++)
513 "id": this.id
+ '_' + n
,
516 this.textNodes
.push( text_clip
);