oxjs/source/Ox.UI/js/Video/Ox.BlockTimeline.js

374 lines
12 KiB
JavaScript

// vim: et:ts=4:sw=4:sts=4:ft=javascript
/*@
Ox.BlockTimeline <f:Ox.Element> BlockTimeline Object
() -> <f> BlockTimeline Object
(options) -> <f> BlockTimeline Object
(options, self) -> <f> BlockTimeline Object
options <o> Options object
self <o> shared private variable
@*/
Ox.BlockTimeline = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
duration: 0,
find: '',
matches: [],
points: [0, 0],
position: 0,
subtitles: [],
videoId: '',
width: 0
})
.options(options || {})
.addClass('OxTimelineSmall')
.mousedown(mousedown)
.mouseleave(mouseleave)
.mousemove(mousemove)
.bindEvent({
drag: function(data) {
mousedown(data);
}
});
Ox.extend(self, {
$images: [],
$lines: [],
$markerPoint: [],
$selection: [],
$subtitles: [],
$tooltip: Ox.Tooltip({
animate: false
}).css({
textAlign: 'center'
}),
hasSubtitles: self.options.subtitles.length,
height: 16,
lines: Math.ceil(self.options.duration / self.options.width),
margin: 8
});
that.css({
width: (self.options.width + self.margin) + 'px',
height: ((self.height + self.margin) * self.lines + 4) + 'px'
});
getTimelineImageURL(function(url) {
self.timelineImageURL = url;
Ox.range(0, self.lines).forEach(function(i) {
addLine(i);
});
self.$markerPosition = $('<img>')
.addClass('OxMarkerPosition')
.attr({
src: Ox.UI.PATH + 'png/videoMarkerPlay.png'
})
.css({
position: 'absolute',
width: '9px',
height: '5px',
zIndex: 10
})
.appendTo(that.$element);
setPosition();
['in', 'out'].forEach(function(v, i) {
var titleCase = Ox.toTitleCase(v);
self.$markerPoint[i] = $('<img>')
.addClass('OxMarkerPoint' + titleCase)
.attr({
src: Ox.UI.PATH + 'png/videoMarker' + titleCase + '.png'
})
.appendTo(that.$element);
setMarkerPoint(i);
});
});
function addLine(i) {
// fixme: get URLs once, not once for every line
self.$lines[i] = Ox.Element()
.css({
top: i * (self.height + self.margin) + 'px',
width: self.options.width + 'px'
})
.appendTo(that);
self.$images[i] = $('<img>')
.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] = $('<img>')
.addClass('OxTimelineSmallSubtitles')
.attr({
src: self.subtitlesImageURL
})
.css({
marginLeft: (-i * self.options.width) + 'px'
})
.appendTo(self.$lines[i].$element);
}
if (self.options.points[0] != self.options.points[1]) {
addSelection(i);
}
}
function addSelection(i) {
self.selectionImageURL = getSelectionImageURL();
self.$selection[i] && self.$selection[i].remove();
self.$selection[i] = $('<img>')
.addClass('OxTimelineSmallSelection')
.attr({
src: self.selectionImageURL
})
.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 getSelectionImageURL() {
var height = 18,
width = Math.ceil(self.options.duration),
$canvas = $('<canvas>')
.attr({
height: height,
width: width
}),
canvas = $canvas[0],
context = canvas.getContext('2d'),
imageData = context.createImageData(width, height),
data = imageData.data,
points = self.options.points.map(function(v, i) {
return Math.round(v) + i;
}),
top = 0,
bottom = 18;
Ox.range(points[0], points[1]).forEach(function(x) {
Ox.range(top, bottom).forEach(function(y) {
var color = (y == top || y == bottom - 1) ? [255, 255, 255, 255] : [255, 255, 255, 64],
index = x * 4 + y * 4 * width;
data[index] = color[0];
data[index + 1] = color[1];
data[index + 2] = color[2];
data[index + 3] = color[3];
});
});
context.putImageData(imageData, 0, 0);
return canvas.toDataURL();
}
function getSubtitle(position) {
var subtitle = null;
Ox.forEach(self.options.subtitles, function(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 = $('<canvas>')
.attr({
height: height,
width: width
}),
canvas = $canvas[0],
context = canvas.getContext('2d'),
imageData = context.createImageData(width, height),
data = imageData.data;
self.options.subtitles.forEach(function(v) {
//var color = self.options.matches.indexOf(i) > -1 ? [255, 255, 0] : [255, 255, 255]
var inPoint = Math.round(v['in']),
outPoint = Math.round(v.out) + 1,
lines = v.value.split('\n').length,
bottom = 15,
top = bottom - lines - 2;
Ox.range(inPoint, outPoint).forEach(function(x) {
Ox.range(top, bottom).forEach(function(y) {
var color = (y == top || y == bottom - 1) ? [0, 0, 0] : [255, 255, 255],
index = x * 4 + y * 4 * width;
data[index] = color[0];
data[index + 1] = color[1];
data[index + 2] = color[2];
data[index + 3] = 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 = $('<canvas>')
.attr({
height: height,
width: width
}),
canvas = $canvas[0],
context = canvas.getContext('2d');
Ox.range(images).forEach(function(i) {
var $img = $('<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) {
var $target = $(e.target);
if (
$target.hasClass('OxTimelineSmallImage') ||
$target.hasClass('OxTimelineSmallSubtitles') ||
$target.hasClass('OxTimelineSmallSelection')
) {
self.options.position = getPosition(e);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
e.preventDefault();
}
function mouseleave(e) {
self.$tooltip.hide();
}
function mousemove(e) {
var $target = $(e.target),
position,
subtitle;
if (
$target.hasClass('OxTimelineSmallImage') ||
$target.hasClass('OxTimelineSmallSubtitles') ||
$target.hasClass('OxTimelineSmallSelection')
) {
position = getPosition(e);
subtitle = getSubtitle(position);
self.$tooltip.options({
title: subtitle ?
'<span class=\'OxBright\'>' +
Ox.highlight(subtitle.value, self.options.find, 'OxHighlight').replace(/\n/g, '<br/>') +
'</span><br/>' +
Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) :
Ox.formatDuration(position, 3)
})
.show(e.clientX, e.clientY);
} else {
self.$tooltip.hide();
}
}
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 = Math.round(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);
that.css({
width: (self.options.width + self.margin) + 'px',
height: ((self.height + self.margin) * self.lines + 4) + 'px'
});
Ox.range(self.lines).forEach(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].removeElement();
self.$lines.pop();
}
setMarker();
setMarkerPoint(0);
setMarkerPoint(1);
}
function updateSelection() {
self.$lines.forEach(function($line, i) {
addSelection(i);
});
}
self.setOption = function(key, value) {
//Ox.print('onChange:', key, value)
if (key == 'points') {
//Ox.print('key', key, 'value', value)
setMarkerPoint(0);
setMarkerPoint(1);
updateSelection();
} else if (key == 'position') {
setPosition();
} else if (key == 'width') {
setWidth();
}
};
return that;
};