forked from 0x2620/oxjs
add video timeline player and demo
This commit is contained in:
parent
3551e73249
commit
8521eb36ba
5 changed files with 1347 additions and 14 deletions
|
|
@ -40,10 +40,11 @@ Ox.BlockVideoTimeline = function(options, self) {
|
|||
self.$interfaces = [];
|
||||
self.$lines = [];
|
||||
self.$tooltip = Ox.Tooltip({
|
||||
animate: false
|
||||
}).css({
|
||||
textAlign: 'center'
|
||||
});
|
||||
animate: false
|
||||
})
|
||||
.css({
|
||||
textAlign: 'center'
|
||||
});
|
||||
self.height = 16;
|
||||
self.lines = getLines();
|
||||
self.margin = 8;
|
||||
|
|
@ -93,7 +94,7 @@ Ox.BlockVideoTimeline = function(options, self) {
|
|||
self.$lines[i] = $('<div>')
|
||||
.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] = $('<div>')
|
||||
|
|
@ -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 ?
|
||||
'<span class=\'OxBright\'>' +
|
||||
Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '<br/>') +
|
||||
'</span><br/>' +
|
||||
Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) :
|
||||
Ox.formatDuration(position)
|
||||
title: subtitle
|
||||
? '<span class=\'OxBright\'>' +
|
||||
Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '<br/>') +
|
||||
'</span><br/>' +
|
||||
Ox.formatDuration(subtitle['in'], 3) + ' - '
|
||||
+ Ox.formatDuration(subtitle['out'], 3)
|
||||
: Ox.formatDuration(position)
|
||||
})
|
||||
.show(e.clientX, e.clientY);
|
||||
} else {
|
||||
|
|
|
|||
532
source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js
Normal file
532
source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js
Normal file
|
|
@ -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 = $('<div>')
|
||||
.css({
|
||||
float: 'left',
|
||||
width: self.options.width - 80 + 'px',
|
||||
height: '16px',
|
||||
borderRadius: '8px',
|
||||
background: 'rgb(0, 0, 0)'
|
||||
})
|
||||
.appendTo(self.$bottomBar);
|
||||
self.$smallTimelineImage = $('<img>')
|
||||
.attr({
|
||||
src: self.options.timelineURL
|
||||
})
|
||||
.css({
|
||||
width: self.options.width - 96 + 'px',
|
||||
height: '16px',
|
||||
margin: '0 8px 0 8px'
|
||||
})
|
||||
.appendTo(self.$smallTimeline);
|
||||
self.$position = $('<div>')
|
||||
.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 = $('<div>')
|
||||
.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 = $('<img>')
|
||||
.attr({
|
||||
src: self.options.getFrameURL(self.options.position)
|
||||
})
|
||||
.css({
|
||||
width: self.videoWidth + 'px',
|
||||
height: self.tileHeight + 'px',
|
||||
})
|
||||
.appendTo(self.$frameBox)
|
||||
$('<div>')
|
||||
.addClass('OxFrameInterface')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: self.videoWidth + 'px',
|
||||
height: self.tileHeight + 'px',
|
||||
})
|
||||
.appendTo(self.$frameBox);
|
||||
|
||||
self.$videoBox = $('<div>')
|
||||
.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);
|
||||
$('<div>')
|
||||
.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] = $('<div>')
|
||||
.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
|
||||
? '<span class=\'OxBright\'>' +
|
||||
Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '<br/>') +
|
||||
'</span><br/>'
|
||||
: ''
|
||||
) + Ox.formatDuration(position, 3)
|
||||
})
|
||||
.show(e.clientX, e.clientY);
|
||||
} else {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function renderTimeline() {
|
||||
var $timeline = $('<div>')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
width: self.frames + 'px',
|
||||
height: self.tileHeight + self.margin + 'px',
|
||||
overflow: 'hidden'
|
||||
});
|
||||
Ox.loop(self.tiles, function(i) {
|
||||
$('<img>')
|
||||
.attr({
|
||||
src: self.options.getImageURL(i)
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: i * self.tileWidth + 'px',
|
||||
top: self.margin / 2 + 'px'
|
||||
})
|
||||
.appendTo($timeline);
|
||||
});
|
||||
$('<div>')
|
||||
.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] = $('<div>')
|
||||
.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;
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue