oxjs/source/Ox.UI/js/Video/Ox.VideoPlayer.js
2011-05-13 00:12:41 +02:00

590 lines
No EOL
18 KiB
JavaScript

/*@
Ox.VideoPlayer <f> Generic Video Player Element
(options, self) -> <o> Video Player
options <o> Options
subtitles <s|[o]> URL or SRT or subtitles object
start <n> Start (sec)
stop <n> Stop (sec)
text <s> Text
@*/
Ox.VideoPlayer = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
autoplay: false,
height: 192,
logoLink: '',
logoTitle: '',
logoURL: '',
position: 0,
showControls: false,
showVolume: false,
subtitles: [],
timelineURL: '',
title: '',
videoURL: '',
width: 256
})
.options(options || {})
.css({
position: 'absolute',
width: self.options.width + 'px',
height: self.options.height + 'px'
//background: 'red'
})
.bind({
mouseenter: showControls,
mouseleave: hideControls
});
if (Ox.isString(self.options.subtitles)) {
if (self.options.subtitles.indexOf('\n') > -1) {
self.options.subtitles = Ox.parseSRT(self.options.subtitles);
} else {
Ox.get(self.options.subtitles, function(data) {
self.options.subtitles = Ox.parseSRT(data);
});
//self.options.subtitles = [];
}
}
self.buffered = [];
self.controlsTimeout;
self.dragTimeout;
self.controlsHeight = 16;
self.outerTrackWidth = self.options.width - 96;
self.innerTrackWidth = self.outerTrackWidth - self.controlsHeight;
self.markerOffset = -self.innerTrackWidth - 8;
self.$video = Ox.VideoElement({
height: self.options.height,
paused: true,
url: self.options.videoURL,
width: self.options.width
})
.css({
position: 'absolute',
})
.bindEvent({
loadedmetadata: loadedmetadata,
paused: function(data) {
// called when playback ends
/*
self.$playButton.toggleTitle();
self.$positionMarkerRing.css({
borderColor: 'rgba(255, 255, 255, 0.5)'
});
*/
},
playing: function(data) {
setPosition(data.position);
},
progress: function(data) {
var buffered = data.video.buffered;
for (var i = 0; i < buffered.length; i++) {
self.buffered[i] = [buffered.start(i), buffered.end(i)];
// fixme: firefox weirdness
if (self.buffered[i][0] > self.buffered[i][1]) {
self.buffered[i][0] = 0;
}
//Ox.print(i, self.buffered[i][0], self.buffered[i][1])
}
self.$buffered.attr({
src: getBufferedImageURL()
})
//self.$bar.html(Ox.formatDuration(data.video.buffered.end(data.video.buffered.length - 1)))
},
seeked: hideLoadingIcon,
seeking: showLoadingIcon
})
.appendTo(that);
if (self.options.title) {
self.$titlebar = $('<div>')
.css({
position: 'absolute',
width: self.options.width + 'px',
height: '15px',
paddingTop: '1px',
textAlign: 'center'
})
.css({
backgroundImage: '-moz-linear-gradient(top, rgba(64, 64, 64, 0.5), rgba(0, 0, 0, 0.5))'
})
.css({
backgroundImage: '-webkit-linear-gradient(top, rgba(64, 64, 64, 0.5), rgba(0, 0, 0, 0.5))'
})
.html(self.options.title)
.appendTo(that.$element);
}
self.$loadingIcon = $('<img>')
.attr({
src: Ox.UI.getImagePath('symbolLoadingAnimated.svg').replace('/classic/', '/modern/')
})
.css({
position: 'absolute',
left: parseInt(self.options.width / 2) - 16 + 'px', // fixme
top: parseInt(self.options.height / 2) - 16 + 'px',
width: '32px',
height: '32px'
})
.appendTo(that.$element);
self.$subtitle = $('<div>')
//.addClass('OxSubtitle')
.css({
position: 'absolute',
left: 0,
right: 0,
textAlign: 'center',
textShadow: 'rgba(0, 0, 0, 1) 0 0 4px',
color: 'rgb(255, 255, 255)'
})
.appendTo(that.$element);
setSubtitleSize();
self.$controls = Ox.Bar({
size: self.controlsHeight
})
.css({
position: 'absolute',
bottom: 0,
width: self.options.width + 'px',
})
.css({
backgroundImage: '-moz-linear-gradient(top, rgba(64, 64, 64, 0.5), rgba(0, 0, 0, 0.5))'
})
.css({
backgroundImage: '-webkit-linear-gradient(top, rgba(64, 64, 64, 0.5), rgba(0, 0, 0, 0.5))'
})
.appendTo(that);
self.$buttons = Ox.Element()
.css({
float: 'left',
width: '48px'
})
.appendTo(self.$controls);
self.$playButton = Ox.Button({
style: 'symbol',
title: [
{id: 'play', title: 'play'},
{id: 'pause', title: 'pause'}
],
tooltip: ['Play', 'Pause'],
type: 'image'
})
.css({
borderRadius: 0
})
.bindEvent('click', togglePlay)
.appendTo(self.$buttons);
self.$muteButton = Ox.Button({
style: 'symbol',
title: [
{id: 'mute', title: 'mute'},
{id: 'unmute', title: 'unmute'}
],
tooltip: ['Mute', 'Unmute'],
type: 'image'
})
.css({
borderRadius: 0
})
.bindEvent('click', toggleMute)
.appendTo(self.$buttons);
self.$menuButton = Ox.Button({
id: 'select',
style: 'symbol',
title: 'select',
tooltip: ['Menu'],
type: 'image'
})
.css({
borderRadius: 0
})
.bindEvent('click', togglePlay)
.appendTo(self.$buttons);
self.$outerTrack = Ox.Element()
.css({
float: 'left',
width: self.outerTrackWidth + 'px',
height: self.controlsHeight + 'px',
background: 'rgba(0, 0, 0, 0.75)',
borderRadius: self.controlsHeight / 2 + 'px'
})
/*
.css({
backgroundImage: '-moz-linear-gradient(top, rgba(0, 0, 0, 0.75), rgba(64, 64, 64, 0.75))'
})
.css({
backgroundImage: '-webkit-linear-gradient(top, rgba(0, 0, 0, 0.75), rgba(64, 64, 64, 0.75))'
})
*/
.appendTo(self.$controls);
self.$innerTrack = Ox.Element()
.css({
float: 'left',
width: self.innerTrackWidth + 'px',
height: self.controlsHeight + 'px',
marginLeft: self.controlsHeight / 2 + 'px'
})
.appendTo(self.$outerTrack);
self.$timeline = $('<img>')
.attr({
src: self.options.timelineURL
})
.css({
float: 'left',
width: self.innerTrackWidth + 'px',
height: self.controlsHeight + 'px'
})
.appendTo(self.$innerTrack.$element);
self.$buffered = $('<img>')
.attr({
src: getBufferedImageURL()
})
.css({
float: 'left',
marginLeft: -self.innerTrackWidth + 'px',
width: self.innerTrackWidth + 'px',
height: self.controlsHeight + 'px',
})
.appendTo(self.$innerTrack.$element);
self.$positionMarker = $('<div>')
.css({
float: 'left',
width: '14px',
height: '14px',
marginLeft: self.markerOffset + 'px',
border: '1px solid rgba(0, 0, 0, 0.5)',
borderRadius: '8px'
})
.append(
self.$positionMarkerRing = $('<div>')
.css({
width: '10px',
height: '10px',
border: '2px solid rgba(255, 255, 255, 0.5)',
borderRadius: '7px',
})
.append(
$('<div>')
.css({
width: '8px',
height: '8px',
border: '1px solid rgba(0, 0, 0, 0.5)',
borderRadius: '5px',
})
)
)
.appendTo(self.$outerTrack.$element);
self.$trackInterface = Ox.Element()
.css({
float: 'left',
width: self.outerTrackWidth + 'px',
height: self.controlsHeight + 'px',
marginLeft: - self.outerTrackWidth + 'px'
})
.appendTo(self.$controls);
self.$tooltip = Ox.Tooltip({
animate: false
});
self.$position = $('<div>')
.css({
float: 'left',
width: '44px',
height: '12px',
padding: '2px',
fontSize: '9px',
textAlign: 'center'
})
.html(Ox.formatDuration(self.options.position))
.bind({
click: function() {
if (!self.$video.paused()) {
self.wasPlaying = true;
self.$video.pause();
self.$playButton.toggleTitle();
}
self.$position.hide();
self.$positionInput
.options({
value: Ox.formatDuration(self.options.position)
})
.show()
.focusInput(false);
}
})
.appendTo(self.$controls.$element)
self.$positionInput = Ox.Input({
value: Ox.formatDuration(self.options.position),
width: 48
})
.css({
float: 'left',
background: 'rgba(0, 0, 0, 0)',
MozBoxShadow: '0 0 0',
WebkitBoxShadow: '0 0 0'
})
.bindEvent({
blur: submitPositionInput,
change: submitPositionInput,
//submit: submitPositionInput
})
.hide()
.appendTo(self.$controls.$element);
self.$positionInput.children('input').css({
width: '42px',
height: '16px',
padding: '0 3px 0 3px',
border: '0px',
borderRadius: '8px',
fontSize: '9px',
color: 'rgb(255, 255, 255)'
})
.css({
background: '-moz-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))'
});
function submitPositionInput() {
self.$positionInput.hide();
self.$position.html('').show();
setPosition(parsePositionInput(self.$positionInput.options('value')));
self.$video.position(self.options.position);
if (self.wasPlaying) {
self.$video.play();
self.$playButton.toggleTitle();
self.wasPlaying = false;
}
}
function parsePositionInput(str) {
var position,
split = str.split(':').reverse();
while (split.length > 3) {
split.pop();
}
position = split.reduce(function(prev, curr, i) {
return prev + (parseFloat(curr) || 0) * Math.pow(60, i);
}, 0)
return Ox.limit(position, 0, self.duration);
}
function getPosition(e) {
// fixme: no offsetX in firefox???
if ($.browser.mozilla) {
//Ox.print(e, e.layerX - 56)
return Ox.limit(
(e.layerX - 48 - self.controlsHeight / 2) / self.innerTrackWidth * self.duration,
0, self.duration
);
} else {
/*Ox.print(e.offsetX, Ox.limit(
(e.offsetX - self.controlsHeight / 2) / self.innerTrackWidth * self.duration,
0, self.duration
))*/
return Ox.limit(
(e.offsetX - self.controlsHeight / 2) / self.innerTrackWidth * self.duration,
0, self.duration
);
}
}
function getBufferedImageURL() {
var width = self.innerTrackWidth,
height = self.controlsHeight,
$canvas = $('<canvas>')
.attr({
width: width,
height: height,
}),
canvas = $canvas[0],
context = canvas.getContext('2d');
//Ox.print(width, height)
context.fillStyle = 'rgba(255, 0, 0, 0.5)';
context.fillRect(0, 0, width, height);
var imageData = context.getImageData(0, 0, width, height),
data = imageData.data;
self.buffered.forEach(function(range) {
var left = Math.round(range[0] * width / self.duration),
right = Math.round(range[1] * width / self.duration);
Ox.loop(left, right, function(x) {
Ox.loop(height, function(y) {
index = x * 4 + y * 4 * width;
data[index + 3] = 0;
});
});
});
context.putImageData(imageData, 0, 0);
return canvas.toDataURL();
}
function getSubtitle() {
var subtitle = '';
Ox.forEach(self.options.subtitles, function(v) {
if (
v['in'] <= self.options.position &&
v.out > self.options.position
) {
subtitle = v.text;
return false;
}
});
return subtitle;
}
function hideControls() {
Ox.print('!!!!!!', self.$positionInput.hasFocus())
if (!self.$positionInput.hasFocus()) {
self.controlsTimeout = setTimeout(function() {
// fixme: use class
self.$titlebar.animate({
opacity: 0
}, 250);
self.$controls.animate({
opacity: 0
}, 250);
}, 1000);
}
}
function hideLoadingIcon() {
self.$loadingIcon.animate({
opacity: 0
}, 0);
}
function loadedmetadata(data) {
//self.$position.html(Ox.formatDuration(data.video.duration))
//Ox.print('!!!!', data.video.width, data.video.height, data.video.videoWidth, data.video.videoHeight)
self.duration = data.video.duration;
//Ox.print('DURATION', Ox.formatDuration(self.duration));
hideLoadingIcon();
that.gainFocus().bindEvent({
key_space: function() {
self.$playButton.toggleTitle();
togglePlay();
}
});
self.$trackInterface
.bind({
mousedown: mousedownTrack,
mouseleave: mouseleaveTrack,
mousemove: mousemoveTrack,
})
.bindEvent({
drag: dragTrack,
dragpause: dragpauseTrack,
dragend: dragpauseTrack
});
}
function dragTrack(e) {
setPosition(getPosition(e));
if (self.dragTimeout) {
clearTimeout(self.dragTimeout);
self.dragTimeout = 0;
}
}
function dragpauseTrack(e) {
self.$video.position(self.options.position);
}
function mousedownTrack(e) {
setPosition(getPosition(e));
self.$video.position(self.options.position);
}
function mouseleaveTrack(e) {
self.$tooltip.hide();
}
function mousemoveTrack(e) {
self.$tooltip.options({
title: Ox.formatDuration(getPosition(e))
}).show(e.clientX, e.clientY);
}
function showControls() {
clearTimeout(self.controlsTimeout);
self.$titlebar.animate({
opacity: 1
}, 250);
self.$controls.animate({
opacity: 1
}, 250);
}
function setPosition(position) {
self.options.position = position;
//Ox.print(-self.barWidth - 4 + self.barWidth * position / self.duration)
setSubtitle();
self.$positionMarker.css({
marginLeft: self.innerTrackWidth * position / self.duration + self.markerOffset + 'px',
});
self.$position.html(Ox.formatDuration(position));
}
function setSubtitle() {
var subtitle = getSubtitle();
if (subtitle != self.subtitle) {
self.subtitle = subtitle;
self.$subtitle.html(
Ox.highlight(self.subtitle, self.options.find, 'Ox.Highlight')
.replace(/\n/g, '<br/>')
);
}
}
function setSubtitleSize() {
self.$subtitle.css({
bottom: parseInt(self.controlsHeight + self.options.height / 16) + 'px',
width: self.options.width + 'px',
fontSize: parseInt(self.options.height / 20) + 'px',
WebkitTextStroke: (self.options.height / 1000) + 'px rgb(0, 0, 0)'
});
}
function showLoadingIcon() {
self.$loadingIcon.animate({
opacity: 1
}, 0);
}
function toggleMute() {
self.$video.toggleMute();
}
function togglePlay() {
self.$video.togglePlay();
self.$positionMarkerRing.css({
borderColor: 'rgba(255, 255, 255, ' + (self.$video.paused() ? 0.5 : 1) + ')'
});
}
return that;
};