67eb04e624425bc66c241951ccdd0d21a981b431
[lhc/web/wiklou.git] / js2 / mwEmbed / libSequencer / mvSequencer.js
1 /*
2 * mvSequencer.js Created on Oct 17, 2007
3 *
4 * All Metavid Wiki code is Released under the GPL2
5 * for more info visit http://metavid.org/wiki/Code
6 *
7 * @author Michael Dale
8 * @email mdale@wikimedia.org
9 *
10 * further developed in open source development partnership with kaltura.
11 * more info at http://kaltura.com & http://kaltura.org
12 *
13 * mv_sequencer.js
14 * is a basic embeddeble sequencer.
15 * extends the playlist with drag/drop/sortable/add/remove functionality
16 * editing of annotative content (mostly for wiki)
17 * enables more dynamic layouts
18 * exports back out to json or inline format
19 */
20
21 loadGM({
22 "menu_clipedit" : "Edit Media",
23 "menu_transition" : "Transitions & Effects",
24 "menu_cliplib" : "Add Media",
25 "menu_resource_overview" : "Resource Overview",
26 "menu_options" : "Options",
27
28 "loading_timeline" : "Loading TimeLine <blink>...</blink>",
29 "loading_user_rights" : "Loading user rights <blink>...</blink>",
30
31 "no_edit_permissions" : "You don't have permissions to save changes to this sequence",
32
33 "edit_clip" : "Edit Clip",
34 "edit_save" : "Save Sequence Changes",
35 "saving_wait": "Save in Progress (please wait)",
36 "save_done" : "Save Done",
37 "edit_cancel" : "Cancel Sequence Edit",
38 "edit_cancel_confirm" : "Are you sure you want to cancel your edit. Changes will be lost",
39
40 "zoom_in" : "Zoom In",
41 "zoom_out" : "Zoom Out",
42 "cut_clip" : "Cut Clips",
43 "expand_track" : "Expand Track",
44 "colapse_track" : "Collapse Track",
45 "play_from_position" : "Play From Playline Position",
46 "pixle2sec" : "pixles to seconds",
47 "rmclip" : "Remove Clip",
48 "clip_in" : "clip in",
49 "clip_out" : "clip out",
50
51 "mv_welcome_to_sequencer" : "<h3>Welcome to the sequencer demo</h3> very <b>limited</b> functionality right now. Not much documentation yet either",
52
53 "no_selected_resource" : "<h3>No Resource Selected</h3> Select a Clip to enable editing",
54 "error_edit_multiple" : "<h3>Multiple Resources Selected</h3> Select a single clip to edit it",
55
56 "mv_editor_options" : "Editor options",
57 "mv_editor_mode" : "Editor mode",
58 "mv_simple_editor_desc" : "simple editor (iMovie style)",
59 "mv_advanced_editor_desc" : "advanced editor (Final Cut style)",
60 "mv_other_options" : "Other Options",
61 "mv_contextmenu_opt" : "Enable Context Menus",
62
63 "mv_sequencer_credit_line":"Developed by <a href=\"http://kaltura.com\">Kaltura, Inc.</a> in partnership with the <a href=\"http://wikimediafoundation.org/wiki/Home\">Wikimedia Foundation</a> ( <a href=\"#\">more info</a> )"
64 });
65 //used to set default values and validate the passed init object
66 var sequencerDefaultValues = {
67
68 instance_name:'mvSeq', //for now only one instance by name mvSeq is allowed
69
70 target_sequence_container:null,//text value (so that its a valid property)
71 target_form_text: null,
72
73 //what is our save mode:
74 // can save to 'api' url or 'form'
75 saveMode : 'api',
76
77 video_container_id:'mv_video_container',
78
79 video_width : 400,
80 video_height: 300,
81
82 sequence_tools_id:'mv_sequence_tools',
83 timeline_id:'mv_timeline',
84 plObj_id:'seq_pl',
85 plObj:'null',
86
87 timeline_scale:.06, //in pixel to second ratio ie 100pixles for every ~30seconds
88 timeline_duration:500, //default timeline length in seconds
89 playline_time:0,
90 track_thumb_height:60,
91 track_text_height:20,
92
93 //default timeline mode: "story" (i-movie like) or "time" (finalCut like)
94 timeline_mode:'storyboard',
95
96 track_clipThumb_height:80, // how large are the i-movie type clips
97
98 base_adj_duration:.5, //default time to subtract or add when adjusting clips.
99
100 //default clipboard is empty:
101 clipboard:new Array(),
102 //stores the clipboard edit token (if user has rights to edit their User page)
103 clipboardEditToken:null,
104 //stores the sequence edit token (if user has rights to edit the current sequence)
105 sequenceEditToken:null,
106 //the time the sequence was last touched (grabbed at time of startup)
107 sequenceTouchedTime:null,
108
109
110
111 //Msg are all the language specific values ...
112 // (@@todo overwrite by msg values preloaded in the page)
113 //tack/clips can be pushed via json or inline playlist format
114 inline_playlist:'null', //text value so its a valid property
115 inline_playlist_id:'null',
116 mv_pl_src:'null',
117 //the edit stack:
118 edit_stack:new Array(),
119 disp_menu_item:null,
120 //trackObj used to payload playlist Track Object (when inline not present)
121 tracks:{}
122 }
123 var mvSequencer = function(iObj) {
124 return this.init(iObj);
125 };
126 //set up the mvSequencer object
127 mvSequencer.prototype = {
128 //the menu_items Object contains: default html, js setup/loader functions
129 menu_items : {
130 'clipedit':{
131 'd':0,
132 'html':'',
133 'js': function(this_seq){
134 this_seq.doEditSelectedClip();
135 },
136 'click_js':function( this_seq ){
137 this_seq.doEditSelectedClip();
138 }
139 },
140 'transition':{
141 'd':0,
142 'html' : '<h3>' + gM('menu_transition') + '</h3>',
143 'js':function(this_seq){
144 this_seq.doEditTransitionSelectedClip();
145 },
146 'click_js':function(this_seq){
147 //highlight the transition of the selected clip:
148 this_seq.doEditTransitionSelectedClip();
149 }
150 },
151 'cliplib':{
152 'd':0,
153 'html': gM('loading_txt'),
154 'js':function( this_seq ){
155 //load the search interface with sequence tool targets
156 mvJsLoader.doLoad( [
157 'remoteSearchDriver',
158 'seqRemoteSearchDriver'
159 ], function(){
160 this_seq.mySearch = new seqRemoteSearchDriver(this_seq);
161 this_seq.mySearch.doInitDisplay();
162 });
163 }
164 },
165 'options':{
166 'd':0,
167 'html' : '<h3>' + gM('menu_options') + '</h3>' +
168 gM('mv_editor_mode') + '<br> ' +
169 '<blockquote><input type="radio" value="simple_editor" name="opt_editor">' +
170 gM('mv_simple_editor_desc') + ' </blockquote>' +
171 '<blockquote><input type="radio" value="advanced_editor" name="opt_editor">' +
172 gM('mv_advanced_editor_desc') + ' </blockquote>'+
173 gM('mv_other_options') + '<br>' +
174 '<blockquote><input type="checkbox" value="contextmenu_opt" name="contextmenu_opt">' +
175 gM('mv_contextmenu_opt') + ' </blockquote>',
176 'js':function(this_seq){
177 $j('#options_ic input[value=\'simple_editor\']').attr({
178 'checked':(this_seq.timeline_mode=='storyboard')?true:false
179 }).click(function(){
180 this_seq.doSimpleTl();
181 });
182 $j('#options_ic input[value=\'advanced_editor\']').attr({
183 'checked':( this_seq.timeline_mode=='time' )?true:false
184 }).click(function(){
185 this_seq.doAdvancedTl();
186 });
187 //set up the options for context menus
188 }
189 }
190 },
191
192 //set up initial key states:
193 key_shift_down:false,
194 key_ctrl_down:false,
195 inputFocus:false,
196
197 init:function( iObj ){
198 //set up pointer to this_seq for current scope:
199 var this_seq = this;
200 //set the default values:
201 for(var i in sequencerDefaultValues){
202 this[ i ] = sequencerDefaultValues[i];
203 }
204 for(var i in iObj){
205 //js_log('on '+ i + ' :' + iObj[i]);
206 if(typeof sequencerDefaultValues[i] != 'undefined'){ //make sure its a valid property
207 this[i] = iObj[i];
208 }
209 }
210
211 //check for sequence_container
212 if($j(this.target_sequence_container).length === 0){
213 js_log("Error: missing target_sequence_container");
214 return false;
215 }
216
217 //$j(this.target_sequence_container).css('position', 'relative');
218 this['base_width'] = $j(this.target_sequence_container).width();
219 this['base_height'] = $j(this.target_sequence_container).height();
220
221 //add the container divs (with basic layout ~universal~
222 $j(this.target_sequence_container).html(''+
223 '<div id="' + this.video_container_id + '" style="position:absolute;right:0px;top:0px;' +
224 'width:' + this.video_width + 'px;height:'+this.video_height+'px;border:solid thin blue;background:#FFF;font-color:black;"/>'+
225 '<div id="' + this.timeline_id + '" class="ui-widget ui-widget-content ui-corner-all" style="position:absolute;' +
226 'left:0px;right:0px;top:'+(this.video_height+34)+'px;bottom:35px;overflow:auto;">'+
227 gM('loading_timeline')+ '</div>'+
228 '<div class="seq_status" style="position:absolute;left:0px;width:300px;"></div>'+
229 '<div class="seq_save_cancel" style="position:absolute;'+
230 'left:5px;bottom:0px;height:25px;">'+
231 gM('loading_user_rights') +
232 '</div>'+
233 '<div class="about_editor" style="position:absolute;right:5px;bottom:7px;">' +
234 gM('mv_sequencer_credit_line') +
235 '</div>'+
236 '<div id="'+this.sequence_tools_id+'" style="position:absolute;' +
237 'left:0px;right:'+(this.video_width+15)+'px;top:0px;height:'+(this.video_height+23)+'px;"/>'
238 ).css({
239 'min-width':'850px'
240 });
241
242 /*js_log('set: '+this.target_sequence_container + ' html to:'+ "\n"+
243 $j(this.target_sequence_container).html()
244 );*/
245 //first check if we got a cloned PL object:
246 //(when the editor is invoked with the plalylist already on the page)
247 //@@NOT WORKING... (need a better "clone" function)
248 /*if( this.plObj != 'null' ){
249 js_log('found plObj clone');
250 //extend with mvSeqPlayList object:
251 this.plObj = new mvSeqPlayList(this.plObj);
252 js_log('mvSeqPlayList added: ' + this.plObj.org_control_height );
253 $j('#'+this.video_container_id).get(0).attachNode( this.plObj );
254 this.plObj.getHTML();
255 this.checkReadyPlObj();
256 return ;
257 }*/
258
259 //else check for source based sequence editor (a clean page load of the editor)
260 if( this.mv_pl_src != 'null' ) {
261 js_log( ' pl src:: ' + this.mv_pl_src );
262 var src_attr=' src="' + this.mv_pl_src+'" ';
263 }else{
264 js_log( ' null playlist src .. (start empty) ');
265 var src_attr='';
266 }
267 $j('#'+this.video_container_id).html('<playlist ' + src_attr +
268 ' style="width:' + this.video_width + 'px;height:' + this.video_height + 'px;" '+
269 ' controls="false" id="' + this.plObj_id + '" />');
270 rewrite_by_id( this.plObj_id );
271 setTimeout(this.instance_name +'.checkReadyPlObj()', 25);
272 },
273 updateSeqSaveButtons:function(){
274 var _this = this;
275 if( this.sequenceEditToken ){
276 $j(this.target_sequence_container+' .seq_save_cancel').html(
277 $j.btnHtml( gM('edit_save'), 'seq_edit_save', 'close') + ' ' +
278 $j.btnHtml( gM('edit_cancel'), 'seq_edit_cancel', 'close')
279 );
280 }else{
281 $j(this.target_sequence_container+' .seq_save_cancel').html( cancel_button + gM('no_edit_permissions') );
282 }
283 //assing bindings
284 $j(this.target_sequence_container +' .seq_edit_cancel').unbind().click(function(){
285 var x = window.confirm( gM('edit_cancel_confirm') );
286 if( x ){
287 _this.closeModEditor();
288 }else{
289 //close request canceled.
290 }
291 });
292 $j(this.target_sequence_container +' .seq_edit_save').unbind().click(function(){
293 //pop up progress dialog ~requesting edit line summary~
294 //remove any other save dialog
295 $j('#seq_save_dialog').remove();
296 $j('body').append('<div id="seq_save_dialog" title="'+ gM('edit_save') +'">' +
297 '<span class="mw-summary">'+
298 '<label for="seq_save_summary">Edit summary: </label>' +
299 '</span>'+
300 '<input id="seq_save_summary" tabindex="1" maxlength="200" value="" size="30" name="seq_save_summary"/>'+
301 '</div>');
302 //dialog:
303 $j('#seq_save_dialog').dialog({
304 bgiframe: true,
305 autoOpen: true,
306 modal: true,
307 buttons:{
308 "Save":function(){
309 var saveReq = {
310 'action' : 'edit',
311 'title' : _this.plObj.mTitle,
312 //the text is the sequence XML + the description
313 'text' : _this.getSeqOutputHLRDXML() + "\n" +
314 _this.plObj.wikiDesc,
315 'token' : _this.sequenceEditToken,
316 'summary' : $j('#seq_save_summary').val()
317 };
318 //change to progress bar and save:
319 $j('#seq_save_dialog').html('<div class="progress" /><br>' +
320 gM('saving_wait')
321 )
322 $j('#seq_save_dialog .progress').progressbar({
323 value: 100
324 });
325 //run the Seq Save Request:
326 do_api_req( {
327 'data': saveReq,
328 'url' : _this.getLocalApiUrl()
329 },function(data){
330 $j('#seq_save_dialog').html( gM('save_done') );
331 $j('#seq_save_dialog').dialog('option',
332 'buttons', {
333 "Done":function(){
334 //refresh the page?
335 window.location.reload();
336 },
337 "Do More Edits": function() {
338 $j(this).dialog("close");
339 }
340 });
341 });
342 },
343 "Cancel":function(){
344 $j(this).dialog('close');
345 }
346 }
347 });
348 })
349 },
350 //display a menu item (hide the rest)
351 disp:function( item, dispCall ){
352 js_log('menu_item disp: ' + item);
353 this.disp_menu_item = item;
354 //update the display and item state:
355 if(this.menu_items[item]){
356 //update the tabs display:
357 if(!dispCall)
358 $j("#seq_menu").tabs('select', this.menu_items[item].inx);
359
360 this.menu_items[item].d = 1;
361 //do any click_js actions:getInsertControl
362 if( this.menu_items[item].click_js )
363 this.menu_items[item].click_js( this );
364 }
365 },
366 //setup the menu items:
367 setupMenuItems:function(){
368 js_log('loadInitMenuItems');
369 var this_seq = this;
370 //do all the menu_items setup: @@we could defer this to once the menu item is requested
371 for( var i in this.menu_items ){
372 if( this.menu_items[i].js )
373 this.menu_items[i].js( this );
374 }
375 },
376 renderTimeLine:function(){
377 //empty out the top level html:
378 $j('#'+this.timeline_id).html('');
379 //add html general for timeline
380 if( this.timeline_mode == 'time'){
381 $j('#'+this.timeline_id).html(''+
382 '<div id="'+this.timeline_id+'_left_cnt" class="mv_tl_left_cnt">'+
383 '<div id="'+this.timeline_id+'_head_control" style="position:absolute;top:0px;left:0px;right:0px;height:30px;">' +
384 '<a title="'+gM('play_from_position')+'" href="javascript:'+this.instance_name+'.play_jt()">'+
385 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/control_play_blue.png">'+
386 '</a>'+
387 '<a title="'+gM('zoom_in')+'" href="javascript:'+this.instance_name+'.zoom_in()">'+
388 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/zoom_in.png">'+
389 '</a>'+
390 '<a title="'+gM('zoom_out')+'" href="javascript:'+this.instance_name+'.zoom_out()">'+
391 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/zoom_out.png">'+
392 '</a>'+
393 '<a title="'+gM('cut_clip')+'" href="javascript:'+this.instance_name+'.cut_mode()">'+
394 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/cut.png">'+
395 '</a>'+
396 '</div>' +
397 '</div>' +
398 '<div id="'+this.timeline_id+'_tracks" class="mv_seq_tracks">' +
399 '<div id="'+this.timeline_id+'_head_jump" class="mv_head_jump" style="position:absolute;top:0px;left:0px;height:20px;"></div>'+
400 '<div id="'+this.timeline_id+'_playline" class="mv_playline"></div>'+
401 '</div>'
402 );
403 //add playlist hook to update timeline
404 this.plObj.update_tl_hook = this.instance_name+'.update_tl_hook';
405 var this_sq = this;
406 var top_pos=25;
407 //add tracks:
408 for(var i in this.plObj.tracks){
409 var track = this.plObj.tracks[i];
410 //js_log("on track: "+ i + ' t:'+ $j('#'+this.timeline_id+'_left_cnt').html() );
411 //set up track based on disp type
412 switch(track.disp_mode){
413 case 'timeline_thumb':
414 var track_height=60;
415 var exc_img = 'opened';
416 var exc_action='close';
417 var exc_msg = gM('colapse_track');
418 break;
419 case 'text':
420 var track_height=20;
421 var exc_img = 'closed';
422 var exc_action='open';
423 var exc_msg = gM('expand_track');
424 break;
425 }
426 //add track name:
427 $j('#'+this.timeline_id+'_left_cnt').append(
428 '<div id="track_cnt_'+i+'" style="top:'+top_pos+'px;height:'+track_height+'px;" class="track_name">'+
429 '<a id="mv_exc_'+i+'" title="'+exc_msg+'" href="javascript:'+this_sq.instance_name+'.exc_track('+i+',\''+exc_action+'\')">'+
430 '<img id="'+this_sq.timeline_id+'_close_expand" style="width:16px;height:16px;border:0" '+
431 ' src="'+mv_embed_path + 'images/'+exc_img+'.png">'+
432 '</a>'+
433 track.title+'</div>'
434 );
435 //also render the clips in the trackset container: (thumb or text view)
436 $j('#'+this.timeline_id+'_tracks').append(
437 '<div id="container_track_'+i+'" style="top:'+top_pos+'px;height:'+(track_height+2)+'px;left:0px;right:0px;" class="container_track" />'
438 );
439 top_pos+=track_height+20;
440 }
441 }
442 if( this.timeline_mode=='storyboard'){
443 var top_pos=this.plObj.org_control_height;
444 //debugger;
445 for(var i in this.plObj.tracks){
446 var track_height=this.track_clipThumb_height;
447 var timeline_id = this.timeline_id
448 //add in play box and container tracks
449 $j('#'+timeline_id).append(''+
450 '<div id="interface_container_track_' + i + '" ' +
451 ' style="position:absolute;top:25px;height:'+(track_height+30)+'px;left:10px;right:0px;"' +
452 '>'+
453 '<div id="container_track_'+i+'" style="position:relative;top:0px;' +
454 'height:'+(track_height+30)+'px;left:0px;right:0px;" class="container_track">' +
455 '</div>'+
456 '<div id="' + timeline_id + '_playline" class="mv_story_playline">' +
457 '<div class="mv_playline_top"/>'+
458 '</div>'+
459 '</div>'
460 );
461 top_pos+=track_height+20;
462 }
463 }
464 },
465 //once playlist is ready continue
466 checkReadyPlObj:function(){
467 //set up pointers from sequencer to pl obj
468 this.plObj = $j('#'+ this.plObj_id ).get(0);
469 //& from seq obj to sequencer
470 this.plObj.pSeq = this;
471
472 if( this.plObj )
473 if( ! this.plObj.loading )
474 this.plReadyInit();
475
476 //else keep checking for the playlist to be ready
477 if( this.plObj.loading ){
478 if(this.plReadyTimeout==200){
479 js_error('error playlist never ready');
480 }else{
481 this.plReadyTimeout++;
482 setTimeout(this.instance_name +'.checkReadyPlObj()', 25);
483 }
484 }
485 },
486 getLocalApiUrl:function(){
487 return this.plObj.interface_url;
488 },
489 plReadyInit:function(){
490 var _this = this;
491 js_log('plReadyInit');
492 js_log( this.plObj );
493 //give the playlist a pointer to its parent seq:
494 this.plObj['seqObj'] = this;
495
496 //update playlist (if its empty right now)
497 if(this.plObj.getClipCount()==0){
498 $j('#'+this.plObj_id).html('empty playlist');
499 }
500
501 //propagate the edit tokens
502 //if on an edit page just grab from the form:
503 this.sequenceEditToken = $j('input[wpEditToken]').val();
504
505 if(typeof this.sequenceEditToken == 'undefined' && this.getLocalApiUrl()!=null){
506 get_mw_token(_this.plObj.mTitle, _this.getLocalApiUrl(),
507 function(token){
508 if(token){
509 _this.sequenceEditToken = token;
510 _this.updateSeqSaveButtons();
511 }
512 }
513 );
514 get_mw_token(_this.plObj.mTalk, _this.getLocalApiUrl(),
515 function(token){
516 _this.clipboardEditToken = token;
517 }
518 );
519 //also grab permissions for sending clipboard commands to the server
520
521 //(calling the sequencer inline) try and get edit token via api call:
522 //(somewhat fragile way to get at the api... should move to config
523 /*var token_url = this.plObj.interface_url.replace(/index\.php/, 'api.php');
524 token_url += '?action=query&format=xml&prop=info&intoken=edit&titles=';
525 $j.ajax({
526 type: "GET",
527 url: token_url + this_seq.plObj.mTitle,
528 success:function(data){
529 var pageElm = data.getElementsByTagName('page')[0];
530 if( $j(pageElm).attr('edittoken') ){
531 this_seq.sequenceEditToken = $j(pageElm).attr('edittoken');
532 }
533
534 }
535 });*/
536 //also grab permissions for sending clipboard commands to the server
537 /*$j.ajax({
538 type:"GET",
539 url: token_url + this_seq.plObj.mTalk,
540 success:function(data){
541 var pageElm = data.getElementsByTagName('page')[0];
542 if( $j(pageElm).attr('edittoken') ){
543 this_seq.clipboardEditToken = $j(pageElm).attr('edittoken');
544 }
545 }
546 });*/
547 }
548
549
550 //render the menu tabs::
551 var item_containers ='';
552 var inx = 0;
553 var selected_tab = 0;
554 var tabc ='';
555 var o='<div id="seq_menu" style="width:100%;height:100%">';
556 o+='<ul>';
557 for(var tab_id in this.menu_items){
558 menu_item = this.menu_items[tab_id];
559 menu_item.inx = inx;
560 if(menu_item.d){
561 selected_tab=inx;
562 _this.disp_menu_item =tab_id;
563 }
564
565 o+='<li>' +
566 '<a id="mv_menu_item_'+tab_id+'" href="#' + tab_id + '_ic">'+gM('menu_' + tab_id ) + '</a>' +
567 '</li>';
568
569 tabc += '<div id="' + tab_id + '_ic" style="overflow:auto;height:268px;" >';
570 tabc += (menu_item.html) ? menu_item.html : '<h3>' + gM('menu_'+tab_id) + '</h3>';
571 tabc +='</div>';
572 inx++;
573 };
574 o+='</ul>';
575 o+=tabc;
576 $j('#'+this.sequence_tools_id).html( o );
577
578
579 $j("#seq_menu").tabs({
580 selected:selected_tab,
581 select: function(event, ui) {
582 _this.disp( $j(ui.tab).attr('id').replace('mv_menu_item_', ''), true );
583 }
584 //add sorting
585 }).find(".ui-tabs-nav").sortable({ axis : 'x' });
586
587
588 //render the timeline
589 this.renderTimeLine();
590 this.do_refresh_timeline();
591
592 //load init content into containers
593 this.setupMenuItems();
594
595 this.doFocusBindings();
596
597 //set up key bidnings
598 $j(window).keydown(function(e){
599 js_log('pushed down on:' + e.which);
600 if( e.which == 16 )
601 _this.key_shift_down = true;
602
603 if( e.which == 17)
604 _this.key_ctrl_down = true;
605
606 if( (e.which == 67 && _this.key_ctrl_down) && !_this.inputFocus)
607 _this.copySelectedClips();
608
609 if( (e.which == 88 && _this.key_ctrl_down) && !_this.inputFocus)
610 _this.cutSelectedClips();
611
612 //paste cips on v + ctrl while not focused on a text area:
613 if( (e.which == 86 && _this.key_ctrl_down) && !_this.inputFocus)
614 _this.pasteClipBoardClips();
615
616 });
617 $j(window).keyup(function(e){
618 js_log('key up on ' + e.which);
619 //user let go of "shift" turn off multi-select
620 if( e.which == 16 )
621 _this.key_shift_down = false;
622
623 if( e.which == 17 )
624 _this.key_ctrl_down = false;
625
626 //escape key (for now deselect)
627 if( e.which == 27 )
628 _this.deselectClip();
629
630
631 //backspace or delete key while not focused on a text area:
632 if( (e.which == 8 || e.which == 46) && !_this.inputFocus)
633 _this.removeSelectedClips();
634 });
635 },
636 //check all nodes for focus
637 //@@todo it would probably be faster to search a given subnode instead of all text
638 doFocusBindings:function(){
639 var _this = this;
640 //if an input or text area has focus disable delete key binding
641 $j("input,textarea").focus(function () {
642 js_log("inputFocus:true");
643 _this.inputFocus = true;
644 });
645 $j("input,textarea").blur( function () {
646 js_log("inputFocus:blur");
647 _this.inputFocus = false;
648 })
649 },
650 update_tl_hook:function(jh_time_ms){
651 //put into seconds scale:
652 var jh_time_sec_float = jh_time_ms/1000;
653 //render playline at given time
654 //js_log('tl scale: '+this.timeline_scale);
655 $j('#'+this.timeline_id+'_playline').css('left', Math.round(jh_time_sec_float/this.timeline_scale)+'px' );
656 //js_log('at time:'+ jh_time_sec + ' px:'+ Math.round(jh_time_sec_float/this.timeline_scale));
657 },
658 /*returns a xml or json representation of the current sequence */
659 getSeqOutputJSON:function(){
660 js_log('json output:');
661 },
662 getSeqOutputHLRDXML:function(){
663 var o='<sequence_hlrd>' +"\n";
664 o+="\t<head>\n";
665 //get transitions
666 for(var i in this.plObj.transitions){
667 if( this.plObj.transitions[i] ){
668 var tObj = this.plObj.transitions[i].getAttributeObj();
669 o+="\t\t<transition ";
670 for(var j in tObj){
671 o+=' '+j+'="' + tObj[j] + '"\n\t\t';
672 }
673 o+='/>'+"\n"; //transitions don't have children
674 }
675 }
676 o+="\t</head>\n";
677
678 //get clips
679 o+="\t<body>\n";
680 //output each track:
681 for(var i in this.plObj.tracks){
682 var curTrack = this.plObj.tracks[i];
683 o+="\t<seq";
684 var tAttr = curTrack.getAttributeObj();
685 for(var j in tAttr){
686 o+=' '+j+'="' + tAttr[j] + '"\n\t\t\t';
687 }
688 o+=">\n";
689 for( var k in curTrack.clips ){
690 var curClip = curTrack.clips[k];
691 o+="\t\t<ref ";
692 var cAttr = curClip.getAttributeObj();
693 var lt = '';
694 for(var j in cAttr){
695 var val = (j=='transIn' || j=='transOut') ? cAttr[j].id : cAttr[j];
696 o+=lt + j+'="' + val + '"';
697 lt ="\n\t\t";
698 }
699 o+=">\n" //close the clip
700 for(var pName in curClip.params){
701 var pVal = curClip.params[pName];
702 o+="\t\t\t" + '<param name="'+ pName + '">' + pVal + '</param>' + "\n";
703 }
704 o+="\t\t</ref>\n\n";
705 }
706 o+="\n</seq>\n";
707 }
708 o+="\t</body>\n";
709 //close the tag
710 o+='</sequence_hlrd>';
711
712 return o;
713 },
714 editClip:function(track_inx, clip_inx){
715 var cObj = this.plObj.tracks[ track_inx ].clips[ clip_inx ];
716 this.doEditClip( cObj );
717 },
718 doEditTransitionSelectedClip:function(){
719 var _this = this;
720 js_log("f:doEditTransitionSelectedClip:" + $j('.mv_selected_clip').length);
721 if( $j('.mv_selected_clip').length == 1){
722 _this.doEditTransition( _this.getClipFromSeqID( $j('.mv_selected_clip').parent().attr('id') ) );
723 }else if( $j('.mv_selected_clip').length === 0){
724 //no clip selected warning:
725 $j('#transition_ic').html( gM('no_selected_resource') );
726 }else{
727 //multiple clip selected warning:
728 $j('#transition_ic').html( gM('error_edit_multiple') );
729 }
730 },
731 doEditSelectedClip:function(){
732 js_log("f:doEditSelectedClip:");
733 //and only one clip selected
734 if( $j('.mv_selected_clip').length == 1){
735 this.doEditClip( this.getClipFromSeqID( $j('.mv_selected_clip').parent().attr('id') ) );
736 }else if( $j('.mv_selected_clip').length === 0){
737 //no clip selected warning:
738 $j('#clipedit_ic').html( gM('no_selected_resource') );
739 }else{
740 //multiple clip selected warning:
741 $j('#clipedit_ic').html( gM('error_edit_multiple') );
742 }
743 },
744 doEditTransition:function( cObj ){
745 js_log("sequence:doEditTransition");
746 var _this = this;
747 mv_get_loading_img( '#transitions_ic' );
748 mvJsLoader.doLoad([
749 '$j.fn.ColorPicker',
750 'mvTimedEffectsEdit'
751 ],function(){
752 //no idea why this works / is needed.
753 var localSeqRef = _this;
754 _this.myEffectEdit = new mvTimedEffectsEdit({
755 'rObj' : cObj,
756 'control_ct' : 'transition_ic',
757 'pSeq' : localSeqRef
758 });
759 })
760 },
761 //updates the clip details div if edit resource is set
762 doEditClip:function( cObj){
763 var _this = this;
764
765 //set default edit action (maybe edit_action can be sent via by context click)
766 var edit_action = 'fileopts';
767
768 mv_get_loading_img( '#clipedit_ic' );
769 //load the clipEdit library if not already loaded:
770 mvJsLoader.doLoad( [
771 'mvClipEdit'
772 ], function(){
773 _this.myClipEditor = {};
774 //setup the cliploader
775 _this.myClipEditor = new mvClipEdit({
776 'rObj' : cObj,
777 'control_ct' : 'clipedit_ic',
778 'clip_disp_ct' : cObj.id,
779 'edit_action' : edit_action,
780 'p_seqObj' : _this,
781 'profile' : 'sequence'
782 });
783 });
784 },
785 //save new clip segment
786 saveClipEdit:function(){
787 //saves the clip updates
788 },
789 closeModEditor:function(){
790 //unset the sequencer
791 _global['mvSeq'] = null;
792 $j(this.target_sequence_container + ',.ui-widget-overlay').remove();
793 },
794 pasteClipBoardClips:function(){
795 js_log('f:pasteClipBoardClips');
796 //@@todo query the server for updated clipboard
797 //paste before the "current clip"
798 this.addClips( this.clipboard, this.plObj.cur_clip.order );
799 },
800 copySelectedClips:function(){
801 var this_seq = this;
802 //set all the selected clips
803 this.clipboard = new Array();
804 $j('.mv_selected_clip').each(function(){
805 //add each clip to the clip board:
806 var cur_clip = this_seq.getClipFromSeqID( $j(this).parent().attr('id') );
807 this_seq.clipboard.push( cur_clip.getAttributeObj() );
808 });
809 //upload clipboard to the server (if possible)
810 if( parseUri( document.URL ).host != parseUri( this_seq.plObj.interface_url ).host ){
811 js_log('error: presently we can\'t copy clips across domains');
812 }else{
813 //@@we need a api entry point to store a "clipboard"
814 if( this_seq.clipboardEditToken && this_seq.plObj.interface_url ){
815 var req_url = this_seq.plObj.interface_url.replace(/api.php/, 'index.php') + '?action=ajax&rs=mv_seqtool_clipboard&rsargs[]=copy';
816 $j.ajax({
817 type: "POST",
818 url:req_url,
819 data: $j.param( {
820 "clipboard_data": $j.toJSON( this_seq.clipboard ),
821 "clipboardEditToken": this_seq.clipboardEditToken
822 }),
823 success:function(data){
824 //callback( data );
825 js_log('did clipboard push ' + $j.toJSON( this_seq.clipboard ) );
826 }
827 });
828 }else{
829 js_log('error: no clipboardEditToken to uplaod clipboard to server');
830 }
831 }
832 },
833 cutSelectedClips:function(){
834 this.copySelectedClips();
835 this.removeSelectedClips();
836 },
837 removeSelectedClips:function(){
838 var remove_clip_ary=new Array();
839 //remove selected clips from display
840 $j('.container_track .mv_selected_clip').each(function(){
841 //grab the track index from the id (assumes track_#_clip_#
842 remove_clip_ary.push ( $j(this).parent().attr('id').replace('track_','').replace('clip_','').split('_') );
843 });
844 if(remove_clip_ary.length !=0 )
845 this.removeClips(remove_clip_ary);
846
847 //doEdit selected clips (updated selected resource)
848 //@@todo refresh menu of current
849 this.doEditSelectedClip();
850 },
851 addClip:function( clip, before_clip_pos, track_inx){
852 this.addClips([clip], before_clip_pos, track_inx)
853 },
854 //add a single or set of clips
855 //to a given position and track_inx
856 addClips:function( clipSet, before_clip_pos, track_inx){
857 this_seq = this;
858
859 if(!track_inx)
860 track_inx = this.plObj.default_track.inx;
861
862 if(!before_clip_pos)
863 before_clip_pos= this.plObj.default_track.getClipCount();
864
865 js_log("seq: add clip: at: "+ before_clip_pos + ' in track: ' + track_inx);
866 var cur_pos = before_clip_pos;
867
868 $j.each(clipSet, function(inx, clipInitDom){
869 var mediaElement = document.createElement('ref');
870 for(var i in clipInitDom){
871 js_log("set: " + i + ' to ' + clipInitDom[i]);
872 if(i!='id')
873 $j(mediaElement).attr(i, clipInitDom[i]);
874 }
875 if( this_seq.plObj.tryAddMedia( mediaElement, cur_pos, track_inx ) )
876 cur_pos++;
877 });
878 //debugger;
879 this.do_refresh_timeline();
880 },
881 removeClips:function( remove_clip_ary ){
882 var this_seq = this;
883 var jselect = coma ='';
884 js_log('clip count before removal : ' + this_seq.plObj.default_track.clips.length + ' should remove ' + remove_clip_ary.length );
885 var afected_tracks = new Array();
886 //add order to track_clip before we start removing:
887 $j.each( remove_clip_ary, function(inx, track_clip){
888 remove_clip_ary[inx]['order'] = this_seq.plObj.tracks[ track_clip[0] ].clips[ track_clip[1] ].order;
889 });
890 $j.each( remove_clip_ary, function(inx, track_clip){
891 var track_inx = track_clip[0];
892 var clip_inx = track_clip[1];
893 var clip_rm_order = track_clip['order'];
894 js_log('remove t:' + track_inx + ' c:'+ clip_inx + ' id:' +' #track_'+track_inx+'_clip_'+clip_inx + ' order:' + clip_rm_order);
895 //remove the clips from the base tracks
896 for(var i in this_seq.plObj.tracks[ track_inx ].clips){
897 cur_clip = this_seq.plObj.tracks[ track_inx ].clips[i]
898 if(cur_clip.order == clip_rm_order){
899 this_seq.plObj.tracks[ track_clip[0] ].clips.splice( i, 1);
900 }
901 }
902 //add track to affected track list:
903 afected_tracks[ track_inx ]=true;
904 jselect += coma + '#track_' +track_inx + '_clip_' + clip_inx;
905 coma=',';
906 });
907 //update/ reorder:
908 $j.each(afected_tracks, function(track_inx, affected){
909 this_seq.plObj.tracks[track_inx].reOrderClips();
910 });
911
912 js_log('clip count after removal : ' + this_seq.plObj.default_track.clips.length);
913 //animate the removal (@@todo should be able to call the resulting fadeOut only once without a flag)
914 var done_with_refresh=false;
915 $j(jselect).fadeOut("slow", function(){
916 if( !done_with_refresh )
917 this_seq.do_refresh_timeline();
918 done_with_refresh=true;
919 }).empty(); //empty to remove any persistent bindings
920 },
921 doEdit:function( editObj ){
922 //add the current editObj to the edit stack (should allow for "undo")
923 this.edit_stack.push( editObj );
924 //make the adjustments
925 this.makeAdjustment( editObj );
926 },
927 /*
928 * takes adjust ment object with options:
929 * track_inx, clip_inx, start, end delta
930 */
931 makeAdjustment:function(e){
932 switch(e.type){
933 case 'resize_start':
934 this.plObj.tracks[e.track_inx].clips[e.clip_inx].doAdjust('start', e.delta);
935 break;
936 case 'resize_end':
937 this.plObj.tracks[e.track_inx].clips[e.clip_inx].doAdjust('end', e.delta);
938 break;
939 }
940 js_log('re render: '+e.track_inx);
941 //refresh the playlist after adjustment
942 this.do_refresh_timeline();
943 },
944 //@@todo set up key bindings for undo
945 undoEdit:function(){
946 var editObj = this.edit_stack.pop();
947 //invert the delta
948
949 },
950 exc_track:function(inx,req){
951 this_seq = this;
952 if(req=='close'){
953 $j('#mv_exc_'+inx).attr('href', 'javascript:'+this.instance_name+'.exc_track('+inx+',\'open\')');
954 $j('#mv_exc_'+inx + ' > img').attr('src',mv_embed_path + 'images/closed.png');
955 $j('#track_cnt_'+inx+',#container_track_'+inx).animate({height:this.track_text_height}, "slow",'',
956 function(){
957 this_seq.plObj.tracks[inx].disp_mode='text';
958 this_seq.render_tracks( inx );
959 });
960 }else if(req=='open'){
961 $j('#mv_exc_'+inx).attr('href', 'javascript:'+this.instance_name+'.exc_track('+inx+',\'close\')');
962 $j('#mv_exc_'+inx + ' > img').attr('src',mv_embed_path + 'images/opened.png');
963 $j('#track_cnt_'+inx+',#container_track_'+inx).animate({height:this.track_thumb_height}, "slow",'',
964 function(){
965 this_seq.plObj.tracks[inx].disp_mode='timeline_thumb';
966 this_seq.render_tracks(inx);
967 });
968
969 }
970 },
971 //adds tracks
972 add_track:function(inx, track){
973
974 },
975 //toggle cut mode (change icon to cut)
976 cut_mode:function(){
977 js_log('do cut mode');
978 //add cut layer ontop of clips
979 },
980 doAdvancedTl:function(){
981 this.timeline_mode='time';
982 this.renderTimeLine();
983 this.do_refresh_timeline();
984 return false;
985 },
986 doSimpleTl:function(){
987 this.timeline_mode='storyboard';
988 this.renderTimeLine();
989 this.do_refresh_timeline();
990 return false;
991 },
992 //renders updates the timeline based on the current scale
993 render_tracks:function( track_inx ){
994 js_log("f::render track: "+track_inx);
995 var this_seq = this;
996 //inject the tracks into the timeline (if not already there)
997 for(var track_id in this.plObj.tracks){
998 if( track_inx==track_id || typeof track_inx=='undefined' ){
999 //empty out the track container:
1000 //$j('#container_track_'+track_id).empty();
1001 var track_html=droppable_html='';
1002 //set up per track vars:
1003 var track = this.plObj.tracks[track_id];
1004 var cur_clip_time=0;
1005
1006 //set up some constants for timeline_mode == storyboard:
1007 if(this.timeline_mode == 'storyboard'){
1008 var frame_width = Math.round( this.track_clipThumb_height * 1.3333333 );
1009 var container_width = frame_width+60;
1010 }
1011
1012 //for each clip:
1013 for(var j in track.clips){
1014 clip = track.clips[j];
1015 //var img = clip.getClipImg('icon');
1016 if( this.timeline_mode == 'storyboard' ){
1017 clip.left_px = j*container_width;
1018 clip.width_px = container_width;
1019 var base_id = 'track_'+track_id+'_clip_'+j;
1020 track_html += '<span id="'+base_id+'" '+
1021 'class="mv_storyboard_container mv_clip_drag" '+
1022 'style="'+
1023 'left:'+clip.left_px+'px;'+
1024 'height:' + (this.track_clipThumb_height+30) + 'px;' +
1025 'width:'+(container_width)+'px;" >';
1026 track_html += clip.embed.renderTimelineThumbnail({
1027 'width' : frame_width,
1028 'thumb_class' : 'mv_clip_thumb',
1029 'height':this.track_clipThumb_height,
1030 'time':0
1031 });
1032 //render out edit button
1033 /*track_html+='<div class="clip_edit_button clip_edit_base clip_control"/>';*/
1034
1035 //check if the clip has transitions
1036 var imgHtml = '';
1037 var imsrc = '';
1038 var cat = clip;
1039 if(clip.transIn || clip.transOut){
1040 if( clip.transIn && clip.transIn.getIconSrc )
1041 imsrc = clip.transIn.getIconSrc();
1042 //@@todo put transOut somewhere else
1043 if( clip.transOut && clip.transOut.getIconSrc )
1044 imsrc = clip.transOut.getIconSrc();
1045 if(imsrc != '')
1046 imgHtml = '<img style="width:32px;height:32px" src="' + imsrc + '" />';
1047 }
1048 //render out transition edit box
1049 track_html += '<div id="tb_' + base_id + '" class="clip_trans_box">' +
1050 imgHtml +
1051 '</div>'
1052
1053 //render out adjustment text
1054 /*track_html+='<div id="' + base_id + '_adj' + '" class="mv_adj_text" style="top:'+ (this.track_clipThumb_height+10 )+'px;">'+
1055 '<span class="mv_adjust_click" onClick="'+this.instance_name+'.adjClipDur(' + track_id + ',' + j + ',\'-\')" /> - </span>'+
1056 ( (clip.getDuration() > 60 )? seconds2npt(clip.getDuration()): clip.getDuration() ) +
1057 '<span class="mv_adjust_click" onClick="'+this.instance_name+'.adjClipDur(' + track_id + ',' + j + ',\'+\')" /> + </span>'+
1058 '</div>';
1059 */
1060 track_html+='</span>';
1061
1062 }
1063 //do timeline_mode rendering:
1064 if(this.timeline_mode == 'time'){
1065 clip.left_px = Math.round( cur_clip_time/this.timeline_scale);
1066 clip.width_px = Math.round( Math.round( clip.getDuration() )/this.timeline_scale);
1067 clip.height_px = 60;
1068 js_log('at time:' + cur_clip_time + ' left: ' +clip.left_px + ' clip dur: ' + Math.round( clip.getDuration() ) + ' clip width:' + clip.width_px);
1069
1070 //for every clip_width pixle output image
1071 if(track.disp_mode=='timeline_thumb'){
1072 track_html+='<span id="track_'+track_id+'_clip_'+j+'" '+
1073 'class="mv_tl_clip mv_clip_drag" '+
1074 'style="'+
1075 'left:' + clip.left_px + 'px;'+
1076 'width:'+ clip.width_px + 'px;'+
1077 'height:'+ clip.height_px + 'px" >';
1078 track_html+= this.render_clip_frames( clip );
1079 }else if(track.disp_mode=='text'){
1080 //'+left_px+
1081 track_html+='<span id="track_'+track_id+'_clip_'+j+'" style="left:'+clip.left_px+'px;'+
1082 'width:'+clip.width_px+'px;background:'+clip.getColor()+
1083 '" class="mv_time_clip_text mv_clip_drag">'+clip.title;
1084 }
1085 //add in per clip controls
1086 track_html+='<div title="'+gM('clip_in')+' '+clip.embed.start_ntp+'" class="ui-resizable-w ui-resizable-handle" style="width: 16px; height: 16px; left: 0px; top: 2px;background:url(\''+mv_embed_path+'images/application_side_contract.png\');" ></div>'+"\n";
1087 track_html+='<div title="'+gM('clip_out')+' '+clip.embed.end_ntp+'" class="ui-resizable-e ui-resizable-handle" style="width: 16px; height: 16px; right: 0px; top: 2px;background:url(\''+mv_embed_path+'images/application_side_expand.png\');" ></div>'+"\n";
1088 track_html+='<div title="'+gM('rmclip')+'" onClick="'+this.instance_name + '.removeClips(new Array([' + track_id + ',' + j + ']))" style="position:absolute;cursor:pointer;width: 16px; height: 16px; left: 0px; bottom:2px;background:url(\''+mv_embed_path+'images/delete.png\');"></div>'+"\n";
1089 track_html+='<span style="display:none;" class="mv_clip_stats"></span>';
1090
1091 track_html+='</span>';
1092 //droppable_html+='<div id="dropBefore_'+i+'_c_'+j+'" class="mv_droppable" style="height:'+this.track_thumb_height+'px;left:'+clip.left_px+'px;width:'+Math.round(clip.width_px/2)+'px"></div>';
1093 //droppable_html+='<div id="dropAfter_'+i+'_c_'+j+'" class="mv_droppable" style="height:'+this.track_thumb_height+'px;left:'+(clip.left_px+Math.round(clip.width_px/2))+'px;width:'+(clip.width_px/2)+'px"></div>';
1094 cur_clip_time+=Math.round( clip.getDuration() ); //increment cur_clip_time
1095 }
1096
1097 }
1098
1099 //js_log("new htmL for track i: "+track_id + ' html:'+track_html);
1100 $j('#container_track_'+track_id).html( track_html );
1101
1102 //apply transition click action
1103 $j('.clip_trans_box').click(function(){
1104 if($j(this).hasClass('mv_selected_transition')){
1105 $j(this).removeClass('mv_selected_transition');
1106 this_seq.deselectClip( $j(this).siblings('.mv_clip_thumb').get(0) );
1107 }else{
1108 //deselect others
1109 this_seq.deselectClip();
1110 $j('.clip_trans_box').removeClass('mv_selected_transition');
1111 $j(this).addClass("mv_selected_transition");
1112 $j(this).siblings('.mv_clip_thumb').addClass("mv_selected_clip");
1113 var sClipObj = this_seq.getClipFromSeqID( $j(this).parent().attr('id') );
1114 //jump to the current clip
1115 this_seq.plObj.updateCurrentClip( sClipObj );
1116 //display the transition edit tab:
1117 this_seq.disp( 'transition' );
1118 }
1119 });
1120
1121 //apply edit button mouse over effect:
1122 $j('.clip_edit_button').hover(function(){
1123 $j(this).removeClass("clip_edit_base").addClass("clip_edit_over");
1124 },function(){
1125 $j(this).removeClass("clip_edit_over").addClass("clip_edit_base");
1126 }).click(function(){
1127 //deselect everything else:
1128 $j('.mv_selected_clip').each(function(inx, selected_clip){
1129 this_seq.deselectClip( this );
1130 });
1131
1132 var sClipObj = this_seq.getClipFromSeqID( $j(this).parent().attr('id') );
1133 this_seq.plObj.updateCurrentClip( sClipObj );
1134 //get the clip (siblings with mv_clip_thumb class)
1135 var cur_clip_elm = $j(this).siblings('.mv_clip_thumb');
1136 //select the clip (add mv_selected_clip if not already selected)
1137 if( ! $j( cur_clip_elm ).hasClass("mv_selected_clip") ){
1138 $j( cur_clip_elm ).addClass('mv_selected_clip');
1139 $j('#' + $j( cur_clip_elm ).parent().attr("id") + '_adj').fadeIn("fast");
1140 }
1141 //display the edit tab:
1142 this_seq.disp( 'clipedit' );
1143 //display edit dialog:
1144 this_seq.doEditClip( sClipObj );
1145 });
1146
1147 //apply onClick edit controls:
1148 $j('.mv_clip_thumb').click(function(){
1149 var cur_clip_click = this;
1150 //if not in multi select mode remove all existing selections
1151 //(except for the current click which is handled down below)
1152 js_log(' ks: ' + this_seq.key_shift_down + ' ctrl_down:' +this_seq.key_ctrl_down);
1153 if( ! this_seq.key_shift_down && ! this_seq.key_ctrl_down){
1154 $j('.mv_selected_clip').each(function(inx, selected_clip){
1155 if( $j(this).parent().attr('id') != $j(cur_clip_click).parent().attr('id')
1156 || ( $j('.mv_selected_clip').length > 1 ) ){
1157 this_seq.deselectClip( this );
1158 }
1159 });
1160 }
1161
1162 //jump to clip time
1163 var sClipObj = this_seq.getClipFromSeqID( $j(this).parent().attr('id') );
1164 this_seq.plObj.updateCurrentClip( sClipObj );
1165 if( $j(this).hasClass("mv_selected_clip") ){
1166 $j(this).removeClass("mv_selected_clip");
1167 $j('#' + $j(this).parent().attr("id") + '_adj').fadeOut("fast");
1168 }else{
1169 $j(this).addClass('mv_selected_clip');
1170 $j('#' + $j(this).parent().attr("id") + '_adj').fadeIn("fast");
1171 }
1172 //if shift select is down select the in-between clips
1173 if( this_seq.key_shift_down ){
1174 //get the min max of current selection (within the current track)
1175 var max_order = 0;
1176 var min_order = 999999999;
1177 $j('.mv_selected_clip').each(function(){
1178 var cur_clip = this_seq.getClipFromSeqID( $j(this).parent().attr('id') );
1179 //get min max
1180 if(cur_clip.order < min_order)
1181 min_order = cur_clip.order;
1182 if(cur_clip.order > max_order)
1183 max_order = cur_clip.order;
1184 });
1185 //select all non-selected between max or min
1186 js_log('sOrder: ' + sClipObj.order + ' min:' + min_order + ' max:'+ max_order);
1187 if( sClipObj.order <= min_order ){
1188 for( var i = sClipObj.order; i <= max_order; i++ ){
1189 $j('#track_' + track_id + '_clip_' + i + ' > .mv_clip_thumb' ).addClass('mv_selected_clip');
1190 }
1191 }
1192 if( sClipObj.order >= max_order ){
1193 for( var i =min_order; i <= max_order; i++ ){
1194 $j('#track_' + track_id + '_clip_' + i + ' > .mv_clip_thumb' ).addClass('mv_selected_clip');
1195 }
1196 }
1197 }
1198 this_seq.doEditSelectedClip();
1199 });
1200 //add in control for time based display
1201 //debugger;
1202 if(this.timeline_mode == 'time'){
1203 $j('.ui-resizable-handle').mousedown( function(){
1204 js_log('hid: ' + $j(this).attr('class'));
1205 this_seq.resize_mode = ($j(this).attr('class').indexOf('ui-resizable-e')!=-1)?
1206 'resize_end':'resize_start';
1207 });
1208 }
1209 var insert_key='na';
1210 // drag hooks:
1211 //@@todo support multiple clips
1212 for(var j in track.clips){
1213 $j('#track_'+track_id+'_clip_'+j).draggable({
1214 axis:'x',
1215 containment:'#container_track_'+track_id,
1216 opacity:50,
1217 handle: ":not(.clip_control)",
1218 scroll:true,
1219 drag:function(e, ui){
1220 //debugger;
1221 insert_key = this_seq.clipDragUpdate(ui, this);
1222 },
1223 start:function(e,ui){
1224 js_log('start drag:' + this.id);
1225 //make sure we are ontop
1226 $j(this).css({top:'0px',zindex:10});
1227 },
1228 stop:function(e, ui){
1229 $j(this).css({top:'0px',zindex:0});
1230
1231 var id_parts = this.id.split('_');
1232 var track_inx = id_parts[1];
1233 var clip_inx = id_parts[3];
1234 var clips = this_seq.plObj.tracks[track_inx].clips;
1235 var cur_drag_clip = clips[clip_inx];
1236
1237 if(insert_key!='na' && insert_key!='end' ){
1238 cur_drag_clip.order=insert_key-.5;
1239 }else if (insert_key=='end'){
1240 cur_drag_clip.order=clips.length;
1241 }
1242 //reorder array based on new order
1243 clips.sort(sort_func);
1244 function sort_func(a, b){
1245 return a.order - b.order;
1246 }
1247 //assign keys back to order:
1248 this_seq.plObj.tracks[track_inx].reOrderClips();
1249 //redraw:
1250 this_seq.do_refresh_timeline();
1251 }
1252 });
1253 //add in resize hook if in time mode:
1254 if(this.timeline_mode == 'time'){
1255 $j('#track_'+track_id+'_clip_'+j).resizable({
1256 minWidth:10,
1257 maxWidth:6000,
1258 start: function(e,ui) {
1259 //set border to red
1260 $j(this).css({'border':'solid thin red'});
1261 //fade In Time stats (end or start based on handle)
1262 //dragging east (adjusting end time)
1263 js_log( 'append to: '+ this.id);
1264 $j('#' + this.id + ' > .mv_clip_stats').fadeIn("fast");
1265 },
1266 stop: function(e,ui) {
1267 js_log('stop resize');
1268 //restore border
1269 $j(this).css('border', 'solid thin white');
1270 //remove stats
1271 var clip_drag = this;
1272 $j('#'+this.id+' > .mv_clip_stats').fadeOut("fast",function(){
1273 var id_parts = clip_drag.id.split('_');
1274 var track_inx = id_parts[1];
1275 var clip_inx = id_parts[3];
1276 //update clip
1277 this_seq.doEdit({
1278 type:this_seq.resize_mode,
1279 delta:this_seq.edit_delta,
1280 track_inx:track_inx,
1281 clip_inx:clip_inx})
1282 });
1283 },
1284 resize: function(e,ui) {
1285 //update time stats & render images:
1286 this_seq.update_clip_resize(this);
1287 }
1288 });
1289 }
1290 }
1291 $j('#container_track_'+track_id).width(Math.round( this.timeline_duration / this.timeline_scale));
1292 }
1293 //debugger;
1294 }
1295 },
1296 clipDragUpdate:function( ui, clipElm){
1297 var this_seq = this;
1298
1299 var insert_key='na';
1300 //animate re-arrange by left position:
1301 //js_log('left: '+ui.position.left);
1302 //locate clip (based on clip duration not animate)
1303 var id_parts = clipElm.id.split('_');
1304 var track_inx = id_parts[1];
1305 var clip_inx = id_parts[3];
1306 var clips = this_seq.plObj.tracks[track_inx].clips;
1307 var cur_drag_clip = clips[clip_inx];
1308 var return_org = true;
1309 $j(clipElm).css('zindex',10);
1310 //find out where we are inserting and set left border to solid red thick
1311 for(var k in clips){
1312 if( ui.position.left > clips[k].left_px &&
1313 ui.position.left < (clips[k].left_px + clips[k].width_px)){
1314 if(clip_inx!=k){
1315 //also make sure we are not where we started
1316 if(k-1!=clip_inx){
1317 $j('#track_'+track_inx+'_clip_'+k).css('border-left', 'solid thick red');
1318 insert_key=k;
1319 }else{
1320 insert_key='na';
1321 }
1322 }else{
1323 insert_key='na';
1324 }
1325 }else{
1326 $j('#track_'+track_inx+'_clip_'+k).css('border-left', 'solid thin white');
1327 }
1328 }
1329 //if greater than the last k insert after
1330 if(ui.position.left > (clips[k].left_px + clips[k].width_px) &&
1331 k!=clip_inx ){
1332 $j('#track_'+track_inx+'_clip_'+k).css('border-right', 'solid thick red');
1333 insert_key='end';
1334 }else{
1335 $j('#track_'+track_inx+'_clip_'+k).css('border-right', 'solid thin white');
1336 }
1337 return insert_key;
1338 },
1339 deselectClip:function( clipElm ){
1340 if(!clipElm){
1341 $j('.mv_selected_clip').removeClass("mv_selected_clip");
1342 }else{
1343 $j(clipElm).removeClass("mv_selected_clip");
1344 //make sure the transition sibling is removed:
1345 $j(clipElm).siblings('.clip_trans_box').removeClass( 'mv_selected_transition' );
1346 $j('#' + $j(clipElm).parent().attr("id") + '_adj').fadeOut("fast");
1347 }
1348 },
1349 getClipFromSeqID:function( clip_seq_id ){
1350 js_log('get id from: ' + clip_seq_id);
1351 var ct = clip_seq_id.replace('track_','').replace('clip_','').split('_');
1352 return this.plObj.tracks[ ct[0] ].clips[ ct[1] ];
1353 },
1354 //renders clip frames
1355 render_clip_frames:function(clip, frame_offset_count){
1356 js_log('f:render_clip_frames: ' + clip.id + ' foc:' + frame_offset_count);
1357 var clip_frames_html='';
1358 var frame_width = Math.round(this.track_thumb_height*1.3333333);
1359
1360 var pint = (frame_offset_count==null)?0:frame_offset_count*frame_width;
1361
1362 //js_log("pinit: "+ pint+ ' < '+clip.width_px+' ++'+frame_width);
1363 for(var p=pint;p<clip.width_px;p+=frame_width){
1364 var clip_time = (p==0)?0:Math.round(p*this.timeline_scale);
1365 js_log('rendering clip frames: p:' +p+' pts:'+ (p*this.timeline_scale)+' time:' + clip_time + ' height:'+this.track_thumb_height);
1366 clip_frames_html+=clip.embed.renderTimelineThumbnail({
1367 'width': frame_width,
1368 'thumb_class':'mv_tl_thumb',
1369 'height': this.track_thumb_height,
1370 'size' : "icon", //set size to "icon" preset
1371 'time': clip_time
1372 });
1373 }
1374 js_log('render_clip_frames:'+clip_frames_html);
1375 return clip_frames_html;
1376 },
1377 update_clip_resize:function(clip_element){
1378 //js_log('update_clip_resize');
1379 var this_seq = this;
1380 var id_parts = clip_element.id.split('_');
1381 track_inx = id_parts[1];
1382 clip_inx = id_parts[3];
1383 //set clip:
1384 var clip = this.plObj.tracks[ track_inx ].clips[ clip_inx ];
1385 var clip_desc ='';
1386 //would be nice if getting the width did not flicker the border
1387 //@@todo do a work around e in resize function has some screen based offset values
1388 clip.width_px = $j(clip_element).width();
1389 var width_dif = clip.width_px - Math.round( Math.round( clip.getDuration() )/this.timeline_scale);
1390 //var left_px = $j(clip_element).css('left');
1391
1392 var new_clip_dur = Math.round( clip.width_px*this.timeline_scale );
1393 var clip_dif = (new_clip_dur - clip.getDuration() );
1394 var clip_dif_str = (clip_dif >0)?'+'+clip_dif:clip_dif;
1395 //set the edit global delta
1396 this.edit_delta = clip_dif;
1397
1398 //get new length:
1399 clip_desc+='length: ' + seconds2npt(new_clip_dur) +'('+clip_dif_str+')';
1400 if(this_seq.resize_mode=='resize_end'){
1401 //expanding right
1402 var new_end = seconds2npt(npt2seconds(clip.embed.end_ntp)+clip_dif);
1403 clip_desc+='<br>end time: ' + new_end;
1404 //also shift all the other clips (after the current)
1405 //js_log("track_inx: " + track_inx + ' clip inx:'+clip_inx);
1406 //$j('#container_track_'+track_inx+' > .mv_clip_drag :gt('+clip_inx+')').each(function(){
1407 $j('#container_track_'+track_inx+' > :gt('+clip_inx+')').each(function(){
1408 var move_id_parts = this.id.split('_');
1409 var move_clip = this_seq.plObj.tracks[move_id_parts[1]].clips[move_id_parts[3]];
1410 //js_log('should move:'+ this.id);
1411 $j(this).css('left', move_clip.left_px + width_dif);
1412 });
1413 }else{
1414 //expanding left (resize_start)
1415 var new_start = seconds2npt(npt2seconds(clip.embed.start_ntp)+clip_dif);
1416 clip_desc+='<br>start time: ' + new_start;
1417 }
1418
1419 //update clip stats:
1420 $j('#'+clip_element.id+' > .mv_clip_stats').html(clip_desc);
1421 var frame_width = Math.round(this.track_thumb_height*1.3333333);
1422 //check if we need to append some images:
1423 var frame_count = $j('#'+clip_element.id+' > img').length;
1424 if(clip.width_px > (frame_count * frame_width) ){
1425 //if dragging left append
1426 js_log('width_px:'+clip.width_px+' framecount:'+frame_count+' Xcw='+(frame_count * frame_width));
1427 $j('#'+clip_element.id).append(this.render_clip_frames(clip, frame_count));
1428 }
1429 },
1430 //renders cnt_time
1431 render_playheadhead_seeker:function(){
1432 js_log('render_playheadhead_seeker');
1433 //render out time stamps and time "jump" links
1434 //first get total width
1435
1436 //remove the old one if its still there
1437 $j('#'+this.timeline_id +'_pl_control').remove();
1438 //render out a playlist clip wide and all the way to the right (only playhead and play button) (outside of timeline)
1439 $j(this.target_sequence_container).append('<div id="'+ this.timeline_id +'_pl_control"'+
1440 ' style="position:absolute;top:' + (this.plObj.height) +'px;'+
1441 'right:1px;width:'+this.plObj.width+'px;height:'+this.plObj.org_control_height+'" '+
1442 'class="videoPlayer"><div class="ui-widget ui-corner-bottom ui-state-default controls">'+
1443 this.plObj.getControlsHTML() +
1444 '</div>'+
1445 '</div>');
1446 //update time and render out clip dividers .. should be used to show load progress
1447 this.plObj.updateBaseStatus();
1448
1449 //once the controls are in the DOM add hooks:
1450 ctrlBuilder.addControlHooks(this.plObj);
1451
1452 //render out the "jump" div
1453 if(this.timeline_mode=='time'){
1454 /*$j('#'+this.timeline_id+'_head_jump').width(pixle_length);
1455 //output times every 50pixles
1456 var out='';
1457 //output time-desc every 50pixles and jump links every 10 pixles
1458 var n=0;
1459 for(i=0;i<pixle_length;i+=10){
1460 out+='<div onclick="'+this.instance_name+'.jt('+i*this.timeline_scale+');"' +
1461 ' style="z-index:2;position:absolute;left:'+i+'px;width:10px;height:20px;top:0px;"></div>';
1462 if(n==0)
1463 out+='<span style="position:absolute;left:'+i+'px;">|'+seconds2npt(Math.round(i*this.timeline_scale))+'</span>';
1464 n++;
1465 if(n==10)n=0;
1466 }*/
1467
1468 }
1469 },
1470 jt:function( jh_time ){
1471 js_log('jt:' + jh_time);
1472 var this_seq = this;
1473 this.playline_time = jh_time;
1474 js_log('time: ' + seconds2npt(jh_time) + ' ' + Math.round(jh_time/this.timeline_scale));
1475 //render playline at given time
1476 $j('#'+this.timeline_id+'_playline').css('left', Math.round(jh_time/this.timeline_scale)+'px' );
1477 cur_pl_time=0;
1478 //update the thumb with the requested time:
1479 this.plObj.updateThumbTime( jh_time );
1480 },
1481 //adjusts the current scale
1482 zoom_in:function(){
1483 this.timeline_scale = this.timeline_scale*.75;
1484 this.do_refresh_timeline();
1485 js_log('zoomed in:'+this.timeline_scale);
1486 },
1487 zoom_out:function(){
1488 this.timeline_scale = this.timeline_scale*(1+(1/3));
1489 this.do_refresh_timeline();
1490 js_log('zoom out: '+this.timeline_scale);
1491 },
1492 do_refresh_timeline:function( preserve_selection ){
1493 js_log('Sequencer:do_refresh_timeline()');
1494 //@@todo should "lock" interface while refreshing timeline
1495 var pSelClips = [];
1496 if(preserve_selection){
1497 $j('.mv_selected_clip').each(function(){
1498 pSelClips.push( $j(this).parent().attr('id') );
1499 });
1500 }
1501 //regen duration
1502 this.plObj.getDuration( true );
1503 //refresh player:
1504 this.plObj.getHTML();
1505
1506 this.render_playheadhead_seeker();
1507 this.render_tracks();
1508 this.jt(this.playline_time);
1509
1510 if(preserve_selection){
1511 for(var i=0;i < pSelClips.length; i++){
1512 $j('#' + pSelClips[i] + ' .mv_clip_thumb').addClass('mv_selected_clip');
1513 }
1514 }
1515 }
1516
1517 }
1518 /* extension to mvPlayList to support sequencer features properties */
1519 var mvSeqPlayList = function( element ){
1520 return this.init( element );
1521 }
1522 mvSeqPlayList.prototype = {
1523 init:function(element){
1524 var myPlObj = new mvPlayList(element);
1525
1526 //inherit mvClip
1527 for(var method in myPlObj){
1528 if(typeof this[method] != 'undefined' ){
1529 this[ 'parent_' + method ]=myPlObj[method];
1530 }else{
1531 this[method] = myPlObj[method];
1532 }
1533 }
1534
1535 this.org_control_height = this.pl_layout.control_height;
1536 //do specific mods:(controls and title are managed by the sequencer)
1537 this.pl_layout.title_bar_height=0;
1538 this.pl_layout.control_height=0;
1539 },
1540 setSliderValue:function( perc ){
1541 js_log('setSliderValue::'+ perc);
1542 //get the track_clipThumb_height from parent mvSequencer
1543 var frame_width = Math.round( this.pSeq.track_clipThumb_height * 1.3333333 );
1544 var container_width = frame_width+60;
1545
1546 var perc_clip = this.cur_clip.embed.currentTime / this.cur_clip.getDuration();
1547
1548 var left_px = parseInt( (this.cur_clip.order * container_width) + (frame_width*perc_clip) ) + 'px';
1549 js_log("set " + perc + ' of cur_clip: ' + this.cur_clip.order + ' lp:'+left_px);
1550
1551
1552 //update the timeline playhead and
1553 $j('#' + this.seqObj.timeline_id + '_playline').css('left', left_px);
1554
1555 //pass update request to parent:
1556 this.parent_setSliderValue( perc );
1557 },
1558 getControlsHTML:function(){
1559 //get controls from current clip add some playlist specific controls:
1560 this.cur_clip.embed.supports['prev_next'] = true;
1561 this.cur_clip.embed.supports['options'] = false;
1562 return ctrlBuilder.getControls( this.cur_clip.embed );
1563 },
1564 //override renderDisplay
1565 renderDisplay:function(){
1566 js_log('mvSequence:renderDisplay');
1567 //setup layout for title and dc_ clip container
1568 $j(this).html('<div id="dc_'+this.id+'" style="width:'+this.width+'px;' +
1569 'height:'+(this.height)+'px;position:relative;" />');
1570
1571 this.setupClipDisplay();
1572 }
1573 };