/*@ Ox.VideoPlayer Generic Video Player Element (options, self) -> Video Player options Options subtitles URL or SRT or subtitles object start Start (sec) stop Stop (sec) text 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 = $('
') .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 = $('') .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 = $('
') //.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 = $('') .attr({ src: self.options.timelineURL }) .css({ float: 'left', width: self.innerTrackWidth + 'px', height: self.controlsHeight + 'px' }) .appendTo(self.$innerTrack.$element); self.$buffered = $('') .attr({ src: getBufferedImageURL() }) .css({ float: 'left', marginLeft: -self.innerTrackWidth + 'px', width: self.innerTrackWidth + 'px', height: self.controlsHeight + 'px', }) .appendTo(self.$innerTrack.$element); self.$positionMarker = $('
') .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 = $('
') .css({ width: '10px', height: '10px', border: '2px solid rgba(255, 255, 255, 0.5)', borderRadius: '7px', }) .append( $('
') .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 = $('
') .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 = $('') .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, '
') ); } } 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; };