2012-03-14 10:35:46 +00:00
|
|
|
'use strict';
|
|
|
|
|
2012-05-21 10:38:18 +00:00
|
|
|
/*@
|
2012-05-22 13:14:40 +00:00
|
|
|
Ox.VideoTimelinePlayer <f:Ox.Element> Video Timeline Player
|
2012-05-21 10:38:18 +00:00
|
|
|
(options[, self]) -> <o> Video Timeline Player
|
|
|
|
options <o> Options
|
|
|
|
self <o> Shared private variable
|
|
|
|
@*/
|
2012-03-14 10:35:46 +00:00
|
|
|
Ox.VideoTimelinePlayer = function(options, self) {
|
|
|
|
|
|
|
|
self = self || {};
|
|
|
|
var that = Ox.Element({}, self)
|
|
|
|
.defaults({
|
2012-04-18 11:21:06 +00:00
|
|
|
censored: [],
|
2012-04-22 09:57:17 +00:00
|
|
|
censoredIcon: '',
|
|
|
|
censoredTooltip: '',
|
2012-03-14 10:35:46 +00:00
|
|
|
cuts: [],
|
|
|
|
duration: 0,
|
|
|
|
find: '',
|
2012-04-17 12:01:24 +00:00
|
|
|
followPlayer: false,
|
2012-03-14 10:35:46 +00:00
|
|
|
getFrameURL: null,
|
2012-04-18 07:43:32 +00:00
|
|
|
getLargeTimelineURL: null,
|
2012-03-14 10:35:46 +00:00
|
|
|
height: 0,
|
|
|
|
'in': 0,
|
|
|
|
matches: [],
|
2012-04-17 07:52:37 +00:00
|
|
|
muted: false,
|
2012-03-14 10:35:46 +00:00
|
|
|
out: 0,
|
|
|
|
paused: false,
|
|
|
|
position: 0,
|
2012-04-17 12:01:24 +00:00
|
|
|
showMilliseconds: false,
|
2012-04-18 07:43:32 +00:00
|
|
|
smallTimelineURL: '',
|
2012-03-14 10:35:46 +00:00
|
|
|
subtitles: [],
|
2012-04-17 12:01:24 +00:00
|
|
|
timeline: '',
|
|
|
|
timelines: [],
|
|
|
|
video: '',
|
2012-03-14 10:35:46 +00:00
|
|
|
videoRatio: 1,
|
2012-04-17 07:52:37 +00:00
|
|
|
volume: 1,
|
2012-03-14 10:35:46 +00:00
|
|
|
width: 0
|
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.options(options || {});
|
2012-03-14 10:35:46 +00:00
|
|
|
|
|
|
|
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;
|
2012-04-19 07:15:35 +00:00
|
|
|
self.contentWidth = self.options.width - 2 * self.margin;
|
2012-03-14 10:35:46 +00:00
|
|
|
self.contentHeight = self.options.height - 32;
|
2012-04-17 12:01:24 +00:00
|
|
|
self.positionWidth = 48
|
|
|
|
+ !!self.options.showMilliseconds * 2
|
|
|
|
+ self.options.showMilliseconds * 6;
|
2012-03-14 10:35:46 +00:00
|
|
|
self.tiles = Math.ceil(self.frames / self.tileWidth);
|
|
|
|
self.videoWidth = Math.round(self.tileHeight * self.options.videoRatio);
|
2012-04-19 07:54:30 +00:00
|
|
|
self.lines = getLines(); // may update self.contentWidth
|
2012-03-14 10:35:46 +00:00
|
|
|
self.videoLines = getVideoLines();
|
|
|
|
|
2012-04-17 12:01:24 +00:00
|
|
|
self.$menubar = Ox.Bar({size: 16});
|
|
|
|
|
|
|
|
self.$menuButton = Ox.MenuButton({
|
|
|
|
items: [
|
|
|
|
{id: 'timelines', title: 'Timeline', disabled: true},
|
|
|
|
{group: 'timeline', min: 1, max: 1, items: Ox.map(
|
|
|
|
self.options.timelines,
|
|
|
|
function(timeline) {
|
|
|
|
return Ox.extend({
|
|
|
|
checked: timeline.id == self.options.timeline,
|
|
|
|
disabled: true
|
|
|
|
}, timeline);
|
|
|
|
}
|
|
|
|
)},
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
id: 'followPlayer',
|
|
|
|
title: 'Follow Player While Playing',
|
|
|
|
checked: self.options.followPlayer
|
|
|
|
}
|
|
|
|
],
|
|
|
|
style: 'square',
|
|
|
|
title: 'set',
|
|
|
|
tooltip: 'Options',
|
|
|
|
type: 'image'
|
|
|
|
})
|
|
|
|
.css({float: 'left'})
|
|
|
|
.bindEvent({
|
|
|
|
change: function(data) {
|
|
|
|
var id = data.id;
|
|
|
|
if (id == 'timeline') {
|
2012-04-18 07:43:32 +00:00
|
|
|
// data.checked[0].id
|
2012-04-17 12:01:24 +00:00
|
|
|
} else if (id == 'followPlayer') {
|
2012-04-17 13:42:46 +00:00
|
|
|
self.options.followPlayer = data.checked;
|
|
|
|
if (!self.options.paused && self.options.followPlayer) {
|
|
|
|
self.scrollTimeout && clearTimeout(self.scrollTimeout);
|
|
|
|
scrollToPosition();
|
|
|
|
}
|
|
|
|
that.triggerEvent('follow', {
|
|
|
|
follow: self.options.followPlayer
|
|
|
|
});
|
2012-04-17 12:01:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.appendTo(self.$menubar);
|
2012-03-14 10:35:46 +00:00
|
|
|
|
2012-04-19 07:54:30 +00:00
|
|
|
self.$scrollButton = Ox.Button({
|
|
|
|
style: 'symbol',
|
|
|
|
title: 'arrowDown',
|
|
|
|
tooltip: 'Scroll to Player',
|
|
|
|
type: 'image'
|
|
|
|
})
|
|
|
|
.css({float: 'right'})
|
|
|
|
.hide()
|
|
|
|
.bindEvent({
|
|
|
|
click: function() {
|
|
|
|
self.scrollTimeout && clearTimeout(self.scrollTimeout);
|
|
|
|
scrollToPosition();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.appendTo(self.$menubar);
|
|
|
|
|
2012-04-17 13:42:46 +00:00
|
|
|
self.$timelinePlayer = Ox.Element()
|
2012-04-19 07:15:35 +00:00
|
|
|
.css({overflowX: 'hidden', overflowY: 'auto'})
|
2012-03-14 10:35:46 +00:00
|
|
|
.bind({
|
|
|
|
mousedown: mousedown,
|
|
|
|
mouseleave: mouseleave,
|
2012-04-17 13:42:46 +00:00
|
|
|
mousemove: mousemove,
|
|
|
|
scroll: scroll
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
|
|
|
.bindEvent({
|
|
|
|
mousedown: function() {
|
|
|
|
this.gainFocus();
|
|
|
|
},
|
2012-04-17 12:01:24 +00:00
|
|
|
key_0: toggleMuted,
|
2012-03-14 10:35:46 +00:00
|
|
|
key_down: function() {
|
|
|
|
self.options.position += self.contentWidth / self.fps;
|
|
|
|
setPosition();
|
|
|
|
},
|
2012-04-17 12:01:24 +00:00
|
|
|
key_equal: function() {
|
|
|
|
changeVolume(0.1);
|
|
|
|
},
|
2012-03-14 10:35:46 +00:00
|
|
|
key_enter: function() {
|
|
|
|
scrollToPosition();
|
|
|
|
},
|
|
|
|
key_left: function() {
|
|
|
|
self.options.position -= self.videoWidth / self.fps;
|
|
|
|
setPosition();
|
|
|
|
},
|
2012-04-17 12:01:24 +00:00
|
|
|
key_minus: function() {
|
|
|
|
changeVolume(-0.1);
|
|
|
|
},
|
2012-03-14 10:35:46 +00:00
|
|
|
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();
|
|
|
|
},
|
2012-03-14 10:38:32 +00:00
|
|
|
key_space: function() {
|
|
|
|
togglePaused()
|
|
|
|
},
|
2012-03-14 10:35:46 +00:00
|
|
|
key_up: function() {
|
|
|
|
self.options.position -= self.contentWidth / self.fps;
|
|
|
|
setPosition();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-04-17 12:01:24 +00:00
|
|
|
self.$playerbar = Ox.Bar({size: 16});
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
self.$playButton = Ox.Button({
|
|
|
|
style: 'symbol',
|
|
|
|
title: 'play',
|
2012-04-19 07:54:30 +00:00
|
|
|
tooltip: 'Play',
|
2012-03-14 10:35:46 +00:00
|
|
|
type: 'image'
|
|
|
|
})
|
|
|
|
.css({
|
|
|
|
float: 'left'
|
|
|
|
})
|
|
|
|
.bindEvent({
|
2012-03-14 10:38:32 +00:00
|
|
|
click: function() {
|
|
|
|
togglePaused();
|
|
|
|
}
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.appendTo(self.$playerbar);
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
self.$muteButton = Ox.Button({
|
|
|
|
style: 'symbol',
|
2012-04-19 07:54:30 +00:00
|
|
|
title: self.options.muted ? 'unmute' : 'mute',
|
|
|
|
tooltip: self.options.muted ? 'Unmute' : 'Mute',
|
2012-03-14 10:35:46 +00:00
|
|
|
type: 'image'
|
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.css({float: 'left'})
|
2012-03-14 10:35:46 +00:00
|
|
|
.bindEvent({
|
2012-04-17 12:01:24 +00:00
|
|
|
click: toggleMuted
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.appendTo(self.$playerbar);
|
|
|
|
|
|
|
|
self.$smallTimeline = getSmallTimeline().appendTo(self.$playerbar);
|
|
|
|
|
|
|
|
self.$position = Ox.Element()
|
|
|
|
.addClass('OxPosition')
|
2012-03-14 10:35:46 +00:00
|
|
|
.css({
|
2012-04-17 12:01:24 +00:00
|
|
|
width: self.positionWidth - 4 + 'px',
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.html(formatPosition())
|
|
|
|
.bind({
|
|
|
|
click: function() {
|
|
|
|
if (!self.options.paused) {
|
|
|
|
self.playOnSubmit = true;
|
|
|
|
togglePaused();
|
|
|
|
}
|
|
|
|
self.$position.hide();
|
|
|
|
self.$positionInput
|
|
|
|
.value(formatPosition())
|
|
|
|
.show()
|
|
|
|
.focusInput(false);
|
|
|
|
}
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.appendTo(self.$playerbar);
|
|
|
|
|
|
|
|
self.$positionInput = Ox.Input({
|
|
|
|
value: formatPosition(),
|
|
|
|
width: self.positionWidth
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.addClass('OxPositionInput')
|
|
|
|
.bindEvent({
|
|
|
|
blur: submitPositionInput,
|
|
|
|
submit: submitPositionInput
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-17 12:01:24 +00:00
|
|
|
.appendTo(self.$playerbar);
|
|
|
|
|
|
|
|
self.$positionInput.children('input').css({
|
|
|
|
width: (self.positionWidth - 6) + 'px',
|
|
|
|
fontSize: '9px'
|
|
|
|
});
|
2012-03-14 10:35:46 +00:00
|
|
|
|
|
|
|
self.$panel = Ox.SplitPanel({
|
2012-04-17 12:01:24 +00:00
|
|
|
elements: [
|
|
|
|
{
|
|
|
|
element: self.$menubar,
|
|
|
|
size: 16
|
|
|
|
},
|
|
|
|
{
|
2012-04-17 13:42:46 +00:00
|
|
|
element: self.$timelinePlayer
|
2012-04-17 12:01:24 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
element: self.$playerbar,
|
|
|
|
size: 16
|
|
|
|
}
|
|
|
|
],
|
|
|
|
orientation: 'vertical'
|
|
|
|
})
|
|
|
|
.addClass('OxVideoTimelinePlayer');
|
2012-03-14 10:35:46 +00:00
|
|
|
|
|
|
|
that.setElement(self.$panel);
|
|
|
|
|
|
|
|
self.$lines = [];
|
|
|
|
self.$timelines = [];
|
|
|
|
self.$interfaces = [];
|
|
|
|
|
|
|
|
self.$timeline = renderTimeline();
|
|
|
|
//setSubtitles();
|
|
|
|
Ox.loop(self.lines, function(i) {
|
|
|
|
addLine(i);
|
|
|
|
});
|
2012-04-19 07:00:04 +00:00
|
|
|
Ox.last(self.$lines).css({
|
|
|
|
height: self.tileHeight + 1.5 * self.margin + 'px'
|
|
|
|
});
|
2012-03-14 10:35:46 +00:00
|
|
|
|
|
|
|
self.$frameBox = $('<div>')
|
|
|
|
.css({
|
|
|
|
position: 'absolute',
|
|
|
|
right: 0,
|
|
|
|
top: self.margin / 2 - 1 + 'px',
|
|
|
|
width: self.videoWidth + 'px',
|
|
|
|
height: self.tileHeight + 'px',
|
2012-04-17 13:52:45 +00:00
|
|
|
borderTop: '1px solid rgb(128, 128, 128)',
|
|
|
|
borderBottom: '1px solid rgb(128, 128, 128)',
|
2012-03-14 10:35:46 +00:00
|
|
|
background: 'rgb(0, 0, 0)'
|
|
|
|
})
|
|
|
|
.appendTo(self.$timelines[self.videoLines[1]][0]);
|
2012-04-17 12:01:24 +00:00
|
|
|
|
2012-04-18 11:21:06 +00:00
|
|
|
self.$frame = Ox.VideoPlayer({
|
|
|
|
censored: self.options.censored,
|
2012-04-22 09:57:17 +00:00
|
|
|
censoredIcon: self.options.censoredIcon,
|
|
|
|
censoredTooltip: self.options.censoredTooltip,
|
2012-04-18 11:21:06 +00:00
|
|
|
duration: self.options.duration,
|
|
|
|
height: self.tileHeight,
|
|
|
|
position: self.options.position,
|
|
|
|
scaleToFill: true,
|
|
|
|
type: 'in',
|
|
|
|
video: self.options.getFrameURL,
|
|
|
|
width: self.videoWidth
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
2012-04-22 09:57:17 +00:00
|
|
|
.bindEvent({
|
|
|
|
censored: function() {
|
|
|
|
that.triggerEvent('censored');
|
|
|
|
}
|
|
|
|
})
|
2012-04-18 11:21:06 +00:00
|
|
|
.appendTo(self.$frameBox);
|
2012-04-17 12:01:24 +00:00
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
$('<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',
|
2012-04-17 13:52:45 +00:00
|
|
|
borderTop: '1px solid rgb(128, 128, 128)',
|
|
|
|
borderBottom: '1px solid rgb(128, 128, 128)',
|
2012-03-14 10:35:46 +00:00
|
|
|
background: 'rgb(0, 0, 0)',
|
|
|
|
zIndex: 5
|
|
|
|
})
|
|
|
|
.appendTo(self.$timelines[self.videoLines[0]][0]);
|
2012-04-17 12:01:24 +00:00
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
self.$video = Ox.VideoPlayer({
|
2012-04-18 11:21:06 +00:00
|
|
|
censored: self.options.censored,
|
2012-04-22 09:57:17 +00:00
|
|
|
censoredIcon: self.options.censoredIcon,
|
|
|
|
censoredTooltip: self.options.censoredTooltip,
|
2012-03-14 10:35:46 +00:00
|
|
|
duration: self.options.duration,
|
|
|
|
height: self.tileHeight,
|
2012-04-19 08:00:12 +00:00
|
|
|
muted: self.options.muted,
|
2012-03-14 10:35:46 +00:00
|
|
|
paused: self.options.paused,
|
|
|
|
position: self.options.position,
|
|
|
|
scaleToFill: true,
|
2012-04-17 12:01:24 +00:00
|
|
|
video: self.options.video,
|
2012-03-14 10:35:46 +00:00
|
|
|
width: self.videoWidth
|
|
|
|
})
|
|
|
|
.bindEvent({
|
2012-04-22 09:57:17 +00:00
|
|
|
censored: function() {
|
|
|
|
that.triggerEvent('censored');
|
|
|
|
},
|
2012-03-14 10:35:46 +00:00
|
|
|
ended: function() {
|
|
|
|
togglePaused(true);
|
|
|
|
},
|
|
|
|
playing: function(data) {
|
|
|
|
self.options.position = data.position;
|
|
|
|
setPosition(true);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.appendTo(self.$videoBox);
|
2012-04-17 12:01:24 +00:00
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
$('<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'
|
|
|
|
})
|
2012-04-17 13:42:46 +00:00
|
|
|
.appendTo(self.$timelinePlayer);
|
2012-03-14 10:35:46 +00:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
|
2012-04-17 12:01:24 +00:00
|
|
|
function changeVolume(num) {
|
|
|
|
self.options.volume = Ox.limit(self.options.volume + num, 0, 1);
|
|
|
|
setVolume();
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatPosition(position) {
|
|
|
|
position = Ox.isUndefined(position) ? self.options.position : position;
|
|
|
|
return Ox.formatDuration(position, self.options.showMilliseconds);
|
|
|
|
}
|
|
|
|
|
2012-04-19 07:15:35 +00:00
|
|
|
function getLines(scrollbarIsVisible) {
|
2012-04-23 08:11:34 +00:00
|
|
|
// Returns the number of lines
|
2012-04-19 07:15:35 +00:00
|
|
|
var lines;
|
|
|
|
if (scrollbarIsVisible) {
|
|
|
|
self.contentWidth -= Ox.UI.SCROLLBAR_SIZE;
|
|
|
|
}
|
|
|
|
lines = Math.ceil(
|
|
|
|
(self.frames - 1 + self.videoWidth) / self.contentWidth
|
|
|
|
);
|
|
|
|
return !scrollbarIsVisible && lines * (
|
|
|
|
self.tileHeight + self.margin
|
2012-04-19 12:25:39 +00:00
|
|
|
) + self.margin > self.contentHeight ? getLines(true) : lines;
|
2012-03-14 10:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getPosition(e) {
|
|
|
|
return (
|
|
|
|
e.offsetX ? e.offsetX
|
|
|
|
: e.clientX - $(e.target).offset().left
|
|
|
|
) / self.fps;
|
|
|
|
}
|
|
|
|
|
2012-04-19 07:54:30 +00:00
|
|
|
function getPositionScrollTop() {
|
|
|
|
// Returns the target scrolltop if the player is not fully visible,
|
|
|
|
// otherwise returns null
|
|
|
|
var scrollTop = self.$timelinePlayer.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;
|
|
|
|
return videoTop[0] < scrollTop + self.margin ? videoTop[0] - self.margin
|
|
|
|
: videoTop[1] > scrollTop + offset ? videoTop[1] - offset
|
|
|
|
: null;
|
|
|
|
}
|
|
|
|
|
2012-04-17 12:01:24 +00:00
|
|
|
function getSmallTimeline() {
|
|
|
|
var $timeline = Ox.SmallVideoTimeline({
|
|
|
|
duration: self.options.duration,
|
2012-04-18 07:43:32 +00:00
|
|
|
imageURL: self.options.smallTimelineURL,
|
|
|
|
mode: 'player',
|
2012-04-17 12:01:24 +00:00
|
|
|
paused: self.options.paused,
|
|
|
|
position: self.options.position,
|
|
|
|
showMilliseconds: self.options.showMilliseconds,
|
|
|
|
width: getSmallTimelineWidth()
|
|
|
|
})
|
|
|
|
.css({float: 'left'})
|
|
|
|
.css({background: '-moz-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'})
|
|
|
|
.css({background: '-o-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'})
|
|
|
|
.css({background: '-webkit-linear-gradient(top, rgba(0, 0, 0, 0.5), rgba(64, 64, 64, 0.5))'})
|
|
|
|
.bindEvent({
|
|
|
|
position: function(data) {
|
|
|
|
self.options.position = data.position;
|
|
|
|
setPosition();
|
|
|
|
that.triggerEvent('position', {
|
|
|
|
position: self.options.position
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$timeline.children().css({marginLeft: '32px'});
|
|
|
|
$timeline.find('.OxInterface').css({marginLeft: '32px'});
|
|
|
|
return $timeline;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSmallTimelineWidth() {
|
|
|
|
return self.options.width - 32 - self.positionWidth;
|
|
|
|
}
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
function getSubtitle(position) {
|
|
|
|
var subtitle = '';
|
|
|
|
Ox.forEach(self.options.subtitles, function(v) {
|
|
|
|
if (v['in'] <= position && v.out > position) {
|
|
|
|
subtitle = v;
|
2012-05-25 07:46:34 +00:00
|
|
|
Ox.Break();
|
2012-03-14 10:35:46 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return subtitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVideoLine() {
|
|
|
|
self.videoLine = Math.floor(getVideoFrame() / self.contentWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVideoLines() {
|
2012-04-23 08:11:34 +00:00
|
|
|
// Returns the lines the video is at, as an array of two line numbers.
|
|
|
|
// If the video fits in one line, they're both the same, otherwise, the
|
|
|
|
// line that has most of the video (and thus the player) comes first.
|
2012-03-14 10:35:46 +00:00
|
|
|
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({
|
2012-05-19 13:24:00 +00:00
|
|
|
src: self.options.getLargeTimelineURL(self.options.timeline, i)
|
2012-03-14 10:35:46 +00:00
|
|
|
})
|
|
|
|
.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;
|
|
|
|
}
|
|
|
|
|
2012-04-17 13:42:46 +00:00
|
|
|
function scroll() {
|
2012-04-19 07:54:30 +00:00
|
|
|
updateScrollButton();
|
2012-04-17 13:42:46 +00:00
|
|
|
if (!self.options.paused && self.options.followPlayer) {
|
|
|
|
self.scrollTimeout && clearTimeout(self.scrollTimeout);
|
|
|
|
self.scrollTimeout = setTimeout(function() {
|
|
|
|
scrollToPosition();
|
|
|
|
self.scrollTimeout = 0;
|
|
|
|
}, 2500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
function scrollToPosition() {
|
2012-04-19 07:54:30 +00:00
|
|
|
var positionScrollTop = getPositionScrollTop();
|
|
|
|
positionScrollTop && self.$timelinePlayer.stop().animate({
|
|
|
|
scrollTop: positionScrollTop
|
|
|
|
}, 250, function() {
|
|
|
|
self.$scrollButton.hide();
|
|
|
|
});
|
2012-04-17 13:42:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function setHeight() {
|
|
|
|
self.contentHeight = self.options.height - 32;
|
|
|
|
if (!self.options.paused && self.options.followPlayer) {
|
|
|
|
self.scrollTimeout && clearTimeout(self.scrollTimeout);
|
|
|
|
scrollToPosition();
|
2012-03-14 10:35:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setPosition(fromVideo) {
|
2012-04-17 13:42:46 +00:00
|
|
|
var isPlaying = !self.options.paused,
|
2012-04-23 08:11:34 +00:00
|
|
|
max, min, videoLines, videoLines_;
|
2012-03-14 10:35:46 +00:00
|
|
|
self.options.position = Ox.limit(self.options.position, 0, self.options.duration);
|
|
|
|
self.frame = Math.floor(self.options.position * self.fps);
|
|
|
|
videoLines = getVideoLines();
|
2012-04-23 08:11:34 +00:00
|
|
|
// current and previous video lines
|
|
|
|
videoLines_ = Ox.flatten([self.videoLines, videoLines]);
|
|
|
|
min = Ox.min(videoLines_);
|
|
|
|
max = Ox.max(videoLines_);
|
2012-03-14 10:35:46 +00:00
|
|
|
Ox.loop(min, max + 1, function(i) {
|
|
|
|
self.$timelines[i][0].css({
|
|
|
|
width: self.frame + self.videoWidth + 'px'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
if (videoLines[1] != self.videoLines[1]) {
|
2012-04-23 08:11:34 +00:00
|
|
|
// move frame to new line
|
2012-03-14 10:35:46 +00:00
|
|
|
self.$frameBox.detach().appendTo(self.$timelines[videoLines[1]][0]);
|
|
|
|
}
|
|
|
|
if (videoLines[0] != self.videoLines[0]) {
|
2012-04-23 08:11:34 +00:00
|
|
|
// move video to new line
|
2012-03-14 10:35:46 +00:00
|
|
|
isPlaying && self.$video.togglePaused();
|
|
|
|
self.$videoBox.detach().appendTo(self.$timelines[videoLines[0]][0]);
|
|
|
|
isPlaying && self.$video.togglePaused();
|
|
|
|
}
|
|
|
|
if (videoLines[0] != videoLines[1]) {
|
2012-04-23 08:11:34 +00:00
|
|
|
// if the player spans two lines, update the frame
|
|
|
|
// (but only once per second if the video is playing)
|
2012-04-18 11:21:06 +00:00
|
|
|
self.$frame.options({
|
|
|
|
position: self.paused
|
|
|
|
? self.options.position
|
|
|
|
: Math.floor(self.options.position)
|
2012-03-14 10:35:46 +00:00
|
|
|
});
|
|
|
|
}
|
2012-04-17 13:42:46 +00:00
|
|
|
if (
|
2012-04-19 07:54:30 +00:00
|
|
|
fromVideo && !self.scrollTimeout
|
2012-04-17 13:42:46 +00:00
|
|
|
&& videoLines[1] != self.videoLines[1]
|
|
|
|
&& videoLines[1] > videoLines[0]
|
|
|
|
) {
|
2012-04-23 08:11:34 +00:00
|
|
|
// if the video is moving, the user has not scrolled away
|
|
|
|
// and the video has reached a line break, then either
|
|
|
|
// follow the video or update the scroll button
|
2012-04-17 13:42:46 +00:00
|
|
|
self.videoLines = videoLines;
|
2012-04-19 07:54:30 +00:00
|
|
|
self.options.followPlayer ? scrollToPosition() : updateScrollButton();
|
2012-04-17 13:42:46 +00:00
|
|
|
} else {
|
|
|
|
self.videoLines = videoLines;
|
|
|
|
}
|
2012-03-14 10:35:46 +00:00
|
|
|
if (!fromVideo) {
|
|
|
|
self.$video.options({position: self.options.position});
|
2012-04-18 11:21:06 +00:00
|
|
|
self.$frame.options({position: self.options.position});
|
2012-03-14 10:35:46 +00:00
|
|
|
scrollToPosition();
|
|
|
|
}
|
2012-04-17 12:01:24 +00:00
|
|
|
self.$smallTimeline.options({position: self.options.position});
|
2012-04-17 13:42:46 +00:00
|
|
|
self.$position.html(formatPosition());
|
2012-04-17 14:53:21 +00:00
|
|
|
that.triggerEvent(fromVideo ? 'playing' : 'position', {
|
|
|
|
position: self.options.position
|
|
|
|
});
|
2012-03-14 10:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2012-04-17 12:01:24 +00:00
|
|
|
function setVolume() {
|
|
|
|
self.$video.options({volume: self.options.volume});
|
|
|
|
that.triggerEvent('volume', {
|
|
|
|
volume: self.options.volume
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
function setWidth() {
|
2012-04-19 13:03:16 +00:00
|
|
|
self.contentWidth = self.options.width - 2 * self.margin;
|
2012-03-14 10:35:46 +00:00
|
|
|
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({
|
2012-04-19 13:03:16 +00:00
|
|
|
marginLeft: -i * self.contentWidth + self.videoWidth - 1 + 'px'
|
2012-03-14 10:35:46 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
addLine(i);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
while (self.$lines.length > self.lines) {
|
|
|
|
self.$lines[self.$lines.length - 1].remove();
|
|
|
|
self.$lines.pop();
|
|
|
|
self.$timelines.pop();
|
|
|
|
}
|
2012-04-19 07:00:04 +00:00
|
|
|
Ox.last(self.$lines).css({
|
|
|
|
height: self.tileHeight + 1.5 * self.margin + 'px'
|
|
|
|
});
|
2012-04-17 13:42:46 +00:00
|
|
|
if (!self.options.paused && self.options.followPlayer) {
|
|
|
|
self.scrollTimeout && clearTimeout(self.scrollTimeout);
|
|
|
|
scrollToPosition();
|
|
|
|
}
|
2012-04-17 12:01:24 +00:00
|
|
|
self.$smallTimeline.options({width: getSmallTimelineWidth()})
|
|
|
|
}
|
|
|
|
|
|
|
|
function submitPositionInput() {
|
|
|
|
self.$positionInput.hide();
|
|
|
|
self.$position.html('').show();
|
|
|
|
self.options.position = Ox.parseDuration(self.$positionInput.value());
|
|
|
|
setPosition();
|
|
|
|
if (self.playOnSubmit) {
|
|
|
|
togglePaused();
|
|
|
|
self.playOnSubmit = false;
|
|
|
|
}
|
|
|
|
that.triggerEvent('position', {
|
|
|
|
position: self.options.position
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleMuted() {
|
|
|
|
self.options.muted = !self.options.muted;
|
|
|
|
self.$video.options({muted: self.options.muted});
|
|
|
|
self.$muteButton.options({
|
2012-04-19 07:54:30 +00:00
|
|
|
title: self.options.muted ? 'unmute' : 'mute',
|
|
|
|
tooltip: self.options.muted ? 'Unmute' : 'Mute'
|
2012-04-17 12:01:24 +00:00
|
|
|
});
|
|
|
|
that.triggerEvent('muted', {
|
|
|
|
muted: self.options.muted
|
|
|
|
});
|
2012-03-14 10:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function togglePaused(fromVideo) {
|
|
|
|
self.options.paused = !self.options.paused;
|
2012-04-17 13:42:46 +00:00
|
|
|
if (!self.options.paused && self.options.followPlayer) {
|
|
|
|
self.scrollTimeout && clearTimeout(self.scrollTimeout);
|
|
|
|
scrollToPosition();
|
|
|
|
}
|
2012-03-14 10:35:46 +00:00
|
|
|
!fromVideo && self.$video.options({
|
|
|
|
paused: self.options.paused
|
|
|
|
});
|
|
|
|
self.$playButton.options({
|
2012-04-17 12:01:24 +00:00
|
|
|
title: self.options.paused ? 'play' : 'pause'
|
2012-03-14 10:35:46 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2012-04-19 07:54:30 +00:00
|
|
|
function updateScrollButton() {
|
|
|
|
var scrollTop = self.$timelinePlayer.scrollTop(),
|
|
|
|
positionScrollTop = getPositionScrollTop();
|
|
|
|
if (positionScrollTop === null) {
|
|
|
|
self.$scrollButton.hide();
|
|
|
|
} else {
|
|
|
|
self.$scrollButton.options({
|
|
|
|
title: positionScrollTop < scrollTop ? 'arrowUp' : 'arrowDown'
|
|
|
|
}).show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
self.setOption = function(key, value) {
|
2012-04-17 13:42:46 +00:00
|
|
|
if (key == 'height') {
|
|
|
|
setHeight();
|
2012-04-18 09:30:49 +00:00
|
|
|
} else if (key == 'position') {
|
|
|
|
setPosition();
|
2012-04-17 13:42:46 +00:00
|
|
|
} else if (key == 'width') {
|
2012-03-14 10:35:46 +00:00
|
|
|
setWidth();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-05-21 10:38:18 +00:00
|
|
|
/*@
|
|
|
|
togglePaused <f> toggle paused
|
|
|
|
() -> <o> toggle paused
|
|
|
|
@*/
|
2012-04-24 08:18:08 +00:00
|
|
|
that.togglePaused = function() {
|
|
|
|
togglePaused();
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
|
2012-03-14 10:35:46 +00:00
|
|
|
return that;
|
|
|
|
|
|
|
|
};
|