oxjs/source/Ox.UI/js/Video/Ox.VideoEditor.js

1151 lines
39 KiB
JavaScript
Raw Normal View History

2011-07-29 18:48:43 +00:00
// vim: et:ts=4:sw=4:sts=4:ft=javascript
2011-05-16 08:24:46 +00:00
2011-11-05 16:46:53 +00:00
'use strict';
2011-05-16 08:24:46 +00:00
/*@
Ox.VideoEditor <f:Ox.Element> VideoEditor Object
() -> <f> VideoEditor Object
(options) -> <f> VideoEditor Object
(options, self) -> <f> VideoEditor Object
options <o> Options object
self <o> shared private variable
@*/
2011-10-27 13:13:28 +00:00
// fixme: should be VideoAnnotationEditor
2011-04-22 22:03:10 +00:00
Ox.VideoEditor = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
2011-04-22 22:03:10 +00:00
.defaults({
annotationsFont: 'small',
annotationsRange: 'all',
2012-01-12 10:39:05 +00:00
annotationsSize: 256,
annotationsSort: 'position',
2011-10-22 21:03:42 +00:00
censored: [],
2011-04-22 22:03:10 +00:00
cuts: [],
duration: 0,
2011-12-22 15:47:46 +00:00
enableSubtitles: false,
2011-04-22 22:03:10 +00:00
find: '',
2011-05-18 16:00:29 +00:00
fps: 25,
2011-05-18 18:30:58 +00:00
getFrameURL: null,
2011-05-18 16:00:29 +00:00
getLargeTimelineImageURL: null,
getSmallTimelineImageURL: null,
'in': 0,
2011-04-22 22:03:10 +00:00
height: 0,
layers: [],
muted: false,
2011-05-18 16:00:29 +00:00
out: 0,
2011-04-22 22:03:10 +00:00
position: 0,
posterFrame: 0,
posterFrameControls: false,
resolution: 0,
selected: '',
2011-04-22 22:03:10 +00:00
showAnnotations: false,
2011-05-24 19:19:27 +00:00
showLargeTimeline: true,
2012-01-11 07:52:01 +00:00
showLayers: {},
2012-01-12 10:39:05 +00:00
showUsers: false,
2011-04-22 22:03:10 +00:00
subtitles: [],
tooltips: false,
2011-08-19 14:44:03 +00:00
videoRatio: 16/9,
2011-04-22 22:03:10 +00:00
videoSize: 'small',
video: '',
volume: 1,
2011-04-22 22:03:10 +00:00
width: 0
})
.options(options || {})
.mousedown(function() {
that.gainFocus();
})
.bindEvent({
2011-05-18 16:00:29 +00:00
key_0: toggleMuted,
2011-04-22 22:03:10 +00:00
key_shift_0: function() {
movePositionBy(-self.options.position);
},
key_alt_left: function() {
},
key_alt_right: function() {
},
key_alt_shift_left: function() {
},
key_alt_shift_right: function() {
},
key_backslash: function() {
2012-01-10 14:49:28 +00:00
selectAnnotation();
2011-04-22 22:03:10 +00:00
},
key_closebracket: function() {
2012-01-10 14:49:28 +00:00
movePositionTo('annotation', 1);
2011-04-22 22:03:10 +00:00
},
key_comma: function() {
movePositionTo('cut', -1);
},
key_dot: function() {
movePositionTo('cut', 1);
},
key_down: function() {
movePositionBy(self.sizes.timeline[0].width);
},
2012-01-10 14:49:28 +00:00
key_enter: function() {
if (self.editiing) {
submitAnnotation();
} else if (self.options.selected) {
editAnnotation();
}
},
2012-01-11 07:52:01 +00:00
key_equal: function() {
self.options.videoSize == 'small' && toggleSize();
},
2012-01-10 14:49:28 +00:00
key_escape: function() {
if (self.editing) {
blurAnnotation();
} else if (self.options.selected) {
2012-01-12 10:39:05 +00:00
selectAnnotation({id: ''});
2012-01-10 14:49:28 +00:00
}
},
key_f: function() {
setTimeout(function() {
2011-12-18 09:44:11 +00:00
self.$findInput.focusInput(true);
});
},
key_g: function() {
self.results.length && setPosition(getNextPosition('result', 1));
},
2011-04-22 22:03:10 +00:00
key_i: function() {
2011-05-19 10:18:39 +00:00
setPoint('in', self.options.position);
2011-04-22 22:03:10 +00:00
},
key_left: function() {
2011-05-18 18:30:58 +00:00
movePositionBy(-0.04);
2011-04-22 22:03:10 +00:00
},
2012-01-11 07:52:01 +00:00
key_minus: function() {
self.options.videoSize == 'large' && toggleSize();
},
2011-04-22 22:03:10 +00:00
key_o: function() {
2011-05-19 10:18:39 +00:00
setPoint('out', self.options.position);
2011-04-22 22:03:10 +00:00
},
key_openbracket: function() {
2012-01-10 14:49:28 +00:00
movePositionTo('annotation', -1);
2011-04-22 22:03:10 +00:00
},
key_p: playInToOut,
key_right: function() {
2011-05-18 18:30:58 +00:00
movePositionBy(0.04);
2011-04-22 22:03:10 +00:00
},
key_s: function() {
// toggleSize
},
key_shift_comma: function() {
movePositionTo('match', -1);
2011-04-22 22:03:10 +00:00
},
key_shift_dot: function() {
movePositionTo('match', 1);
2011-04-22 22:03:10 +00:00
},
key_shift_down: function() {
movePositionBy(self.options.duration);
},
key_shift_g: function() {
self.results.length && setPosition(getNextPosition('result', -1));
},
2011-04-22 22:03:10 +00:00
key_shift_left: function() {
movePositionBy(-1);
2011-04-22 22:03:10 +00:00
},
key_shift_i: function() {
goToPoint('in');
},
key_shift_o: function() {
goToPoint('out');
},
key_shift_p: function() {
// go to poster frame
},
2011-04-22 22:03:10 +00:00
key_shift_right: function() {
2011-05-18 18:30:58 +00:00
movePositionBy(1);
2011-04-22 22:03:10 +00:00
},
key_shift_up: function() {
2011-05-18 18:30:58 +00:00
movePositionBy(-self.options.position);
2011-04-22 22:03:10 +00:00
},
key_slash: function() {
2012-01-10 14:49:28 +00:00
selectCut();
2011-04-22 22:03:10 +00:00
},
2011-05-18 16:00:29 +00:00
key_space: togglePaused,
2011-04-22 22:03:10 +00:00
key_up: function() {
movePositionBy(-self.sizes.timeline[0].width);
}
});
2012-01-10 14:49:28 +00:00
Ox.loop(1, self.options.layers.length + 1, function(i) {
that.bindEvent('key_' + i, function() {
addAnnotation(self.options.layers[i - 1].id);
});
});
Ox.extend(self, {
2011-04-22 22:03:10 +00:00
$player: [],
$timeline: [],
controlsHeight: 16,
2012-01-09 20:25:38 +00:00
editing: false,
2011-04-22 22:03:10 +00:00
margin: 8,
});
2012-01-10 14:49:28 +00:00
Ox.print('VIDEO EDITOR OPTIONS', self.options)
2012-01-06 11:58:53 +00:00
self.words = [];
2011-05-19 19:03:35 +00:00
Ox.forEach(Ox.count(Ox.words(self.options.subtitles.map(function(subtitle) {
return subtitle.text;
}).join(' '))), function(count, word) {
self.words.push({count: count, word: word});
});
self.words = self.words.sort(function(a, b) {
return b.count - a.count;
}).map(function(obj) {
return obj.word;
});
self.$editor = Ox.Element()
2011-04-22 22:03:10 +00:00
.addClass('OxVideoEditor')
2012-01-09 20:25:38 +00:00
.mousedown(function(e) {
var $target = $(e.target);
!$target.is('.OxPosition') && !$target.is('input') && that.gainFocus();
2012-01-09 20:25:38 +00:00
// the following is needed to determine
// how to handle annotation input blur
if (self.editing) {
self.focused = true;
setTimeout(function() {
self.focused = false;
}, 25);
}
2011-04-22 22:03:10 +00:00
});
self.sizes = getSizes();
['play', 'in', 'out'].forEach(function(type, i) {
self.$player[i] = Ox.VideoPlayer({
2011-10-22 21:03:42 +00:00
censored: self.options.censored,
2011-05-18 16:00:29 +00:00
controlsBottom: type == 'play' ?
2011-05-20 14:11:42 +00:00
['play', 'playInToOut', 'volume', 'size', 'space', 'position'] :
2011-05-18 16:00:29 +00:00
['goto', 'set', 'space', 'position'],
2011-04-22 22:03:10 +00:00
duration: self.options.duration,
2011-08-18 14:03:48 +00:00
enableMouse: true,
2011-12-19 21:13:11 +00:00
enablePosition: true,
2011-12-22 15:47:46 +00:00
enableSubtitles: self.options.enableSubtitles,
2011-05-18 16:00:29 +00:00
externalControls: true,
2011-04-22 22:03:10 +00:00
find: self.options.find,
height: self.sizes.player[i].height,
id: 'player' + Ox.toTitleCase(type),
2011-05-18 16:00:29 +00:00
'in': self.options['in'],
muted: self.options.muted,
2011-05-18 16:00:29 +00:00
out: self.options.out,
paused: true,
position: type == 'play' ? self.options.position : self.options[type],
2011-04-22 22:03:10 +00:00
posterFrame: self.options.posterFrame,
resolution: self.options.resolution,
2011-05-18 16:00:29 +00:00
showMarkers: true,
showMilliseconds: 3,
2011-08-19 06:41:48 +00:00
sizeIsLarge: self.options.videoSize == 'large',
2011-04-22 22:03:10 +00:00
subtitles: self.options.subtitles,
type: type,
2011-05-18 18:30:58 +00:00
video: type == 'play' ? self.options.video : self.options.getFrameURL,
volume: self.options.volume,
2011-04-22 22:03:10 +00:00
width: self.sizes.player[i].width
})
.css({
left: self.sizes.player[i].left + 'px',
top: self.sizes.player[i].top + 'px'
})
.bindEvent(type == 'play' ? {
muted: function(data) {
that.triggerEvent('muted', data);
},
paused: function(data) {
that.triggerEvent('paused', data);
},
playing: function(data) {
setPosition(data.position, true);
},
position: function(data) {
setPosition(data.position);
},
resolution: function(data) {
that.triggerEvent('resolution', data);
},
size: toggleSize,
2011-12-22 15:47:46 +00:00
subtitles: function(data) {
that.triggerEvent('subtitles', data);
},
volume: function(data) {
that.triggerEvent('volume', data);
}
2011-04-22 22:03:10 +00:00
} : {
2011-05-18 16:00:29 +00:00
gotopoint: function() {
2011-04-22 22:03:10 +00:00
goToPoint(type);
},
position: function(data) {
setPoint(type, data.position);
},
2011-05-18 16:00:29 +00:00
setpoint: function() {
2011-05-19 10:18:39 +00:00
setPoint(type, self.options.position);
2011-04-22 22:03:10 +00:00
}
})
.appendTo(self.$editor);
});
self.$timeline[0] = Ox.LargeVideoTimeline({
2011-04-22 22:03:10 +00:00
cuts: self.options.cuts,
duration: self.options.duration,
find: self.options.find,
2011-05-18 16:00:29 +00:00
getImageURL: self.options.getLargeTimelineImageURL,
2011-04-22 22:03:10 +00:00
id: 'timelineLarge',
2011-05-18 16:00:29 +00:00
'in': self.options['in'],
//matches: self.options.matches,
out: self.options.out,
2011-04-22 22:03:10 +00:00
position: self.options.position,
subtitles: self.options.subtitles,
2011-05-18 16:00:29 +00:00
type: 'editor',
2011-04-22 22:03:10 +00:00
width: self.sizes.timeline[0].width
})
.css({
left: self.sizes.timeline[0].left + 'px',
top: self.sizes.timeline[0].top + 'px'
})
2011-08-17 19:34:34 +00:00
.bindEvent({
position: function(data) {
setPosition(data.position);
}
2011-08-17 19:34:34 +00:00
})
2011-04-22 22:03:10 +00:00
.appendTo(self.$editor);
self.$timeline[1] = Ox.BlockVideoTimeline({
2011-04-22 22:03:10 +00:00
cuts: self.options.cuts,
duration: self.options.duration,
find: self.options.find,
2011-05-18 16:00:29 +00:00
getImageURL: self.options.getSmallTimelineImageURL,
2011-04-22 22:03:10 +00:00
id: 'timelineSmall',
2011-05-18 16:00:29 +00:00
'in': self.options['in'],
out: self.options.out,
2011-04-22 22:03:10 +00:00
position: self.options.position,
2011-10-20 13:32:53 +00:00
results: find(self.options.find),
2011-10-17 10:23:16 +00:00
showPointMarkers: true,
showSubtitles: true,
2012-01-09 20:25:38 +00:00
state: self.options.selected ? 'selected' : 'default',
2011-04-22 22:03:10 +00:00
subtitles: self.options.subtitles,
videoId: self.options.videoId,
width: self.sizes.timeline[1].width
})
.css({
left: self.sizes.timeline[1].left + 'px',
2011-05-18 16:00:29 +00:00
top: self.sizes.timeline[1].top + 'px',
2011-04-22 22:03:10 +00:00
})
2011-08-17 19:34:34 +00:00
.bindEvent({
2012-01-10 14:49:28 +00:00
edit: editAnnotation,
position: function(data) {
setPosition(data.position);
2012-01-10 14:49:28 +00:00
},
select: function() {
selectAnnotation();
}
2011-08-17 19:34:34 +00:00
})
2011-04-22 22:03:10 +00:00
.appendTo(self.$editor);
2012-01-12 10:39:05 +00:00
self.$menubar = Ox.Bar({
size: 16
})
.addClass('OxVideoPlayer');
self.resolutions = [];
Ox.forEach(self.options.video, function(url, resolution) {
Ox.Log('Video', url, resolution);
2012-01-12 10:39:05 +00:00
self.resolutions.push({
id: resolution + '',
title: resolution + 'p',
checked: self.options.resolution == resolution
});
});
self.$keyboardShortcuts = $('<div>').css({margin: '16px'});
2011-05-20 14:11:42 +00:00
[
{key: Ox.UI.symbols.space, action: 'Play/Pause'},
{key: 'P', action: 'Play In to Out'},
{key: '0', action: 'Mute/Unmute'},
{key: '-', action: 'Turn Volume Down'},
{key: '+', action: 'Turn Volume Up'},
{key: Ox.UI.symbols.arrow_left, action: 'Go One Frame Back'},
{key: Ox.UI.symbols.arrow_right, action: 'Go One Frame Forward'},
{key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_left, action: 'Go One Second Back'},
{key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_right, action: 'Go One Second Forward'},
{key: Ox.UI.symbols.arrow_up, action: 'Go One Line Up'},
{key: Ox.UI.symbols.arrow_down, action: 'Go One Line Down'},
{key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_up, action: 'Go to First Frame'},
{key: Ox.UI.symbols.shift + Ox.UI.symbols.arrow_down, action: 'Go to Last Frame'},
{key: 'I', action: 'Set In Point'},
{key: 'O', action: 'Set Out Point'},
{key: Ox.UI.symbols.shift + 'I', action: 'Go to Out Point'},
{key: Ox.UI.symbols.shift + 'O', action: 'Go to Out Point'},
{key: '[', action: 'Go to Previous Annotation'},
{key: ']', action: 'Go to Next Annotation'},
{key: '\\', action: 'Select Current Annotation'},
{key: '<', action: 'Go to Previous Cut'},
{key: '>', action: 'Go to Next Cut'},
{key: '/', action: 'Select Current Cut'},
2011-05-20 14:11:42 +00:00
{key: 'F', action: 'Find'},
{key: Ox.UI.symbols.shift + 'G', action: 'Go to Previous Result'},
{key: 'G', action: 'Go to Next Result'},
{key: 'S', action: 'Select Current Annotation'},
{key: 'E', action: 'Edit Selected Annotation'},
2011-05-25 07:29:42 +00:00
{key: Ox.UI.symbols['return'], action: 'Submit'},
2011-05-20 14:11:42 +00:00
{key: Ox.UI.symbols.escape, action: 'Cancel'},
].forEach(function(shortcut) {
self.$keyboardShortcuts.append(
$('<div>').css({display: 'table-row'})
.append(
$('<div>').css({
display: 'table-cell',
height: '16px',
paddingRight: '16px',
//fontWeight: 'bold',
textAlign: 'right'
})
.html(shortcut.key)
)
.append(
$('<div>').css({display: 'table-cell'})
.html(shortcut.action)
)
);
});
2011-05-20 14:11:42 +00:00
2011-12-20 07:20:37 +00:00
self.$videoMenuButton = Ox.MenuButton({
items: [
2012-01-03 07:54:29 +00:00
{id: 'toggleSize', title: 'Large Player', checked: self.options.playerSize == 'large', keyboard: 'shift +'},
{},
{group: 'resolution', min: 1, max: 1, items: self.resolutions},
{},
{id: 'largeTimeline', title: 'Hide Large Timeline', disabled: true},
{id: 'subtitlesTimeline', title: 'Hide Subtitles on Large Timeline', disabled: true},
{},
{id: 'downloadVideo', title: 'Download Video...', disabled: true},
{id: 'downloadSelection', title: 'Download Selection...', disabled: true},
{id: 'embedSelection', title: 'Embed Selection...', disabled: true},
{},
{id: 'keyboard', title: 'Keyboard Shortcuts...', keyboard: 'h'}
],
style: 'square',
title: 'set',
2011-05-20 14:11:42 +00:00
tooltip: 'Actions and Settings',
type: 'image'
})
.css({float: 'left'})
.bindEvent({
2011-05-20 14:11:42 +00:00
click: function(data) {
var id = data.id;
2012-01-03 07:54:29 +00:00
if (id == 'keyboard') {
2011-05-20 14:11:42 +00:00
var dialog = Ox.Dialog({
buttons: [
Ox.Button({id: 'close', title: 'Close'})
.bindEvent({click: function() { dialog.close(); }})
],
content: self.$keyboardShortcuts,
height: 384,
keys: {enter: 'close', escape: 'close'},
title: 'Keyboard Shortcuts',
width: 256
}).open();
}
},
change: function(data) {
2012-01-03 07:54:29 +00:00
var id = data.id;
if (id == 'toggleSize') {
toggleSize();
} else if (data.id == 'resolution') {
2011-12-28 10:29:53 +00:00
self.options.resolution = parseInt(data.checked[0].id);
2011-12-20 13:45:24 +00:00
self.$player[0].options({resolution: self.options.resolution});
2011-05-20 14:11:42 +00:00
}
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
2011-11-03 15:42:41 +00:00
if (self.options.posterFrameControls) {
2011-05-20 14:11:42 +00:00
self.$goToPosterButton = Ox.Button({
style: 'symbol',
title: 'goToPoster',
tooltip: 'Go to Poster Frame',
type: 'image'
})
.css({float: 'left'})
.bindEvent({
click: function() {
setPosition(self.options.posterFrame);
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
self.$setPosterButton = Ox.Button({
disabled: true,
style: 'symbol',
title: 'setPoster',
tooltip: 'Set Poster Frame',
type: 'image'
})
.css({float: 'left'})
.bindEvent({
click: function() {
2011-12-22 07:24:20 +00:00
self.$goToPosterButton.toggleOption('disabled');
self.$setPosterButton.toggleOption('disabled');
self.$unlockPosterButton.toggle();
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
self.$unlockPosterButton = Ox.Button({
style: 'symbol',
title: [
{id: 'lock', title: 'lock'},
{id: 'unlock', title: 'unlock', selected: true}
],
tooltip: ['Lock Poster Frame', 'Unlock Poster Frame'],
type: 'image'
})
.css({float: 'left'})
.bindEvent({
click: function() {
2011-12-22 07:24:20 +00:00
self.$setPosterButton.toggleOption('disabled');
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
}
self.$clearButton = Ox.Button({
disabled: self.options.find === '',
style: 'symbol',
title: 'close',
tooltip: 'Clear',
type: 'image'
})
.css({float: 'right'})
.bindEvent({
click: function() {
self.$findInput.clearInput();
submitFindInput('');
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
self.$findInput = Ox.Input({
2011-05-19 19:03:35 +00:00
autocomplete: self.words,
autocompleteReplace: true,
2011-05-19 19:03:35 +00:00
autocompleteSelect: true,
autocompleteSelectHighlight: true,
autocompleteSelectMax: 10,
2011-05-19 19:03:35 +00:00
autocompleteSelectSubmit: true,
changeOnKeypress: true,
placeholder: 'Find...',
2011-10-20 13:32:53 +00:00
value: self.options.find,
width: 96
})
.css({float: 'right', background: 'transparent'})
.bindEvent({
change: function(data) {
submitFindInput(data.value, false);
},
submit: function(data) {
submitFindInput(data.value, true);
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
self.$nextButton = Ox.Button({
disabled: true,
style: 'symbol',
title: 'arrowRight',
tooltip: 'Next Result',
type: 'image'
})
.css({float: 'right'})
.bindEvent({
click: function() {
setPosition(getNextPosition('result', 1));
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
self.$previousButton = Ox.Button({
disabled: true,
style: 'symbol',
title: 'arrowLeft',
tooltip: 'Previous Result',
type: 'image'
})
.css({float: 'right'})
.bindEvent({
click: function() {
setPosition(getNextPosition('result', -1));
}
})
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar);
self.$results = $('<div>')
.css({float: 'right', width: '36px', padding: '2px 4px 0 0', fontSize: '9px', textAlign: 'right', cursor: 'default', opacity: 0.25})
.html('0')
2012-01-12 10:39:05 +00:00
.appendTo(self.$menubar.$element);
2011-05-20 14:11:42 +00:00
2012-01-12 10:39:05 +00:00
self.$annotationPanel = Ox.AnnotationPanel({
editable: true,
font: self.options.annotationsFont,
'in': self.options['in'],
layers: self.options.layers,
out: self.options.out,
position: self.options.position,
range: self.options.annotationsRange,
selected: self.options.selected,
sort: self.options.annotationsSort,
showLayers: self.options.showLayers,
showUsers: self.options.showUsers,
width: self.options.annotationsSize
2011-05-20 14:11:42 +00:00
})
.bindEvent({
2012-01-12 10:39:05 +00:00
add: function(data) {
addAnnotation(data.layer);
},
annotationsfont: function(data) {
that.triggerEvent('annotationsfont', data);
},
annotationsrange: function(data) {
that.triggerEvent('annotationsrange', data);
},
annotationssort: function(data) {
that.triggerEvent('annotationssort', data);
},
blur: function() {
Ox.print('VE-BLUR')
self.editing = false;
setTimelineState();
},
edit: function() {
self.editing = true;
setTimelineState();
},
paused: togglePaused,
remove: function(data) {
that.triggerEvent('removeannotation', {
id: data.id,
layer: data.layer
});
},
2012-01-12 19:04:32 +00:00
resize: resizeAnnotations,
resizeend: resizeendAnnotations,
select: selectAnnotation,
2012-01-12 10:39:05 +00:00
submit: submitAnnotation,
2012-01-12 19:04:32 +00:00
toggle: toggleAnnotations,
togglelayer: function(data) {
2012-01-12 10:39:05 +00:00
that.triggerEvent('togglelayer', {
collapsed: data.collapsed,
layer: data.layer
});
2012-01-11 07:52:01 +00:00
}
2012-01-12 10:39:05 +00:00
});
2012-01-11 07:52:01 +00:00
that.setElement(
Ox.SplitPanel({
elements: [
{
element: Ox.SplitPanel({
elements: [
{
2012-01-12 10:39:05 +00:00
element: self.$menubar,
size: 16
},
{
element: self.$editor
}
],
orientation: 'vertical'
})
},
{
collapsed: !self.options.showAnnotations,
collapsible: true,
2012-01-12 10:39:05 +00:00
element: self.$annotationPanel,
resizable: true,
resize: [192, 256, 320, 384],
size: self.options.annotationsSize,
tooltip: self.options.tooltips ? 'annotations' : false
}
],
orientation: 'horizontal'
})
);
2011-04-22 22:03:10 +00:00
// we need a timeout so that a chained bindEvent
// actually catches the event
self.options.find && setTimeout(function() {
submitFindInput(self.options.find, true);
}, 0);
2012-01-10 14:49:28 +00:00
function addAnnotation(layer) {
that.triggerEvent('addannotation', {
'in': self.options['in'],
layer: layer,
out: self.options.out,
value: ''
});
}
function blurAnnotation() {
2012-01-09 20:25:38 +00:00
self.editing = false;
2012-01-10 14:49:28 +00:00
setTimelineState();
2012-01-12 10:39:05 +00:00
self.$annotationPanel.blurItem();
2012-01-10 14:49:28 +00:00
}
function editAnnotation() {
self.editing = true;
setTimelineState();
2012-01-12 10:39:05 +00:00
self.$annotationPanel.editItem();
2012-01-03 10:26:15 +00:00
}
function find(query) {
var results = [];
if (query.length) {
query = query.toLowerCase();
results = Ox.map(self.options.subtitles, function(subtitle) {
return subtitle.text.toLowerCase().indexOf(query) > -1 ? {
'in': subtitle['in'],
out: subtitle.out
} : null;
});
}
return results;
}
2012-01-10 14:49:28 +00:00
function getAnnotation() {
// Get annotation at current position
var annotations = [];
self.options.layers.forEach(function(layer) {
layer.items.forEach(function(item) {
if (
item['in'] <= self.options.position
&& item.out >= self.options.position
) {
annotations.push(item);
}
});
});
annotations.sort(function(a, b) {
var aValue = self.options.position - a['in'],
bValue = self.options.position - b['in'],
ret = 0;
if (aValue < bValue) {
ret = -1;
} else if (aValue > bValue) {
ret = 1;
} else if (a.duration < b.duration) {
ret = -1
} else if (a.duration > b.duration) {
ret = 1;
} else if (a.value < b.value) {
ret = -1
} else if (a.value > b.value) {
ret = 1;
}
return ret;
});
return annotations.length ? annotations[0] : {id: ''};
}
// fixme: why not goToNextPosition()?
2011-04-22 22:03:10 +00:00
function getNextPosition(type, direction) {
var found = false,
position = 0,
positions;
2012-01-10 14:49:28 +00:00
if (type == 'annotation') {
positions = Ox.sort(Ox.unique(Ox.flatten(
self.options.layers.map(function(layer) {
return layer.items.map(function(item) {
return item['in'];
});
})
)));
} else if (type == 'cut') {
positions = Ox.merge(0, self.options.cuts, self.options.duration);
} else if (type == 'result') {
positions = self.results.map(function(v) {
return v['in'];
2011-04-22 22:03:10 +00:00
});
}
direction == -1 && positions.reverse();
Ox.forEach(positions, function(v) {
if (direction == 1 ? v > self.options.position : v < self.options.position) {
position = v;
found = true;
return false;
}
});
direction == -1 && positions.reverse();
if (!found) {
position = positions[direction == 1 ? 0 : positions.length - 1];
}
return position;
}
2012-01-10 14:49:28 +00:00
/*
2011-04-22 22:03:10 +00:00
function getPoints(type) {
var found = false,
points,
positions = [];
2012-01-10 14:49:28 +00:00
if (type == 'annotation') {
} else if (type == 'cut') {
2011-04-22 22:03:10 +00:00
positions = self.options.cuts;
2012-01-10 14:49:28 +00:00
} else if (type == 'result') {
2011-04-22 22:03:10 +00:00
// ...
} else if (type == 'subtitle') {
2012-01-10 14:49:28 +00:00
// FIXME: remove? annotation?
2011-04-22 22:03:10 +00:00
self.options.subtitles.forEach(function(v, i) {
positions.push(v['in']);
positions.push(v.out);
});
}
positions.indexOf(0) == -1 && positions.unshift(0);
2012-01-10 14:49:28 +00:00
positions.indexOf(self.options.duration) == -1
&& positions.push(self.options.duration);
2011-04-22 22:03:10 +00:00
Ox.forEach(positions, function(v, i) {
if (v > self.options.position) {
points = [positions[i - 1], positions[i]];
found = true;
return false;
}
});
return points;
}
2012-01-10 14:49:28 +00:00
*/
2011-04-22 22:03:10 +00:00
function getSizes(scrollbarIsVisible) {
var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE,
2011-11-03 15:42:41 +00:00
contentWidth = self.options.width
- (self.options.showAnnotations * self.options.annotationsSize) - 1
- (scrollbarIsVisible ? scrollbarWidth : 0),
2011-04-22 22:03:10 +00:00
height,
lines,
size = {
player: [],
timeline: []
},
width, widths;
2012-01-09 20:25:38 +00:00
function getHeight() {
return size.player[0].height + self.controlsHeight
+ size.timeline[0].height + lines * 16
+ (lines + 3) * self.margin;
}
2011-04-22 22:03:10 +00:00
if (self.options.videoSize == 'small') {
width = 0;
widths = Ox.divideInt(contentWidth - 4 * self.margin, 3);
[1, 0, 2].forEach(function(v, i) {
size.player[v] = {
left: (i + 0.5) * self.margin + width,
top: self.margin / 2,
width: widths[i],
2011-08-19 14:44:03 +00:00
height: Math.round(widths[1] / self.options.videoRatio)
};
2011-04-22 22:03:10 +00:00
width += widths[i];
});
} else {
size.player[0] = {
left: self.margin / 2,
top: self.margin / 2,
2011-08-19 14:44:03 +00:00
width: Math.round((contentWidth - 3 * self.margin + (self.controlsHeight + self.margin) / 2 * self.options.videoRatio) * 2/3),
};
2011-08-19 14:44:03 +00:00
size.player[0].height = Math.round(size.player[0].width / self.options.videoRatio);
2011-04-22 22:03:10 +00:00
size.player[1] = {
left: size.player[0].left + size.player[0].width + self.margin,
top: size.player[0].top,
width: contentWidth - 3 * self.margin - size.player[0].width
};
2011-08-19 14:44:03 +00:00
size.player[1].height = Math.ceil(size.player[1].width / self.options.videoRatio);
2011-04-22 22:03:10 +00:00
size.player[2] = {
left: size.player[1].left,
top: size.player[0].top + size.player[1].height + self.controlsHeight + self.margin,
width: size.player[1].width,
height: size.player[0].height - size.player[1].height - self.controlsHeight - self.margin
};
2011-04-22 22:03:10 +00:00
}
size.timeline[0] = {
left: self.margin / 2,
top: size.player[0].height + self.controlsHeight + 1.5 * self.margin,
width: contentWidth - 2 * self.margin,
height: 64
};
2011-04-22 22:03:10 +00:00
size.timeline[1] = {
left: size.timeline[0].left,
top: size.timeline[0].top + size.timeline[0].height + self.margin,
width: size.timeline[0].width
};
2011-04-22 22:03:10 +00:00
lines = Math.ceil(self.options.duration / size.timeline[1].width);
height = getHeight();
self.$editor.css({
overflowY: (scrollbarIsVisible && height <= self.options.height - 16) ? 'scroll' : 'auto'
2011-04-22 22:03:10 +00:00
});
return (!scrollbarIsVisible && height > self.options.height - 16) ? getSizes(true) : size;
2011-04-22 22:03:10 +00:00
}
function goToPoint(point) {
2011-05-19 10:18:39 +00:00
setPosition(self.options[point]);
2011-04-22 22:03:10 +00:00
}
function movePositionBy(sec) {
2011-05-19 10:18:39 +00:00
setPosition(Ox.limit(self.options.position + sec, 0, self.options.duration));
2011-04-22 22:03:10 +00:00
}
function movePositionTo(type, direction) {
2011-05-19 10:18:39 +00:00
setPosition(getNextPosition(type, direction));
2011-04-22 22:03:10 +00:00
}
function playInToOut() {
self.$player[0].playInToOut();
}
function resizeAnnotations(data) {
self.options.annotationsSize = data.size;
2011-04-22 22:03:10 +00:00
setSizes();
2012-01-12 19:04:32 +00:00
self.$annotationPanel.options({width: data.size});
2011-04-22 22:03:10 +00:00
}
function resizeendAnnotations(data) {
2012-01-12 19:04:32 +00:00
that.triggerEvent('annotationssize', {size: data.size});
2011-04-22 22:03:10 +00:00
}
function selectAnnotation(data) {
2012-01-09 20:25:38 +00:00
//Ox.print('VE.sA')
2012-01-10 14:49:28 +00:00
if (Ox.isUndefined(data)) {
2012-01-12 10:39:05 +00:00
// doubleclick on small timeline
2012-01-10 14:49:28 +00:00
data = getAnnotation();
2012-01-12 19:04:32 +00:00
} else if (!data.id) {
that.gainFocus();
2012-01-10 14:49:28 +00:00
}
2012-01-09 20:25:38 +00:00
self.editing = false;
2012-01-10 14:49:28 +00:00
self.options.selected = data.id;
2012-01-12 10:39:05 +00:00
// fixme: what is the following supposed to do?
2012-01-10 14:49:28 +00:00
if (self.options.selected && self.options.annotationsRange != 'position') {
setPosition(data['in']);
}
2012-01-09 20:25:38 +00:00
if (self.options.selected) {
setPoint('in', data['in']);
setPoint('out', data.out);
}
2012-01-12 10:39:05 +00:00
self.$annotationPanel.options({selected: self.options.selected});
2012-01-10 14:49:28 +00:00
setTimelineState();
that.triggerEvent('select', {
id: self.options.selected
});
2011-04-22 22:03:10 +00:00
}
2012-01-10 14:49:28 +00:00
function selectCut() {
var points = {
'in': Ox.last(self.options.cuts),
out: self.options.duration
};
Ox.forEach(self.options.cuts, function(cut, i) {
if (cut > self.options.position) {
points = {
'in': i ? self.options.cuts[i - 1] : 0,
out: cut - 1 / self.options.fps
};
return false;
}
});
self.options.selected = '';
setTimelineState();
setPoint('in', points['in']);
setPoint('out', points.out);
2011-04-22 22:03:10 +00:00
}
2012-01-06 11:58:53 +00:00
function setPoint(point, position, annotation) {
2011-05-18 18:30:58 +00:00
var otherPoint = point == 'in' ? 'out' : 'in';
2011-05-19 10:18:39 +00:00
self.options[point] = position;
2012-01-09 20:25:38 +00:00
/*
// should only happen through interaction
if (self.options.selected && !self.editing) {
self.options.selected = '';
2012-01-10 14:49:28 +00:00
setTimelineState();
}
2012-01-09 20:25:38 +00:00
*/
2011-05-18 18:30:58 +00:00
self.$player.forEach(function($player) {
$player.options(point, self.options[point]);
});
2011-05-18 16:00:29 +00:00
self.$player[point == 'in' ? 1 : 2].options({
position: self.options[point]
2011-04-22 22:03:10 +00:00
});
2011-05-18 16:00:29 +00:00
self.$timeline.forEach(function($timeline) {
$timeline.options(point, self.options[point]);
2011-04-22 22:03:10 +00:00
});
2011-05-18 16:00:29 +00:00
if (self.options['in'] > self.options.out) {
2011-05-19 10:18:39 +00:00
setPoint(point == 'in' ? 'out' : 'in', position);
2011-05-18 16:00:29 +00:00
}
2012-01-12 10:39:05 +00:00
self.$annotationPanel.options({
'in': self.options['in'],
out: self.options.out
});
that.triggerEvent('points', {
'in': self.options['in'],
out: self.options.out
});
2012-01-09 20:25:38 +00:00
if (self.editing) {
that.triggerEvent('editannotation', {
id: self.options.selected,
'in': self.options['in'],
out: self.options.out
});
}
2011-04-22 22:03:10 +00:00
}
function setPosition(position, playing) {
2011-05-19 10:18:39 +00:00
self.options.position = position;
!playing && self.$player[0].options({
2011-04-22 22:03:10 +00:00
position: self.options.position
});
self.$timeline.forEach(function($timeline) {
$timeline.options({
position: self.options.position
});
});
2012-01-12 10:39:05 +00:00
self.$annotationPanel.options({
position: self.options.position
2011-04-22 22:03:10 +00:00
});
!playing && that.triggerEvent('position', {
position: self.options.position
});
2011-04-22 22:03:10 +00:00
}
function setSizes() {
self.sizes = getSizes();
2011-05-18 16:00:29 +00:00
self.$player.forEach(function($player, i) {
$player.options({
2011-04-22 22:03:10 +00:00
height: self.sizes.player[i].height,
width: self.sizes.player[i].width
})
.css({
left: self.sizes.player[i].left + 'px',
top: self.sizes.player[i].top + 'px'
});
});
2011-05-18 16:00:29 +00:00
self.$timeline.forEach(function($timeline, i) {
$timeline.options({
2011-04-22 22:03:10 +00:00
width: self.sizes.timeline[i].width
})
.css({
left: self.sizes.timeline[i].left + 'px',
top: self.sizes.timeline[i].top + 'px'
});
});
}
2012-01-10 14:49:28 +00:00
function setTimelineState() {
2012-01-09 20:25:38 +00:00
self.$timeline[1].options({
state: self.editing ? 'editing'
: self.options.selected ? 'selected'
: 'default'
});
//Ox.print('SET STATE', self.$timeline[1].options('state'))
}
2012-01-10 14:49:28 +00:00
function submitAnnotation(data) {
self.editing = false;
setTimelineState();
data['in'] = self.options['in'];
data.out = self.options.out;
that.triggerEvent('editannotation', data);
}
function submitFindInput(value, hasPressedEnter) {
self.options.find = value;
self.results = find(self.options.find);
2011-12-23 06:08:41 +00:00
self.$results
.css({opacity: self.results.length ? 1 : 0.25})
.html(self.results.length);
self.$previousButton.options({
disabled: self.results.length <= 1
});
self.$nextButton.options({
disabled: self.results.length <= 1
});
self.$clearButton.options({
disabled: !self.options.find
});
2011-12-23 06:08:41 +00:00
self.$player.forEach(function($player) {
$player.options({find: self.options.find});
});
2011-12-23 06:08:41 +00:00
self.$timeline.forEach(function($timeline) {
$timeline.options({find: self.options.find});
});
2011-12-23 06:08:41 +00:00
self.$timeline[1].options({results: self.results});
if (hasPressedEnter) {
that.triggerEvent('find', {find: self.options.find});
if (self.results.length) {
setPosition(getNextPosition('result', 1));
} else {
2011-12-18 09:44:11 +00:00
self.$findInput.focusInput(true);
}
}
}
function toggleAnnotations(data) {
2011-04-22 22:03:10 +00:00
self.options.showAnnotations = !data.collapsed;
setSizes();
2011-08-17 19:34:34 +00:00
that.triggerEvent('toggleannotations', {
showAnnotations: self.options.showAnnotations
});
2011-04-22 22:03:10 +00:00
}
2011-05-18 16:00:29 +00:00
function toggleMuted() {
self.$player[0].toggleMuted();
2011-04-22 22:03:10 +00:00
}
2011-05-18 16:00:29 +00:00
function togglePaused() {
self.$player[0].togglePaused();
self.$player[0].options('paused') && that.triggerEvent('position', {
2012-01-11 11:54:58 +00:00
position: self.$player[0].options('position')
});
2011-04-22 22:03:10 +00:00
}
function toggleSize() {
self.options.videoSize = self.options.videoSize == 'small' ? 'large' : 'small';
2011-04-22 22:03:10 +00:00
setSizes();
that.triggerEvent('togglesize', {
size: self.options.videoSize
});
}
2011-04-29 12:40:51 +00:00
self.setOption = function(key, value) {
2011-04-22 22:03:10 +00:00
if (key == 'width' || key == 'height') {
setSizes();
} else if (key == 'position') {
setPosition(value);
} else if (key == 'showAnnotations') {
that.$element.toggle(1);
2011-04-22 22:03:10 +00:00
}
};
2011-09-14 14:34:33 +00:00
/*@
addAnnotation <f> add annotation
(layer, item) -> <o> add annotation to layer
layer <s> layer id
2012-01-12 10:39:05 +00:00
annotation <o> annotation to add
2011-09-14 14:34:33 +00:00
@*/
2012-01-12 10:39:05 +00:00
that.addAnnotation = function(layer, annotation) {
self.$annotationPanel.addItem(layer, annotation);
};
2011-04-22 22:03:10 +00:00
2011-09-14 14:34:33 +00:00
/*@
2012-01-03 10:26:15 +00:00
removeAnnotation <f> remove annotation
2011-09-14 14:34:33 +00:00
(layer, ids) -> <o> remove annotation from layer
layer <s> layer id
ids <a> array of item ids to remove
@*/
2012-01-12 10:39:05 +00:00
/*
2012-01-03 10:26:15 +00:00
that.removeAnnotation = function(layer, id) {
var i = Ox.getIndexById(self.options.layers, layer);
2012-01-03 10:26:15 +00:00
self.$annotationPanel[i].removeItem(id);
};
2012-01-12 10:39:05 +00:00
*/
2011-04-22 22:03:10 +00:00
return that;
};