From 2752328c66adad6e40445896e3acbc7def9ad98b Mon Sep 17 00:00:00 2001 From: rolux Date: Sat, 18 Sep 2010 00:10:07 +0200 Subject: [PATCH] add timeline view --- build/css/ox.ui.classic.css | 24 + build/css/ox.ui.css | 182 +++ build/css/ox.ui.modern.css | 11 + build/js/ox.js | 8 + build/js/ox.ui.js | 1260 +++++++++++++++++ build/json/ox.ui.images.json | 2 +- build/png/ox.ui.classic/symbolGoToIn.png | Bin 0 -> 2896 bytes build/png/ox.ui.classic/symbolGoToOut.png | Bin 0 -> 2898 bytes build/png/ox.ui.classic/symbolPlayInToOut.png | Bin 0 -> 2892 bytes build/png/ox.ui.classic/symbolSetIn.png | Bin 0 -> 2894 bytes build/png/ox.ui.classic/symbolSetOut.png | Bin 0 -> 2898 bytes 11 files changed, 1486 insertions(+), 1 deletion(-) create mode 100644 build/png/ox.ui.classic/symbolGoToIn.png create mode 100644 build/png/ox.ui.classic/symbolGoToOut.png create mode 100644 build/png/ox.ui.classic/symbolPlayInToOut.png create mode 100644 build/png/ox.ui.classic/symbolSetIn.png create mode 100644 build/png/ox.ui.classic/symbolSetOut.png diff --git a/build/css/ox.ui.classic.css b/build/css/ox.ui.classic.css index c6bd7ba..4e93180 100644 --- a/build/css/ox.ui.classic.css +++ b/build/css/ox.ui.classic.css @@ -337,4 +337,28 @@ Scrollbars .OxThemeClassic ::-webkit-scrollbar:active, .OxThemeClassic ::-webkit-scrollbar-thumb:active { background: rgb(208, 208, 208); +} + +/* +================================================================================ +Video +================================================================================ +*/ + +.OxThemeClassic .OxVideoPlayer { + background: rgb(255, 255, 255); +} + +/* +================================================================================ +Miscellaneous +================================================================================ +*/ + +.OxThemeClassic .OxTooltip { + border: 1px solid rgba(128, 128, 128, 0.75); + background: rgba(255, 255, 255, 0.75); + color: rgba(128, 128, 128, 1); + -moz-box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); } \ No newline at end of file diff --git a/build/css/ox.ui.css b/build/css/ox.ui.css index 69477f7..e13078a 100644 --- a/build/css/ox.ui.css +++ b/build/css/ox.ui.css @@ -1196,6 +1196,188 @@ Scrollbars -webkit-border-radius: 6px; } +/* +================================================================================ +Video +================================================================================ +*/ + +.OxEditor .OxVideoPlayer { + position: absolute; + margin: 4px; + //background: red; +} + + + +.OxTimelineLarge { + position: absolute; + height: 72px; + margin: 0 4px 0 4px; + overflow: hidden; +} +.OxTimelineLarge > div { + position: absolute; + height: 72px; +} +.OxTimelineLarge > div > img { + position: absolute; + top: 4px; +} +.OxTimelineLarge .OxCut { + position: absolute; + top: 66px; + width: 2px; + height: 4px; + margin-left: -1px; + z-index: 10; +} +.OxTimelineLarge .OxMarkerPointIn { + position: absolute; + top: 64px; + width: 6px; + height: 6px; + margin-left: -5px; + z-index: 10; +} +.OxTimelineLarge .OxMarkerPointOut { + position: absolute; + top: 64px; + width: 6px; + height: 6px; + z-index: 10; +} +.OxTimelineLarge .OxMarkerPosition { + position: absolute; + top: 2px; + width: 9px; + height: 5px; + z-index: 10; +} +.OxTimelineLarge .OxSubtitle { + position: absolute; + bottom: 9px; + border: 1px solid rgba(255, 255, 255, 0.5); + padding: 1px; + background: rgba(0, 0, 0, 0.25); + font-size: 8px; + line-height: 10px; + text-align: center; + text-overflow: ellipsis; + text-shadow: rgba(0, 0, 0, 1) 1px 1px 1px; + color: rgb(255, 255, 255); + cursor: default; + overflow: hidden; + z-index: 10; + -moz-box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); +} +.OxTimelineLarge .OxSubtitle.OxHighlight { + border-color: rgba(255, 255, 0, 1); +} +.OxTimelineSmall { + position: absolute; +} +.OxTimelineSmall > div { + position: absolute; + height: 18px; + margin: 3px 4px 3px 4px; + overflow: hidden; +} +.OxTimelineSmall > div > img { + position: absolute; + left: 0; + top: 0; +} +.OxTimelineSmall > div > .OxTimelineSmallImage { + margin-top: 1px; +} +.OxTimelineSmall .OxMarkerPointIn { + position: absolute; + width: 6px; + height: 6px; + margin-left: -1px; +} +.OxTimelineSmall .OxMarkerPointOut { + position: absolute; + width: 6px; + height: 6px; + margin-left: 4px; +} + +.OxVideoPlayer > .OxBar .OxInputGroup { + //width: 98px; +} +.OxVideoPlayer > .OxBar .OxButton { + margin-right: -1px; +} +.OxVideoPlayer > .OxBar .OxButton, +.OxVideoPlayer > .OxBar .OxInput, +.OxVideoPlayer > .OxBar .OxLabel { + padding: 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; +} +.OxVideoPlayer > .OxBar .OxLabel { + //width: 22px; + //background: rgb(32, 32, 32); +} +.OxVideoPlayer .OxLoadingIcon { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 16px; + margin: auto; +} +.OxVideoPlayer .OxMarkerFrame { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 16px; +} +.OxVideoPlayer .OxMarkerFrame > div { + float: left; +} +.OxVideoPlayer .OxMarkerFrame > .OxFrame { + background: rgba(0, 0, 0, 0.5); +} +.OxVideoPlayer .OxMarkerFrame > .OxPoster { + border: 1px solid rgba(255, 255, 255, 0.5); +} +.OxVideoPlayer .OxMarkerPoint { + position: absolute; + width: 16px; + height: 16px; +} +.OxVideoPlayer .OxMarkerInTop { + left: 4px; + top: 4px; +} +.OxVideoPlayer .OxMarkerInBottom { + left: 4px; + bottom: 20px; +} +.OxVideoPlayer .OxMarkerOutTop { + right: 4px; + top: 4px; +} +.OxVideoPlayer .OxMarkerOutBottom { + right: 4px; + bottom: 20px; +} +.OxVideoPlayer .OxSubtitle { + position: absolute; + left: 0; + right: 0; + text-align: center; + //text-shadow: rgba(0, 0, 0, 1) 2px 2px 0px; + text-shadow: rgba(0, 0, 0, 1) 0 0 4px; + color: rgb(255, 255, 255); + z-index: 10; +} + /* ================================================================================ Miscellaneous diff --git a/build/css/ox.ui.modern.css b/build/css/ox.ui.modern.css index 8060d87..4c81760 100644 --- a/build/css/ox.ui.modern.css +++ b/build/css/ox.ui.modern.css @@ -311,6 +311,17 @@ Scrollbars background: rgb(64, 64, 64); } + +/* +================================================================================ +Video +================================================================================ +*/ + +.OxThemeModern .OxVideoPlayer { + background: rgb(0, 0, 0); +} + /* ================================================================================ Miscellaneous diff --git a/build/js/ox.js b/build/js/ox.js index 26ca291..9cb3b94 100644 --- a/build/js/ox.js +++ b/build/js/ox.js @@ -1573,6 +1573,14 @@ Ox.endsWith = function(str, sub) { return str.substr(-sub.length) === sub; }; +Ox.highlight = function(txt, str) { + // fixme: move to ox.ui + return str ? txt.replace( + new RegExp('(' + str + ')', 'ig'), + '$1' + ) : txt; +} + Ox.pad = function(str, len, pad, pos) { /* >>> Ox.pad(1, 2) diff --git a/build/js/ox.ui.js b/build/js/ox.ui.js index b63ac5c..ab6911f 100644 --- a/build/js/ox.ui.js +++ b/build/js/ox.ui.js @@ -8941,6 +8941,1266 @@ requires ---------------------------------------------------------------------------- */ + + /* + ============================================================================ + Video + ============================================================================ + */ + + Ox.LargeTimeline = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + cuts: [], + duration: 0, + find: '', + matches: [], + points: [0, 0], + position: 0, + style: 'default', + subtitles: [], + videoId: '', + width: 0 + }) + .options(options || {}) + .addClass('OxTimelineLarge') + .mousedown(mousedown) + .mouseleave(mouseleave) + .mousemove(mousemove); + + $.extend(self, { + $cuts: [], + $markerPoint: [], + $subtitles: [], + $tiles: {}, + $tooltip: new Ox.Tooltip(), + center: parseInt(self.options.width / 2), + element: that.$element[0], + fps: 25, + height: 64, + tileWidth: 1500 + }); + self.tiles = self.options.duration * self.fps / self.tileWidth; + + self.$timeline = $('
') + .css({ + left: self.center + 'px' + }) + .appendTo(that.$element) + + $.each(self.options.subtitles, function(i, v) { + self.$subtitles[i] = $('
') + .addClass('OxSubtitle' + (self.options.matches.indexOf(i) > -1 ? ' OxHighlight' : '')) + .css({ + left: (v['in'] * self.fps) + 'px', + width: (((v['out'] - v['in']) * self.fps) - 4) + 'px' + }) + .html(Ox.highlight(v.text, self.options.find)) + .appendTo(self.$timeline) + }); + + $.each(self.options.cuts, function(i, v) { + self.$cuts[i] = $('') + .addClass('OxCut') + .attr({ + src: '/static/oxjs/build/png/ox.ui/videoMarkerCut.png' + }) + .css({ + left: (v * self.fps) + 'px' + }) + .appendTo(self.$timeline) + }); + + self.$markerPosition = $('') + .addClass('OxMarkerPosition') + .attr({ + src: '/static/oxjs/build/png/ox.ui/videoMarkerPlay.png' + }) + .appendTo(that.$element); + setMarker(); + + $.each(['In', 'Out'], function(i, v) { + self.$markerPoint[i] = $('') + .addClass('OxMarkerPoint' + v) + .attr({ + src: '/static/oxjs/build/png/ox.ui/videoMarker' + v + '.png' + }) + .appendTo(self.$timeline); + setMarkerPoint(i); + }); + + setWidth(); + setPosition(); + + function mousedown(e) { + var mousemove = false, + x = e.clientX; + $window.mousemove(function(e) { + mousemove = true; + self.options.position = Ox.limit( + self.options.position + (x - e.clientX) / self.fps, + 0, self.options.duration + ); + x = e.clientX; + setPosition(); + that.triggerEvent('change', { + position: self.options.position + }); + }); + $window.one('mouseup', function() { + $window.unbind('mousemove'); + if (!mousemove) { + self.options.position = Ox.limit( + self.options.position + (e.clientX - that.$element.offset().left - self.center) / self.fps, + 0, self.options.duration + ); + setPosition(); + } + that.triggerEvent('change', { + position: self.options.position + }); + }); + e.preventDefault(); + } + + function mouseleave(e) { + self.$tooltip.hide(); + } + + function mousemove(e) { + var position = self.options.position + (e.clientX - that.offset().left - self.center) / self.fps; + if (position >= 0) { + self.$tooltip + .options({ + title: Ox.formatDuration(position, 3) + }) + .show(e.clientX, e.clientY); + } else { + self.$tooltip.hide(); + } + } + + function setMarkerPoint(i) { + self.$markerPoint[i].css({ + left: (self.options.points[i] * self.fps) + 'px' + }); + } + + function setMarker() { + self.$markerPosition.css({ + left: (self.center - 4) + 'px', + }); + } + + function setPosition() { + self.tile = parseInt(self.options.position * self.fps / self.tileWidth); + self.$timeline.css({ + marginLeft: (-self.options.position * self.fps) + 'px' + }); + $.each(Ox.range(Math.max(self.tile - 1, 0), Math.min(self.tile + 2, self.tiles)), function(i, v) { + if (!self.$tiles[v]) { + self.$tiles[v] = $('') + .attr({ + src: '/' + self.options.videoId + '/timelines/' + + (self.options.style == 'default' ? 'timeline' : self.options.style) + '.64.' + v + '.png' + }) + .css({ + left: (v * self.tileWidth) + 'px' + }) + .appendTo(self.$timeline); + } + }); + } + + function setWidth() { + self.center = parseInt(self.options.width / 2); + that.css({ + width: self.options.width + 'px' + }); + self.$timeline.css({ + left: self.center + 'px' + }); + setMarker(); + } + + self.onChange = function(key, value) { + if (key == 'points') { + setMarkerPoint(0); + setMarkerPoint(1); + } else if (key == 'position') { + setPosition(); + } else if (key == 'width') { + setWidth(); + } + }; + + return that; + + }; + + Ox.SmallTimeline = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + cuts: [], + duration: 0, + find: '', + matches: [], + points: [0, 0], + position: 0, + subtitles: [], + videoId: '', + width: 0 + }) + .options(options || {}) + .addClass('OxTimelineSmall') + .mousedown(mousedown) + .mouseleave(mouseleave) + .mousemove(mousemove); + + $.extend(self, { + $images: [], + $lines: [], + $markerPoint: [], + $subtitles: [], + hasSubtitles: self.options.subtitles.length, + height: 16, + lines: Math.ceil(self.options.duration / self.options.width), + margin: 8 + }); + + getTimelineImageURL(function(url) { + + self.timelineImageURL = url; + + $.each(Ox.range(0, self.lines), function(i) { + addLine(i); + }); + + self.$markerPosition = $('') + .addClass('OxMarkerPosition') + .attr({ + src: '/static/oxjs/build/png/ox.ui/videoMarkerPlay.png' + }) + .css({ + position: 'absolute', + width: '9px', + height: '5px', + zIndex: 10 + }) + .appendTo(that.$element); + + setPosition(); + + $.each(['in', 'out'], function(i, v) { + var titleCase = Ox.toTitleCase(v); + self.$markerPoint[i] = $('') + .addClass('OxMarkerPoint' + titleCase) + .attr({ + src: '/static/oxjs/build/png/ox.ui/videoMarker' + titleCase + '.png' + }) + .appendTo(that.$element); + setMarkerPoint(i); + }); + + }); + + function addLine(i) { + self.$lines[i] = new Ox.Element('div') + .css({ + top: i * (self.height + self.margin) + 'px', + width: self.options.width + 'px' + }) + .appendTo(that); + self.$images[i] = $('') + .addClass('OxTimelineSmallImage') + .attr({ + src: self.timelineImageURL + }) + .css({ + marginLeft: (-i * self.options.width) + 'px' + }) + .appendTo(self.$lines[i].$element) + if (self.hasSubtitles) { + self.subtitlesImageURL = getSubtitlesImageURL(); + self.$subtitles[i] = $('') + .attr({ + src: self.subtitlesImageURL + }) + .css({ + marginLeft: (-i * self.options.width) + 'px' + }) + .appendTo(self.$lines[i].$element); + } + } + + function getPosition(e) { + //FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript + return (e.offsetX ? e.offsetX : e.clientX - $(e.target).offset().left), + } + + function getSubtitle(position) { + var subtitle = null; + $.each(self.options.subtitles, function(i, v) { + if (v['in'] <= position && v['out'] >= position) { + subtitle = v; + return false; + } + }); + return subtitle; + } + + function getSubtitlesImageURL() { + var height = 18, + width = Math.ceil(self.options.duration), + $canvas = $('') + .attr({ + height: height, + width: width + }), + canvas = $canvas[0], + context = canvas.getContext('2d'), + imageData = context.createImageData(width, height), + data = imageData.data; + $.each(self.options.subtitles, function(i, v) { + var color = matches.indexOf(i) > -1 ? [255, 255, 0] : [255, 255, 255] + $.each(Ox.range( + Math.round(v['in']), + Math.round(v['out']) + 1 + ), function(i, x) { + $.each(Ox.range(0, 18), function(i, y) { + var index = x * 4 + y * 4 * width; + data[index] = color[0]; + data[index + 1] = color[1]; + data[index + 2] = color[2]; + data[index + 3] = (y == 0 || y == 17) ? 255 : 128 + }); + }); + }); + context.putImageData(imageData, 0, 0); + return canvas.toDataURL(); + } + + function getTimelineImageURL(callback) { + var height = 16, + images = Math.ceil(self.options.duration / 3600), + loaded = 0, + width = Math.ceil(self.options.duration), + $canvas = $('') + .attr({ + height: height, + width: width + }), + canvas = $canvas[0], + context = canvas.getContext('2d'); + Ox.range(images).forEach(function(i) { + var $img = $('') + .attr({ + src: '/' + self.options.videoId + '/timelines/timeline.16.' + i + '.png' + }) + .load(function() { + context.drawImage($img[0], i * 3600, 0); + Ox.print('loaded, images', loaded, images, $img[0]) + if (++loaded == images) { + Ox.print('callback', canvas.toDataURL().length) + callback(canvas.toDataURL()); + } + }); + }); + } + + function mousedown(e) { + if ($(e.target).is('img')) { + self.options.position = getPosition(e); + setPosition(); + that.triggerEvent('change', { + position: self.options.position + }); + } + $window.mousemove(function(e) { + if ($(e.target).is('img')) { + self.options.position = getPosition(e); + setPosition(); + that.triggerEvent('change', { + position: self.options.position + }); + } + }); + $window.one('mouseup', function() { + $window.unbind('mousemove'); + }) + e.preventDefault(); + } + + function mouseleave(e) { + self.$tooltip.hide(); + } + + function mousemove(e) { + var $target = $(e.target), + position, + subtitle; + if ($target.is('img')) { + //FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript + position = getPosition(e), + subtitle = getSubtitle(position); + Ox.print('position', position, e) + self.$tooltip = new Ox.Tooltip({ + title: subtitle ? + '' + + Ox.highlight(subtitle.text, self.options.find).replace(/\n/g, '
') + '

' + + Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) : + Ox.formatDuration(position, 3) + }) + .css({ + textAlign: 'center' + }) + .show(e.clientX, e.clientY); + } + + } + + function setMarker() { + self.$markerPosition + .css({ + left: (self.options.position % self.options.width) + 'px', + top: (parseInt(self.options.position / self.options.width) * (self.height + self.margin) + 2) + 'px', + }); + } + + function setMarkerPoint(i) { + var position = self.options.points[i]; + self.$markerPoint[i] + .css({ + left: (position % self.options.width) + 'px', + top: (parseInt(position / self.options.width) * (self.height + self.margin) + 16) + 'px', + }); + } + + function setPosition() { + self.options.position = Ox.limit(self.options.position, 0, self.options.duration); + setMarker(); + } + + function setWidth() { + self.lines = Math.ceil(self.options.duration / self.options.width); + $.each(Ox.range(self.lines), function(i) { + if (self.$lines[i]) { + self.$lines[i].css({ + width: self.options.width + 'px' + }); + self.$images[i].css({ + marginLeft: (-i * self.options.width) + 'px' + }); + if (self.hasSubtitles) { + self.$subtitles[i].css({ + marginLeft: (-i * self.options.width) + 'px' + }); + } + } else { + addLine(i); + } + }); + while (self.$lines.length > self.lines) { + self.$lines[self.$lines.length - 1].remove(); + self.$lines.pop(); + } + setMarker(); + setMarkerPoint(0); + setMarkerPoint(1); + } + + self.onChange = function(key, value) { + if (key == 'points') { + setMarkerPoint(0); + setMarkerPoint(1); + } else if (key == 'position') { + setPosition(); + } else if (key == 'width') { + setWidth(); + } + }; + + return that; + + }; + + Ox.VideoEditor = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + cuts: [], + duration: 0, + find: '', + largeTimeline: true, + matches: [], + points: [0, 0], + position: 0, + posterFrame: 0, + subtitles: [], + videoHeight: 0, + videoId: '', + videoWidth: 0, + videoSize: 'small', + videoURL: '', + width: 0 + }) + .options(options || {}) + .addClass('OxEditor'); + + $.extend(self, { + $player: [], + $timeline: [], + controlsHeight: 16, + margin: 8, + videoRatio: self.options.videoWidth / self.options.videoHeight + }); + self.sizes = getSizes(); + + $.each(['play', 'in', 'out'], function(i, type) { + self.$player[i] = new Ox.VideoPlayer({ + 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: self.options.videoURL, + width: self.sizes.player[i].width + }) + .css({ + left: self.sizes.player[i].left + 'px', + top: self.sizes.player[i].top + 'px' + }) + .appendTo(that); + if (type == 'in' || type == 'out') { + self.$player[i].bindEvent({ + change: function() { + goToPoint(type); + }, + set: function() { + setPoint(type); + } + }) + } + }); + self.$player[0].bindEvent('change', changePlayer); + + 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(that); + + self.$timeline[1] = new Ox.SmallTimeline({ + 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(that); + + that.addEvent({ + key_alt_left: function() { + movePositionBy(-0.04); + }, + key_alt_right: function() { + movePositionBy(0.04); + }, + key_alt_shift_left: function() { + movePositionTo('cut', -1); + }, + key_alt_shift_right: function() { + movePositionTo('cut', 1); + }, + key_closebracket: function() { + goToPoint('out'); + }, + key_comma: function() { + movePositionTo('subtitle', -1); + }, + key_dot: function() { + movePositionTo('subtitle', 1); + }, + key_down: function() { + movePositionBy(self.options.width - 2 * self.margin); + }, + key_i: function() { + setPoint('in'); + }, + key_left: function() { + movePositionBy(-1); + }, + key_m: toggleMute, + key_o: function() { + setPoint('out'); + }, + key_openbracket: function() { + goToPoint('in'); + }, + key_p: playInToOut, + key_right: function() { + movePositionBy(1); + }, + 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(-60); + }, + key_shift_right: function() { + movePositionBy(60); + }, + key_shift_up: function() { + movePositionBy(-self.options.duration); + }, + key_space: togglePlay, + key_up: function() { + movePositionBy(-(self.options.width - 2 * self.margin)); + } + }); + + that.gainFocus(); + + 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(); + $.each(positions, function(i, 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 getSizes() { + var size = { + player: [], + timeline: [] + }, + width, widths; + if (self.options.videoSize == 'small') { + width = 0; + widths = Ox.divideInt(self.options.width - 4 * self.margin, 3); + Ox.range(3).forEach(function(i) { + size.player[i] = { + 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((self.options.width - 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: self.options.width - 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: self.options.width - 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 + } + return size; + } + + 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 resizeEditor(event, data) { + var width = data - 2 * margin + 100; + resizeVideoPlayers(width); + $timelineLarge.options({ + width: width + }); + $timelineSmall.options({ + width: width + }); + } + + function resizePlayers() { + $.each(self.$player, function(i, v) { + v.options({ + width: size[i].width, + height: size[i].height + }) + .css({ + left: size[i].left + 'px', + top: size[i].top + 'px', + }); + }); + } + + function setPoint(point) { + self.options.points[point == 'in' ? 0 : 1] = self.options.position; + self.$player[point == 'in' ? 1 : 2].options({ + position: self.options.position + }); + if (self.options.points[1] < self.options.points[0]) { + self.options.points[point == 'in' ? 1 : 0] = self.options.position; + self.$player[point == 'in' ? 2 : 1].options({ + position: self.options.position + }); + } + $.each(self.$player, function(i, v) { + v.options({ + points: self.options.points + }); + }); + $.each(self.$timeline, function(i, v) { + v.options({ + points: self.options.points + }); + }); + } + + function setPosition() { + self.$player[0].options({ + position: self.options.position + }); + $.each(self.$timeline, function(i, v) { + v.options({ + position: self.options.position + }); + }); + } + + function toggleMute() { + self.$player[0].toggleMute(); + } + + function togglePlay() { + self.$player[0].togglePlay(); + } + + self.onChange = function(key, value) { + if (key == 'width') { + self.sizes = getSizes(); + $.each(self.$player, function(i, v) { + 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' + }); + }); + $.each(self.$timeline, function(i, v) { + v.options({ + width: self.sizes.timeline[i].width + }) + .css({ + left: self.sizes.timeline[i].left + 'px', + top: self.sizes.timeline[i].top + 'px' + }); + }); + } + }; + + return that; + + }; + + Ox.VideoPlayer = function(options, self) { + + var self = self || {}, + that = new Ox.Element('div', self) + .defaults({ + find: '', + height: 0, + points: [0, 0], + position: 0, + posterFrame: 0, + subtitles: [], + type: 'play', + url: '', + width: 0 + }) + .options(options || {}) + .addClass('OxVideoPlayer') + .css({ + height: (self.options.height + 16) + 'px', + width: self.options.width + 'px' + }); + + self.controlsHeight = 16; + + self.$video = $('