oxjs/source/js/Ox.VideoPlayer.js

590 lines
19 KiB
JavaScript
Raw Normal View History

2011-04-23 16:45:50 +00:00
// vim: et:ts=4:sw=4:sts=4:ft=js
2011-04-22 22:03:10 +00:00
Ox.VideoEditor = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
annotationsSize: 0,
cuts: [],
duration: 0,
find: '',
frameURL: function() {},
fps: 25, // fixme: doesn't get handed through to player
height: 0,
largeTimeline: true,
layers: [],
matches: [],
points: [0, 0],
position: 0,
posterFrame: 0,
showAnnotations: false,
subtitles: [],
videoHeight: 0,
videoId: '',
videoWidth: 0,
videoSize: 'small',
videoURL: '',
width: 0
})
.options(options || {})
.mousedown(function() {
that.gainFocus();
})
.bindEvent({
key_shift_0: function() {
movePositionBy(-self.options.position);
},
key_alt_left: function() {
},
key_alt_right: function() {
},
key_alt_shift_left: function() {
},
key_alt_shift_right: function() {
},
key_backslash: function() {
select('subtitle');
},
key_closebracket: function() {
movePositionTo('subtitle', 1);
},
key_comma: function() {
movePositionTo('cut', -1);
},
key_dot: function() {
movePositionTo('cut', 1);
},
key_down: function() {
movePositionBy(self.sizes.timeline[0].width);
},
key_i: function() {
setPoint('in');
},
key_left: function() {
movePositionBy(-1);
},
key_m: toggleMute,
key_o: function() {
setPoint('out');
},
key_openbracket: function() {
movePositionTo('subtitle', -1);
},
key_p: playInToOut,
key_right: function() {
movePositionBy(1);
},
key_s: function() {
// toggleSize
},
key_shift_comma: function() {
movePositionTo('match', -1)
},
key_shift_dot: function() {
movePositionTo('match', 1)
},
key_shift_down: function() {
movePositionBy(self.options.duration);
},
key_shift_left: function() {
movePositionBy(-0.04);
//movePositionBy(-60);
},
key_shift_i: function() {
goToPoint('in');
},
key_shift_o: function() {
goToPoint('out');
},
key_shift_right: function() {
movePositionBy(0.04);
//movePositionBy(60);
},
key_shift_up: function() {
movePositionBy(-self.options.duration);
},
key_slash: function() {
select('cut');
},
key_space: togglePlay,
key_up: function() {
movePositionBy(-self.sizes.timeline[0].width);
}
});
$.extend(self, {
$player: [],
$timeline: [],
controlsHeight: 16,
margin: 8,
videoRatio: self.options.videoWidth / self.options.videoHeight
});
self.$editor = new Ox.Element()
.addClass('OxVideoEditor')
.click(function() {
that.gainFocus()
});
self.sizes = getSizes();
['play', 'in', 'out'].forEach(function(type, i) {
self.$player[i] = new Ox.VideoEditorPlayer({
duration: self.options.duration,
find: self.options.find,
height: self.sizes.player[i].height,
id: 'player' + Ox.toTitleCase(type),
points: self.options.points,
position: type == 'play' ? self.options.position : self.options.points[type == 'in' ? 0 : 1],
posterFrame: self.options.posterFrame,
subtitles: self.options.subtitles,
type: type,
url: type == 'play' ? self.options.videoURL : self.options.frameURL,
width: self.sizes.player[i].width
})
.css({
left: self.sizes.player[i].left + 'px',
top: self.sizes.player[i].top + 'px'
})
.bindEvent(type == 'play' ? {
playing: changePlayer,
togglesize: toggleSize
} : {
change: function() {
goToPoint(type);
},
set: function() {
setPoint(type);
}
})
.appendTo(self.$editor);
});
self.$timeline[0] = new Ox.LargeTimeline({
cuts: self.options.cuts,
duration: self.options.duration,
find: self.options.find,
id: 'timelineLarge',
matches: self.options.matches,
points: self.options.points,
position: self.options.position,
subtitles: self.options.subtitles,
videoId: self.options.videoId,
width: self.sizes.timeline[0].width
})
.css({
left: self.sizes.timeline[0].left + 'px',
top: self.sizes.timeline[0].top + 'px'
})
.bindEvent('change', changeTimelineLarge)
.bindEvent('changeEnd', changeTimelineLarge)
.appendTo(self.$editor);
self.$timeline[1] = new Ox.BlockTimeline({
cuts: self.options.cuts,
duration: self.options.duration,
find: self.options.find,
id: 'timelineSmall',
matches: self.options.matches,
points: self.options.points,
position: self.options.position,
subtitles: self.options.subtitles,
videoId: self.options.videoId,
width: self.sizes.timeline[1].width
})
.css({
left: self.sizes.timeline[1].left + 'px',
top: self.sizes.timeline[1].top + 'px'
})
.bindEvent('change', changeTimelineSmall)
.appendTo(self.$editor);
self.$annotations = new Ox.Element()
.css({
overflowY: 'auto'
})
.bindEvent({
resize: resizeAnnotations,
toggle: toggleAnnotations
});
self.$annotationPanel = [];
self.options.layers.forEach(function(layer, i) {
self.$annotationPanel[i] = new Ox.AnnotationPanel(
$.extend({
width: self.options.annotationSize
}, layer)
)
.bindEvent({
add: function(event, data) {
data.layer = layer.id;
data['in'] = self.options.points[0];
data.out = self.options.points[1];
that.triggerEvent('addAnnotation', data);
},
'delete': function(event, data) {
data.layer = layer.id;
that.triggerEvent('removeAnnotations', data);
},
select: function(event, data) {
self.options.layers.forEach(function(l, j) { // fixme: l? j?
if(l.id != layer.id) {
self.$annotationPanel[j].deselectItems();
}
});
selectAnnotation(event, data);
},
submit: updateAnnotation
});
self.$annotationPanel[i]
.appendTo(self.$annotations);
});
that.$element = new Ox.SplitPanel({
elements: [
{
element: self.$editor
},
{
collapsed: !self.options.showAnnotations,
collapsible: true,
element: self.$annotations,
resizable: true,
resize: [192, 256, 320, 384],
size: self.options.annotationsSize
}
],
orientation: 'horizontal'
});
function changePlayer(event, data) {
self.options.position = data.position;
self.$timeline[0].options({
position: data.position
});
self.$timeline[1].options({
position: data.position
});
}
function changeTimelineLarge(event, data) {
self.options.position = data.position;
self.$player[0].options({
position: data.position
});
self.$timeline[1].options({
position: data.position
});
}
function changeTimelineSmall(event, data) {
self.options.position = data.position;
self.$player[0].options({
position: data.position
});
self.$timeline[0].options({
position: data.position
});
}
function getNextPosition(type, direction) {
var found = false,
position = 0,
positions;
if (type == 'cut') {
positions = self.options.cuts;
} else if (type == 'match') {
positions = $.map(self.options.matches, function(v, i) {
return self.options.subtitles[v]['in'];
});
} else if (type == 'subtitle') {
positions = $.map(self.options.subtitles, function(v, i) {
return v['in'];
});
}
direction == -1 && positions.reverse();
Ox.forEach(positions, function(v) {
if (direction == 1 ? v > self.options.position : v < self.options.position) {
position = v;
found = true;
return false;
}
});
direction == -1 && positions.reverse();
if (!found) {
position = positions[direction == 1 ? 0 : positions.length - 1];
}
return position;
}
function getPoints(type) {
var found = false,
points,
positions = [];
if (type == 'cut') {
positions = self.options.cuts;
} else if (type == 'match') {
// ...
} else if (type == 'subtitle') {
self.options.subtitles.forEach(function(v, i) {
positions.push(v['in']);
positions.push(v.out);
});
}
positions.indexOf(0) == -1 && positions.unshift(0);
positions.indexOf(self.options.duration) == -1 &&
positions.push(self.options.duration);
Ox.forEach(positions, function(v, i) {
if (v > self.options.position) {
points = [positions[i - 1], positions[i]];
found = true;
return false;
}
});
return points;
}
function getSizes(scrollbarIsVisible) {
//Ox.print('getSizes', scrollbarIsVisible)
var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE,
contentWidth = self.options.width -
(self.options.showAnnotations * self.options.annotationsSize) - 1 -
(scrollbarIsVisible ? scrollbarWidth : 0),
height,
lines,
size = {
player: [],
timeline: []
},
width, widths;
if (self.options.videoSize == 'small') {
width = 0;
widths = Ox.divideInt(contentWidth - 4 * self.margin, 3);
[1, 0, 2].forEach(function(v, i) {
size.player[v] = {
left: (i + 0.5) * self.margin + width,
top: self.margin / 2,
width: widths[i],
height: Math.round(widths[1] / self.videoRatio)
}
width += widths[i];
});
} else {
size.player[0] = {
left: self.margin / 2,
top: self.margin / 2,
width: Math.round((contentWidth - 3 * self.margin + (self.controlsHeight + self.margin) / 2 * self.videoRatio) * 2/3),
}
size.player[0].height = Math.round(size.player[0].width / self.videoRatio);
size.player[1] = {
left: size.player[0].left + size.player[0].width + self.margin,
top: size.player[0].top,
width: contentWidth - 3 * self.margin - size.player[0].width
}
size.player[1].height = Math.ceil(size.player[1].width / self.videoRatio)
size.player[2] = {
left: size.player[1].left,
top: size.player[0].top + size.player[1].height + self.controlsHeight + self.margin,
width: size.player[1].width,
height: size.player[0].height - size.player[1].height - self.controlsHeight - self.margin
}
}
size.timeline[0] = {
left: self.margin / 2,
top: size.player[0].height + self.controlsHeight + 1.5 * self.margin,
width: contentWidth - 2 * self.margin,
height: 64
}
size.timeline[1] = {
left: size.timeline[0].left,
top: size.timeline[0].top + size.timeline[0].height + self.margin,
width: size.timeline[0].width
}
lines = Math.ceil(self.options.duration / size.timeline[1].width);
height = getHeight();
self.$editor.css({
overflowY: (scrollbarIsVisible && height <= self.options.height) ? 'scroll' : 'auto'
});
return (!scrollbarIsVisible && height > self.options.height) ? getSizes(true) : size;
function getHeight() {
return size.player[0].height + self.controlsHeight +
size.timeline[0].height + lines * 16 +
(lines + 3) * self.margin;
}
}
function goToPoint(point) {
self.options.position = self.options.points[point == 'in' ? 0 : 1];
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
function movePositionBy(sec) {
self.options.position = Ox.limit(self.options.position + sec, 0, self.options.duration);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
function movePositionTo(type, direction) {
self.options.position = getNextPosition(type, direction);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
function playInToOut() {
self.$player[0].playInToOut();
}
function resizeAnnotations(event, data) {
self.options.annotationsSize = data;
setSizes();
}
function resizeEditor(event, data) {
var width = data - 2 * margin + 100;
resizeVideoPlayers(width);
$timelineLarge.options({
width: width
});
$timelineSmall.options({
width: width
});
}
function resizePlayers() {
self.$player.forEach(function(v, i) {
v.options({
width: size[i].width,
height: size[i].height
})
.css({
left: size[i].left + 'px',
top: size[i].top + 'px',
});
});
}
function selectAnnotation(event, data) {
self.options.position = data['in']
self.options.points = [data['in'], data.out];
setPosition();
setPoints();
}
function updateAnnotation(event, data) {
data['in'] = self.options.points[0];
data.out = self.options.points[1];
that.triggerEvent('updateAnnotation', data);
}
function select(type) {
self.options.points = getPoints(type);
setPoints();
}
function setPoint(point) {
self.options.points[point == 'in' ? 0 : 1] = self.options.position;
if (self.options.points[1] < self.options.points[0]) {
self.options.points[point == 'in' ? 1 : 0] = self.options.position;
}
setPoints();
}
function setPoints() {
self.$player.forEach(function(v, i) {
v.options($.extend({
points: self.options.points
}, i ? {
position: self.options.points[i - 1]
} : {}));
});
self.$timeline.forEach(function(v) {
v.options({
points: self.options.points
});
});
}
function setPosition() {
self.$player[0].options({
position: self.options.position
});
self.$timeline.forEach(function(v) {
v.options({
position: self.options.position
});
});
}
function setSizes() {
self.sizes = getSizes();
self.$player.forEach(function(v, i) {
v.options({
height: self.sizes.player[i].height,
width: self.sizes.player[i].width
})
.css({
left: self.sizes.player[i].left + 'px',
top: self.sizes.player[i].top + 'px'
});
});
self.$timeline.forEach(function(v, i) {
v.options({
width: self.sizes.timeline[i].width
})
.css({
left: self.sizes.timeline[i].left + 'px',
top: self.sizes.timeline[i].top + 'px'
});
});
}
function toggleAnnotations(event, data) {
self.options.showAnnotations = !data.collapsed;
setSizes();
}
function toggleMute() {
self.$player[0].toggleMute();
}
function togglePlay() {
self.$player[0].togglePlay();
}
function toggleSize(event, data) {
self.options.videoSize = data.size
setSizes();
that.triggerEvent('togglesize', {
size: self.options.videoSize
});
}
self.onChange = function(key, value) {
if (key == 'width' || key == 'height') {
//Ox.print('XXXX setSizes', key, value, self.options.width, self.options.height)
setSizes();
} else if (key == 'position') {
self.$player[0].position(value);
}
};
that.addAnnotation = function(layer, item) {
var i = Ox.getPositionById(self.options.layers, layer);
self.$annotationPanel[i].addItem(item);
}
that.removeAnnotations = function(layer, ids) {
var i = Ox.getPositionById(self.options.layers, layer);
self.$annotationPanel[i].removeItems(ids);
}
return that;
};