Ox.BlockTimeline = function(options, self) { var self = self || {}, that = new Ox.Element('div', self) .defaults({ cuts: [], 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(event, e) { mousedown(e); } }); $.extend(self, { $images: [], $lines: [], $markerPoint: [], $selection: [], $subtitles: [], $tooltip: new 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 = $('') .addClass('OxMarkerPosition') .attr({ src: Ox.UI.PATH + 'png/ox.ui/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] = $('') .addClass('OxMarkerPoint' + titleCase) .attr({ src: Ox.UI.PATH + 'png/ox.ui/videoMarker' + titleCase + '.png' }) .appendTo(that.$element); setMarkerPoint(i); }); }); function addLine(i) { // fixme: get URLs once, not once for every line self.$lines[i] = new Ox.Element('div') .css({ top: i * (self.height + self.margin) + 'px', width: self.options.width + 'px' }) .appendTo(that); self.$images[i] = $('') .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] = $('') .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] = $('') .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 = $('') .attr({ height: height, width: width }), canvas = $canvas[0], context = canvas.getContext('2d'), imageData = context.createImageData(width, height), data = imageData.data, points = $.map(self.options.points, 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 = $('') .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 = $('') .attr({ height: height, width: width }), canvas = $canvas[0], context = canvas.getContext('2d'); Ox.range(images).forEach(function(i) { var $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 ? '' + Ox.highlight(subtitle.value, self.options.find).replace(/\n/g, '
') + '

' + 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.onChange = 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; };