Ox.VideoEditor = function(options, self) { var self = self || {}, that = new Ox.Element('div', self) .defaults({ annotationsSize: 0, cuts: [], duration: 0, find: '', frameURL: function() {}, fps: 25, // fixme: doesn't get handed through to player height: 0, largeTimeline: true, layers: [], matches: [], points: [0, 0], position: 0, posterFrame: 0, showAnnotations: false, subtitles: [], videoHeight: 0, videoId: '', videoWidth: 0, videoSize: 'small', videoURL: '', width: 0 }) .options(options || {}) .mousedown(function() { that.gainFocus(); }) .bindEvent({ 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() { select('subtitle'); }, key_closebracket: function() { movePositionTo('subtitle', 1); }, key_comma: function() { movePositionTo('cut', -1); }, key_dot: function() { movePositionTo('cut', 1); }, key_down: function() { movePositionBy(self.sizes.timeline[0].width); }, key_i: function() { setPoint('in'); }, key_left: function() { movePositionBy(-1); }, key_m: toggleMute, key_o: function() { setPoint('out'); }, key_openbracket: function() { movePositionTo('subtitle', -1); }, key_p: playInToOut, key_right: function() { movePositionBy(1); }, key_s: function() { // toggleSize }, key_shift_comma: function() { movePositionTo('match', -1) }, key_shift_dot: function() { movePositionTo('match', 1) }, key_shift_down: function() { movePositionBy(self.options.duration); }, key_shift_left: function() { movePositionBy(-0.04); //movePositionBy(-60); }, key_shift_i: function() { goToPoint('in'); }, key_shift_o: function() { goToPoint('out'); }, key_shift_right: function() { movePositionBy(0.04); //movePositionBy(60); }, key_shift_up: function() { movePositionBy(-self.options.duration); }, key_slash: function() { select('cut'); }, key_space: togglePlay, key_up: function() { movePositionBy(-self.sizes.timeline[0].width); } }); $.extend(self, { $player: [], $timeline: [], controlsHeight: 16, margin: 8, videoRatio: self.options.videoWidth / self.options.videoHeight }); self.$editor = new Ox.Element() .addClass('OxVideoEditor') .click(function() { that.gainFocus() }); self.sizes = getSizes(); ['play', 'in', 'out'].forEach(function(type, i) { self.$player[i] = new Ox.VideoEditorPlayer({ duration: self.options.duration, find: self.options.find, height: self.sizes.player[i].height, id: 'player' + Ox.toTitleCase(type), points: self.options.points, position: type == 'play' ? self.options.position : self.options.points[type == 'in' ? 0 : 1], posterFrame: self.options.posterFrame, subtitles: self.options.subtitles, type: type, url: type == 'play' ? self.options.videoURL : self.options.frameURL, width: self.sizes.player[i].width }) .css({ left: self.sizes.player[i].left + 'px', top: self.sizes.player[i].top + 'px' }) .bindEvent(type == 'play' ? { playing: changePlayer, togglesize: toggleSize } : { change: function() { goToPoint(type); }, set: function() { setPoint(type); } }) .appendTo(self.$editor); }); self.$timeline[0] = new Ox.LargeTimeline({ cuts: self.options.cuts, duration: self.options.duration, find: self.options.find, id: 'timelineLarge', matches: self.options.matches, points: self.options.points, position: self.options.position, subtitles: self.options.subtitles, videoId: self.options.videoId, width: self.sizes.timeline[0].width }) .css({ left: self.sizes.timeline[0].left + 'px', top: self.sizes.timeline[0].top + 'px' }) .bindEvent('change', changeTimelineLarge) .bindEvent('changeEnd', changeTimelineLarge) .appendTo(self.$editor); self.$timeline[1] = new Ox.BlockTimeline({ cuts: self.options.cuts, duration: self.options.duration, find: self.options.find, id: 'timelineSmall', matches: self.options.matches, points: self.options.points, position: self.options.position, subtitles: self.options.subtitles, videoId: self.options.videoId, width: self.sizes.timeline[1].width }) .css({ left: self.sizes.timeline[1].left + 'px', top: self.sizes.timeline[1].top + 'px' }) .bindEvent('change', changeTimelineSmall) .appendTo(self.$editor); self.$annotations = new Ox.Element() .css({ overflowY: 'auto' }) .bindEvent({ resize: resizeAnnotations, toggle: toggleAnnotations }); self.$annotationPanel = []; self.options.layers.forEach(function(layer, i) { self.$annotationPanel[i] = new Ox.AnnotationPanel( $.extend({ width: self.options.annotationSize }, layer) ) .bindEvent({ add: function(event, data) { data.layer = layer.id; data['in'] = self.options.points[0]; data.out = self.options.points[1]; that.triggerEvent('addAnnotation', data); }, 'delete': function(event, data) { data.layer = layer.id; that.triggerEvent('removeAnnotations', data); }, select: function(event, data) { self.options.layers.forEach(function(l, j) { // fixme: l? j? if(l.id != layer.id) { self.$annotationPanel[j].deselectItems(); } }); selectAnnotation(event, data); }, submit: updateAnnotation }); self.$annotationPanel[i] .appendTo(self.$annotations); }); that.$element = new Ox.SplitPanel({ elements: [ { element: self.$editor }, { collapsed: !self.options.showAnnotations, collapsible: true, element: self.$annotations, resizable: true, resize: [192, 256, 320, 384], size: self.options.annotationsSize } ], orientation: 'horizontal' }); function changePlayer(event, data) { self.options.position = data.position; self.$timeline[0].options({ position: data.position }); self.$timeline[1].options({ position: data.position }); } function changeTimelineLarge(event, data) { self.options.position = data.position; self.$player[0].options({ position: data.position }); self.$timeline[1].options({ position: data.position }); } function changeTimelineSmall(event, data) { self.options.position = data.position; self.$player[0].options({ position: data.position }); self.$timeline[0].options({ position: data.position }); } function getNextPosition(type, direction) { var found = false, position = 0, positions; if (type == 'cut') { positions = self.options.cuts; } else if (type == 'match') { positions = $.map(self.options.matches, function(v, i) { return self.options.subtitles[v]['in']; }); } else if (type == 'subtitle') { positions = $.map(self.options.subtitles, function(v, i) { return v['in']; }); } 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; } function getPoints(type) { var found = false, points, positions = []; if (type == 'cut') { positions = self.options.cuts; } else if (type == 'match') { // ... } else if (type == 'subtitle') { self.options.subtitles.forEach(function(v, i) { positions.push(v['in']); positions.push(v.out); }); } positions.indexOf(0) == -1 && positions.unshift(0); positions.indexOf(self.options.duration) == -1 && positions.push(self.options.duration); Ox.forEach(positions, function(v, i) { if (v > self.options.position) { points = [positions[i - 1], positions[i]]; found = true; return false; } }); return points; } function getSizes(scrollbarIsVisible) { //Ox.print('getSizes', scrollbarIsVisible) var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE, contentWidth = self.options.width - (self.options.showAnnotations * self.options.annotationsSize) - 1 - (scrollbarIsVisible ? scrollbarWidth : 0), height, lines, size = { player: [], timeline: [] }, width, widths; 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], height: Math.round(widths[1] / self.videoRatio) } width += widths[i]; }); } else { size.player[0] = { left: self.margin / 2, top: self.margin / 2, width: Math.round((contentWidth - 3 * self.margin + (self.controlsHeight + self.margin) / 2 * self.videoRatio) * 2/3), } size.player[0].height = Math.round(size.player[0].width / self.videoRatio); 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 } size.player[1].height = Math.ceil(size.player[1].width / self.videoRatio) 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 } } 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 } size.timeline[1] = { left: size.timeline[0].left, top: size.timeline[0].top + size.timeline[0].height + self.margin, width: size.timeline[0].width } lines = Math.ceil(self.options.duration / size.timeline[1].width); height = getHeight(); self.$editor.css({ overflowY: (scrollbarIsVisible && height <= self.options.height) ? 'scroll' : 'auto' }); return (!scrollbarIsVisible && height > self.options.height) ? getSizes(true) : size; function getHeight() { return size.player[0].height + self.controlsHeight + size.timeline[0].height + lines * 16 + (lines + 3) * self.margin; } } function goToPoint(point) { self.options.position = self.options.points[point == 'in' ? 0 : 1]; setPosition(); that.triggerEvent('change', { position: self.options.position }); } function movePositionBy(sec) { self.options.position = Ox.limit(self.options.position + sec, 0, self.options.duration); setPosition(); that.triggerEvent('change', { position: self.options.position }); } function movePositionTo(type, direction) { self.options.position = getNextPosition(type, direction); setPosition(); that.triggerEvent('change', { position: self.options.position }); } function playInToOut() { self.$player[0].playInToOut(); } function resizeAnnotations(event, data) { self.options.annotationsSize = data; setSizes(); } function resizeEditor(event, data) { var width = data - 2 * margin + 100; resizeVideoPlayers(width); $timelineLarge.options({ width: width }); $timelineSmall.options({ width: width }); } function resizePlayers() { self.$player.forEach(function(v, i) { v.options({ width: size[i].width, height: size[i].height }) .css({ left: size[i].left + 'px', top: size[i].top + 'px', }); }); } function selectAnnotation(event, data) { self.options.position = data['in'] self.options.points = [data['in'], data.out]; setPosition(); setPoints(); } function updateAnnotation(event, data) { data['in'] = self.options.points[0]; data.out = self.options.points[1]; that.triggerEvent('updateAnnotation', data); } function select(type) { self.options.points = getPoints(type); setPoints(); } function setPoint(point) { self.options.points[point == 'in' ? 0 : 1] = self.options.position; if (self.options.points[1] < self.options.points[0]) { self.options.points[point == 'in' ? 1 : 0] = self.options.position; } setPoints(); } function setPoints() { self.$player.forEach(function(v, i) { v.options($.extend({ points: self.options.points }, i ? { position: self.options.points[i - 1] } : {})); }); self.$timeline.forEach(function(v) { v.options({ points: self.options.points }); }); } function setPosition() { self.$player[0].options({ position: self.options.position }); self.$timeline.forEach(function(v) { v.options({ position: self.options.position }); }); } function setSizes() { self.sizes = getSizes(); self.$player.forEach(function(v, i) { v.options({ 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' }); }); self.$timeline.forEach(function(v, i) { v.options({ width: self.sizes.timeline[i].width }) .css({ left: self.sizes.timeline[i].left + 'px', top: self.sizes.timeline[i].top + 'px' }); }); } function toggleAnnotations(event, data) { self.options.showAnnotations = !data.collapsed; setSizes(); } function toggleMute() { self.$player[0].toggleMute(); } function togglePlay() { self.$player[0].togglePlay(); } function toggleSize(event, data) { self.options.videoSize = data.size setSizes(); that.triggerEvent('togglesize', { size: self.options.videoSize }); } self.onChange = function(key, value) { if (key == 'width' || key == 'height') { //Ox.print('XXXX setSizes', key, value, self.options.width, self.options.height) setSizes(); } else if (key == 'position') { self.$player[0].position(value); } }; that.addAnnotation = function(layer, item) { var i = Ox.getPositionById(self.options.layers, layer); self.$annotationPanel[i].addItem(item); } that.removeAnnotations = function(layer, ids) { var i = Ox.getPositionById(self.options.layers, layer); self.$annotationPanel[i].removeItems(ids); } return that; };