')
.css({
position: 'absolute',
- left: (self.margin / 2) + 'px',
+ left: self.margin / 2 + 'px',
top: i * (self.height + self.margin) + 'px',
width: self.options.width + 'px',
height: '24px',
@@ -103,7 +104,7 @@ Ox.BlockVideoTimeline = function(options, self) {
self.$images[i] = self.$image.clone()
.css({
position: 'absolute',
- marginLeft: (-i * self.options.width) + 'px'
+ marginLeft: -i * self.options.width + 'px'
})
.appendTo(self.$lines[i]);
self.$interfaces[i] = $('
')
@@ -113,7 +114,7 @@ Ox.BlockVideoTimeline = function(options, self) {
top: '2px',
width: Math.round(self.options.duration) + 'px',
height: '20px',
- marginLeft: (-i * self.options.width) + 'px',
+ marginLeft: -i * self.options.width + 'px',
//background: 'rgba(255, 0, 0, 0.1)',
})
.appendTo(self.$lines[i]);
@@ -141,7 +142,8 @@ Ox.BlockVideoTimeline = function(options, self) {
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);
+ return e.offsetX ? e.offsetX
+ : e.clientX - $(e.target).offset().left;
}
function getSubtitle(position) {
@@ -188,12 +190,13 @@ Ox.BlockVideoTimeline = function(options, self) {
position = getPosition(e);
subtitle = getSubtitle(position);
self.$tooltip.options({
- title: subtitle ?
- '
' +
- Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '
') +
- '' +
- Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) :
- Ox.formatDuration(position)
+ title: subtitle
+ ? '
' +
+ Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '
') +
+ '' +
+ Ox.formatDuration(subtitle['in'], 3) + ' - '
+ + Ox.formatDuration(subtitle['out'], 3)
+ : Ox.formatDuration(position)
})
.show(e.clientX, e.clientY);
} else {
diff --git a/source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js b/source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js
new file mode 100644
index 00000000..e84fcefa
--- /dev/null
+++ b/source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js
@@ -0,0 +1,532 @@
+// vim: et:ts=4:sw=4:sts=4:ft=javascript
+
+'use strict';
+
+Ox.VideoTimelinePlayer = function(options, self) {
+
+ self = self || {};
+ var that = Ox.Element({}, self)
+ .defaults({
+ cuts: [],
+ duration: 0,
+ find: '',
+ getFrameURL: null,
+ getImageURL: null,
+ height: 0,
+ 'in': 0,
+ matches: [],
+ out: 0,
+ paused: false,
+ position: 0,
+ subtitles: [],
+ timelineURL: '',
+ videoRatio: 1,
+ width: 0
+ })
+ .options(options || {})
+ .addClass('OxVideoTimelinePlayer');
+
+ self.fps = 25;
+ self.frame = self.options.position * self.fps;
+ self.frames = self.options.duration * self.fps;
+ self.tileWidth = 1500;
+ self.tileHeight = 64;
+ self.margin = 8;
+ self.contentWidth = self.options.width - 2 * self.margin - Ox.UI.SCROLLBAR_SIZE;
+ self.contentHeight = self.options.height - 32;
+ self.tiles = Math.ceil(self.frames / self.tileWidth);
+ self.videoWidth = Math.round(self.tileHeight * self.options.videoRatio);
+ self.videoLines = getVideoLines();
+ self.lines = getLines();
+
+ self.$topBar = Ox.Bar({size: 16});
+
+ self.$content = Ox.Element()
+ .addClass('OxVideoTimelinePlayer')
+ .css({
+ overflow: 'hidden',
+ overflowY: 'scroll'
+ })
+ .bind({
+ mousedown: mousedown,
+ mouseleave: mouseleave,
+ mousemove: mousemove
+ })
+ .bindEvent({
+ mousedown: function() {
+ this.gainFocus();
+ },
+ key_down: function() {
+ self.options.position += self.contentWidth / self.fps;
+ setPosition();
+ },
+ key_enter: function() {
+ scrollToPosition();
+ },
+ key_left: function() {
+ self.options.position -= self.videoWidth / self.fps;
+ setPosition();
+ },
+ key_right: function() {
+ self.options.position += self.videoWidth / self.fps;
+ setPosition();
+ },
+ key_shift_left: function() {
+ self.options.position -= 1 / self.fps;
+ setPosition();
+ },
+ key_shift_right: function() {
+ self.options.position += 1 / self.fps;
+ setPosition();
+ },
+ key_space: togglePaused,
+ key_up: function() {
+ self.options.position -= self.contentWidth / self.fps;
+ setPosition();
+ }
+
+ });
+
+ self.$bottomBar = Ox.Bar({size: 16});
+ self.$playButton = Ox.Button({
+ style: 'symbol',
+ title: 'play',
+ type: 'image'
+ })
+ .css({
+ float: 'left'
+ })
+ .bindEvent({
+ click: togglePaused
+ })
+ .appendTo(self.$bottomBar);
+ self.$muteButton = Ox.Button({
+ style: 'symbol',
+ title: 'mute',
+ type: 'image'
+ })
+ .css({
+ float: 'left'
+ })
+ .bindEvent({
+
+ })
+ .appendTo(self.$bottomBar);
+ self.$smallTimeline = $('
')
+ .css({
+ float: 'left',
+ width: self.options.width - 80 + 'px',
+ height: '16px',
+ borderRadius: '8px',
+ background: 'rgb(0, 0, 0)'
+ })
+ .appendTo(self.$bottomBar);
+ self.$smallTimelineImage = $('
')
+ .attr({
+ src: self.options.timelineURL
+ })
+ .css({
+ width: self.options.width - 96 + 'px',
+ height: '16px',
+ margin: '0 8px 0 8px'
+ })
+ .appendTo(self.$smallTimeline);
+ self.$position = $('
')
+ .css({
+ float: 'left',
+ width: '40px',
+ height: '12px',
+ padding: '2px 4px 2px 4px',
+ fontSize: '9px'
+ })
+ .html(Ox.formatDuration(self.options.position))
+ .appendTo(self.$bottomBar);
+
+ self.$panel = Ox.SplitPanel({
+ elements: [
+ {
+ element: self.$topBar,
+ size: 16
+ },
+ {
+ element: self.$content
+ },
+ {
+ element: self.$bottomBar,
+ size: 16
+ }
+ ],
+ orientation: 'vertical'
+ });
+
+ that.setElement(self.$panel);
+
+ self.$lines = [];
+ self.$timelines = [];
+ self.$interfaces = [];
+
+ self.$timeline = renderTimeline();
+ //setSubtitles();
+ Ox.loop(self.lines, function(i) {
+ addLine(i);
+ });
+
+ self.$frameBox = $('
')
+ .css({
+ position: 'absolute',
+ right: 0,
+ top: self.margin / 2 - 1 + 'px',
+ width: self.videoWidth + 'px',
+ height: self.tileHeight + 'px',
+ borderTop: '1px solid rgba(255, 255, 255, 0.5)',
+ borderBottom: '1px solid rgba(255, 255, 255, 0.5)',
+ background: 'rgb(0, 0, 0)'
+ })
+ .appendTo(self.$timelines[self.videoLines[1]][0]);
+ self.$frame = $('
')
+ .attr({
+ src: self.options.getFrameURL(self.options.position)
+ })
+ .css({
+ width: self.videoWidth + 'px',
+ height: self.tileHeight + 'px',
+ })
+ .appendTo(self.$frameBox)
+ $('
')
+ .addClass('OxFrameInterface')
+ .css({
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: self.videoWidth + 'px',
+ height: self.tileHeight + 'px',
+ })
+ .appendTo(self.$frameBox);
+
+ self.$videoBox = $('
')
+ .css({
+ position: 'absolute',
+ right: 0,
+ top: self.margin / 2 - 1 + 'px',
+ width: self.videoWidth + 'px',
+ height: self.tileHeight + 'px',
+ borderTop: '1px solid rgba(255, 255, 255, 0.5)',
+ borderBottom: '1px solid rgba(255, 255, 255, 0.5)',
+ background: 'rgb(0, 0, 0)',
+ zIndex: 5
+ })
+ .appendTo(self.$timelines[self.videoLines[0]][0]);
+ self.$video = Ox.VideoPlayer({
+ duration: self.options.duration,
+ height: self.tileHeight,
+ paused: self.options.paused,
+ position: self.options.position,
+ scaleToFill: true,
+ video: self.options.videoURL,
+ width: self.videoWidth
+ })
+ .bindEvent({
+ ended: function() {
+ togglePaused(true);
+ },
+ playing: function(data) {
+ self.options.position = data.position;
+ setPosition(true);
+ }
+ })
+ .appendTo(self.$videoBox);
+ $('
')
+ .addClass('OxFrameInterface OxVideoInterface')
+ .css({
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ width: self.videoWidth + 'px',
+ height: self.tileHeight + 'px',
+ })
+ .appendTo(self.$videoBox);
+
+ self.$tooltip = Ox.Tooltip({
+ animate: false
+ })
+ .css({
+ textAlign: 'center'
+ });
+
+ setTimeout(function() {
+ scrollToPosition();
+ });
+
+ function addLine(i) {
+ self.$lines[i] = $('
')
+ .css({
+ position: 'absolute',
+ left: self.margin + 'px',
+ top: self.margin / 2 + i * (self.tileHeight + self.margin) + 'px',
+ width: self.contentWidth + 'px',
+ height: self.tileHeight + self.margin + 'px',
+ overflowX: 'hidden'
+ })
+ .appendTo(self.$content);
+ self.$timelines[i] = [
+ self.$timeline.clone()
+ .css({
+ width: self.frame + self.videoWidth + 'px',
+ marginLeft: -i * self.contentWidth + 'px',
+ }),
+ self.$timeline.clone()
+ .css({
+ marginLeft: -i * self.contentWidth + self.videoWidth - 1 + 'px',
+ })
+ ];
+ self.$lines[i]
+ .append(self.$timelines[i][1])
+ .append(self.$timelines[i][0]);
+
+ }
+
+ function getLines() {
+ return Math.ceil((self.frames - 1 + self.videoWidth) / self.contentWidth)
+ }
+
+ function getPosition(e) {
+ return (
+ e.offsetX ? e.offsetX
+ : e.clientX - $(e.target).offset().left
+ ) / self.fps;
+ }
+
+ function getSubtitle(position) {
+ var subtitle = '';
+ Ox.forEach(self.options.subtitles, function(v) {
+ if (v['in'] <= position && v.out > position) {
+ subtitle = v;
+ return false;
+ }
+ });
+ return subtitle;
+ }
+
+ function getVideoLine() {
+ self.videoLine = Math.floor(getVideoFrame() / self.contentWidth);
+ }
+
+ function getVideoLines() {
+ var videoFrame = getVideoFrame(),
+ videoLeft = videoFrame % self.contentWidth,
+ lines = [];
+ lines[0] = Math.floor(videoFrame / self.contentWidth);
+ lines[1] = lines[0] + (
+ videoLeft + self.videoWidth > self.contentWidth ? 1 : 0
+ )
+ if (videoLeft + Math.floor(self.videoWidth / 2) > self.contentWidth) {
+ lines.reverse();
+ }
+ return lines;
+ }
+
+ function getVideoFrame() {
+ return Math.floor(self.options.position * self.fps);
+ }
+
+ function mousedown(e) {
+ var $target = $(e.target),
+ isTimeline = $target.is('.OxTimelineInterface'),
+ isVideo = $target.is('.OxFrameInterface');
+ if (isTimeline) {
+ self.options.position = getPosition(e);
+ setPosition();
+ if (!self.triggered) {
+ that.triggerEvent('position', {
+ position: self.options.position
+ });
+ self.triggered = true;
+ setTimeout(function() {
+ self.triggered = false;
+ }, 250);
+ }
+ } else if (isVideo) {
+ togglePaused();
+ }
+ }
+
+ function mouseleave() {
+ self.$tooltip.hide();
+ }
+
+ function mousemove(e) {
+ var $target = $(e.target),
+ isTimeline = $target.is('.OxTimelineInterface'),
+ isVideo = $target.is('.OxFrameInterface'),
+ position, subtitle;
+ if (isTimeline || isVideo) {
+ position = isTimeline ? getPosition(e) : self.options.position;
+ subtitle = getSubtitle(position);
+ self.$tooltip.options({
+ title: (
+ subtitle
+ ? '
' +
+ Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '
') +
+ ''
+ : ''
+ ) + Ox.formatDuration(position, 3)
+ })
+ .show(e.clientX, e.clientY);
+ } else {
+ self.$tooltip.hide();
+ }
+ }
+
+ function renderTimeline() {
+ var $timeline = $('
')
+ .css({
+ position: 'absolute',
+ width: self.frames + 'px',
+ height: self.tileHeight + self.margin + 'px',
+ overflow: 'hidden'
+ });
+ Ox.loop(self.tiles, function(i) {
+ $('
')
+ .attr({
+ src: self.options.getImageURL(i)
+ })
+ .css({
+ position: 'absolute',
+ left: i * self.tileWidth + 'px',
+ top: self.margin / 2 + 'px'
+ })
+ .appendTo($timeline);
+ });
+ $('
')
+ .addClass('OxTimelineInterface')
+ .css({
+ position: 'absolute',
+ left: 0,
+ top: self.margin / 2 + 'px',
+ width: self.frames + 'px',
+ height: self.tileHeight + 'px',
+ //background: 'rgba(255, 0, 0, 0.5)'
+ })
+ .appendTo($timeline);
+
+ return $timeline;
+ }
+
+ function scrollToPosition() {
+ var scrollTop = self.$content.scrollTop(),
+ videoTop = [
+ self.margin + Ox.min(self.videoLines) * (self.tileHeight + self.margin),
+ self.margin + Ox.max(self.videoLines) * (self.tileHeight + self.margin)
+ ],
+ offset = self.contentHeight - self.tileHeight - self.margin;
+ if (videoTop[0] < scrollTop + self.margin) {
+ self.$content.scrollTop(videoTop[0] - self.margin);
+ } else if (videoTop[1] > scrollTop + offset) {
+ self.$content.scrollTop(videoTop[1] - offset);
+ }
+ }
+
+ function setPosition(fromVideo) {
+ var max, min, videoLines, isPlaying = !self.options.paused;
+ self.options.position = Ox.limit(self.options.position, 0, self.options.duration);
+ self.frame = Math.floor(self.options.position * self.fps);
+ videoLines = getVideoLines();
+ min = Ox.min(Ox.flatten([self.videoLines, videoLines]));
+ max = Ox.max(Ox.flatten([self.videoLines, videoLines]));
+ Ox.loop(min, max + 1, function(i) {
+ self.$timelines[i][0].css({
+ width: self.frame + self.videoWidth + 'px'
+ });
+ });
+ if (videoLines[1] != self.videoLines[1]) {
+ self.videoLines[1] = videoLines[1];
+ self.$frameBox.detach().appendTo(self.$timelines[videoLines[1]][0]);
+ }
+ if (videoLines[0] != self.videoLines[0]) {
+ self.videoLines[0] = videoLines[0];
+ isPlaying && self.$video.togglePaused();
+ self.$videoBox.detach().appendTo(self.$timelines[videoLines[0]][0]);
+ isPlaying && self.$video.togglePaused();
+ }
+ if (videoLines[0] != videoLines[1]) {
+ self.$frame.attr({
+ src: self.options.getFrameURL(
+ self.paused
+ ? self.options.position
+ : Math.floor(self.options.position)
+ )
+ });
+ }
+ self.videoLines = videoLines;
+ if (!fromVideo) {
+ self.$video.options({position: self.options.position});
+ self.$frame.attr({
+ src: self.options.getFrameURL(self.options.position)
+ });
+ scrollToPosition();
+ }
+ self.$position.html(Ox.formatDuration(self.options.position))
+ }
+
+ function setSubtitles() {
+ self.$timeline.find('.OxSubtitle').remove();
+ self.$subtitles = [];
+ self.options.subtitles.forEach(function(subtitle, i) {
+ var found = self.options.find
+ && subtitle.text.toLowerCase().indexOf(self.options.find.toLowerCase()) > -1;
+ self.$subtitles[i] = $('
')
+ .addClass('OxSubtitle' + (found ? ' OxHighlight' : ''))
+ .css({
+ position: 'absolute',
+ left: (subtitle['in'] * self.fps) + 'px',
+ width: (((subtitle.out - subtitle['in']) * self.fps) - 2) + 'px'
+ })
+ .html(Ox.highlight(subtitle.text, self.options.find, 'OxHighlight'))
+ .appendTo(self.$timeline);
+ });
+ }
+
+ function setWidth() {
+ self.contentWidth = self.options.width - 2 * self.margin - Ox.UI.SCROLLBAR_SIZE;
+ self.lines = getLines();
+ Ox.loop(self.lines, function(i) {
+ if (self.$lines[i]) {
+ self.$lines[i].css({
+ width: self.contentWidth + 'px'
+ });
+ self.$timelines[i][0].css({
+ marginLeft: -i * self.contentWidth + 'px'
+ });
+ self.$timelines[i][1].css({
+ marginLeft: -i * self.contentWidth + self.videoWidth + 'px'
+ });
+ } else {
+ addLine(i);
+ }
+ });
+ while (self.$lines.length > self.lines) {
+ self.$lines[self.$lines.length - 1].remove();
+ self.$lines.pop();
+ self.$timelines.pop();
+ }
+ }
+
+ function togglePaused(fromVideo) {
+ self.options.paused = !self.options.paused;
+ !fromVideo && self.$video.options({
+ paused: self.options.paused
+ });
+ self.$playButton.options({
+ title: !self.options.paused ? 'pause' : 'play'
+ });
+ }
+
+ self.setOption = function(key, value) {
+ if (key == 'width') {
+ setWidth();
+ }
+ };
+
+ return that;
+
+};