mirror of
https://github.com/videojs/video.js.git
synced 2024-12-25 02:42:10 +02:00
Optimized slider controls.
Fixed issue with fullscreen progress bar tracking (offset left wasn't working in new fullscreen)
This commit is contained in:
parent
afc9369849
commit
e4f6bbb0b2
@ -79,7 +79,7 @@ so you can upgrade to newer versions easier. You can remove all these styles by
|
||||
}
|
||||
|
||||
.vjs-default-skin .vjs-control:focus {
|
||||
outline: 0;
|
||||
/* outline: 0;*/
|
||||
}
|
||||
|
||||
/* Hide control text visually, but have it available for screenreaders: h5bp.com/v */
|
||||
|
@ -61,11 +61,8 @@ _V_.Component = _V_.Class.extend({
|
||||
|
||||
destroy: function(){},
|
||||
|
||||
createElement: function(type, options){
|
||||
options = _V_.merge({
|
||||
/* Standar Options */
|
||||
}, options || {});
|
||||
return _V_.createElement(type || "div", options);
|
||||
createElement: function(type, attrs){
|
||||
return _V_.createElement(type || "div", attrs);
|
||||
},
|
||||
|
||||
buildCSSClass: function(){
|
||||
|
374
src/controls.js
vendored
374
src/controls.js
vendored
@ -1,9 +1,6 @@
|
||||
/* Control - Base class for all control elements
|
||||
================================================================================ */
|
||||
_V_.Control = _V_.Component.extend({
|
||||
init: function(player, options){
|
||||
this._super(player, options);
|
||||
},
|
||||
|
||||
buildCSSClass: function(){
|
||||
return "vjs-control " + this._super();
|
||||
@ -18,27 +15,21 @@ _V_.Button = _V_.Control.extend({
|
||||
init: function(player, options){
|
||||
this._super(player, options);
|
||||
|
||||
_V_.addEvent(this.el, "click", _V_.proxy(this, this.onClick));
|
||||
_V_.addEvent(this.el, "focus", _V_.proxy(this, this.onFocus));
|
||||
_V_.addEvent(this.el, "blur", _V_.proxy(this, this.onBlur));
|
||||
|
||||
return "fdsa";
|
||||
this.addEvent("click", this.onClick);
|
||||
this.addEvent("focus", this.onFocus);
|
||||
this.addEvent("blur", this.onBlur);
|
||||
},
|
||||
|
||||
createElement: function(type, attrs){
|
||||
// Default to Div element
|
||||
type = type || "div";
|
||||
|
||||
// Add standard Aria and Tabindex info
|
||||
attrs = _V_.merge({
|
||||
className: this.buildCSSClass(),
|
||||
innerHTML: '<div><span class="vjs-control-text">' + (this.buttonText || "Need Text") + '</span></div>',
|
||||
role: "button",
|
||||
tabIndex: 0
|
||||
}, attrs || {});
|
||||
}, attrs);
|
||||
|
||||
return this._super(type, {
|
||||
className: attrs.className || this.buildCSSClass(),
|
||||
innerHTML: attrs.innerHTML || '<div><span class="vjs-control-text">' + (this.buttonText || "Need Text") + '</span></div>'
|
||||
});
|
||||
return this._super(type, attrs);
|
||||
},
|
||||
|
||||
// Click - Override with specific functionality for button
|
||||
@ -186,8 +177,8 @@ _V_.ControlBar = _V_.Component.extend({
|
||||
init: function(player, options){
|
||||
this._super(player, options);
|
||||
|
||||
// player.addEvent("mouseover", _V_.proxy(this, this.show));
|
||||
// player.addEvent("mouseout", _V_.proxy(this, this.hide));
|
||||
player.addEvent("mouseover", _V_.proxy(this, this.show));
|
||||
player.addEvent("mouseout", _V_.proxy(this, this.hide));
|
||||
},
|
||||
|
||||
createElement: function(){
|
||||
@ -312,6 +303,138 @@ _V_.RemainingTimeDisplay = _V_.Component.extend({
|
||||
|
||||
});
|
||||
|
||||
/* Slider - Parent for seek bar and volume slider
|
||||
================================================================================ */
|
||||
_V_.Slider = _V_.Component.extend({
|
||||
|
||||
init: function(player, options){
|
||||
this._super(player, options);
|
||||
|
||||
_V_.each.call(this, this.components, function(comp){
|
||||
if (comp instanceof _V_[this.barClass]) {
|
||||
this.bar = comp;
|
||||
} else if (comp instanceof _V_[this.handleClass]) {
|
||||
this.handle = comp;
|
||||
}
|
||||
});
|
||||
|
||||
player.addEvent(this.playerEvent, _V_.proxy(this, this.update));
|
||||
|
||||
this.addEvent("mousedown", this.onMouseDown);
|
||||
this.addEvent("focus", this.onFocus);
|
||||
this.addEvent("blur", this.onBlur);
|
||||
},
|
||||
|
||||
createElement: function(type, attrs) {
|
||||
attrs = _V_.merge({
|
||||
role: "slider",
|
||||
"aria-valuenow": 0,
|
||||
"aria-valuemin": 0,
|
||||
"aria-valuemax": 100,
|
||||
tabIndex: 0
|
||||
}, attrs);
|
||||
|
||||
return this._super(type, attrs);
|
||||
},
|
||||
|
||||
onMouseDown: function(event){
|
||||
event.preventDefault();
|
||||
_V_.blockTextSelection();
|
||||
|
||||
_V_.addEvent(document, "mousemove", _V_.proxy(this, this.onMouseMove));
|
||||
_V_.addEvent(document, "mouseup", _V_.proxy(this, this.onMouseUp));
|
||||
|
||||
this.onMouseMove(event);
|
||||
},
|
||||
|
||||
onMouseUp: function(event) {
|
||||
_V_.unblockTextSelection();
|
||||
_V_.removeEvent(document, "mousemove", this.onMouseMove, false);
|
||||
_V_.removeEvent(document, "mouseup", this.onMouseUp, false);
|
||||
},
|
||||
|
||||
update: function(){
|
||||
// If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
|
||||
// On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
|
||||
// var progress = (this.player.scrubbing) ? this.player.values.currentTime / this.player.duration() : this.player.currentTime() / this.player.duration();
|
||||
|
||||
var progress = this.getPercent();
|
||||
handle = this.handle,
|
||||
bar = this.bar,
|
||||
barProgress = progress;
|
||||
|
||||
// Protect against no duration and other division issues
|
||||
if (isNaN(progress)) { progress = 0; }
|
||||
|
||||
// If there is a handle, we need to account for the handle in our calculation for progress bar
|
||||
// so that it doesn't fall short of or extend past the handle.
|
||||
if (handle) {
|
||||
var box = this.el,
|
||||
boxWidth = box.offsetWidth,
|
||||
|
||||
// The width of the handle in percent of the containing box
|
||||
handlePercent = handle.el.offsetWidth / boxWidth,
|
||||
|
||||
// Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
|
||||
// There is a margin of half the handle's width on both sides.
|
||||
boxAdjustedPercent = 1 - handlePercent;
|
||||
|
||||
// Adjust the progress that we'll use to set widths to the new adjusted box width
|
||||
adjustedProgress = progress * boxAdjustedPercent,
|
||||
|
||||
// The bar does reach the left side, so we need to account for this in the bar's width
|
||||
barProgress = adjustedProgress + (handlePercent / 2);
|
||||
|
||||
// Move the handle from the left based on the adjected progress
|
||||
handle.el.style.left = _V_.round(adjustedProgress * 100, 2) + "%";
|
||||
}
|
||||
|
||||
// Set the new bar width
|
||||
bar.el.style.width = _V_.round(barProgress * 100, 2) + "%";
|
||||
},
|
||||
|
||||
calculateDistance: function(event){
|
||||
var box = this.el,
|
||||
boxX = _V_.findPosX(box),
|
||||
boxW = box.offsetWidth,
|
||||
handle = this.handle;
|
||||
|
||||
// _V_.log(box.offsetLeft, box.offsetLeft)
|
||||
|
||||
if (handle) {
|
||||
var handleW = handle.el.offsetWidth;
|
||||
|
||||
// Adjusted X and Width, so handle doesn't go outside the bar
|
||||
boxX = boxX + (handleW / 2);
|
||||
boxW = boxW - handleW;
|
||||
}
|
||||
|
||||
// _V_.log(event.pageX, boxX, boxW);
|
||||
|
||||
// Percent that the click is through the adjusted area
|
||||
return Math.max(0, Math.min(1, (event.pageX - boxX) / boxW));
|
||||
},
|
||||
|
||||
onFocus: function(event){
|
||||
_V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
|
||||
},
|
||||
|
||||
onKeyPress: function(event){
|
||||
if (event.which == 37) { // Left Arrow
|
||||
event.preventDefault();
|
||||
this.stepBack();
|
||||
} else if (event.which == 39) { // Right Arrow
|
||||
event.preventDefault();
|
||||
this.stepForward();
|
||||
}
|
||||
},
|
||||
|
||||
onBlur: function(event){
|
||||
_V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* Progress
|
||||
================================================================================ */
|
||||
|
||||
@ -327,28 +450,14 @@ _V_.ProgressControl = _V_.Component.extend({
|
||||
});
|
||||
|
||||
// Seek Bar and holder for the progress bars
|
||||
_V_.SeekBar = _V_.Component.extend({
|
||||
_V_.SeekBar = _V_.Slider.extend({
|
||||
|
||||
barClass: "PlayProgressBar",
|
||||
handleClass: "SeekHandle",
|
||||
playerEvent: "timeupdate",
|
||||
|
||||
init: function(player, options){
|
||||
this._super(player, options);
|
||||
|
||||
_V_.each.call(this, this.components, function(comp){
|
||||
if (comp instanceof _V_.PlayProgressBar) {
|
||||
this.playProgressBar = comp;
|
||||
} else if (comp instanceof _V_.SeekHandle) {
|
||||
this.seekHandle = comp;
|
||||
}
|
||||
});
|
||||
|
||||
player.addEvent("timeupdate", _V_.proxy(this, this.update));
|
||||
|
||||
_V_.addEvent(this.el, "mousedown", _V_.proxy(this, this.onMouseDown));
|
||||
_V_.addEvent(this.el, "focus", _V_.proxy(this, this.onFocus));
|
||||
_V_.addEvent(this.el, "blur", _V_.proxy(this, this.onBlur));
|
||||
|
||||
// _V_.addEvent(element, "mousedown", _V_.proxy(element, function(event){
|
||||
// player.onSeekBarMouseDown(event, this);
|
||||
// }));
|
||||
},
|
||||
|
||||
createElement: function(){
|
||||
@ -357,66 +466,21 @@ _V_.SeekBar = _V_.Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
update: function(){
|
||||
// If scrubbing, use the cached currentTime value for speed
|
||||
var progress = /* (this.player.scrubbing) ? this.player.values.currentTime / this.player.duration() : */ this.player.currentTime() / this.player.duration();
|
||||
// Protect against no duration and other division issues
|
||||
if (isNaN(progress)) { progress = 0; }
|
||||
|
||||
var // barData = _V_.getData(bar),
|
||||
barX = _V_.findPosX(this.el),
|
||||
barW = this.el.offsetWidth,
|
||||
handle = this.seekHandle,
|
||||
progBar = this.playProgressBar,
|
||||
handleW = (handle) ? handle.el.offsetWidth : 0;
|
||||
|
||||
// Adjusted X and Width, so handle doesn't go outside the bar
|
||||
barAX = barX + (handleW / 2),
|
||||
barAW = barW - handleW,
|
||||
progBarProgress = _V_.round(progress * barAW + handleW / 2) + "px";
|
||||
|
||||
if (progBar && progBar.el.style) {
|
||||
progBar.el.style.width = progBarProgress;
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
handle.el.style.left = _V_.round(progress * barAW)+"px";
|
||||
}
|
||||
getPercent: function(){
|
||||
return this.player.currentTime() / this.player.duration();
|
||||
},
|
||||
|
||||
onMouseDown: function(event){
|
||||
event.preventDefault();
|
||||
_V_.blockTextSelection();
|
||||
|
||||
this.player.currSeekBar = this;
|
||||
this.player.currHandle = this.seekHandle || false;
|
||||
this._super(event);
|
||||
|
||||
this.player.scrubbing = true;
|
||||
|
||||
this.player.videoWasPlaying = !this.player.paused();
|
||||
this.videoWasPlaying = !this.player.paused();
|
||||
this.player.pause();
|
||||
|
||||
this.setCurrentTimeWithScrubber(event);
|
||||
_V_.addEvent(document, "mousemove", _V_.proxy(this, this.onMouseMove));
|
||||
_V_.addEvent(document, "mouseup", _V_.proxy(this, this.onMouseUp));
|
||||
},
|
||||
|
||||
setCurrentTimeWithScrubber: function(event){
|
||||
var bar = this.el,
|
||||
barX = _V_.findPosX(bar),
|
||||
barW = bar.offsetWidth,
|
||||
handle = this.player.currHandle.el,
|
||||
handleW = (handle) ? handle.offsetWidth : 0;
|
||||
|
||||
// Adjusted X and Width, so handle doesn't go outside the bar
|
||||
barAX = barX + (handleW / 2),
|
||||
barAW = barW - handleW,
|
||||
// Percent that the click is through the adjusted area
|
||||
percent = Math.max(0, Math.min(1, (event.pageX - barAX) / barAW)),
|
||||
// Percent translated to pixels
|
||||
percentPix = percent * barAW,
|
||||
// Percent translated to seconds
|
||||
newTime = percent * this.player.duration();
|
||||
onMouseMove: function(event){
|
||||
var newTime = this.calculateDistance(event) * this.player.duration();
|
||||
|
||||
// Don't let video end while scrubbing.
|
||||
if (newTime == this.player.duration()) { newTime = newTime - 0.1; }
|
||||
@ -425,33 +489,21 @@ _V_.SeekBar = _V_.Component.extend({
|
||||
this.player.currentTime(newTime);
|
||||
},
|
||||
|
||||
onMouseMove: function(event){ // Removeable
|
||||
this.setCurrentTimeWithScrubber(event);
|
||||
},
|
||||
onMouseUp: function(event){ // Removeable
|
||||
_V_.unblockTextSelection();
|
||||
_V_.removeEvent(document, "mousemove", this.onMouseMove, false);
|
||||
_V_.removeEvent(document, "mouseup", this.onMouseUp, false);
|
||||
onMouseUp: function(event){
|
||||
this._super(event);
|
||||
|
||||
this.player.scrubbing = false;
|
||||
if (this.player.videoWasPlaying) {
|
||||
if (this.videoWasPlaying) {
|
||||
this.player.play();
|
||||
}
|
||||
},
|
||||
|
||||
onFocus: function(event){
|
||||
_V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
|
||||
stepForward: function(){
|
||||
this.player.currentTime(this.player.currentTime() + 1);
|
||||
},
|
||||
onKeyPress: function(event){
|
||||
if (event.which == 37) {
|
||||
event.preventDefault();
|
||||
this.player.currentTime(this.player.currentTime() - 1);
|
||||
} else if (event.which == 39) {
|
||||
event.preventDefault();
|
||||
this.player.currentTime(this.player.currentTime() + 1);
|
||||
}
|
||||
},
|
||||
onBlur: function(event){
|
||||
_V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
|
||||
|
||||
stepBack: function(){
|
||||
this.player.currentTime(this.player.currentTime() - 1);
|
||||
}
|
||||
|
||||
});
|
||||
@ -497,9 +549,7 @@ _V_.SeekHandle = _V_.Component.extend({
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-seek-handle",
|
||||
innerHTML: '<span class="vjs-control-text">00:00</span>',
|
||||
tabIndex: 0,
|
||||
role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100
|
||||
innerHTML: '<span class="vjs-control-text">00:00</span>'
|
||||
});
|
||||
}
|
||||
|
||||
@ -518,25 +568,11 @@ _V_.VolumeControl = _V_.Component.extend({
|
||||
|
||||
});
|
||||
|
||||
_V_.VolumeBar = _V_.Component.extend({
|
||||
_V_.VolumeBar = _V_.Slider.extend({
|
||||
|
||||
init: function(player, options){
|
||||
this._super(player, options);
|
||||
|
||||
_V_.each.call(this, this.components, function(comp){
|
||||
if (comp instanceof _V_.VolumeLevel) {
|
||||
this.volumeLevel = comp;
|
||||
} else if (comp instanceof _V_.VolumeHandle) {
|
||||
this.volumeHandle = comp;
|
||||
}
|
||||
});
|
||||
|
||||
player.addEvent("volumechange", _V_.proxy(this, this.update));
|
||||
|
||||
_V_.addEvent(this.el, "mousedown", _V_.proxy(this, this.onMouseDown));
|
||||
// _V_.addEvent(this.el, "focus", _V_.proxy(this, this.onFocus));
|
||||
// _V_.addEvent(this.el, "blur", _V_.proxy(this, this.onBlur));
|
||||
},
|
||||
barClass: "VolumeLevel",
|
||||
handleClass: "VolumeHandle",
|
||||
playerEvent: "volumechange",
|
||||
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
@ -544,70 +580,21 @@ _V_.VolumeBar = _V_.Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
onMouseDown: function(event){
|
||||
event.preventDefault();
|
||||
_V_.blockTextSelection();
|
||||
|
||||
this.player.currVolumeBar = this;
|
||||
this.player.currHandle = this.volumeHandle || false;
|
||||
|
||||
this.setVolumeWithSlider(event);
|
||||
_V_.addEvent(document, "mousemove", _V_.proxy(this, this.onMouseMove));
|
||||
_V_.addEvent(document, "mouseup", _V_.proxy(this, this.onMouseUp));
|
||||
},
|
||||
onMouseMove: function(event){ // Removeable
|
||||
this.setVolumeWithSlider(event);
|
||||
},
|
||||
onMouseUp: function(event){ // Removeable
|
||||
_V_.unblockTextSelection();
|
||||
_V_.removeEvent(document, "mousemove", this.onMouseMove, false);
|
||||
_V_.removeEvent(document, "mouseup", this.onMouseUp, false);
|
||||
onMouseMove: function(event) {
|
||||
this.player.volume(this.calculateDistance(event));
|
||||
},
|
||||
|
||||
setVolumeWithSlider: function(event){
|
||||
var bar = this.el,
|
||||
barX = _V_.findPosX(bar),
|
||||
barW = bar.offsetWidth,
|
||||
handle = (this.player.currHandle) ? this.player.currHandle.el : false,
|
||||
handleW = (handle) ? handle.offsetWidth : 0;
|
||||
|
||||
// Adjusted X and Width, so handle doesn't go outside the bar
|
||||
barAX = barX + (handleW / 2),
|
||||
barAW = barW - handleW,
|
||||
// Percent that the click is through the adjusted area
|
||||
percent = Math.max(0, Math.min(1, (event.pageX - barAX) / barAW)),
|
||||
// Percent translated to pixels
|
||||
percentPix = percent * barAW,
|
||||
// Percent translated to seconds
|
||||
newTime = percent * this.player.duration();
|
||||
|
||||
this.player.volume(percent);
|
||||
getPercent: function(){
|
||||
return this.player.volume();
|
||||
},
|
||||
|
||||
update: function(){
|
||||
var vol = this.player.volume();
|
||||
|
||||
var bar = this.el;
|
||||
barX = _V_.findPosX(bar),
|
||||
barW = bar.offsetWidth,
|
||||
handle = (this.volumeHandle) ? this.volumeHandle.el : false,
|
||||
level = (this.volumeLevel) ? this.volumeLevel.el : false,
|
||||
handleW = (handle) ? handle.offsetWidth : 0;
|
||||
|
||||
// Adjusted X and Width, so handle doesn't go outside the bar
|
||||
barAX = barX + (handleW / 2),
|
||||
barAW = barW - handleW,
|
||||
progBarProgress = _V_.round(vol * barAW + handleW / 2) + "px";
|
||||
|
||||
if (level) {
|
||||
level.style.width = progBarProgress;
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
handle.style.left = _V_.round(vol * barAW)+"px";
|
||||
}
|
||||
}
|
||||
stepForward: function(){
|
||||
this.player.volume(this.player.volume() + 0.1);
|
||||
},
|
||||
|
||||
stepBack: function(){
|
||||
this.player.volume(this.player.volume() - 0.1);
|
||||
}
|
||||
});
|
||||
|
||||
_V_.VolumeLevel = _V_.Component.extend({
|
||||
@ -626,9 +613,9 @@ _V_.VolumeHandle = _V_.Component.extend({
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-volume-handle",
|
||||
innerHTML: '<span class="vjs-control-text"></span>',
|
||||
tabindex: 0,
|
||||
role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100
|
||||
innerHTML: '<span class="vjs-control-text"></span>'
|
||||
// tabindex: 0,
|
||||
// role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100
|
||||
});
|
||||
}
|
||||
|
||||
@ -708,16 +695,19 @@ _V_.SubtitlesDisplay = _V_.TextTrackDisplay.extend({
|
||||
trackType: "subtitles"
|
||||
|
||||
});
|
||||
|
||||
_V_.CaptionsDisplay = _V_.TextTrackDisplay.extend({
|
||||
|
||||
trackType: "captions"
|
||||
|
||||
});
|
||||
|
||||
_V_.ChaptersDisplay = _V_.TextTrackDisplay.extend({
|
||||
|
||||
trackType: "chapters"
|
||||
|
||||
});
|
||||
|
||||
_V_.DescriptionsDisplay = _V_.TextTrackDisplay.extend({
|
||||
|
||||
trackType: "descriptions"
|
||||
|
46
src/lib.js
46
src/lib.js
@ -1,4 +1,7 @@
|
||||
_V_.merge = function(obj1, obj2, safe){
|
||||
// Make sure second object exists
|
||||
if (!obj2) { obj2 = {}; };
|
||||
|
||||
for (var attrname in obj2){
|
||||
if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
|
||||
}
|
||||
@ -128,14 +131,7 @@ _V_.extend({
|
||||
getRelativePosition: function(x, relativeElement){
|
||||
return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
|
||||
},
|
||||
// Get an objects position on the page
|
||||
findPosX: function(obj) {
|
||||
var curleft = obj.offsetLeft;
|
||||
while(obj = obj.offsetParent) {
|
||||
curleft += obj.offsetLeft;
|
||||
}
|
||||
return curleft;
|
||||
},
|
||||
|
||||
getComputedStyleValue: function(element, style){
|
||||
return window.getComputedStyle(element, null).getPropertyValue(style);
|
||||
},
|
||||
@ -300,4 +296,38 @@ _V_.log = function(){
|
||||
(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try
|
||||
{console.log();return window.console;}catch(err){return window.console={};}})());
|
||||
|
||||
// Offset Left
|
||||
if ("getBoundingClientRect" in document.documentElement) {
|
||||
_V_.findPosX = function(el) {
|
||||
var box;
|
||||
|
||||
try {
|
||||
box = el.getBoundingClientRect();
|
||||
} catch(e) {}
|
||||
|
||||
if (!box) { return 0; }
|
||||
|
||||
var docEl = document.documentElement,
|
||||
body = document.body,
|
||||
clientLeft = docEl.clientLeft || body.clientLeft || 0,
|
||||
scrollLeft = window.pageXOffset || body.scrollLeft,
|
||||
left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return left;
|
||||
};
|
||||
} else {
|
||||
_V_.findPosX = function(el) {
|
||||
var curleft = el.offsetLeft;
|
||||
// _V_.log(obj.className, obj.offsetLeft)
|
||||
while(el = obj.offsetParent) {
|
||||
if (el.className.indexOf("video-js") == -1) {
|
||||
// _V_.log(el.offsetParent, "OFFSETLEFT", el.offsetLeft)
|
||||
// _V_.log("-webkit-full-screen", el.webkitMatchesSelector("-webkit-full-screen"));
|
||||
// _V_.log("-webkit-full-screen", el.querySelectorAll(".video-js:-webkit-full-screen"));
|
||||
} else {
|
||||
}
|
||||
curleft += el.offsetLeft;
|
||||
}
|
||||
return curleft;
|
||||
};
|
||||
}
|
||||
|
@ -362,9 +362,15 @@ _V_.Player.prototype.extend({
|
||||
|
||||
currentTime: function(seconds){
|
||||
if (seconds !== undefined) {
|
||||
this.values.currentTime = seconds; // Cache the last set value for smoother scrubbing.
|
||||
|
||||
// Cache the last set value for smoother scrubbing.
|
||||
this.values.currentTime = seconds;
|
||||
|
||||
this.apiCall("setCurrentTime", seconds);
|
||||
if (this.manualTimeUpdates) { this.triggerEvent("timeupdate"); }
|
||||
|
||||
if (this.manualTimeUpdates) {
|
||||
this.triggerEvent("timeupdate");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
return this.apiCall("currentTime");
|
||||
|
@ -1,671 +0,0 @@
|
||||
/* UI Component- Base class for all UI objects
|
||||
================================================================================ */
|
||||
_V_.Player = _V_.Component.extend({
|
||||
|
||||
init: function(tag, addOptions, ready){
|
||||
|
||||
this.tag = tag; // Store the original tag used to set options
|
||||
|
||||
var el = this.el = _V_.createElement("div"), // Div to contain video and controls
|
||||
options = this.options = {},
|
||||
width = options.width = tag.width,
|
||||
height = options.height = tag.height,
|
||||
|
||||
// Browsers default to 300x150 if there's no width/height or video size data.
|
||||
initWidth = width || 300,
|
||||
initHeight = height || 150;
|
||||
|
||||
// Make player findable on elements
|
||||
tag.player = el.player = this;
|
||||
|
||||
// Add callback to ready queue
|
||||
this.ready(ready);
|
||||
|
||||
// Wrap video tag in div (el/box) container
|
||||
tag.parentNode.insertBefore(el, tag);
|
||||
el.appendChild(tag); // Breaks iPhone, fixed in HTML5 setup.
|
||||
|
||||
// Give video tag properties to box
|
||||
el.id = this.id = tag.id; // ID will now reference box, not the video tag
|
||||
el.className = tag.className;
|
||||
// Update tag id/class for use as HTML5 playback tech
|
||||
tag.id += "_html5_api";
|
||||
tag.className = "vjs-tech";
|
||||
|
||||
// Make player easily findable by ID
|
||||
_V_.players[el.id] = this;
|
||||
|
||||
// Make box use width/height of tag, or default 300x150
|
||||
el.setAttribute("width", initWidth);
|
||||
el.setAttribute("height", initHeight);
|
||||
// Enforce with CSS since width/height attrs don't work on divs
|
||||
el.style.width = initWidth+"px";
|
||||
el.style.height = initHeight+"px";
|
||||
// Remove width/height attrs from tag so CSS can make it 100% width/height
|
||||
tag.removeAttribute("width");
|
||||
tag.removeAttribute("height");
|
||||
|
||||
// Set Options
|
||||
_V_.merge(options, _V_.options); // Copy Global Defaults
|
||||
_V_.merge(options, this.getVideoTagSettings()); // Override with Video Tag Options
|
||||
_V_.merge(options, addOptions); // Override/extend with options from setup call
|
||||
|
||||
// Store controls setting, and then remove immediately so native controls don't flash.
|
||||
tag.removeAttribute("controls");
|
||||
|
||||
// Empty video tag sources and tracks so the built in player doesn't use them also.
|
||||
if (tag.hasChildNodes()) {
|
||||
for (var i=0,j=tag.childNodes;i<j.length;i++) {
|
||||
if (j[i].nodeName == "SOURCE" || j[i].nodeName == "TRACK") {
|
||||
tag.removeChild(j[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Holder for playback tech components
|
||||
this.techs = {};
|
||||
|
||||
// Cache for video property values.
|
||||
this.values = {};
|
||||
|
||||
this.addClass("vjs-paused");
|
||||
|
||||
this.addEvent("ended", this.onEnded);
|
||||
this.addEvent("play", this.onPlay);
|
||||
this.addEvent("pause", this.onPause);
|
||||
this.addEvent("error", this.onError);
|
||||
|
||||
// When the API is ready, loop through the components and add to the player.
|
||||
if (this.options.controls) {
|
||||
this.ready(function(){
|
||||
this.each(this.options.components, function(set){
|
||||
this.addComponent(set);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If there are no sources when the player is initialized,
|
||||
// load the first supported playback technology.
|
||||
if (!this.options.sources || this.options.sources.length == 0) {
|
||||
for (var i=0,j=this.options.techOrder;i<j.length;i++) {
|
||||
var techName = j[i],
|
||||
tech = _V_[techName];
|
||||
|
||||
// Check if the browser supports this technology
|
||||
if (tech.isSupported()) {
|
||||
this.loadTech(techName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Loop through playback technologies (HTML5, Flash) and check for support
|
||||
// Then load the best source.
|
||||
this.src(this.options.sources);
|
||||
}
|
||||
},
|
||||
|
||||
// Cache for video property values.
|
||||
values: {},
|
||||
|
||||
destroy: function(){},
|
||||
|
||||
createElement: function(type, options){
|
||||
|
||||
},
|
||||
|
||||
getVideoTagSettings: function(){
|
||||
var options = {
|
||||
sources: [],
|
||||
tracks: []
|
||||
};
|
||||
|
||||
options.src = this.tag.src;
|
||||
options.controls = this.tag.getAttribute("controls") !== null;
|
||||
options.poster = this.tag.poster;
|
||||
options.preload = this.tag.preload;
|
||||
options.autoplay = this.tag.getAttribute("autoplay") !== null; // hasAttribute not IE <8 compatible
|
||||
options.loop = this.tag.getAttribute("loop") !== null;
|
||||
options.muted = this.tag.getAttribute("muted") !== null;
|
||||
|
||||
for (var c,i=0,j=this.tag.children;i<j.length;i++) {
|
||||
c = j[i];
|
||||
if (c.nodeName == "SOURCE") {
|
||||
options.sources.push({
|
||||
src: c.src,
|
||||
type: c.type,
|
||||
media: c.media,
|
||||
title: c.title
|
||||
});
|
||||
}
|
||||
if (c.nodeName == "TRACK") {
|
||||
options.tracks.push(new _V_.Track({
|
||||
src: c.getAttribute("src"),
|
||||
kind: c.getAttribute("kind"),
|
||||
srclang: c.getAttribute("srclang"),
|
||||
label: c.getAttribute("label"),
|
||||
'default': c.getAttribute("default") !== null,
|
||||
title: c.getAttribute("title")
|
||||
}, this));
|
||||
|
||||
}
|
||||
}
|
||||
return options;
|
||||
},
|
||||
|
||||
/* PLayback Technology (tech)
|
||||
================================================================================ */
|
||||
// Load/Create an instance of playback technlogy including element and API methods
|
||||
// And append playback element in player div.
|
||||
loadTech: function(techName, source){
|
||||
|
||||
this.triggerEvent("loadingtech");
|
||||
|
||||
// Pause and remove current playback technology
|
||||
if (this.tech) {
|
||||
this.removeTech(this.tech);
|
||||
|
||||
// Turn off any manual progress or timeupdate tracking
|
||||
if (this.manualProgress) {
|
||||
this.manualProgressOff()
|
||||
}
|
||||
|
||||
if (this.manualTimeUpdates) {
|
||||
this.manualTimeUpdatesOff()
|
||||
}
|
||||
|
||||
// If the first time loading, HTML5 tag will exist but won't be initialized
|
||||
// So we need to remove it if we're not loading HTML5
|
||||
} else if (!this.tech && techName != "HTML5") {
|
||||
this.removeTechElement(this.tag);
|
||||
}
|
||||
|
||||
this.techName = techName;
|
||||
|
||||
// Turn off API access because we're loading a new tech that might load asynchronously
|
||||
this.isReady = false;
|
||||
|
||||
var techReady = function(){
|
||||
// Set up playback technology's event triggers
|
||||
this.setupTriggers();
|
||||
this.player.triggerReady();
|
||||
|
||||
// Manually track progress in cases where the browser/flash player doesn't report it.
|
||||
if (!_V_.techSupports(this.name, "event", "progress")) {
|
||||
this.player.manualProgressOn();
|
||||
}
|
||||
|
||||
// Manually track timeudpates in cases where the browser/flash player doesn't report it.
|
||||
if (!_V_.techSupports(this.name, "event", "timeupdate")) {
|
||||
this.player.manualTimeUpdatesOn();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize new tech if it hasn't been yet and load source
|
||||
// Add tech element to player div
|
||||
if (this.techs[techName] === undefined) {
|
||||
this.techs[techName] = this.tech = new _V_[techName](this, { source: source });
|
||||
this.tech.ready(techReady)
|
||||
} else {
|
||||
this.tech = this.techs[techName];
|
||||
_V_.log("here3")
|
||||
_V_.insertFirst(this.techs[techName].el, this.el);
|
||||
this.src(source);
|
||||
}
|
||||
},
|
||||
|
||||
removeTech: function(tech){
|
||||
this.removeTechElement(tech.el);
|
||||
// TODO: Remove API listeners as well
|
||||
},
|
||||
|
||||
removeTechElement: function(el){
|
||||
this.el.removeChild(el);
|
||||
},
|
||||
|
||||
/* Fallbacks for unsupported event types
|
||||
================================================================================ */
|
||||
// Manually trigger progress events based on changes to the buffered amount
|
||||
// Many flash players and older HTML5 browsers don't send progress or progress-like events
|
||||
manualProgressOn: function(){
|
||||
this.manualProgress = true;
|
||||
|
||||
// Trigger progress watching when a source begins loading
|
||||
this.trackProgress();
|
||||
|
||||
// Watch for a native progress event call on the tech element
|
||||
// In HTML5, some older versions don't support the progress event
|
||||
// So we're assuming they don't, and turning off manual progress if they do.
|
||||
this.tech.addEvent("progress", function(){
|
||||
|
||||
// Remove this listener from the element
|
||||
this.removeEvent("progress", arguments.callee);
|
||||
|
||||
// Update known progress support for this playback technology
|
||||
_V_.updateTechSupport(this.name, "event", "progress", true);
|
||||
|
||||
// Turn off manual progress tracking
|
||||
this.player.manualProgressOff();
|
||||
});
|
||||
},
|
||||
|
||||
manualProgressOff: function(){
|
||||
this.manualProgress = false;
|
||||
this.stopTrackingProgress();
|
||||
},
|
||||
|
||||
trackProgress: function(){
|
||||
this.progressInterval = setInterval(_V_.proxy(this, function(){
|
||||
// Don't trigger unless buffered amount is greater than last time
|
||||
// log(this.values.bufferEnd, this.buffered().end(0), this.duration())
|
||||
/* TODO: update for multiple buffered regions */
|
||||
if (this.values.bufferEnd < this.buffered().end(0)) {
|
||||
this.triggerEvent("progress");
|
||||
} else if (this.bufferedPercent() == 1) {
|
||||
this.stopTrackingProgress();
|
||||
this.triggerEvent("progress"); // Last update
|
||||
}
|
||||
}), 500);
|
||||
},
|
||||
stopTrackingProgress: function(){ clearInterval(this.progressInterval); },
|
||||
|
||||
/* Time Tracking -------------------------------------------------------------- */
|
||||
manualTimeUpdatesOn: function(){
|
||||
this.manualTimeUpdates = true;
|
||||
|
||||
this.addEvent("play", this.trackCurrentTime);
|
||||
this.addEvent("pause", this.stopTrackingCurrentTime);
|
||||
// timeupdate is also called by .currentTime whenever current time is set
|
||||
|
||||
// Watch for native timeupdate event
|
||||
this.tech.addEvent("timeupdate", function(){
|
||||
|
||||
// Remove this listener from the element
|
||||
this.removeEvent("timeupdate", arguments.callee);
|
||||
|
||||
// Update known progress support for this playback technology
|
||||
_V_.updateTechSupport(this.name, "event", "timeupdate", true);
|
||||
|
||||
// Turn off manual progress tracking
|
||||
this.player.manualTimeUpdatesOff();
|
||||
});
|
||||
},
|
||||
|
||||
manualTimeUpdatesOff: function(){
|
||||
this.manualTimeUpdates = false;
|
||||
this.stopTrackingCurrentTime();
|
||||
this.removeEvent("play", this.trackCurrentTime);
|
||||
this.removeEvent("pause", this.stopTrackingCurrentTime);
|
||||
},
|
||||
|
||||
trackCurrentTime: function(){
|
||||
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
|
||||
this.currentTimeInterval = setInterval(_V_.proxy(this, function(){
|
||||
this.triggerEvent("timeupdate");
|
||||
}), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
|
||||
},
|
||||
|
||||
// Turn off play progress tracking (when paused or dragging)
|
||||
stopTrackingCurrentTime: function(){ clearInterval(this.currentTimeInterval); },
|
||||
|
||||
/* Player event handlers (how the player reacts to certain events)
|
||||
================================================================================ */
|
||||
onEnded: function(){
|
||||
if (this.options.loop) {
|
||||
this.currentTime(0);
|
||||
this.play();
|
||||
} else {
|
||||
// this.pause();
|
||||
// this.currentTime(0);
|
||||
// this.pause();
|
||||
}
|
||||
},
|
||||
|
||||
onPlay: function(){
|
||||
_V_.removeClass(this.el, "vjs-paused");
|
||||
_V_.addClass(this.el, "vjs-playing");
|
||||
},
|
||||
|
||||
onPause: function(){
|
||||
_V_.removeClass(this.el, "vjs-playing");
|
||||
_V_.addClass(this.el, "vjs-paused");
|
||||
},
|
||||
|
||||
onError: function(e) {
|
||||
_V_.log("Video Error", e);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/* Player API
|
||||
================================================================================ */
|
||||
_V_.Player.prototype.extend({
|
||||
|
||||
apiCall: function(method, arg){
|
||||
if (this.isReady) {
|
||||
return this.tech[method](arg);
|
||||
} else {
|
||||
_V_.log("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]")
|
||||
return false;
|
||||
// throw new Error("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]");
|
||||
}
|
||||
},
|
||||
|
||||
play: function(){
|
||||
this.apiCall("play"); return this;
|
||||
},
|
||||
pause: function(){
|
||||
this.apiCall("pause"); return this;
|
||||
},
|
||||
paused: function(){
|
||||
return this.apiCall("paused");
|
||||
},
|
||||
|
||||
currentTime: function(seconds){
|
||||
if (seconds !== undefined) {
|
||||
this.values.currentTime = seconds; // Cache the last set value for smoother scrubbing.
|
||||
this.apiCall("setCurrentTime", seconds);
|
||||
if (this.manualTimeUpdates) { this.triggerEvent("timeupdate"); }
|
||||
return this;
|
||||
}
|
||||
return this.apiCall("currentTime");
|
||||
},
|
||||
duration: function(){
|
||||
return this.apiCall("duration");
|
||||
},
|
||||
remainingTime: function(){
|
||||
return this.duration() - this.currentTime();
|
||||
},
|
||||
|
||||
buffered: function(){
|
||||
var buffered = this.apiCall("buffered"),
|
||||
start = 0, end = this.values.bufferEnd = this.values.bufferEnd || 0,
|
||||
timeRange;
|
||||
|
||||
if (buffered && buffered.length > 0 && buffered.end(0) > end) {
|
||||
end = buffered.end(0);
|
||||
// Storing values allows them be overridden by setBufferedFromProgress
|
||||
this.values.bufferEnd = end;
|
||||
}
|
||||
|
||||
return _V_.createTimeRange(start, end);
|
||||
},
|
||||
|
||||
// Calculates amount of buffer is full
|
||||
bufferedPercent: function(){
|
||||
return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
|
||||
},
|
||||
|
||||
volume: function(percentAsDecimal){
|
||||
if (percentAsDecimal !== undefined) {
|
||||
var vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
|
||||
this.values.volume = vol;
|
||||
this.apiCall("setVolume", vol);
|
||||
_V_.setLocalStorage("volume", vol);
|
||||
return this;
|
||||
}
|
||||
// if (this.values.volume) { return this.values.volume; }
|
||||
return this.apiCall("volume");
|
||||
},
|
||||
muted: function(muted){
|
||||
if (muted !== undefined) {
|
||||
this.apiCall("setMuted", muted);
|
||||
return this;
|
||||
}
|
||||
return this.apiCall("muted");
|
||||
},
|
||||
|
||||
width: function(width, skipListeners){
|
||||
if (width !== undefined) {
|
||||
this.el.width = width;
|
||||
this.el.style.width = width+"px";
|
||||
if (!skipListeners) { this.triggerEvent("resize"); }
|
||||
return this;
|
||||
}
|
||||
return parseInt(this.el.getAttribute("width"));
|
||||
},
|
||||
height: function(height){
|
||||
if (height !== undefined) {
|
||||
this.el.height = height;
|
||||
this.el.style.height = height+"px";
|
||||
this.triggerEvent("resize");
|
||||
return this;
|
||||
}
|
||||
return parseInt(this.el.getAttribute("height"));
|
||||
},
|
||||
size: function(width, height){
|
||||
// Skip resize listeners on width for optimization
|
||||
return this.width(width, true).height(height);
|
||||
},
|
||||
|
||||
supportsFullScreen: function(){ return this.apiCall("supportsFullScreen"); },
|
||||
|
||||
// Turn on fullscreen (or window) mode
|
||||
enterFullScreen: function(){
|
||||
<<<<<<< HEAD
|
||||
if (this.supportsFullScreen()) {
|
||||
this.apiCall("enterFullScreen");
|
||||
} else {
|
||||
this.enterFullWindow();
|
||||
}
|
||||
this.triggerEvent("enterFullScreen");
|
||||
return this;
|
||||
},
|
||||
|
||||
exitFullScreen: function(){
|
||||
if (true || !this.supportsFullScreen()) {
|
||||
this.exitFullWindow();
|
||||
}
|
||||
this.triggerEvent("exitFullScreen");
|
||||
|
||||
// Otherwise Shouldn't be called since native fullscreen uses own controls.
|
||||
return this;
|
||||
},
|
||||
=======
|
||||
this.videoIsFullScreen = true;
|
||||
if (typeof this.el.webkitRequestFullScreen == 'function') {
|
||||
this.el.webkitRequestFullScreen();
|
||||
} else if (this.supportsFullScreen()) {
|
||||
this.apiCall("enterFullScreen");
|
||||
} else {
|
||||
this.enterFullWindow();
|
||||
}
|
||||
this.triggerEvent("enterFullScreen");
|
||||
return this;
|
||||
},
|
||||
|
||||
exitFullScreen: function(){
|
||||
this.videoIsFullScreen = false;
|
||||
if (typeof this.el.webkitRequestFullScreen == 'function') {
|
||||
document.webkitCancelFullScreen();
|
||||
} else if (this.supportsFullScreen()) {
|
||||
document.webkitExitFullScreen();
|
||||
} else {
|
||||
this.exitFullWindow();
|
||||
}
|
||||
this.triggerEvent("exitFullScreen");
|
||||
|
||||
// Otherwise Shouldn't be called since native fullscreen uses own controls.
|
||||
return this;
|
||||
},
|
||||
>>>>>>> 2f99215f8dae0cac216f8c494f9058b087b39725
|
||||
|
||||
enterFullWindow: function(){
|
||||
this.videoIsFullScreen = true;
|
||||
|
||||
// Storing original doc overflow value to return to when fullscreen is off
|
||||
this.docOrigOverflow = document.documentElement.style.overflow;
|
||||
|
||||
// Add listener for esc key to exit fullscreen
|
||||
_V_.addEvent(document, "keydown", _V_.proxy(this, this.fullWindowOnEscKey));
|
||||
|
||||
// Hide any scroll bars
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
|
||||
// Apply fullscreen styles
|
||||
_V_.addClass(document.body, "vjs-full-window");
|
||||
_V_.addClass(this.el, "vjs-fullscreen");
|
||||
|
||||
this.triggerEvent("enterFullWindow");
|
||||
},
|
||||
|
||||
fullWindowOnEscKey: function(event){
|
||||
if (event.keyCode == 27) {
|
||||
this.exitFullScreen();
|
||||
}
|
||||
},
|
||||
|
||||
exitFullWindow: function(){
|
||||
this.videoIsFullScreen = false;
|
||||
_V_.removeEvent(document, "keydown", this.fullWindowOnEscKey);
|
||||
|
||||
// Unhide scroll bars.
|
||||
document.documentElement.style.overflow = this.docOrigOverflow;
|
||||
|
||||
// Remove fullscreen styles
|
||||
_V_.removeClass(document.body, "vjs-full-window");
|
||||
_V_.removeClass(this.el, "vjs-fullscreen");
|
||||
|
||||
// Resize the box, controller, and poster to original sizes
|
||||
// this.positionAll();
|
||||
this.triggerEvent("exitFullWindow");
|
||||
},
|
||||
|
||||
// src is a pretty powerful function
|
||||
// If you pass it an array of source objects, it will find the best source to play and use that object.src
|
||||
// If the new source requires a new playback technology, it will switch to that.
|
||||
// If you pass it an object, it will set the source to object.src
|
||||
// If you pass it anything else (url string) it will set the video source to that
|
||||
src: function(source){
|
||||
// Case: Array of source objects to choose from and pick the best to play
|
||||
if (source instanceof Array) {
|
||||
|
||||
var sources = source;
|
||||
|
||||
techLoop: // Named loop for breaking both loops
|
||||
// Loop through each playback technology in the options order
|
||||
for (var i=0,j=this.options.techOrder;i<j.length;i++) {
|
||||
var techName = j[i],
|
||||
tech = _V_[techName];
|
||||
// tech = _V_.tech[techName];
|
||||
|
||||
// Check if the browser supports this technology
|
||||
if (tech.isSupported()) {
|
||||
|
||||
// Loop through each source object
|
||||
for (var a=0,b=sources;a<b.length;a++) {
|
||||
var source = b[a];
|
||||
|
||||
// Check if source can be played with this technology
|
||||
if (tech.canPlaySource.call(this, source)) {
|
||||
|
||||
// If this technology is already loaded, set source
|
||||
if (techName == this.currentTechName) {
|
||||
this.src(source); // Passing the source object
|
||||
|
||||
// Otherwise load this technology with chosen source
|
||||
} else {
|
||||
this.loadTech(techName, source);
|
||||
}
|
||||
|
||||
break techLoop; // Break both loops
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case: Source object { src: "", type: "" ... }
|
||||
} else if (source instanceof Object) {
|
||||
if (this.tech.canPlaySource(source)) {
|
||||
this.src(source.src);
|
||||
} else {
|
||||
// Send through tech loop to check for a compatible technology.
|
||||
this.src([source]);
|
||||
}
|
||||
// Case: URL String (http://myvideo...)
|
||||
} else {
|
||||
if (!this.isReady) {
|
||||
this.ready(function(){
|
||||
this.src(source);
|
||||
});
|
||||
} else {
|
||||
this.apiCall("src", source);
|
||||
if (this.options.preload == "auto") {
|
||||
this.load();
|
||||
}
|
||||
if (this.options.autoplay) {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// Begin loading the src data
|
||||
load: function(){
|
||||
this.apiCall("load");
|
||||
return this;
|
||||
},
|
||||
currentSrc: function(){
|
||||
return this.apiCall("currentSrc");
|
||||
},
|
||||
|
||||
textTrackValue: function(kind, value){
|
||||
if (value !== undefined) {
|
||||
this.values[kind] = value;
|
||||
this.triggerEvent(kind+"update");
|
||||
return this;
|
||||
}
|
||||
return this.values[kind];
|
||||
},
|
||||
|
||||
// Attributes/Options
|
||||
preload: function(value){
|
||||
if (value !== undefined) {
|
||||
this.apiCall("setPreload", value);
|
||||
this.options.preload = value;
|
||||
return this;
|
||||
}
|
||||
return this.apiCall("preload", value);
|
||||
},
|
||||
autoplay: function(value){
|
||||
if (value !== undefined) {
|
||||
this.apiCall("setAutoplay", value);
|
||||
this.options.autoplay = value;
|
||||
return this;
|
||||
}
|
||||
return this.apiCall("autoplay", value);
|
||||
},
|
||||
loop: function(value){
|
||||
if (value !== undefined) {
|
||||
this.apiCall("setLoop", value);
|
||||
this.options.loop = value;
|
||||
return this;
|
||||
}
|
||||
return this.apiCall("loop", value);
|
||||
},
|
||||
|
||||
controls: function(){ return this.options.controls; },
|
||||
textTracks: function(){ return this.options.tracks; },
|
||||
poster: function(){ return this.apiCall("poster"); },
|
||||
|
||||
error: function(){ return this.apiCall("error"); },
|
||||
networkState: function(){ return this.apiCall("networkState"); },
|
||||
readyState: function(){ return this.apiCall("readyState"); },
|
||||
seeking: function(){ return this.apiCall("seeking"); },
|
||||
initialTime: function(){ return this.apiCall("initialTime"); },
|
||||
startOffsetTime: function(){ return this.apiCall("startOffsetTime"); },
|
||||
played: function(){ return this.apiCall("played"); },
|
||||
seekable: function(){ return this.apiCall("seekable"); },
|
||||
ended: function(){ return this.apiCall("ended"); },
|
||||
videoTracks: function(){ return this.apiCall("videoTracks"); },
|
||||
audioTracks: function(){ return this.apiCall("audioTracks"); },
|
||||
videoWidth: function(){ return this.apiCall("videoWidth"); },
|
||||
videoHeight: function(){ return this.apiCall("videoHeight"); },
|
||||
defaultPlaybackRate: function(){ return this.apiCall("defaultPlaybackRate"); },
|
||||
playbackRate: function(){ return this.apiCall("playbackRate"); },
|
||||
// mediaGroup: function(){ return this.apiCall("mediaGroup"); },
|
||||
// controller: function(){ return this.apiCall("controller"); },
|
||||
controls: function(){ return this.apiCall("controls"); },
|
||||
defaultMuted: function(){ return this.apiCall("defaultMuted"); }
|
||||
});
|
||||
|
@ -116,9 +116,10 @@ _V_.HTML5 = _V_.PlaybackTech.extend({
|
||||
|
||||
currentTime: function(){ return this.el.currentTime; },
|
||||
setCurrentTime: function(seconds){
|
||||
try { this.el.currentTime = seconds; }
|
||||
catch(e) {
|
||||
_V_.log(e);
|
||||
try {
|
||||
this.el.currentTime = seconds;
|
||||
} catch(e) {
|
||||
_V_.log(e, "Video isn't ready. (VideoJS)");
|
||||
// this.warning(VideoJS.warnings.videoNotReady);
|
||||
}
|
||||
},
|
||||
|
431
src/tech.js.orig
431
src/tech.js.orig
@ -1,431 +0,0 @@
|
||||
/* Playback Technology - Base class for playback technologies
|
||||
================================================================================ */
|
||||
_V_.PlaybackTech = _V_.Component.extend({
|
||||
init: function(player, options){
|
||||
// this._super(player, options);
|
||||
|
||||
// Make playback element clickable
|
||||
// _V_.addEvent(this.el, "click", _V_.proxy(this, _V_.PlayToggle.prototype.onClick));
|
||||
|
||||
// player.triggerEvent("techready");
|
||||
},
|
||||
createElement: function(){},
|
||||
setupTriggers: function(){},
|
||||
removeTriggers: function(){},
|
||||
|
||||
canPlaySource: function(source){
|
||||
return _V_[this.name].canPlaySource(source);
|
||||
}
|
||||
});
|
||||
|
||||
// Create placeholder methods for each that warn when a method
|
||||
// isn't supported by the current playback technology
|
||||
_V_.apiMethods = "play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(",");
|
||||
_V_.each(_V_.apiMethods, function(methodName){
|
||||
_V_.PlaybackTech.prototype[methodName] = function(){
|
||||
throw new Error("The '"+method+"' method is not available on the playback technology's API");
|
||||
}
|
||||
});
|
||||
|
||||
/* HTML5 Playback Technology - Wrapper for HTML5 Media API
|
||||
================================================================================ */
|
||||
_V_.HTML5 = _V_.PlaybackTech.extend({
|
||||
name: "HTML5",
|
||||
|
||||
init: function(player, options, ready){
|
||||
this.player = player;
|
||||
this.el = this.createElement();
|
||||
this.ready(ready);
|
||||
|
||||
var source = options.source;
|
||||
|
||||
// If the element source is already set, we may have missed the loadstart event, and want to trigger it.
|
||||
// We don't want to set the source again and interrupt playback.
|
||||
if (source && this.el.currentSrc == source.src) {
|
||||
player.triggerEvent("loadstart");
|
||||
|
||||
// Otherwise set the source if one was provided.
|
||||
} else if (source) {
|
||||
this.el.src = source.src;
|
||||
}
|
||||
|
||||
// Chrome and Safari both have issues with autoplay.
|
||||
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
|
||||
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
|
||||
// This fixes both issues. Need to wait for API, so it updates displays correctly
|
||||
player.ready(function(){
|
||||
if (this.options.autoplay && this.paused()) {
|
||||
this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
|
||||
this.play();
|
||||
}
|
||||
});
|
||||
|
||||
this.triggerReady();
|
||||
},
|
||||
|
||||
createElement: function(){
|
||||
var html5 = _V_.HTML5,
|
||||
player = this.player,
|
||||
|
||||
// Reuse original tag for HTML5 playback technology element
|
||||
el = player.tag,
|
||||
newEl;
|
||||
|
||||
// Check if this browser supports moving the element into the box.
|
||||
// On the iPhone video will break if you move the element,
|
||||
// So we have to create a brand new element.
|
||||
if (html5.supports.movingElementInDOM === false) {
|
||||
newEl = _V_.createElement("video", {
|
||||
id: el.id,
|
||||
className: el.className
|
||||
});
|
||||
|
||||
player.el.removeChild(el);
|
||||
el = newEl;
|
||||
_V_.log("here")
|
||||
_V_.insertFirst(el, player.el);
|
||||
}
|
||||
|
||||
// Update tag settings, in case they were overridden
|
||||
_V_.each(["autoplay","preload","loop","muted","poster"], function(attr){
|
||||
el[attr] = player.options[attr];
|
||||
}, this);
|
||||
|
||||
return el;
|
||||
},
|
||||
|
||||
setupTriggers: function(){
|
||||
// Make video events trigger player events
|
||||
// May seem verbose here, but makes other APIs possible.
|
||||
|
||||
// ["play", "playing", "pause", "ended", "volumechange", "error", "progress", "seeking", "timeupdate"]
|
||||
var types = _V_.HTML5.events,
|
||||
i;
|
||||
for (i = 0;i<types.length; i++) {
|
||||
_V_.addEvent(this.el, types[i], _V_.proxy(this.player, function(e){
|
||||
e.stopPropagation();
|
||||
this.triggerEvent(e);
|
||||
}));
|
||||
}
|
||||
},
|
||||
removeTriggers: function(){},
|
||||
|
||||
play: function(){ this.el.play(); },
|
||||
pause: function(){ this.el.pause(); },
|
||||
paused: function(){ return this.el.paused; },
|
||||
|
||||
currentTime: function(){ return this.el.currentTime; },
|
||||
setCurrentTime: function(seconds){
|
||||
try { this.el.currentTime = seconds; }
|
||||
catch(e) {
|
||||
_V_.log(e);
|
||||
// this.warning(VideoJS.warnings.videoNotReady);
|
||||
}
|
||||
},
|
||||
|
||||
duration: function(){ return this.el.duration || 0; },
|
||||
buffered: function(){ return this.el.buffered; },
|
||||
|
||||
volume: function(){ return this.el.volume; },
|
||||
setVolume: function(percentAsDecimal){ this.el.volume = percentAsDecimal; },
|
||||
muted: function(){ return this.el.muted; },
|
||||
setMuted: function(muted){ this.el.muted = muted },
|
||||
|
||||
width: function(){ return this.el.offsetWidth; },
|
||||
height: function(){ return this.el.offsetHeight; },
|
||||
|
||||
supportsFullScreen: function(){
|
||||
<<<<<<< HEAD
|
||||
if(typeof this.el.webkitEnterFullScreen == 'function') {
|
||||
=======
|
||||
if (typeof this.el.webkitEnterFullScreen == 'function') {
|
||||
|
||||
>>>>>>> 2f99215f8dae0cac216f8c494f9058b087b39725
|
||||
// Seems to be broken in Chromium/Chrome && Safari in Leopard
|
||||
if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
|
||||
return true;
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
}
|
||||
return false;
|
||||
},
|
||||
enterFullScreen: function(){
|
||||
try {
|
||||
this.el.webkitEnterFullScreen();
|
||||
} catch (e) {
|
||||
if (e.code == 11) {
|
||||
// this.warning(VideoJS.warnings.videoNotReady);
|
||||
_V_.log("VideoJS: Video not ready.")
|
||||
}
|
||||
}
|
||||
=======
|
||||
}
|
||||
return false;
|
||||
},
|
||||
enterFullScreen: function(){
|
||||
try {
|
||||
this.el.webkitEnterFullScreen();
|
||||
} catch (e) {
|
||||
if (e.code == 11) {
|
||||
// this.warning(VideoJS.warnings.videoNotReady);
|
||||
_V_.log("VideoJS: Video not ready.")
|
||||
}
|
||||
}
|
||||
>>>>>>> 2f99215f8dae0cac216f8c494f9058b087b39725
|
||||
},
|
||||
src: function(src){ this.el.src = src; },
|
||||
load: function(){ this.el.load(); },
|
||||
currentSrc: function(){ return this.el.currentSrc; },
|
||||
|
||||
preload: function(){ return this.el.preload; },
|
||||
setPreload: function(val){ this.el.preload = val; },
|
||||
autoplay: function(){ return this.el.autoplay; },
|
||||
setAutoplay: function(val){ this.el.autoplay = val; },
|
||||
loop: function(){ return this.el.loop; },
|
||||
setLoop: function(val){ this.el.loop = val; },
|
||||
|
||||
error: function(){ return this.el.error; },
|
||||
networkState: function(){ return this.el.networkState; },
|
||||
readyState: function(){ return this.el.readyState; },
|
||||
seeking: function(){ return this.el.seeking; },
|
||||
initialTime: function(){ return this.el.initialTime; },
|
||||
startOffsetTime: function(){ return this.el.startOffsetTime; },
|
||||
played: function(){ return this.el.played; },
|
||||
seekable: function(){ return this.el.seekable; },
|
||||
ended: function(){ return this.el.ended; },
|
||||
videoTracks: function(){ return this.el.videoTracks; },
|
||||
audioTracks: function(){ return this.el.audioTracks; },
|
||||
videoWidth: function(){ return this.el.videoWidth; },
|
||||
videoHeight: function(){ return this.el.videoHeight; },
|
||||
textTracks: function(){ return this.el.textTracks; },
|
||||
defaultPlaybackRate: function(){ return this.el.defaultPlaybackRate; },
|
||||
playbackRate: function(){ return this.el.playbackRate; },
|
||||
mediaGroup: function(){ return this.el.mediaGroup; },
|
||||
controller: function(){ return this.el.controller; },
|
||||
controls: function(){ return this.player.options.controls; },
|
||||
defaultMuted: function(){ return this.el.defaultMuted; }
|
||||
});
|
||||
|
||||
/* HTML5 Support Testing -------------------------------------------------------- */
|
||||
|
||||
_V_.HTML5.isSupported = function(){
|
||||
return !!document.createElement("video").canPlayType;
|
||||
};
|
||||
|
||||
_V_.HTML5.canPlaySource = function(srcObj){
|
||||
return !!document.createElement("video").canPlayType(srcObj.type);
|
||||
// TODO: Check Type
|
||||
// If no Type, check ext
|
||||
// Check Media Type
|
||||
};
|
||||
|
||||
_V_.HTML5.supports = {};
|
||||
|
||||
// List of all HTML5 events (various uses).
|
||||
_V_.HTML5.events = "loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange".split(",");
|
||||
|
||||
/* HTML5 Device Fixes ---------------------------------------------------------- */
|
||||
|
||||
// iOS
|
||||
if (_V_.isIOS()) {
|
||||
// If you move a video element in the DOM, it breaks video playback.
|
||||
_V_.HTML5.supports.movingElementInDOM = false;
|
||||
}
|
||||
|
||||
// Android
|
||||
if (_V_.isAndroid()) {
|
||||
|
||||
// Override Android 2.2 and less canPlayType method which is broken
|
||||
if (_V_.androidVersion() < 3) {
|
||||
document.createElement("video").constructor.prototype.canPlayType = function(type){
|
||||
return (type && type.toLowerCase().indexOf("video/mp4") != -1) ? "maybe" : "";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* H5SWF - Custom Flash Player with HTML5-ish API
|
||||
================================================================================ */
|
||||
_V_.H5swf = _V_.PlaybackTech.extend({
|
||||
name: "H5swf",
|
||||
|
||||
swf: "flash/video-js.swf",
|
||||
// swf: "https://s3.amazonaws.com/video-js/3.0b/video-js.swf",
|
||||
// swf: "http://video-js.zencoder.com/3.0b/video-js.swf",
|
||||
// swf: "http://video-js.com/test/video-js.swf",
|
||||
// swf: "http://video-js.com/source/flash/video-js.swf",
|
||||
// swf: "http://video-js.com/source/flash/video-js.swf",
|
||||
// swf: "video-js.swf",
|
||||
|
||||
init: function(player, options){
|
||||
this.player = player;
|
||||
var placeHolder = this.el = _V_.createElement("div", { id: player.el.id + "_temp_h5swf" });
|
||||
|
||||
var source = options.source,
|
||||
objId = player.el.id+"_h5swf_api",
|
||||
playerOptions = player.options;
|
||||
|
||||
flashvars = {
|
||||
readyFunction: "_V_.H5swf.onSWFReady",
|
||||
eventProxyFunction: "_V_.H5swf.onSWFEvent",
|
||||
errorEventProxyFunction: "_V_.H5swf.onSWFErrorEvent",
|
||||
autoplay: playerOptions.autoplay,
|
||||
preload: playerOptions.preload,
|
||||
loop: playerOptions.loop,
|
||||
muted: playerOptions.muted
|
||||
},
|
||||
|
||||
params = {
|
||||
allowScriptAccess: "always",
|
||||
wmode: "opaque",
|
||||
bgcolor: "#000000"
|
||||
},
|
||||
|
||||
attributes = {
|
||||
id: objId,
|
||||
name: objId,
|
||||
'class': 'vjs-tech'
|
||||
};
|
||||
|
||||
if (playerOptions.poster) {
|
||||
flashvars.poster = playerOptions.poster;
|
||||
}
|
||||
|
||||
// If source was supplied pass as a flash var.
|
||||
if (source) {
|
||||
flashvars.src = source.src;
|
||||
}
|
||||
|
||||
_V_.insertFirst(placeHolder, player.el);
|
||||
|
||||
swfobject.embedSWF(options.swf || this.swf, placeHolder.id, "480", "270", "9.0.124", "", flashvars, params, attributes);
|
||||
},
|
||||
|
||||
setupTriggers: function(){
|
||||
// Using global onSWFEvent func to distribute events
|
||||
},
|
||||
|
||||
play: function(){ this.el.vjs_play(); },
|
||||
pause: function(){ this.el.vjs_pause(); },
|
||||
src: function(src){
|
||||
this.el.vjs_src(src);
|
||||
|
||||
// Currently the SWF doesn't autoplay if you load a source later.
|
||||
// e.g. Load player w/ no source, wait 2s, set src.
|
||||
if (this.player.autoplay) {
|
||||
var tech = this;
|
||||
setTimeout(function(){ tech.play(); }, 0);
|
||||
}
|
||||
},
|
||||
load: function(){ this.el.vjs_load(); },
|
||||
poster: function(){ this.el.vjs_getProperty("poster"); },
|
||||
|
||||
buffered: function(){
|
||||
return _V_.createTimeRange(0, this.el.vjs_getProperty("buffered"));
|
||||
},
|
||||
|
||||
supportsFullScreen: function(){
|
||||
return false; // Flash does not allow fullscreen through javascript
|
||||
},
|
||||
enterFullScreen: function(){
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Create setters and getters for attributes
|
||||
(function(){
|
||||
var api = _V_.H5swf.prototype,
|
||||
readWrite = "preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted".split(","),
|
||||
readOnly = "error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks".split(","),
|
||||
callOnly = "load,play,pause".split(",");
|
||||
// Overridden: buffered
|
||||
|
||||
createSetter = function(attr){
|
||||
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
|
||||
api["set"+attrUpper] = function(val){ return this.el.vjs_setProperty(attr, val); };
|
||||
},
|
||||
|
||||
createGetter = function(attr){
|
||||
api[attr] = function(){ return this.el.vjs_getProperty(attr); };
|
||||
};
|
||||
|
||||
// Create getter and setters for all read/write attributes
|
||||
_V_.each(readWrite, function(attr){
|
||||
createGetter(attr);
|
||||
createSetter(attr);
|
||||
});
|
||||
|
||||
// Create getters for read-only attributes
|
||||
_V_.each(readOnly, function(attr){
|
||||
createGetter(attr);
|
||||
});
|
||||
})();
|
||||
|
||||
/* Flash Support Testing -------------------------------------------------------- */
|
||||
|
||||
_V_.H5swf.isSupported = function(){
|
||||
return swfobject.hasFlashPlayerVersion("9");
|
||||
};
|
||||
|
||||
_V_.H5swf.canPlaySource = function(srcObj){
|
||||
if (srcObj.type in _V_.H5swf.supports.format) { return "maybe"; }
|
||||
};
|
||||
|
||||
_V_.H5swf.supports = {
|
||||
format: {
|
||||
"video/flv": "FLV",
|
||||
"video/x-flv": "FLV",
|
||||
"video/mp4": "MP4",
|
||||
"video/m4v": "MP4"
|
||||
},
|
||||
|
||||
// Optional events that we can manually mimic with timers
|
||||
event: {
|
||||
progress: false,
|
||||
timeupdate: false
|
||||
}
|
||||
};
|
||||
|
||||
_V_.H5swf.onSWFReady = function(currSwf){
|
||||
|
||||
_V_.log(currSwf, "currSwf")
|
||||
|
||||
// Flash seems to be catching errors, so raising them manally
|
||||
try {
|
||||
// Delay for real swf ready.
|
||||
setTimeout(function(){
|
||||
var el = _V_.el(currSwf);
|
||||
|
||||
// Get player from box
|
||||
var player = el.parentNode.player,
|
||||
tech = player.techs["H5swf"];
|
||||
|
||||
// Reference player on tech element
|
||||
el.player = player;
|
||||
|
||||
// Update reference to playback technology element
|
||||
tech.el = el;
|
||||
|
||||
tech.triggerReady();
|
||||
|
||||
},0);
|
||||
|
||||
} catch(err) {
|
||||
_V_.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
_V_.H5swf.onSWFEvent = function(swfID, eventName, other){
|
||||
try {
|
||||
var player = _V_.el(swfID).player;
|
||||
if (player) {
|
||||
player.triggerEvent(eventName);
|
||||
}
|
||||
} catch(err) {
|
||||
_V_.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
_V_.H5swf.onSWFErrorEvent = function(swfID, eventName){
|
||||
_V_.log("Flash (H5SWF) Error", eventName);
|
||||
};
|
Loading…
Reference in New Issue
Block a user