// 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: [],
muted: false,
out: 0,
paused: false,
position: 0,
subtitles: [],
timelineURL: '',
videoRatio: 1,
volume: 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: function() {
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: function() {
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;
};