1423b97d6673b7fbefc25b7b6bf17b1e2310baaf
3 "select_transcript_set" : "Select layers",
4 "auto_scroll" : "auto scroll",
6 "improve_transcript" : "Improve"
8 // text interface object (for inline display captions)
9 var mvTextInterface = function( parentEmbed
){
10 return this.init( parentEmbed
);
12 mvTextInterface
.prototype = {
13 text_lookahead_time
:0,
15 default_time_range
: "source", //by default just use the source don't get a time-range
18 add_to_end_on_this_pass
:false,
20 init:function( parentEmbed
){
21 //init a new availableTracks obj:
22 this.availableTracks
={};
23 //set the parent embed object:
25 //parse roe if not already done:
26 this.getTimedTextTracks();
28 //@@todo separate out data loader & data display
29 getTimedTextTracks:function(){
30 js_log("load timed text from roe: "+ this.pe
.roe
);
32 //if roe not yet loaded do load it:
34 if(!this.pe
.media_element
.addedROEData
){
35 js_log("load roe data!");
36 $j('#mv_txt_load_'+_this
.pe
.id
).show(); //show the loading icon
37 do_request( _this
.pe
.roe
, function(data
)
40 _this
.pe
.media_element
.addROE(data
);
41 _this
.getParseTimedText_rowReady();
44 js_log('row data ready (no roe request)');
45 _this
.getParseTimedText_rowReady();
48 if( this.pe
.media_element
.timedTextSources() ){
49 _this
.getParseTimedText_rowReady();
51 js_log('no roe data or timed text sources');
55 getParseTimedText_rowReady: function (){
58 var default_found
=false;
59 js_log("mv_txt_load_:SHOW mv_txt_load_");
60 $j('#mv_txt_load_'+_this
.pe
.id
).show(); //show the loading icon
62 $j
.each( this.pe
.media_element
.sources
, function(inx
, source
){
64 if( typeof source
.id
== 'undefined' || source
.id
== null ){
65 source
.id
= 'tt_' + inx
;
67 var tObj
= new timedTextObj( source
);
68 //make sure its a valid timed text format (we have not loaded or parsed yet) : (
69 if( tObj
.lib
!= null ){
70 js_log('adding Track: ' + source
.id
+ ' to ' + _this
.pe
.id
);
71 _this
.availableTracks
[ source
.id
] = tObj
;
72 //js_log( 'is : ' + source.id + ' default: ' + source.default );
73 //display if requested:
74 if( source
['default'] == "true" ){
75 //we did set at least one track by default tag
77 js_log('do load timed text: ' + source
.id
);
78 _this
.loadAndDisplay( source
.id
);
80 //don't load the track and don't display
85 //no default clip found take the first_id
87 $j
.each( _this
.availableTracks
, function(inx
, sourceTrack
){
88 _this
.loadAndDisplay( sourceTrack
.id
);
90 //retun after loading first available
95 //if nothing found anywhere update the loading icon to say no tracks found
97 $j('#mv_txt_load_'+_this
.pe
.id
).html( gM('no_text_tracks_found') );
101 loadAndDisplay: function ( track_id
){
103 $j('#mv_txt_load_'+_this
.pe
.id
).show();//show the loading icon
104 _this
.availableTracks
[ track_id
].load(_this
.default_time_range
, function(){
105 $j('#mv_txt_load_'+_this
.pe
.id
).hide();
106 _this
.addTrack( track_id
);
109 addTrack: function( track_id
){
110 js_log('f:displayTrack:'+ track_id
);
112 //set the display flag to true:
113 _this
.availableTracks
[ track_id
].display
=true;
116 js_log("SHOULD ADD: "+ track_id
+ ' count:' + _this
.availableTracks
[ track_id
].textNodes
.length
);
118 //a flag to avoid checking all clips if we know we are adding to the end:
119 _this
.add_to_end_on_this_pass
= false;
121 //run clip adding on a timed interval to not lock the browser on large srt file merges (should use worker threads)
123 var track_id
= track_id
;
124 var addNextClip = function(){
125 var text_clip
= _this
.availableTracks
[ track_id
].textNodes
[i
];
126 _this
.add_merge_text_clip(text_clip
);
128 if(i
< _this
.availableTracks
[ track_id
].textNodes
.length
){
129 setTimeout(addNextClip
, 1);
134 add_merge_text_clip: function( text_clip
){
136 //make sure the clip does not already exist:
137 if($j('#tc_'+text_clip
.id
).length
==0){
138 var inserted
= false;
139 var text_clip_start_time
= npt2seconds( text_clip
.start
);
141 var insertHTML
= '<div id="tc_'+text_clip
.id
+'" ' +
142 'start_sec="' + text_clip_start_time
+ '" ' +
143 'start="'+text_clip
.start
+'" end="'+text_clip
.end
+'" class="mvtt tt_'+text_clip
.type_id
+'">' +
144 '<div class="mvttseek" style="top:0px;left:0px;right:0px;height:20px;font-size:small">'+
145 text_clip
.start
+ ' to ' +text_clip
.end
+
149 //js_log("ADDING CLIP: " + text_clip_start_time + ' html: ' + insertHTML);
150 if(!_this
.add_to_end_on_this_pass
){
151 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
153 //js_log( npt2seconds($j(this).attr('start')) + ' > ' + text_clip_start_time);
154 if( $j(this).attr('start_sec') > text_clip_start_time
){
156 $j(this).before(insertHTML
);
159 _this
.add_to_end
= true;
163 //js_log('should just add to end: '+insertHTML);
165 $j('#mmbody_'+this.pe
.id
).append(insertHTML
);
168 //apply the mouse over transcript seek/click functions:
169 $j(".mvttseek").click( function() {
170 _this
.pe
.doSeek( $j(this).parent().attr("start_sec") / _this
.pe
.getDuration() );
172 $j(".mvttseek").hoverIntent({
173 interval
:200, //polling interval
174 timeout
:200, //delay before onMouseOut
176 js_log('mvttseek: over');
177 $j(this).parent().addClass('tt_highlight');
178 //do section highlight
179 _this
.pe
.highlightPlaySection( {
180 'start' : $j(this).parent().attr("start"),
181 'end' : $j(this).parent().attr("end")
185 js_log('mvttseek: out');
186 $j(this).parent().removeClass('tt_highlight');
187 //de highlight section
188 _this
.pe
.hideHighlight();
194 setup_layout:function(){
195 //check if we have already loaded the menu/body:
196 if($j('#tt_mmenu_'+this.pe
.id
).length
==0){
197 $j('#metaBox_'+this.pe
.id
).html(
201 this.doMenuBindings();
205 //setup layout if not already done:
207 //display the interface if not already displayed:
208 $j('#metaBox_'+this.pe
.id
).fadeIn("fast");
209 //start the autoscroll timer:
210 if( this.autoscroll
)
211 this.setAutoScroll();
215 $j('#metaBox_'+this.pe
.id
).fadeOut('fast');
217 $j('#metaButton_'+this.pe
.id
).fadeIn('fast');
220 return '<div id="mmbody_'+this.pe
.id
+'" ' +
221 'style="position:absolute;top:30px;left:0px;' +
222 'right:0px;bottom:0px;' +
223 'height:'+(this.pe
.height
-30)+
224 'px;overflow:auto;"><span style="display:none;" id="mv_txt_load_' + this.pe
.id
+ '">'+
225 gM('loading_txt')+'</span>' +
228 getTsSelect:function(){
230 js_log('getTsSelect');
231 var selHTML
= '<div id="mvtsel_' + this.pe
.id
+ '" style="position:absolute;background:#FFF;top:30px;left:0px;right:0px;bottom:0px;overflow:auto;">';
232 selHTML
+='<b>' + gM('select_transcript_set') + '</b><ul>';
234 for(var i
in _this
.availableTracks
){ //for in loop ok on object
235 var checked
= ( _this
.availableTracks
[i
].display
) ? 'checked' : '';
236 selHTML
+='<li><input name="'+i
+'" class="mvTsSelect" type="checkbox" ' + checked
+ '>'+
237 _this
.availableTracks
[i
].getTitle() + '</li>';
240 '<a href="#" onClick="document.getElementById(\'' + this.pe
.id
+ '\').textInterface.applyTsSelect();return false;">'+gM('close')+'</a>'+
242 $j('#metaBox_'+_this
.pe
.id
).append( selHTML
);
244 applyTsSelect:function(){
246 //update availableTracks
247 $j('#mvtsel_'+this.pe
.id
+' .mvTsSelect').each(function(){
249 var track_id
= this.name
;
250 //if not yet loaded now would be a good time
251 if(! _this
.availableTracks
[ track_id
].loaded
){
252 _this
.loadAndDisplay( track_id
);
254 _this
.availableTracks
[this.name
].display
=true;
255 //display the named class:
256 $j('#mmbody_'+_this
.pe
.id
+' .tt_'+this.name
).fadeIn("fast");
259 if(_this
.availableTracks
[this.name
].display
){
260 _this
.availableTracks
[this.name
].display
=false;
262 $j('#mmbody_'+_this
.pe
.id
+' .tt_'+this.name
).fadeOut("fast");
266 $j('#mvtsel_'+_this
.pe
.id
).fadeOut("fast").remove();
270 //grab the time from the video object
271 var cur_time
= this.pe
.currentTime
;
273 var search_for_range
= true;
274 //check if the current transcript is already where we want:
275 if($j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').length
!= 0){
276 var curhl
= $j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').get(0);
277 if(npt2seconds($j(curhl
).attr('start') ) < cur_time
&&
278 npt2seconds($j(curhl
).attr('end') ) > cur_time
){
279 /*js_log('in range of current hl: ' +
280 npt2seconds($j(curhl).attr('start')) + ' to ' + npt2seconds($j(curhl).attr('end')));
282 search_for_range
= false;
284 search_for_range
= true;
285 //remove the highlight from all:
286 $j('#mmbody_'+this.pe
.id
+' .tt_scroll_highlight').removeClass('tt_scroll_highlight');
289 /*js_log('search_for_range:'+search_for_range + ' for: '+ cur_time);*/
290 if( search_for_range
){
291 //search for current time: add tt_scroll_highlight to clip
293 // should do binnary search not iterative
294 // avoid jquery function calls do native loops
295 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
296 if(npt2seconds($j(this).attr('start') ) < cur_time
&&
297 npt2seconds($j(this).attr('end') ) > cur_time
){
298 _this
.prevTimeScroll
=cur_time
;
299 $j('#mmbody_'+_this
.pe
.id
).animate({
300 scrollTop
: $j(this).get(0).offsetTop
302 $j(this).addClass('tt_scroll_highlight');
303 //js_log('should add class to: ' + $j(this).attr('id'));
311 setAutoScroll:function( timer
){
313 this.autoscroll
= ( typeof timer
=='undefined' )?this.autoscroll
:timer
;
315 //start the timer if its not already running
316 if(!this.scrollTimerId
){
317 this.scrollTimerId
= setInterval('$j(\'#'+_this
.pe
.id
+'\').get(0).textInterface.monitor()', 500);
319 //jump to the current position:
320 var cur_time
= parseInt (this.pe
.currentTime
);
321 js_log('cur time: '+ cur_time
);
325 $j('#mmbody_'+this.pe
.id
+' .mvtt').each(function(){
326 if(cur_time
> npt2seconds($j(this).attr('start')) ){
327 _this
.prevTimeScroll
=cur_time
;
328 if( $j(this).attr('id') )
329 scroll_to_id
= $j(this).attr('id');
332 if(scroll_to_id
!= '')
333 $j( '#mmbody_' + _this
.pe
.id
).animate( { scrollTop
: $j('#'+scroll_to_id
).position().top
} , 'slow' );
336 clearInterval(this.scrollTimerId
);
337 this.scrollTimerId
=0;
342 //add in loading icon:
343 var as_checked
= (this.autoscroll
)?'checked':'';
344 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;">' +
345 $j
.btnHtml(gM('select_transcript_set'), 'tt-select', 'shuffle');
346 if(this.pe
.media_element
.linkback
){
347 out
+=' ' + $j
.btnHtml(gM('improve_transcript'), 'tt-improve', 'document', {href
:this.pe
.media_element
.linkback
, target
:'_new'});
349 out
+='<input onClick="document.getElementById(\''+this.pe
.id
+'\').textInterface.setAutoScroll(this.checked);return false;" ' +
350 'type="checkbox" '+as_checked
+'>'+gM('auto_scroll') + ' ' +
351 $j
.btnHtml(gM('close'), 'tt-close', 'circle-close');
355 doMenuBindings:function(){
357 var mt
= '#tt_mmenu_'+ _this
.pe
.id
;
358 $j(mt
+ ' .tt-close').unbind().btnBind().click(function(){
359 $j( '#' + _this
.pe
.id
).get(0).closeTextInterface();
362 $j(mt
+ ' .tt-select').unbind().btnBind().click(function(){
363 $j( '#' + _this
.pe
.id
).get(0).textInterface
.getTsSelect();
366 //use hard-coded link:
367 $j(mt
+ ' .tt-improve').btnBind();
371 /* text format objects
372 * @@todo allow loading from external lib set
374 var timedTextObj = function( source
){
375 //@@todo in the future we could support timed text in oggs if they can be accessed via javascript
376 //we should be able to do a HEAD request to see if we can read transcripts from the file.
377 switch( source
.mime_type
){
386 js_log( source
.mime_type
+ ' is not suported timed text fromat');
390 //extend with the per-mime type lib:
391 eval('var tObj = timedText' + this.lib
+ ';');
392 for( var i
in tObj
){
395 return this.init( source
);
398 //base timedText object
399 timedTextObj
.prototype = {
403 textNodes
:new Array(),
404 init: function( source
){
405 //copy source properties
406 this.source
= source
;
410 return this.source
.title
;
413 return this.source
.src
;
417 // Specific Timed Text formats:
420 load: function( range
, callback
){
422 js_log('textCMML: loading track: '+ this.src
);
424 //:: Load transcript range ::
426 var pcurl
= parseUri( _this
.getSRC() );
427 //check for urls without time keys:
428 if( typeof pcurl
.queryKey
['t'] == 'undefined'){
429 //in which case just get the full time req:
430 do_request( this.getSRC(), function(data
){
431 _this
.doParse( data
);
437 var req_time
= pcurl
.queryKey
['t'].split('/');
438 req_time
[0]=npt2seconds(req_time
[0]);
439 req_time
[1]=npt2seconds(req_time
[1]);
440 if(req_time
[1]-req_time
[0]> _this
.request_length
){
441 //longer than 5 min will only issue a (request 5 min)
442 req_time
[1] = req_time
[0]+_this
.request_length
;
444 //set up request url:
445 url
= pcurl
.protocol
+ '://' + pcurl
.authority
+ pcurl
.path
+'?';
446 $j
.each(pcurl
.queryKey
, function(key
, val
){
448 url
+=key
+'='+val
+'&';
450 url
+= 't=' + seconds2npt(req_time
[0]) + '/' + seconds2npt(req_time
[1]) + '&';
453 do_request( url
, function(data
){
454 js_log("load track clip count:" + data
.getElementsByTagName('clip').length
);
455 _this
.doParse( data
);
460 doParse: function(data
){
462 $j
.each(data
.getElementsByTagName('clip'), function(inx
, clip
){
463 //js_log(' on clip ' + clip.id);
465 start
: $j(clip
).attr('start').replace('npt:', ''),
466 end
: $j(clip
).attr('end').replace('npt:', ''),
468 id
: $j(clip
).attr('id')
470 $j
.each( clip
.getElementsByTagName('body'), function(binx
, bn
){
472 text_clip
.body
= bn
.textContent
;
474 text_clip
.body
= bn
.text
;
477 _this
.textNodes
.push( text_clip
);
482 load: function( range
, callback
){
484 js_log('textSRT: loading : '+ _this
.getSRC() );
485 do_request( _this
.getSRC() , function(data
){
486 _this
.doParse( data
);
491 doParse:function( data
){
492 //split up the transcript chunks:
494 var tc
= data
.split(/[\r]?\n[\r]?\n/);
495 //pushing can take time
496 for(var s
=0; s
< tc
.length
; s
++) {
497 var st
= tc
[s
].split('\n');
500 var i
= st
[1].split(' --> ')[0].replace(/^\s+|\s+$/g,"");
501 var o
= st
[1].split(' --> ')[1].replace(/^\s+|\s+$/g,"");
504 for(j
=3; j
<st
.length
;j
++)
511 "id": this.id
+ '_' + n
,
514 this.textNodes
.push( text_clip
);