add Ox.VideoPlayer, demos/video/, and prove Chrome doesn't get buffered time ranges right
This commit is contained in:
parent
15257623f3
commit
13b887abfb
5 changed files with 364 additions and 2 deletions
10
demos/video/index.html
Normal file
10
demos/video/index.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>OxJS Video Demo</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<script type="text/javascript" src="../../build/Ox.js"></script>
|
||||
<script type="text/javascript" src="js/video.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
17
demos/video/js/video.js
Normal file
17
demos/video/js/video.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
Ox.load('UI', {
|
||||
debug: true,
|
||||
theme: 'modern'
|
||||
}, function() {
|
||||
var id = '0393109';
|
||||
var url = 'http://next.0xdb.org/' + id + '/96p.webm?' + Ox.random(1000000);
|
||||
var timeline = 'http://next.0xdb.org/' + id + '/timeline.16.png';
|
||||
Ox.UI.$body.css({
|
||||
padding: '16px'
|
||||
});
|
||||
Ox.VideoPlayer({
|
||||
height: 288,
|
||||
timeline: timeline,
|
||||
url: url,
|
||||
width: 540
|
||||
}).appendTo(Ox.UI.$body);
|
||||
});
|
|
@ -331,7 +331,7 @@ Ox.Element = function() {
|
|||
Ox.forEach(Ox.makeObject(arguments), function(data, event) {
|
||||
if ([
|
||||
'mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick',
|
||||
'dragstart', 'drag', 'dragpause', 'dragend', 'playing'
|
||||
'dragstart', 'drag', 'dragpause', 'dragend', 'playing', 'progress'
|
||||
].indexOf(event) == -1) {
|
||||
Ox.print(that.id, self.options.id, 'trigger', event, data);
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@ Ox.VideoElement = function(options, self) {
|
|||
})
|
||||
.options(options || {})
|
||||
.attr({
|
||||
//height: self.options.height,
|
||||
poster: self.options.poster,
|
||||
preload: 'auto',
|
||||
src: self.options.url
|
||||
src: self.options.url,
|
||||
//width: self.options.width
|
||||
})
|
||||
.css({
|
||||
height: self.options.height + 'px',
|
||||
|
@ -28,8 +30,23 @@ Ox.VideoElement = function(options, self) {
|
|||
})
|
||||
.bind({
|
||||
ended: ended,
|
||||
canplay: function() {
|
||||
Ox.print('canplay')
|
||||
},
|
||||
durationchange: function() {
|
||||
Ox.print('durationchange')
|
||||
},
|
||||
loadedmetadata: function() {
|
||||
Ox.print('loadedmetadata', self.video.duration)
|
||||
self.video.currentTime = self.options.position;
|
||||
that.triggerEvent('loadedmetadata', {
|
||||
video: self.video
|
||||
})
|
||||
},
|
||||
progress: function() {
|
||||
that.triggerEvent('progress', {
|
||||
video: self.video
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -112,6 +129,7 @@ Ox.VideoElement = function(options, self) {
|
|||
}
|
||||
|
||||
that.position = function(pos) {
|
||||
// fixme: why not use options??
|
||||
if (arguments.length == 0) {
|
||||
return self.video.currentTime;
|
||||
} else {
|
||||
|
|
317
source/Ox.UI/js/Video/Ox.VideoPlayer.js
Normal file
317
source/Ox.UI/js/Video/Ox.VideoPlayer.js
Normal file
|
@ -0,0 +1,317 @@
|
|||
Ox.VideoPlayer = function(options, self) {
|
||||
|
||||
self = self || {};
|
||||
var that = Ox.Element({}, self)
|
||||
.defaults({
|
||||
height: 192,
|
||||
position: 0,
|
||||
timeline: '',
|
||||
url: '',
|
||||
width: 256
|
||||
})
|
||||
.options(options || {})
|
||||
.css({
|
||||
width: self.options.width + 'px',
|
||||
height: self.options.height + 'px'
|
||||
//background: 'red'
|
||||
})
|
||||
.bind({
|
||||
mouseenter: showControls,
|
||||
mouseleave: hideControls
|
||||
});
|
||||
|
||||
self.buffered = [];
|
||||
self.controlsTimeout;
|
||||
|
||||
self.barHeight = 16;
|
||||
self.outerBarWidth = self.options.width - 96;
|
||||
self.innerBarWidth = self.outerBarWidth - self.barHeight;
|
||||
self.markerSize = 12;
|
||||
self.markerBorderSize = 2;
|
||||
self.markerOffset = -self.innerBarWidth - self.markerSize / 2;
|
||||
|
||||
self.$video = Ox.VideoElement({
|
||||
height: self.options.height,
|
||||
paused: true,
|
||||
url: self.options.url,
|
||||
width: self.options.width
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
})
|
||||
.bindEvent({
|
||||
loadedmetadata: loadedmetadata,
|
||||
paused: function(data) {
|
||||
// called when playback ends
|
||||
self.$playButton.toggleTitle();
|
||||
self.$positionMarker.css({
|
||||
borderColor: 'rgb(192, 192, 192)'
|
||||
});
|
||||
},
|
||||
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)))
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$controls = Ox.Bar({
|
||||
size: self.barHeight
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
width: self.options.width + 'px',
|
||||
marginTop: self.options.height - self.barHeight + 'px',
|
||||
})
|
||||
.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.$outerBar = Ox.Element()
|
||||
.css({
|
||||
float: 'left',
|
||||
width: self.outerBarWidth + 'px',
|
||||
height: self.barHeight + 'px',
|
||||
background: 'rgb(0, 0, 0)',
|
||||
borderRadius: self.barHeight / 2 + 'px'
|
||||
})
|
||||
.appendTo(self.$controls);
|
||||
|
||||
self.$innerBar = Ox.Element()
|
||||
.css({
|
||||
float: 'left',
|
||||
width: self.innerBarWidth + 'px',
|
||||
height: self.barHeight + 'px',
|
||||
marginLeft: self.barHeight / 2 + 'px'
|
||||
})
|
||||
.appendTo(self.$outerBar);
|
||||
|
||||
self.$timeline = $('<img>')
|
||||
.attr({
|
||||
src: self.options.timeline
|
||||
})
|
||||
.css({
|
||||
float: 'left',
|
||||
width: self.innerBarWidth + 'px',
|
||||
height: self.barHeight + 'px'
|
||||
})
|
||||
.appendTo(self.$innerBar.$element);
|
||||
|
||||
self.$buffered = $('<img>')
|
||||
.attr({
|
||||
src: getBufferedImageURL()
|
||||
})
|
||||
.css({
|
||||
float: 'left',
|
||||
marginLeft: -self.innerBarWidth + 'px',
|
||||
width: self.innerBarWidth + 'px',
|
||||
height: self.barHeight + 'px',
|
||||
})
|
||||
.appendTo(self.$innerBar.$element);
|
||||
|
||||
self.$positionMarker = Ox.Element()
|
||||
.css({
|
||||
float: 'left',
|
||||
width: self.markerSize - self.markerBorderSize * 2 + 'px',
|
||||
height: self.markerSize - self.markerBorderSize * 2 + 'px',
|
||||
marginTop: (self.barHeight - self.markerSize) / 2 + 'px',
|
||||
marginLeft: self.markerOffset + 'px',
|
||||
border: self.markerBorderSize + 'px solid rgb(192, 192, 192)',
|
||||
borderRadius: self.markerSize - self.markerBorderSize * 2 + 'px',
|
||||
//background: 'rgba(0, 0, 0, 0.5)',
|
||||
boxShadow: '0 0 ' + (self.barHeight - self.markerSize) / 2 + 'px black'
|
||||
})
|
||||
.appendTo(self.$outerBar);
|
||||
|
||||
self.$tooltip = Ox.Tooltip({
|
||||
animate: false
|
||||
});
|
||||
|
||||
self.$position = Ox.Element()
|
||||
.css({
|
||||
float: 'left',
|
||||
width: '44px',
|
||||
height: '12px',
|
||||
padding: '2px',
|
||||
fontSize: '9px',
|
||||
textAlign: 'center'
|
||||
})
|
||||
.html(Ox.formatDuration(self.options.position))
|
||||
.appendTo(self.$controls)
|
||||
|
||||
function mousedownBar(e) {
|
||||
setPosition(getPosition(e));
|
||||
self.$video.position(self.options.position);
|
||||
}
|
||||
function mouseleaveBar(e) {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
function mousemoveBar(e) {
|
||||
self.$tooltip.options({
|
||||
title: Ox.formatDuration(getPosition(e))
|
||||
}).show(e.clientX, e.clientY);
|
||||
}
|
||||
function getPosition(e) {
|
||||
// fixme: no offsetX in firefox???
|
||||
if ($.browser.mozilla) {
|
||||
//Ox.print(e, e.layerX - 56)
|
||||
return Ox.limit(
|
||||
(e.layerX - 48 - self.barHeight / 2) / self.innerBarWidth * self.duration,
|
||||
0, self.duration
|
||||
);
|
||||
} else {
|
||||
return Ox.limit(
|
||||
(e.offsetX - self.barHeight / 2) / self.innerBarWidth * self.duration,
|
||||
0, self.duration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setPosition(position) {
|
||||
self.options.position = position;
|
||||
//Ox.print(-self.barWidth - 4 + self.barWidth * position / self.duration)
|
||||
self.$positionMarker.css({
|
||||
marginLeft: self.innerBarWidth * position / self.duration + self.markerOffset + 'px',
|
||||
});
|
||||
self.$position.html(Ox.formatDuration(position));
|
||||
}
|
||||
|
||||
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));
|
||||
that.gainFocus().bindEvent({
|
||||
key_space: function() {
|
||||
self.$playButton.toggleTitle();
|
||||
togglePlay();
|
||||
}
|
||||
});
|
||||
self.$outerBar.bind({
|
||||
mousedown: mousedownBar,
|
||||
mousemove: mousemoveBar,
|
||||
mouseleave: mouseleaveBar
|
||||
});
|
||||
}
|
||||
|
||||
function getBufferedImageURL() {
|
||||
var width = self.innerBarWidth,
|
||||
height = self.barHeight,
|
||||
$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 hideControls() {
|
||||
self.controlsTimeout = setTimeout(function() {
|
||||
self.$controls.animate({
|
||||
opacity: 0
|
||||
}, 250);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function showControls() {
|
||||
clearTimeout(self.controlsTimeout);
|
||||
self.$controls.animate({
|
||||
opacity: 1
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
self.$video.toggleMute();
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
self.$video.togglePlay();
|
||||
self.$positionMarker.css({
|
||||
borderColor: self.$video.paused() ? 'rgb(192, 192, 192)' : 'rgb(255, 255, 255)'
|
||||
});
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
Loading…
Reference in a new issue