diff --git a/demos/video/index.html b/demos/video/index.html
new file mode 100644
index 00000000..a3190658
--- /dev/null
+++ b/demos/video/index.html
@@ -0,0 +1,10 @@
+
+
+
+ OxJS Video Demo
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/video/js/video.js b/demos/video/js/video.js
new file mode 100644
index 00000000..915c0c4b
--- /dev/null
+++ b/demos/video/js/video.js
@@ -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);
+});
diff --git a/source/Ox.UI/js/Core/Ox.Element.js b/source/Ox.UI/js/Core/Ox.Element.js
index 79acc1e2..8298aa55 100644
--- a/source/Ox.UI/js/Core/Ox.Element.js
+++ b/source/Ox.UI/js/Core/Ox.Element.js
@@ -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);
}
diff --git a/source/Ox.UI/js/Video/Ox.VideoElement.js b/source/Ox.UI/js/Video/Ox.VideoElement.js
index 3432c778..20a68ace 100644
--- a/source/Ox.UI/js/Video/Ox.VideoElement.js
+++ b/source/Ox.UI/js/Video/Ox.VideoElement.js
@@ -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 {
diff --git a/source/Ox.UI/js/Video/Ox.VideoPlayer.js b/source/Ox.UI/js/Video/Ox.VideoPlayer.js
new file mode 100644
index 00000000..6812356f
--- /dev/null
+++ b/source/Ox.UI/js/Video/Ox.VideoPlayer.js
@@ -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 = $('
')
+ .attr({
+ src: self.options.timeline
+ })
+ .css({
+ float: 'left',
+ width: self.innerBarWidth + 'px',
+ height: self.barHeight + 'px'
+ })
+ .appendTo(self.$innerBar.$element);
+
+ self.$buffered = $('
')
+ .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 = $('