2 * mvSequencer.js Created on Oct 17, 2007
4 * All Metavid Wiki code is Released under the GPL2
5 * for more info visit http://metavid.org/wiki/Code
8 * @email mdale@wikimedia.org
10 * further developed in open source development partnership with kaltura.
11 * more info at http://kaltura.com & http://kaltura.org
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
22 "mwe-menu_clipedit" : "Edit media",
23 "mwe-menu_transition" : "Transitions and effects",
24 "mwe-menu_cliplib" : "Add media",
25 "mwe-menu_resource_overview" : "Resource overview",
26 "mwe-menu_options" : "Options",
27 "mwe-loading_timeline" : "Loading timeline <blink>...<\/blink>",
28 "mwe-loading_user_rights" : "Loading user rights <blink>...<\/blink>",
29 "mwe-no_edit_permissions" : "You do not have permissions to save changes to this sequence",
30 "mwe-edit_clip" : "Edit clip",
31 "mwe-edit_save" : "Save sequence changes",
32 "mwe-saving_wait" : "Save in progress (please wait)",
33 "mwe-save_done" : "Save complete",
34 "mwe-edit_cancel" : "Cancel sequence edit",
35 "mwe-edit_cancel_confirm" : "Are you sure you want to cancel your edit? Changes will be lost.",
36 "mwe-zoom_in" : "Zoom in",
37 "mwe-zoom_out" : "Zoom out",
38 "mwe-cut_clip" : "Cut clips",
39 "mwe-expand_track" : "Expand track",
40 "mwe-collapse_track" : "Collapse track",
41 "mwe-play_from_position" : "Play from playline position",
42 "mwe-pixle2sec" : "pixels to seconds",
43 "mwe-rmclip" : "Remove clip",
44 "mwe-clip_in" : "clip in",
45 "mwe-clip_out" : "clip out",
46 "mwe-welcome_to_sequencer" : "<h3>Welcome to the sequencer demo<\/h3> Very <b>limited<\/b> functionality right now. Not much documentation yet either.",
47 "mwe-no_selected_resource" : "<h3>No resource selected<\/h3> Select a clip to enable editing.",
48 "mwe-error_edit_multiple" : "<h3>Multiple resources selected<\/h3> Select a single clip to edit it.",
49 "mwe-editor_options" : "Editor options",
50 "mwe-editor_mode" : "Editor mode",
51 "mwe-simple_editor_desc" : "simple editor (iMovie style)",
52 "mwe-advanced_editor_desc" : "advanced editor (Final Cut style)",
53 "mwe-other_options" : "Other options",
54 "mwe-contextmenu_opt" : "Enable context menus",
55 "mwe-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 information<\/a>)."
57 //used to set default values and validate the passed init object
58 var sequencerDefaultValues
= {
60 instance_name
:'mvSeq', //for now only one instance by name mvSeq is allowed
62 target_sequence_container
:null,//text value (so that its a valid property)
63 target_form_text
: null,
65 //what is our save mode:
66 // can save to 'api' url or 'form'
69 video_container_id
:'mv_video_container',
74 sequence_tools_id
:'mv_sequence_tools',
75 timeline_id
:'mv_timeline',
79 timeline_scale
:.06, //in pixel to second ratio ie 100pixles for every ~30seconds
80 timeline_duration
:500, //default timeline length in seconds
82 track_thumb_height
:60,
85 //default timeline mode: "story" (i-movie like) or "time" (finalCut like)
86 timeline_mode
:'storyboard',
88 track_clipThumb_height
:80, // how large are the i-movie type clips
90 base_adj_duration
:.5, //default time to subtract or add when adjusting clips.
92 //default clipboard is empty:
93 clipboard
:new Array(),
94 //stores the clipboard edit token (if user has rights to edit their User page)
95 clipboardEditToken
:null,
96 //stores the sequence edit token (if user has rights to edit the current sequence)
97 sequenceEditToken
:null,
98 //the time the sequence was last touched (grabbed at time of startup)
99 sequenceTouchedTime
:null,
101 //the default config for the add media wizard
105 //Msg are all the language specific values ...
106 // (@@todo overwrite by msg values preloaded in the page)
107 //tack/clips can be pushed via json or inline playlist format
108 inline_playlist
:'null', //text value so its a valid property
109 inline_playlist_id
:'null',
112 edit_stack
:new Array(),
114 //trackObj used to payload playlist Track Object (when inline not present)
117 var mvSequencer = function(iObj
) {
118 return this.init(iObj
);
120 //set up the mvSequencer object
121 mvSequencer
.prototype = {
122 //the menu_items Object contains: default html, js setup/loader functions
127 'js': function(this_seq
){
128 this_seq
.doEditSelectedClip();
130 'click_js':function( this_seq
){
131 this_seq
.doEditSelectedClip();
136 'html' : '<h3>' + gM('mwe-menu_transition') + '</h3>',
137 'js':function(this_seq
){
138 this_seq
.doEditTransitionSelectedClip();
140 'click_js':function(this_seq
){
141 //highlight the transition of the selected clip:
142 this_seq
.doEditTransitionSelectedClip();
147 'html': gM('mwe-loading_txt'),
148 'js':function( this_seq
){
149 //load the search interface with sequence tool targets
151 'remoteSearchDriver',
152 'seqRemoteSearchDriver'
154 this_seq
.mySearch
= new seqRemoteSearchDriver(this_seq
);
155 this_seq
.mySearch
.doInitDisplay();
161 'html' : '<h3>' + gM('mwe-menu_options') + '</h3>' +
162 gM('mwe-editor_mode') + '<br> ' +
163 '<blockquote><input type="radio" value="simple_editor" name="opt_editor">' +
164 gM('mwe-simple_editor_desc') + ' </blockquote>' +
165 '<blockquote><input type="radio" value="advanced_editor" name="opt_editor">' +
166 gM('mwe-advanced_editor_desc') + ' </blockquote>'+
167 gM('mwe-other_options') + '<br>' +
168 '<blockquote><input type="checkbox" value="contextmenu_opt" name="contextmenu_opt">' +
169 gM('mwe-contextmenu_opt') + ' </blockquote>',
170 'js':function(this_seq
){
171 $j('#options_ic input[value=\'simple_editor\']').attr({
172 'checked':(this_seq
.timeline_mode
=='storyboard')?true:false
174 this_seq
.doSimpleTl();
176 $j('#options_ic input[value=\'advanced_editor\']').attr({
177 'checked':( this_seq
.timeline_mode
=='time' )?true:false
179 this_seq
.doAdvancedTl();
181 //set up the options for context menus
186 //set up initial key states:
187 key_shift_down
:false,
191 init:function( iObj
){
192 //set up pointer to this_seq for current scope:
194 //set the default values:
195 for(var i
in sequencerDefaultValues
){
196 this[ i
] = sequencerDefaultValues
[i
];
199 //js_log('on '+ i + ' :' + iObj[i]);
200 if(typeof sequencerDefaultValues
[i
] != 'undefined'){ //make sure its a valid property
205 //check for sequence_container
206 if($j(this.target_sequence_container
).length
=== 0){
207 js_log("Error: missing target_sequence_container");
211 //$j(this.target_sequence_container).css('position', 'relative');
212 this['base_width'] = $j(this.target_sequence_container
).width();
213 this['base_height'] = $j(this.target_sequence_container
).height();
215 //add the container divs (with basic layout ~universal~
216 $j(this.target_sequence_container
).html(''+
217 '<div id="' + this.video_container_id
+ '" style="position:absolute;right:0px;top:0px;' +
218 'width:' + this.video_width
+ 'px;height:'+this.video_height
+'px;border:solid thin blue;background:#FFF;font-color:black;"/>'+
219 '<div id="' + this.timeline_id
+ '" class="ui-widget ui-widget-content ui-corner-all" style="position:absolute;' +
220 'left:0px;right:0px;top:'+(this.video_height
+34)+'px;bottom:35px;overflow:auto;">'+
221 gM('mwe-loading_timeline')+ '</div>'+
222 '<div class="seq_status" style="position:absolute;left:0px;width:300px;"></div>'+
223 '<div class="seq_save_cancel" style="position:absolute;'+
224 'left:5px;bottom:0px;height:25px;">'+
225 gM('mwe-loading_user_rights') +
227 '<div class="about_editor" style="position:absolute;right:5px;bottom:7px;">' +
228 gM('mwe-sequencer_credit_line') +
230 '<div id="'+this.sequence_tools_id
+'" style="position:absolute;' +
231 'left:0px;right:'+(this.video_width
+15)+'px;top:0px;height:'+(this.video_height
+23)+'px;"/>'
236 /*js_log('set: '+this.target_sequence_container + ' html to:'+ "\n"+
237 $j(this.target_sequence_container).html()
239 //first check if we got a cloned PL object:
240 //(when the editor is invoked with the plalylist already on the page)
241 //@@NOT WORKING... (need a better "clone" function)
242 /*if( this.plObj != 'null' ){
243 js_log('found plObj clone');
244 //extend with mvSeqPlayList object:
245 this.plObj = new mvSeqPlayList(this.plObj);
246 js_log('mvSeqPlayList added: ' + this.plObj.org_control_height );
247 $j('#'+this.video_container_id).get(0).attachNode( this.plObj );
248 this.plObj.getHTML();
249 this.checkReadyPlObj();
253 //else check for source based sequence editor (a clean page load of the editor)
254 if( this.mv_pl_src
!= 'null' ) {
255 js_log( ' pl src:: ' + this.mv_pl_src
);
256 var src_attr
=' src="' + this.mv_pl_src
+'" ';
258 js_log( ' null playlist src .. (start empty) ');
261 $j('#'+this.video_container_id
).html('<playlist ' + src_attr
+
262 ' style="width:' + this.video_width
+ 'px;height:' + this.video_height
+ 'px;" '+
263 ' controls="false" id="' + this.plObj_id
+ '" />');
264 rewrite_by_id( this.plObj_id
);
265 setTimeout(this.instance_name
+'.checkReadyPlObj()', 25);
267 updateSeqSaveButtons:function(){
269 if( this.sequenceEditToken
){
270 $j(this.target_sequence_container
+' .seq_save_cancel').html(
271 $j
.btnHtml( gM('mwe-edit_save'), 'seq_edit_save', 'close') + ' ' +
272 $j
.btnHtml( gM('mwe-edit_cancel'), 'seq_edit_cancel', 'close')
275 $j(this.target_sequence_container
+' .seq_save_cancel').html( cancel_button
+ gM('mwe-no_edit_permissions') );
278 $j(this.target_sequence_container
+' .seq_edit_cancel').unbind().click(function(){
279 var x
= window
.confirm( gM('mwe-edit_cancel_confirm') );
281 _this
.closeModEditor();
283 //close request canceled.
286 $j(this.target_sequence_container
+' .seq_edit_save').unbind().click(function(){
287 //pop up progress dialog ~requesting edit line summary~
288 //remove any other save dialog
289 $j('#seq_save_dialog').remove();
290 $j('body').append('<div id="seq_save_dialog" title="'+ gM('mwe-edit_save') +'">' +
291 '<span class="mw-summary">'+
292 '<label for="seq_save_summary">Edit summary: </label>' +
294 '<input id="seq_save_summary" tabindex="1" maxlength="200" value="" size="30" name="seq_save_summary"/>'+
297 $j('#seq_save_dialog').dialog({
305 'title' : _this
.plObj
.mTitle
,
306 //the text is the sequence XML + the description
307 'text' : _this
.getSeqOutputHLRDXML() + "\n" +
308 _this
.plObj
.wikiDesc
,
309 'token' : _this
.sequenceEditToken
,
310 'summary' : $j('#seq_save_summary').val()
312 //change to progress bar and save:
313 $j('#seq_save_dialog').html('<div class="progress" /><br>' +
314 gM('mwe-saving_wait')
316 $j('#seq_save_dialog .progress').progressbar({
319 //run the Seq Save Request:
322 'url' : _this
.getLocalApiUrl()
324 $j('#seq_save_dialog').html( gM('mwe-save_done') );
325 $j('#seq_save_dialog').dialog('option',
329 window
.location
.reload();
331 "Do More Edits": function() {
332 $j(this).dialog("close");
338 $j(this).dialog('close');
344 //display a menu item (hide the rest)
345 disp:function( item
, dispCall
){
346 js_log('menu_item disp: ' + item
);
347 this.disp_menu_item
= item
;
348 //update the display and item state:
349 if(this.menu_items
[item
]){
350 //update the tabs display:
352 $j("#seq_menu").tabs('select', this.menu_items
[item
].inx
);
354 this.menu_items
[item
].d
= 1;
355 //do any click_js actions:getInsertControl
356 if( this.menu_items
[item
].click_js
)
357 this.menu_items
[item
].click_js( this );
360 //setup the menu items:
361 setupMenuItems:function(){
362 js_log('loadInitMenuItems');
364 //do all the menu_items setup: @@we could defer this to once the menu item is requested
365 for( var i
in this.menu_items
){
366 if( this.menu_items
[i
].js
)
367 this.menu_items
[i
].js( this );
370 renderTimeLine:function(){
371 //empty out the top level html:
372 $j('#'+this.timeline_id
).html('');
373 //add html general for timeline
374 if( this.timeline_mode
== 'time'){
375 $j('#'+this.timeline_id
).html(''+
376 '<div id="'+this.timeline_id
+'_left_cnt" class="mv_tl_left_cnt">'+
377 '<div id="'+this.timeline_id
+'_head_control" style="position:absolute;top:0px;left:0px;right:0px;height:30px;">' +
378 '<a title="'+gM('mwe-play_from_position')+'" href="javascript:'+this.instance_name
+'.play_jt()">'+
379 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path
+ 'images/control_play_blue.png">'+
381 '<a title="'+gM('mwe-zoom_in')+'" href="javascript:'+this.instance_name
+'.zoom_in()">'+
382 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path
+ 'images/zoom_in.png">'+
384 '<a title="'+gM('mwe-zoom_out')+'" href="javascript:'+this.instance_name
+'.zoom_out()">'+
385 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path
+ 'images/zoom_out.png">'+
387 '<a title="'+gM('mwe-cut_clip')+'" href="javascript:'+this.instance_name
+'.cut_mode()">'+
388 '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path
+ 'images/cut.png">'+
392 '<div id="'+this.timeline_id
+'_tracks" class="mv_seq_tracks">' +
393 '<div id="'+this.timeline_id
+'_head_jump" class="mv_head_jump" style="position:absolute;top:0px;left:0px;height:20px;"></div>'+
394 '<div id="'+this.timeline_id
+'_playline" class="mv_playline"></div>'+
397 //add playlist hook to update timeline
398 this.plObj
.update_tl_hook
= this.instance_name
+'.update_tl_hook';
402 for(var i
in this.plObj
.tracks
){
403 var track
= this.plObj
.tracks
[i
];
404 //js_log("on track: "+ i + ' t:'+ $j('#'+this.timeline_id+'_left_cnt').html() );
405 //set up track based on disp type
406 switch(track
.disp_mode
){
407 case 'timeline_thumb':
409 var exc_img
= 'opened';
410 var exc_action
='close';
411 var exc_msg
= gM('mwe-collapse_track');
415 var exc_img
= 'closed';
416 var exc_action
='open';
417 var exc_msg
= gM('mwe-expand_track');
421 $j('#'+this.timeline_id
+'_left_cnt').append(
422 '<div id="track_cnt_'+i
+'" style="top:'+top_pos
+'px;height:'+track_height
+'px;" class="track_name">'+
423 '<a id="mv_exc_'+i
+'" title="'+exc_msg
+'" href="javascript:'+this_sq
.instance_name
+'.exc_track('+i
+',\''+exc_action
+'\')">'+
424 '<img id="'+this_sq
.timeline_id
+'_close_expand" style="width:16px;height:16px;border:0" '+
425 ' src="'+mv_embed_path
+ 'images/'+exc_img
+'.png">'+
429 //also render the clips in the trackset container: (thumb or text view)
430 $j('#'+this.timeline_id
+'_tracks').append(
431 '<div id="container_track_'+i
+'" style="top:'+top_pos
+'px;height:'+(track_height
+2)+'px;left:0px;right:0px;" class="container_track" />'
433 top_pos
+=track_height
+20;
436 if( this.timeline_mode
=='storyboard'){
437 var top_pos
=this.plObj
.org_control_height
;
439 for(var i
in this.plObj
.tracks
){
440 var track_height
=this.track_clipThumb_height
;
441 var timeline_id
= this.timeline_id
442 //add in play box and container tracks
443 $j('#'+timeline_id
).append(''+
444 '<div id="interface_container_track_' + i
+ '" ' +
445 ' style="position:absolute;top:25px;height:'+(track_height
+30)+'px;left:10px;right:0px;"' +
447 '<div id="container_track_'+i
+'" style="position:relative;top:0px;' +
448 'height:'+(track_height
+30)+'px;left:0px;right:0px;" class="container_track">' +
450 '<div id="' + timeline_id
+ '_playline" class="mv_story_playline">' +
451 '<div class="mv_playline_top"/>'+
455 top_pos
+=track_height
+20;
459 //once playlist is ready continue
460 checkReadyPlObj:function(){
461 //set up pointers from sequencer to pl obj
462 this.plObj
= $j('#'+ this.plObj_id
).get(0);
463 //& from seq obj to sequencer
464 this.plObj
.pSeq
= this;
467 if( ! this.plObj
.loading
)
470 //else keep checking for the playlist to be ready
471 if( this.plObj
.loading
){
472 if(this.plReadyTimeout
==200){
473 js_error('error playlist never ready');
475 this.plReadyTimeout
++;
476 setTimeout(this.instance_name
+'.checkReadyPlObj()', 25);
480 getLocalApiUrl:function(){
481 return this.plObj
.interface_url
;
483 plReadyInit:function(){
485 js_log('plReadyInit');
486 js_log( this.plObj
);
487 //give the playlist a pointer to its parent seq:
488 this.plObj
['seqObj'] = this;
490 //update playlist (if its empty right now)
491 if(this.plObj
.getClipCount()==0){
492 $j('#'+this.plObj_id
).html('empty playlist');
495 //propagate the edit tokens
496 //if on an edit page just grab from the form:
497 this.sequenceEditToken
= $j('input[wpEditToken]').val();
499 if(typeof this.sequenceEditToken
== 'undefined' && this.getLocalApiUrl()!=null){
500 get_mw_token(_this
.plObj
.mTitle
, _this
.getLocalApiUrl(),
503 _this
.sequenceEditToken
= token
;
504 _this
.updateSeqSaveButtons();
508 get_mw_token(_this
.plObj
.mTalk
, _this
.getLocalApiUrl(),
510 _this
.clipboardEditToken
= token
;
513 //also grab permissions for sending clipboard commands to the server
515 //(calling the sequencer inline) try and get edit token via api call:
516 //(somewhat fragile way to get at the api... should move to config
517 /*var token_url = this.plObj.interface_url.replace(/index\.php/, 'api.php');
518 token_url += '?action=query&format=xml&prop=info&intoken=edit&titles=';
521 url: token_url + this_seq.plObj.mTitle,
522 success:function(data){
523 var pageElm = data.getElementsByTagName('page')[0];
524 if( $j(pageElm).attr('edittoken') ){
525 this_seq.sequenceEditToken = $j(pageElm).attr('edittoken');
530 //also grab permissions for sending clipboard commands to the server
533 url: token_url + this_seq.plObj.mTalk,
534 success:function(data){
535 var pageElm = data.getElementsByTagName('page')[0];
536 if( $j(pageElm).attr('edittoken') ){
537 this_seq.clipboardEditToken = $j(pageElm).attr('edittoken');
544 //render the menu tabs::
545 var item_containers
='';
547 var selected_tab
= 0;
549 var o
='<div id="seq_menu" style="width:100%;height:100%">';
551 for(var tab_id
in this.menu_items
){
552 menu_item
= this.menu_items
[tab_id
];
556 _this
.disp_menu_item
=tab_id
;
560 '<a id="mv_menu_item_'+tab_id
+'" href="#' + tab_id
+ '_ic">'+gM('mwe-menu_' + tab_id
) + '</a>' +
563 tabc
+= '<div id="' + tab_id
+ '_ic" style="overflow:auto;height:268px;" >';
564 tabc
+= (menu_item
.html
) ? menu_item
.html
: '<h3>' + gM('mwe-menu_'+tab_id
) + '</h3>';
570 $j('#'+this.sequence_tools_id
).html( o
);
573 $j("#seq_menu").tabs({
574 selected
:selected_tab
,
575 select: function(event
, ui
) {
576 _this
.disp( $j(ui
.tab
).attr('id').replace('mv_menu_item_', ''), true );
579 }).find(".ui-tabs-nav").sortable({ axis
: 'x' });
582 //render the timeline
583 this.renderTimeLine();
584 this.do_refresh_timeline();
586 //load init content into containers
587 this.setupMenuItems();
589 this.doFocusBindings();
591 //set up key bidnings
592 $j(window
).keydown(function(e
){
593 js_log('pushed down on:' + e
.which
);
595 _this
.key_shift_down
= true;
598 _this
.key_ctrl_down
= true;
600 if( (e
.which
== 67 && _this
.key_ctrl_down
) && !_this
.inputFocus
)
601 _this
.copySelectedClips();
603 if( (e
.which
== 88 && _this
.key_ctrl_down
) && !_this
.inputFocus
)
604 _this
.cutSelectedClips();
606 //paste cips on v + ctrl while not focused on a text area:
607 if( (e
.which
== 86 && _this
.key_ctrl_down
) && !_this
.inputFocus
)
608 _this
.pasteClipBoardClips();
611 $j(window
).keyup(function(e
){
612 js_log('key up on ' + e
.which
);
613 //user let go of "shift" turn off multi-select
615 _this
.key_shift_down
= false;
618 _this
.key_ctrl_down
= false;
620 //escape key (for now deselect)
622 _this
.deselectClip();
625 //backspace or delete key while not focused on a text area:
626 if( (e
.which
== 8 || e
.which
== 46) && !_this
.inputFocus
)
627 _this
.removeSelectedClips();
630 //check all nodes for focus
631 //@@todo it would probably be faster to search a given subnode instead of all text
632 doFocusBindings:function(){
634 //if an input or text area has focus disable delete key binding
635 $j("input,textarea").focus(function () {
636 js_log("inputFocus:true");
637 _this
.inputFocus
= true;
639 $j("input,textarea").blur( function () {
640 js_log("inputFocus:blur");
641 _this
.inputFocus
= false;
644 update_tl_hook:function(jh_time_ms
){
645 //put into seconds scale:
646 var jh_time_sec_float
= jh_time_ms
/1000;
647 //render playline at given time
648 //js_log('tl scale: '+this.timeline_scale);
649 $j('#'+this.timeline_id
+'_playline').css('left', Math
.round(jh_time_sec_float
/this.timeline_scale
)+'px' );
650 //js_log('at time:'+ jh_time_sec + ' px:'+ Math.round(jh_time_sec_float/this.timeline_scale));
652 /*returns a xml or json representation of the current sequence */
653 getSeqOutputJSON:function(){
654 js_log('json output:');
656 getSeqOutputHLRDXML:function(){
657 var o
='<sequence_hlrd>' +"\n";
660 for(var i
in this.plObj
.transitions
){
661 if( this.plObj
.transitions
[i
] ){
662 var tObj
= this.plObj
.transitions
[i
].getAttributeObj();
663 o
+="\t\t<transition ";
665 o
+=' '+j
+'="' + tObj
[j
] + '"\n\t\t';
667 o
+='/>'+"\n"; //transitions don't have children
675 for(var i
in this.plObj
.tracks
){
676 var curTrack
= this.plObj
.tracks
[i
];
678 var tAttr
= curTrack
.getAttributeObj();
680 o
+=' '+j
+'="' + tAttr
[j
] + '"\n\t\t\t';
683 for( var k
in curTrack
.clips
){
684 var curClip
= curTrack
.clips
[k
];
686 var cAttr
= curClip
.getAttributeObj();
689 var val
= (j
=='transIn' || j
=='transOut') ? cAttr
[j
].id
: cAttr
[j
];
690 o
+=lt
+ j
+'="' + val
+ '"';
693 o
+=">\n" //close the clip
694 for(var pName
in curClip
.params
){
695 var pVal
= curClip
.params
[pName
];
696 o
+="\t\t\t" + '<param name="'+ pName
+ '">' + pVal
+ '</param>' + "\n";
704 o
+='</sequence_hlrd>';
708 editClip:function(track_inx
, clip_inx
){
709 var cObj
= this.plObj
.tracks
[ track_inx
].clips
[ clip_inx
];
710 this.doEditClip( cObj
);
712 doEditTransitionSelectedClip:function(){
714 js_log("f:doEditTransitionSelectedClip:" + $j('.mv_selected_clip').length
);
715 if( $j('.mv_selected_clip').length
== 1){
716 _this
.doEditTransition( _this
.getClipFromSeqID( $j('.mv_selected_clip').parent().attr('id') ) );
717 }else if( $j('.mv_selected_clip').length
=== 0){
718 //no clip selected warning:
719 $j('#transition_ic').html( gM('mwe-no_selected_resource') );
721 //multiple clip selected warning:
722 $j('#transition_ic').html( gM('mwe-error_edit_multiple') );
725 doEditSelectedClip:function(){
726 js_log("f:doEditSelectedClip:");
727 //and only one clip selected
728 if( $j('.mv_selected_clip').length
== 1){
729 this.doEditClip( this.getClipFromSeqID( $j('.mv_selected_clip').parent().attr('id') ) );
730 }else if( $j('.mv_selected_clip').length
=== 0){
731 //no clip selected warning:
732 $j('#clipedit_ic').html( gM('mwe-no_selected_resource') );
734 //multiple clip selected warning:
735 $j('#clipedit_ic').html( gM('mwe-error_edit_multiple') );
738 doEditTransition:function( cObj
){
739 js_log("sequence:doEditTransition");
741 mv_get_loading_img( '#transitions_ic' );
746 //no idea why this works / is needed.
747 var localSeqRef
= _this
;
748 _this
.myEffectEdit
= new mvTimedEffectsEdit({
750 'control_ct' : 'transition_ic',
755 //updates the clip details div if edit resource is set
756 doEditClip:function( cObj
){
759 //set default edit action (maybe edit_action can be sent via by context click)
760 var edit_action
= 'fileopts';
762 mv_get_loading_img( '#clipedit_ic' );
763 //load the clipEdit library if not already loaded:
767 _this
.myClipEditor
= {};
768 //setup the cliploader
769 _this
.myClipEditor
= new mvClipEdit({
771 'control_ct' : 'clipedit_ic',
772 'clip_disp_ct' : cObj
.id
,
773 'edit_action' : edit_action
,
775 'profile' : 'sequence'
779 //save new clip segment
780 saveClipEdit:function(){
781 //saves the clip updates
783 closeModEditor:function(){
784 //unset the sequencer
785 _global
['mvSeq'] = null;
786 $j(this.target_sequence_container
+ ',.ui-widget-overlay').remove();
788 pasteClipBoardClips:function(){
789 js_log('f:pasteClipBoardClips');
790 //@@todo query the server for updated clipboard
791 //paste before the "current clip"
792 this.addClips( this.clipboard
, this.plObj
.cur_clip
.order
);
794 copySelectedClips:function(){
796 //set all the selected clips
797 this.clipboard
= new Array();
798 $j('.mv_selected_clip').each(function(){
799 //add each clip to the clip board:
800 var cur_clip
= this_seq
.getClipFromSeqID( $j(this).parent().attr('id') );
801 this_seq
.clipboard
.push( cur_clip
.getAttributeObj() );
803 //upload clipboard to the server (if possible)
804 if( parseUri( document
.URL
).host
!= parseUri( this_seq
.plObj
.interface_url
).host
){
805 js_log('error: presently we can\'t copy clips across domains');
807 //@@we need a api entry point to store a "clipboard"
808 if( this_seq
.clipboardEditToken
&& this_seq
.plObj
.interface_url
){
809 var req_url
= this_seq
.plObj
.interface_url
.replace(/api.php/, 'index.php') + '?action=ajax&rs=mv_seqtool_clipboard&rsargs[]=copy';
814 "clipboard_data": $j
.toJSON( this_seq
.clipboard
),
815 "clipboardEditToken": this_seq
.clipboardEditToken
817 success:function(data
){
819 js_log('did clipboard push ' + $j
.toJSON( this_seq
.clipboard
) );
823 js_log('error: no clipboardEditToken to uplaod clipboard to server');
827 cutSelectedClips:function(){
828 this.copySelectedClips();
829 this.removeSelectedClips();
831 removeSelectedClips:function(){
832 var remove_clip_ary
=new Array();
833 //remove selected clips from display
834 $j('.container_track .mv_selected_clip').each(function(){
835 //grab the track index from the id (assumes track_#_clip_#
836 remove_clip_ary
.push ( $j(this).parent().attr('id').replace('track_','').replace('clip_','').split('_') );
838 if(remove_clip_ary
.length
!=0 )
839 this.removeClips(remove_clip_ary
);
841 //doEdit selected clips (updated selected resource)
842 //@@todo refresh menu of current
843 this.doEditSelectedClip();
845 addClip:function( clip
, before_clip_pos
, track_inx
){
846 this.addClips([clip
], before_clip_pos
, track_inx
)
848 //add a single or set of clips
849 //to a given position and track_inx
850 addClips:function( clipSet
, before_clip_pos
, track_inx
){
854 track_inx
= this.plObj
.default_track
.inx
;
857 before_clip_pos
= this.plObj
.default_track
.getClipCount();
859 js_log("seq: add clip: at: "+ before_clip_pos
+ ' in track: ' + track_inx
);
860 var cur_pos
= before_clip_pos
;
862 $j
.each(clipSet
, function(inx
, clipInitDom
){
863 var mediaElement
= document
.createElement('ref');
864 for(var i
in clipInitDom
){
865 js_log("set: " + i
+ ' to ' + clipInitDom
[i
]);
867 $j(mediaElement
).attr(i
, clipInitDom
[i
]);
869 if( this_seq
.plObj
.tryAddMedia( mediaElement
, cur_pos
, track_inx
) )
873 this.do_refresh_timeline();
875 removeClips:function( remove_clip_ary
){
877 var jselect
= coma
='';
878 js_log('clip count before removal : ' + this_seq
.plObj
.default_track
.clips
.length
+ ' should remove ' + remove_clip_ary
.length
);
879 var afected_tracks
= new Array();
880 //add order to track_clip before we start removing:
881 $j
.each( remove_clip_ary
, function(inx
, track_clip
){
882 remove_clip_ary
[inx
]['order'] = this_seq
.plObj
.tracks
[ track_clip
[0] ].clips
[ track_clip
[1] ].order
;
884 $j
.each( remove_clip_ary
, function(inx
, track_clip
){
885 var track_inx
= track_clip
[0];
886 var clip_inx
= track_clip
[1];
887 var clip_rm_order
= track_clip
['order'];
888 js_log('remove t:' + track_inx
+ ' c:'+ clip_inx
+ ' id:' +' #track_'+track_inx
+'_clip_'+clip_inx
+ ' order:' + clip_rm_order
);
889 //remove the clips from the base tracks
890 for(var i
in this_seq
.plObj
.tracks
[ track_inx
].clips
){
891 cur_clip
= this_seq
.plObj
.tracks
[ track_inx
].clips
[i
]
892 if(cur_clip
.order
== clip_rm_order
){
893 this_seq
.plObj
.tracks
[ track_clip
[0] ].clips
.splice( i
, 1);
896 //add track to affected track list:
897 afected_tracks
[ track_inx
]=true;
898 jselect
+= coma
+ '#track_' +track_inx
+ '_clip_' + clip_inx
;
902 $j
.each(afected_tracks
, function(track_inx
, affected
){
903 this_seq
.plObj
.tracks
[track_inx
].reOrderClips();
906 js_log('clip count after removal : ' + this_seq
.plObj
.default_track
.clips
.length
);
907 //animate the removal (@@todo should be able to call the resulting fadeOut only once without a flag)
908 var done_with_refresh
=false;
909 $j(jselect
).fadeOut("slow", function(){
910 if( !done_with_refresh
)
911 this_seq
.do_refresh_timeline();
912 done_with_refresh
=true;
913 }).empty(); //empty to remove any persistent bindings
915 doEdit:function( editObj
){
916 //add the current editObj to the edit stack (should allow for "undo")
917 this.edit_stack
.push( editObj
);
918 //make the adjustments
919 this.makeAdjustment( editObj
);
922 * takes adjust ment object with options:
923 * track_inx, clip_inx, start, end delta
925 makeAdjustment:function(e
){
928 this.plObj
.tracks
[e
.track_inx
].clips
[e
.clip_inx
].doAdjust('start', e
.delta
);
931 this.plObj
.tracks
[e
.track_inx
].clips
[e
.clip_inx
].doAdjust('end', e
.delta
);
934 js_log('re render: '+e
.track_inx
);
935 //refresh the playlist after adjustment
936 this.do_refresh_timeline();
938 //@@todo set up key bindings for undo
940 var editObj
= this.edit_stack
.pop();
944 exc_track:function(inx
,req
){
947 $j('#mv_exc_'+inx
).attr('href', 'javascript:'+this.instance_name
+'.exc_track('+inx
+',\'open\')');
948 $j('#mv_exc_'+inx
+ ' > img').attr('src',mv_embed_path
+ 'images/closed.png');
949 $j('#track_cnt_'+inx
+',#container_track_'+inx
).animate({height
:this.track_text_height
}, "slow",'',
951 this_seq
.plObj
.tracks
[inx
].disp_mode
='text';
952 this_seq
.render_tracks( inx
);
954 }else if(req
=='open'){
955 $j('#mv_exc_'+inx
).attr('href', 'javascript:'+this.instance_name
+'.exc_track('+inx
+',\'close\')');
956 $j('#mv_exc_'+inx
+ ' > img').attr('src',mv_embed_path
+ 'images/opened.png');
957 $j('#track_cnt_'+inx
+',#container_track_'+inx
).animate({height
:this.track_thumb_height
}, "slow",'',
959 this_seq
.plObj
.tracks
[inx
].disp_mode
='timeline_thumb';
960 this_seq
.render_tracks(inx
);
966 add_track:function(inx
, track
){
969 //toggle cut mode (change icon to cut)
971 js_log('do cut mode');
972 //add cut layer ontop of clips
974 doAdvancedTl:function(){
975 this.timeline_mode
='time';
976 this.renderTimeLine();
977 this.do_refresh_timeline();
980 doSimpleTl:function(){
981 this.timeline_mode
='storyboard';
982 this.renderTimeLine();
983 this.do_refresh_timeline();
986 //renders updates the timeline based on the current scale
987 render_tracks:function( track_inx
){
988 js_log("f::render track: "+track_inx
);
990 //inject the tracks into the timeline (if not already there)
991 for(var track_id
in this.plObj
.tracks
){
992 if( track_inx
==track_id
|| typeof track_inx
=='undefined' ){
993 //empty out the track container:
994 //$j('#container_track_'+track_id).empty();
995 var track_html
=droppable_html
='';
996 //set up per track vars:
997 var track
= this.plObj
.tracks
[track_id
];
1000 //set up some constants for timeline_mode == storyboard:
1001 if(this.timeline_mode
== 'storyboard'){
1002 var frame_width
= Math
.round( this.track_clipThumb_height
* 1.3333333 );
1003 var container_width
= frame_width
+60;
1007 for(var j
in track
.clips
){
1008 clip
= track
.clips
[j
];
1009 //var img = clip.getClipImg('icon');
1010 if( this.timeline_mode
== 'storyboard' ){
1011 clip
.left_px
= j
*container_width
;
1012 clip
.width_px
= container_width
;
1013 var base_id
= 'track_'+track_id
+'_clip_'+j
;
1014 track_html
+= '<span id="'+base_id
+'" '+
1015 'class="mv_storyboard_container mv_clip_drag" '+
1017 'left:'+clip
.left_px
+'px;'+
1018 'height:' + (this.track_clipThumb_height
+30) + 'px;' +
1019 'width:'+(container_width
)+'px;" >';
1020 track_html
+= clip
.embed
.renderTimelineThumbnail({
1021 'width' : frame_width
,
1022 'thumb_class' : 'mv_clip_thumb',
1023 'height':this.track_clipThumb_height
,
1026 //render out edit button
1027 /*track_html+='<div class="clip_edit_button clip_edit_base clip_control"/>';*/
1029 //check if the clip has transitions
1033 if(clip.transIn || clip.transOut){
1034 if( clip.transIn && clip.transIn.getIconSrc )
1035 imsrc = clip.transIn.getIconSrc();
1036 //@@todo put transOut somewhere else
1037 if( clip.transOut && clip.transOut.getIconSrc )
1038 imsrc = clip.transOut.getIconSrc();
1040 imgHtml = '<img style="width:32px;height:32px" src="' + imsrc + '" />';
1042 //render out transition edit box
1043 track_html += '<div id="tb_' + base_id + '" class="clip_trans_box">' +
1047 //render out adjustment text
1048 /*track_html+='<div id="' + base_id + '_adj' + '" class="mv_adj_text" style="top:'+ (this.track_clipThumb_height+10 )+'px;">'+
1049 '<span class="mv_adjust_click" onClick="'+this.instance_name+'.adjClipDur(' + track_id + ',' + j + ',\'-\')" /> - </span>'+
1050 ( (clip.getDuration() > 60 )? seconds2npt(clip.getDuration()): clip.getDuration() ) +
1051 '<span class="mv_adjust_click" onClick="'+this.instance_name+'.adjClipDur(' + track_id + ',' + j + ',\'+\')" /> + </span>'+
1054 track_html
+='</span>';
1057 //do timeline_mode rendering:
1058 if(this.timeline_mode
== 'time'){
1059 clip
.left_px
= Math
.round( cur_clip_time
/this.timeline_scale
);
1060 clip
.width_px
= Math
.round( Math
.round( clip
.getDuration() )/this.timeline_scale
);
1061 clip
.height_px
= 60;
1062 js_log('at time:' + cur_clip_time
+ ' left: ' +clip
.left_px
+ ' clip dur: ' + Math
.round( clip
.getDuration() ) + ' clip width:' + clip
.width_px
);
1064 //for every clip_width pixle output image
1065 if(track
.disp_mode
=='timeline_thumb'){
1066 track_html
+='<span id="track_'+track_id
+'_clip_'+j
+'" '+
1067 'class="mv_tl_clip mv_clip_drag" '+
1069 'left:' + clip
.left_px
+ 'px;'+
1070 'width:'+ clip
.width_px
+ 'px;'+
1071 'height:'+ clip
.height_px
+ 'px" >';
1072 track_html
+= this.render_clip_frames( clip
);
1073 }else if(track
.disp_mode
=='text'){
1075 track_html
+='<span id="track_'+track_id
+'_clip_'+j
+'" style="left:'+clip
.left_px
+'px;'+
1076 'width:'+clip
.width_px
+'px;background:'+clip
.getColor()+
1077 '" class="mv_time_clip_text mv_clip_drag">'+clip
.title
;
1079 //add in per clip controls
1080 track_html
+='<div title="'+gM('mwe-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";
1081 track_html
+='<div title="'+gM('mwe-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";
1082 track_html
+='<div title="'+gM('mwe-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";
1083 track_html
+='<span style="display:none;" class="mv_clip_stats"></span>';
1085 track_html
+='</span>';
1086 //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>';
1087 //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>';
1088 cur_clip_time
+=Math
.round( clip
.getDuration() ); //increment cur_clip_time
1093 //js_log("new htmL for track i: "+track_id + ' html:'+track_html);
1094 $j('#container_track_'+track_id
).html( track_html
);
1096 //apply transition click action
1097 $j('.clip_trans_box').click(function(){
1098 if($j(this).hasClass('mv_selected_transition')){
1099 $j(this).removeClass('mv_selected_transition');
1100 this_seq
.deselectClip( $j(this).siblings('.mv_clip_thumb').get(0) );
1103 this_seq
.deselectClip();
1104 $j('.clip_trans_box').removeClass('mv_selected_transition');
1105 $j(this).addClass("mv_selected_transition");
1106 $j(this).siblings('.mv_clip_thumb').addClass("mv_selected_clip");
1107 var sClipObj
= this_seq
.getClipFromSeqID( $j(this).parent().attr('id') );
1108 //jump to the current clip
1109 this_seq
.plObj
.updateCurrentClip( sClipObj
);
1110 //display the transition edit tab:
1111 this_seq
.disp( 'transition' );
1115 //apply edit button mouse over effect:
1116 $j('.clip_edit_button').hover(function(){
1117 $j(this).removeClass("clip_edit_base").addClass("clip_edit_over");
1119 $j(this).removeClass("clip_edit_over").addClass("clip_edit_base");
1120 }).click(function(){
1121 //deselect everything else:
1122 $j('.mv_selected_clip').each(function(inx
, selected_clip
){
1123 this_seq
.deselectClip( this );
1126 var sClipObj
= this_seq
.getClipFromSeqID( $j(this).parent().attr('id') );
1127 this_seq
.plObj
.updateCurrentClip( sClipObj
);
1128 //get the clip (siblings with mv_clip_thumb class)
1129 var cur_clip_elm
= $j(this).siblings('.mv_clip_thumb');
1130 //select the clip (add mv_selected_clip if not already selected)
1131 if( ! $j( cur_clip_elm
).hasClass("mv_selected_clip") ){
1132 $j( cur_clip_elm
).addClass('mv_selected_clip');
1133 $j('#' + $j( cur_clip_elm
).parent().attr("id") + '_adj').fadeIn("fast");
1135 //display the edit tab:
1136 this_seq
.disp( 'clipedit' );
1137 //display edit dialog:
1138 this_seq
.doEditClip( sClipObj
);
1141 //apply onClick edit controls:
1142 $j('.mv_clip_thumb').click(function(){
1143 var cur_clip_click
= this;
1144 //if not in multi select mode remove all existing selections
1145 //(except for the current click which is handled down below)
1146 js_log(' ks: ' + this_seq
.key_shift_down
+ ' ctrl_down:' +this_seq
.key_ctrl_down
);
1147 if( ! this_seq
.key_shift_down
&& ! this_seq
.key_ctrl_down
){
1148 $j('.mv_selected_clip').each(function(inx
, selected_clip
){
1149 if( $j(this).parent().attr('id') != $j(cur_clip_click
).parent().attr('id')
1150 || ( $j('.mv_selected_clip').length
> 1 ) ){
1151 this_seq
.deselectClip( this );
1157 var sClipObj
= this_seq
.getClipFromSeqID( $j(this).parent().attr('id') );
1158 this_seq
.plObj
.updateCurrentClip( sClipObj
);
1159 if( $j(this).hasClass("mv_selected_clip") ){
1160 $j(this).removeClass("mv_selected_clip");
1161 $j('#' + $j(this).parent().attr("id") + '_adj').fadeOut("fast");
1163 $j(this).addClass('mv_selected_clip');
1164 $j('#' + $j(this).parent().attr("id") + '_adj').fadeIn("fast");
1166 //if shift select is down select the in-between clips
1167 if( this_seq
.key_shift_down
){
1168 //get the min max of current selection (within the current track)
1170 var min_order
= 999999999;
1171 $j('.mv_selected_clip').each(function(){
1172 var cur_clip
= this_seq
.getClipFromSeqID( $j(this).parent().attr('id') );
1174 if(cur_clip
.order
< min_order
)
1175 min_order
= cur_clip
.order
;
1176 if(cur_clip
.order
> max_order
)
1177 max_order
= cur_clip
.order
;
1179 //select all non-selected between max or min
1180 js_log('sOrder: ' + sClipObj
.order
+ ' min:' + min_order
+ ' max:'+ max_order
);
1181 if( sClipObj
.order
<= min_order
){
1182 for( var i
= sClipObj
.order
; i
<= max_order
; i
++ ){
1183 $j('#track_' + track_id
+ '_clip_' + i
+ ' > .mv_clip_thumb' ).addClass('mv_selected_clip');
1186 if( sClipObj
.order
>= max_order
){
1187 for( var i
=min_order
; i
<= max_order
; i
++ ){
1188 $j('#track_' + track_id
+ '_clip_' + i
+ ' > .mv_clip_thumb' ).addClass('mv_selected_clip');
1192 this_seq
.doEditSelectedClip();
1194 //add in control for time based display
1196 if(this.timeline_mode
== 'time'){
1197 $j('.ui-resizable-handle').mousedown( function(){
1198 js_log('hid: ' + $j(this).attr('class'));
1199 this_seq
.resize_mode
= ($j(this).attr('class').indexOf('ui-resizable-e')!=-1)?
1200 'resize_end':'resize_start';
1203 var insert_key
='na';
1205 //@@todo support multiple clips
1206 for(var j
in track
.clips
){
1207 $j('#track_'+track_id
+'_clip_'+j
).draggable({
1209 containment
:'#container_track_'+track_id
,
1211 handle
: ":not(.clip_control)",
1213 drag:function(e
, ui
){
1215 insert_key
= this_seq
.clipDragUpdate(ui
, this);
1217 start:function(e
,ui
){
1218 js_log('start drag:' + this.id
);
1219 //make sure we are ontop
1220 $j(this).css({top
:'0px',zindex
:10});
1222 stop:function(e
, ui
){
1223 $j(this).css({top
:'0px',zindex
:0});
1225 var id_parts
= this.id
.split('_');
1226 var track_inx
= id_parts
[1];
1227 var clip_inx
= id_parts
[3];
1228 var clips
= this_seq
.plObj
.tracks
[track_inx
].clips
;
1229 var cur_drag_clip
= clips
[clip_inx
];
1231 if(insert_key
!='na' && insert_key
!='end' ){
1232 cur_drag_clip
.order
=insert_key
-.5;
1233 }else if (insert_key
=='end'){
1234 cur_drag_clip
.order
=clips
.length
;
1236 //reorder array based on new order
1237 clips
.sort(sort_func
);
1238 function sort_func(a
, b
){
1239 return a
.order
- b
.order
;
1241 //assign keys back to order:
1242 this_seq
.plObj
.tracks
[track_inx
].reOrderClips();
1244 this_seq
.do_refresh_timeline();
1247 //add in resize hook if in time mode:
1248 if(this.timeline_mode
== 'time'){
1249 $j('#track_'+track_id
+'_clip_'+j
).resizable({
1252 start: function(e
,ui
) {
1254 $j(this).css({'border':'solid thin red'});
1255 //fade In Time stats (end or start based on handle)
1256 //dragging east (adjusting end time)
1257 js_log( 'append to: '+ this.id
);
1258 $j('#' + this.id
+ ' > .mv_clip_stats').fadeIn("fast");
1260 stop: function(e
,ui
) {
1261 js_log('stop resize');
1263 $j(this).css('border', 'solid thin white');
1265 var clip_drag
= this;
1266 $j('#'+this.id
+' > .mv_clip_stats').fadeOut("fast",function(){
1267 var id_parts
= clip_drag
.id
.split('_');
1268 var track_inx
= id_parts
[1];
1269 var clip_inx
= id_parts
[3];
1272 type
:this_seq
.resize_mode
,
1273 delta
:this_seq
.edit_delta
,
1274 track_inx
:track_inx
,
1278 resize: function(e
,ui
) {
1279 //update time stats & render images:
1280 this_seq
.update_clip_resize(this);
1285 $j('#container_track_'+track_id
).width(Math
.round( this.timeline_duration
/ this.timeline_scale
));
1290 clipDragUpdate:function( ui
, clipElm
){
1291 var this_seq
= this;
1293 var insert_key
='na';
1294 //animate re-arrange by left position:
1295 //js_log('left: '+ui.position.left);
1296 //locate clip (based on clip duration not animate)
1297 var id_parts
= clipElm
.id
.split('_');
1298 var track_inx
= id_parts
[1];
1299 var clip_inx
= id_parts
[3];
1300 var clips
= this_seq
.plObj
.tracks
[track_inx
].clips
;
1301 var cur_drag_clip
= clips
[clip_inx
];
1302 var return_org
= true;
1303 $j(clipElm
).css('zindex',10);
1304 //find out where we are inserting and set left border to solid red thick
1305 for(var k
in clips
){
1306 if( ui
.position
.left
> clips
[k
].left_px
&&
1307 ui
.position
.left
< (clips
[k
].left_px
+ clips
[k
].width_px
)){
1309 //also make sure we are not where we started
1311 $j('#track_'+track_inx
+'_clip_'+k
).css('border-left', 'solid thick red');
1320 $j('#track_'+track_inx
+'_clip_'+k
).css('border-left', 'solid thin white');
1323 //if greater than the last k insert after
1324 if(ui
.position
.left
> (clips
[k
].left_px
+ clips
[k
].width_px
) &&
1326 $j('#track_'+track_inx
+'_clip_'+k
).css('border-right', 'solid thick red');
1329 $j('#track_'+track_inx
+'_clip_'+k
).css('border-right', 'solid thin white');
1333 deselectClip:function( clipElm
){
1335 $j('.mv_selected_clip').removeClass("mv_selected_clip");
1337 $j(clipElm
).removeClass("mv_selected_clip");
1338 //make sure the transition sibling is removed:
1339 $j(clipElm
).siblings('.clip_trans_box').removeClass( 'mv_selected_transition' );
1340 $j('#' + $j(clipElm
).parent().attr("id") + '_adj').fadeOut("fast");
1343 getClipFromSeqID:function( clip_seq_id
){
1344 js_log('get id from: ' + clip_seq_id
);
1345 var ct
= clip_seq_id
.replace('track_','').replace('clip_','').split('_');
1346 return this.plObj
.tracks
[ ct
[0] ].clips
[ ct
[1] ];
1348 //renders clip frames
1349 render_clip_frames:function(clip
, frame_offset_count
){
1350 js_log('f:render_clip_frames: ' + clip
.id
+ ' foc:' + frame_offset_count
);
1351 var clip_frames_html
='';
1352 var frame_width
= Math
.round(this.track_thumb_height
*1.3333333);
1354 var pint
= (frame_offset_count
==null)?0:frame_offset_count
*frame_width
;
1356 //js_log("pinit: "+ pint+ ' < '+clip.width_px+' ++'+frame_width);
1357 for(var p
=pint
;p
<clip
.width_px
;p
+=frame_width
){
1358 var clip_time
= (p
==0)?0:Math
.round(p
*this.timeline_scale
);
1359 js_log('rendering clip frames: p:' +p
+' pts:'+ (p
*this.timeline_scale
)+' time:' + clip_time
+ ' height:'+this.track_thumb_height
);
1360 clip_frames_html
+=clip
.embed
.renderTimelineThumbnail({
1361 'width': frame_width
,
1362 'thumb_class':'mv_tl_thumb',
1363 'height': this.track_thumb_height
,
1364 'size' : "icon", //set size to "icon" preset
1368 js_log('render_clip_frames:'+clip_frames_html
);
1369 return clip_frames_html
;
1371 update_clip_resize:function(clip_element
){
1372 //js_log('update_clip_resize');
1373 var this_seq
= this;
1374 var id_parts
= clip_element
.id
.split('_');
1375 track_inx
= id_parts
[1];
1376 clip_inx
= id_parts
[3];
1378 var clip
= this.plObj
.tracks
[ track_inx
].clips
[ clip_inx
];
1380 //would be nice if getting the width did not flicker the border
1381 //@@todo do a work around e in resize function has some screen based offset values
1382 clip
.width_px
= $j(clip_element
).width();
1383 var width_dif
= clip
.width_px
- Math
.round( Math
.round( clip
.getDuration() )/this.timeline_scale
);
1384 //var left_px = $j(clip_element).css('left');
1386 var new_clip_dur
= Math
.round( clip
.width_px
*this.timeline_scale
);
1387 var clip_dif
= (new_clip_dur
- clip
.getDuration() );
1388 var clip_dif_str
= (clip_dif
>0)?'+'+clip_dif
:clip_dif
;
1389 //set the edit global delta
1390 this.edit_delta
= clip_dif
;
1393 clip_desc
+='length: ' + seconds2npt(new_clip_dur
) +'('+clip_dif_str
+')';
1394 if(this_seq
.resize_mode
=='resize_end'){
1396 var new_end
= seconds2npt(npt2seconds(clip
.embed
.end_ntp
)+clip_dif
);
1397 clip_desc
+='<br>end time: ' + new_end
;
1398 //also shift all the other clips (after the current)
1399 //js_log("track_inx: " + track_inx + ' clip inx:'+clip_inx);
1400 //$j('#container_track_'+track_inx+' > .mv_clip_drag :gt('+clip_inx+')').each(function(){
1401 $j('#container_track_'+track_inx
+' > :gt('+clip_inx
+')').each(function(){
1402 var move_id_parts
= this.id
.split('_');
1403 var move_clip
= this_seq
.plObj
.tracks
[move_id_parts
[1]].clips
[move_id_parts
[3]];
1404 //js_log('should move:'+ this.id);
1405 $j(this).css('left', move_clip
.left_px
+ width_dif
);
1408 //expanding left (resize_start)
1409 var new_start
= seconds2npt(npt2seconds(clip
.embed
.start_ntp
)+clip_dif
);
1410 clip_desc
+='<br>start time: ' + new_start
;
1413 //update clip stats:
1414 $j('#'+clip_element
.id
+' > .mv_clip_stats').html(clip_desc
);
1415 var frame_width
= Math
.round(this.track_thumb_height
*1.3333333);
1416 //check if we need to append some images:
1417 var frame_count
= $j('#'+clip_element
.id
+' > img').length
;
1418 if(clip
.width_px
> (frame_count
* frame_width
) ){
1419 //if dragging left append
1420 js_log('width_px:'+clip
.width_px
+' framecount:'+frame_count
+' Xcw='+(frame_count
* frame_width
));
1421 $j('#'+clip_element
.id
).append(this.render_clip_frames(clip
, frame_count
));
1425 render_playheadhead_seeker:function(){
1426 js_log('render_playheadhead_seeker');
1427 //render out time stamps and time "jump" links
1428 //first get total width
1430 //remove the old one if its still there
1431 $j('#'+this.timeline_id
+'_pl_control').remove();
1432 //render out a playlist clip wide and all the way to the right (only playhead and play button) (outside of timeline)
1433 $j(this.target_sequence_container
).append('<div id="'+ this.timeline_id
+'_pl_control"'+
1434 ' style="position:absolute;top:' + (this.plObj
.height
) +'px;'+
1435 'right:1px;width:'+this.plObj
.width
+'px;height:'+this.plObj
.org_control_height
+'" '+
1436 'class="videoPlayer"><div class="ui-widget ui-corner-bottom ui-state-default controls">'+
1437 this.plObj
.getControlsHTML() +
1440 //update time and render out clip dividers .. should be used to show load progress
1441 this.plObj
.updateBaseStatus();
1443 //once the controls are in the DOM add hooks:
1444 ctrlBuilder
.addControlHooks(this.plObj
);
1446 //render out the "jump" div
1447 if(this.timeline_mode
=='time'){
1448 /*$j('#'+this.timeline_id+'_head_jump').width(pixle_length);
1449 //output times every 50pixles
1451 //output time-desc every 50pixles and jump links every 10 pixles
1453 for(i=0;i<pixle_length;i+=10){
1454 out+='<div onclick="'+this.instance_name+'.jt('+i*this.timeline_scale+');"' +
1455 ' style="z-index:2;position:absolute;left:'+i+'px;width:10px;height:20px;top:0px;"></div>';
1457 out+='<span style="position:absolute;left:'+i+'px;">|'+seconds2npt(Math.round(i*this.timeline_scale))+'</span>';
1464 jt:function( jh_time
){
1465 js_log('jt:' + jh_time
);
1466 var this_seq
= this;
1467 this.playline_time
= jh_time
;
1468 js_log('time: ' + seconds2npt(jh_time
) + ' ' + Math
.round(jh_time
/this.timeline_scale
));
1469 //render playline at given time
1470 $j('#'+this.timeline_id
+'_playline').css('left', Math
.round(jh_time
/this.timeline_scale
)+'px' );
1472 //update the thumb with the requested time:
1473 this.plObj
.updateThumbTime( jh_time
);
1475 //adjusts the current scale
1477 this.timeline_scale
= this.timeline_scale
*.75;
1478 this.do_refresh_timeline();
1479 js_log('zoomed in:'+this.timeline_scale
);
1481 zoom_out:function(){
1482 this.timeline_scale
= this.timeline_scale
*(1+(1/3));
1483 this.do_refresh_timeline();
1484 js_log('zoom out: '+this.timeline_scale
);
1486 do_refresh_timeline:function( preserve_selection
){
1487 js_log('Sequencer:do_refresh_timeline()');
1488 //@@todo should "lock" interface while refreshing timeline
1490 if(preserve_selection
){
1491 $j('.mv_selected_clip').each(function(){
1492 pSelClips
.push( $j(this).parent().attr('id') );
1496 this.plObj
.getDuration( true );
1498 this.plObj
.getHTML();
1500 this.render_playheadhead_seeker();
1501 this.render_tracks();
1502 this.jt(this.playline_time
);
1504 if(preserve_selection
){
1505 for(var i
=0;i
< pSelClips
.length
; i
++){
1506 $j('#' + pSelClips
[i
] + ' .mv_clip_thumb').addClass('mv_selected_clip');
1512 /* extension to mvPlayList to support sequencer features properties */
1513 var mvSeqPlayList = function( element
){
1514 return this.init( element
);
1516 mvSeqPlayList
.prototype = {
1517 init:function(element
){
1518 var myPlObj
= new mvPlayList(element
);
1521 for(var method
in myPlObj
){
1522 if(typeof this[method
] != 'undefined' ){
1523 this[ 'parent_' + method
]=myPlObj
[method
];
1525 this[method
] = myPlObj
[method
];
1529 this.org_control_height
= this.pl_layout
.control_height
;
1530 //do specific mods:(controls and title are managed by the sequencer)
1531 this.pl_layout
.title_bar_height
=0;
1532 this.pl_layout
.control_height
=0;
1534 setSliderValue:function( perc
){
1535 js_log('setSliderValue::'+ perc
);
1536 //get the track_clipThumb_height from parent mvSequencer
1537 var frame_width
= Math
.round( this.pSeq
.track_clipThumb_height
* 1.3333333 );
1538 var container_width
= frame_width
+60;
1540 var perc_clip
= this.cur_clip
.embed
.currentTime
/ this.cur_clip
.getDuration();
1542 var left_px
= parseInt( (this.cur_clip
.order
* container_width
) + (frame_width
*perc_clip
) ) + 'px';
1543 js_log("set " + perc
+ ' of cur_clip: ' + this.cur_clip
.order
+ ' lp:'+left_px
);
1546 //update the timeline playhead and
1547 $j('#' + this.seqObj
.timeline_id
+ '_playline').css('left', left_px
);
1549 //pass update request to parent:
1550 this.parent_setSliderValue( perc
);
1552 getControlsHTML:function(){
1553 //get controls from current clip add some playlist specific controls:
1554 this.cur_clip
.embed
.supports
['prev_next'] = true;
1555 this.cur_clip
.embed
.supports
['options'] = false;
1556 return ctrlBuilder
.getControls( this.cur_clip
.embed
);
1558 //override renderDisplay
1559 renderDisplay:function(){
1560 js_log('mvSequence:renderDisplay');
1561 //setup layout for title and dc_ clip container
1562 $j(this).html('<div id="dc_'+this.id
+'" style="width:'+this.width
+'px;' +
1563 'height:'+(this.height
)+'px;position:relative;" />');
1565 this.setupClipDisplay();