mirror of
https://github.com/videojs/video.js.git
synced 2024-12-02 09:11:54 +02:00
2d2e7117b3
Conflicts: src/core.js
899 lines
29 KiB
JavaScript
899 lines
29 KiB
JavaScript
/* 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 = {};
|
|
|
|
// 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
|
|
|
|
// Add callback to ready queue
|
|
this.ready(ready);
|
|
|
|
// Store controls setting, and then remove immediately so native controls don't flash.
|
|
tag.removeAttribute("controls");
|
|
|
|
// Poster will be handled by a manual <img>
|
|
tag.removeAttribute("poster");
|
|
|
|
// Make player findable on elements
|
|
tag.player = el.player = this;
|
|
|
|
// 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
|
|
// ID will now reference box, not the video tag
|
|
this.id = el.id = tag.id;
|
|
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", options.width);
|
|
el.setAttribute("height", options.height);
|
|
|
|
// Enforce with CSS since width/height attrs don't work on divs
|
|
el.style.width = options.width+"px";
|
|
el.style.height = options.height+"px";
|
|
|
|
// Remove width/height attrs from tag so CSS can make it 100% width/height
|
|
tag.removeAttribute("width");
|
|
tag.removeAttribute("height");
|
|
|
|
// Empty video tag sources and tracks so the built-in player doesn't use them also.
|
|
if (tag.hasChildNodes()) {
|
|
var nrOfChildNodes = tag.childNodes.length;
|
|
for (var i=0,j=tag.childNodes;i<nrOfChildNodes;i++) {
|
|
if (j[0].nodeName.toLowerCase() == "source" || j[0].nodeName.toLowerCase() == "track") {
|
|
tag.removeChild(j[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cache for video property values.
|
|
this.values = {};
|
|
|
|
this.addClass("vjs-paused");
|
|
|
|
this.on("ended", this.onEnded);
|
|
this.on("play", this.onPlay);
|
|
this.on("pause", this.onPause);
|
|
this.on("progress", this.onProgress);
|
|
this.on("error", this.onError);
|
|
|
|
// When the API is ready, loop through the components and add to the player.
|
|
if (options.controls) {
|
|
this.ready(function(){
|
|
this.initComponents();
|
|
});
|
|
}
|
|
|
|
// Tracks defined in tracks.js
|
|
this.textTracks = [];
|
|
if (options.tracks && options.tracks.length > 0) {
|
|
this.addTextTracks(options.tracks);
|
|
}
|
|
|
|
// If there are no sources when the player is initialized,
|
|
// load the first supported playback technology.
|
|
if (!options.sources || options.sources.length == 0) {
|
|
for (var i=0,j=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.
|
|
// A few assumptions here:
|
|
// All playback technologies respect preload false.
|
|
this.src(options.sources);
|
|
}
|
|
},
|
|
|
|
// Cache for video property values.
|
|
values: {},
|
|
|
|
destroy: function(){
|
|
// Ensure that tracking progress and time progress will stop and plater deleted
|
|
this.stopTrackingProgress();
|
|
this.stopTrackingCurrentTime();
|
|
_V_.players[this.id] = null;
|
|
delete _V_.players[this.id];
|
|
this.tech.destroy();
|
|
this.el.parentNode.removeChild(this.el);
|
|
},
|
|
|
|
createElement: function(type, options){},
|
|
|
|
getVideoTagSettings: function(){
|
|
var options = {
|
|
sources: [],
|
|
tracks: []
|
|
},
|
|
tag = this.tag,
|
|
getAttribute = "getAttribute"; // For better minification
|
|
|
|
options.src = tag[getAttribute]("src");
|
|
options.controls = tag[getAttribute]("controls") !== null;
|
|
options.poster = tag[getAttribute]("poster");
|
|
options.preload = tag[getAttribute]("preload");
|
|
options.autoplay = tag[getAttribute]("autoplay") !== null; // hasAttribute not IE <8 compatible
|
|
options.loop = tag[getAttribute]("loop") !== null;
|
|
options.muted = tag[getAttribute]("muted") !== null;
|
|
|
|
if (this.tag.hasChildNodes()) {
|
|
for (var c,i=0,j=this.tag.childNodes;i<j.length;i++) {
|
|
c = j[i];
|
|
if (c.nodeName.toLowerCase() == "source") {
|
|
options.sources.push({
|
|
src: c[getAttribute]('src'),
|
|
type: c[getAttribute]('type'),
|
|
media: c[getAttribute]('media'),
|
|
title: c[getAttribute]('title')
|
|
});
|
|
}
|
|
if (c.nodeName.toLowerCase() == "track") {
|
|
options.tracks.push({
|
|
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")
|
|
});
|
|
}
|
|
}
|
|
}
|
|
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){
|
|
|
|
// Pause and remove current playback technology
|
|
if (this.tech) {
|
|
this.unloadTech();
|
|
|
|
// 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 (techName != "html5" && this.tag) {
|
|
this.el.removeChild(this.tag);
|
|
this.tag = false;
|
|
}
|
|
|
|
this.techName = techName;
|
|
|
|
// Turn off API access because we're loading a new tech that might load asynchronously
|
|
this.isReady = false;
|
|
|
|
var techReady = function(){
|
|
this.player.triggerReady();
|
|
|
|
// Manually track progress in cases where the browser/flash player doesn't report it.
|
|
if (!this.support.progressEvent) {
|
|
this.player.manualProgressOn();
|
|
}
|
|
|
|
// Manually track timeudpates in cases where the browser/flash player doesn't report it.
|
|
if (!this.support.timeupdateEvent) {
|
|
this.player.manualTimeUpdatesOn();
|
|
}
|
|
}
|
|
|
|
// Grab tech-specific options from player options and add source and parent element to use.
|
|
var techOptions = _V_.merge({ source: source, parentEl: this.el }, this.options[techName])
|
|
|
|
if (source) {
|
|
if (source.src == this.values.src && this.values.currentTime > 0) {
|
|
techOptions.startTime = this.values.currentTime;
|
|
}
|
|
|
|
this.values.src = source.src;
|
|
}
|
|
|
|
// Initialize tech instance
|
|
this.tech = new _V_[techName](this, techOptions);
|
|
this.tech.ready(techReady);
|
|
},
|
|
|
|
unloadTech: function(){
|
|
this.tech.destroy();
|
|
|
|
// Turn off any manual progress or timeupdate tracking
|
|
if (this.manualProgress) { this.manualProgressOff(); }
|
|
|
|
if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
|
|
|
|
this.tech = false;
|
|
},
|
|
|
|
// There's many issues around changing the size of a Flash (or other plugin) object.
|
|
// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
|
|
// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
|
|
// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
|
|
// reloadTech: function(betweenFn){
|
|
// _V_.log("unloadingTech")
|
|
// this.unloadTech();
|
|
// _V_.log("unloadedTech")
|
|
// if (betweenFn) { betweenFn.call(); }
|
|
// _V_.log("LoadingTech")
|
|
// this.loadTech(this.techName, { src: this.values.src })
|
|
// _V_.log("loadedTech")
|
|
// },
|
|
|
|
/* 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.on("progress", function(){
|
|
|
|
// Remove this listener from the element
|
|
this.removeEvent("progress", arguments.callee);
|
|
|
|
// Update known progress support for this playback technology
|
|
this.support.progressEvent = 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.trigger("progress");
|
|
} else if (this.bufferedPercent() == 1) {
|
|
this.stopTrackingProgress();
|
|
this.trigger("progress"); // Last update
|
|
}
|
|
}), 500);
|
|
},
|
|
stopTrackingProgress: function(){ clearInterval(this.progressInterval); },
|
|
|
|
/* Time Tracking -------------------------------------------------------------- */
|
|
manualTimeUpdatesOn: function(){
|
|
this.manualTimeUpdates = true;
|
|
|
|
this.on("play", this.trackCurrentTime);
|
|
this.on("pause", this.stopTrackingCurrentTime);
|
|
// timeupdate is also called by .currentTime whenever current time is set
|
|
|
|
// Watch for native timeupdate event
|
|
this.tech.on("timeupdate", function(){
|
|
|
|
// Remove this listener from the element
|
|
this.removeEvent("timeupdate", arguments.callee);
|
|
|
|
// Update known progress support for this playback technology
|
|
this.support.timeupdateEvent = 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.trigger("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");
|
|
},
|
|
|
|
onProgress: function(){
|
|
// Add custom event for when source is finished downloading.
|
|
if (this.bufferedPercent() == 1) {
|
|
this.trigger("loadedalldata");
|
|
}
|
|
},
|
|
|
|
onError: function(e) {
|
|
_V_.log("Video Error", e);
|
|
},
|
|
|
|
/* Player API
|
|
================================================================================ */
|
|
|
|
// Pass values to the playback tech
|
|
techCall: function(method, arg){
|
|
|
|
// If it's not ready yet, call method when it is
|
|
if (!this.tech.isReady) {
|
|
this.tech.ready(function(){
|
|
this[method](arg);
|
|
});
|
|
|
|
// Otherwise call method now
|
|
} else {
|
|
try {
|
|
this.tech[method](arg);
|
|
} catch(e) {
|
|
_V_.log(e);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Get calls can't wait for the tech, and sometimes don't need to.
|
|
techGet: function(method){
|
|
|
|
// Make sure tech is ready
|
|
if (this.tech.isReady) {
|
|
|
|
// Flash likes to die and reload when you hide or reposition it.
|
|
// In these cases the object methods go away and we get errors.
|
|
// When that happens we'll catch the errors and inform tech that it's not ready any more.
|
|
try {
|
|
return this.tech[method]();
|
|
} catch(e) {
|
|
|
|
// When building additional tech libs, an expected method may not be defined yet
|
|
if (this.tech[method] === undefined) {
|
|
_V_.log("Video.js: " + method + " method not defined for "+this.techName+" playback technology.", e);
|
|
|
|
} else {
|
|
|
|
// When a method isn't available on the object it throws a TypeError
|
|
if (e.name == "TypeError") {
|
|
_V_.log("Video.js: " + method + " unavailable on "+this.techName+" playback technology element.", e);
|
|
this.tech.isReady = false;
|
|
|
|
} else {
|
|
_V_.log(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
},
|
|
|
|
// Method for calling methods on the current playback technology
|
|
// techCall: function(method, arg){
|
|
//
|
|
// // if (this.isReady) {
|
|
// //
|
|
// // } else {
|
|
// // _V_.log("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]", arguments.callee.caller.arguments.callee.caller.arguments.callee.caller)
|
|
// // return false;
|
|
// // // throw new Error("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]");
|
|
// // }
|
|
// },
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-play
|
|
play: function(){
|
|
this.techCall("play");
|
|
return this;
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-pause
|
|
pause: function(){
|
|
this.techCall("pause");
|
|
return this;
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-paused
|
|
// The initial state of paused should be true (in Safari it's actually false)
|
|
paused: function(){
|
|
return (this.techGet("paused") === false) ? false : true;
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-currenttime
|
|
currentTime: function(seconds){
|
|
if (seconds !== undefined) {
|
|
|
|
// Cache the last set value for smoother scrubbing.
|
|
this.values.lastSetCurrentTime = seconds;
|
|
|
|
this.techCall("setCurrentTime", seconds);
|
|
|
|
// Improve the accuracy of manual timeupdates
|
|
if (this.manualTimeUpdates) { this.trigger("timeupdate"); }
|
|
|
|
return this;
|
|
}
|
|
|
|
// Cache last currentTime and return
|
|
// Default to 0 seconds
|
|
return this.values.currentTime = (this.techGet("currentTime") || 0);
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-duration
|
|
// Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN.
|
|
duration: function(){
|
|
return parseFloat(this.techGet("duration"));
|
|
},
|
|
|
|
// Calculates how much time is left. Not in spec, but useful.
|
|
remainingTime: function(){
|
|
return this.duration() - this.currentTime();
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-buffered
|
|
// Buffered returns a timerange object. Kind of like an array of portions of the video that have been downloaded.
|
|
// So far no browsers return more than one range (portion)
|
|
buffered: function(){
|
|
var buffered = this.techGet("buffered"),
|
|
start = 0,
|
|
end = this.values.bufferEnd = this.values.bufferEnd || 0, // Default end to 0 and store in values
|
|
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. Not in spec but useful.
|
|
bufferedPercent: function(){
|
|
return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-volume
|
|
volume: function(percentAsDecimal){
|
|
var vol;
|
|
|
|
if (percentAsDecimal !== undefined) {
|
|
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
|
|
this.values.volume = vol;
|
|
this.techCall("setVolume", vol);
|
|
_V_.setLocalStorage("volume", vol);
|
|
return this;
|
|
}
|
|
|
|
// Default to 1 when returning current volume.
|
|
vol = parseFloat(this.techGet("volume"));
|
|
return (isNaN(vol)) ? 1 : vol;
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#attr-media-muted
|
|
muted: function(muted){
|
|
if (muted !== undefined) {
|
|
this.techCall("setMuted", muted);
|
|
return this;
|
|
}
|
|
return this.techGet("muted") || false; // Default to false
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
|
|
// Video tag width/height only work in pixels. No percents.
|
|
// We could potentially allow percents but won't for now until we can do testing around it.
|
|
width: function(width, skipListeners){
|
|
if (width !== undefined) {
|
|
this.el.width = width;
|
|
this.el.style.width = width+"px";
|
|
|
|
// skipListeners allows us to avoid triggering the resize event when setting both width and height
|
|
if (!skipListeners) { this.trigger("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.trigger("resize");
|
|
return this;
|
|
}
|
|
return parseInt(this.el.getAttribute("height"));
|
|
},
|
|
// Set both width and height at the same time.
|
|
size: function(width, height){
|
|
// Skip resize listeners on width for optimization
|
|
return this.width(width, true).height(height);
|
|
},
|
|
|
|
// Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
|
|
supportsFullScreen: function(){ return this.techGet("supportsFullScreen") || false; },
|
|
|
|
// Turn on fullscreen (or window) mode
|
|
requestFullScreen: function(){
|
|
var requestFullScreen = _V_.support.requestFullScreen;
|
|
|
|
this.isFullScreen = true;
|
|
|
|
// Check for browser element fullscreen support
|
|
if (requestFullScreen) {
|
|
|
|
// Trigger fullscreenchange event after change
|
|
_V_.on(document, requestFullScreen.eventName, this.proxy(function(){
|
|
this.isFullScreen = document[requestFullScreen.isFullScreen];
|
|
|
|
// If cancelling fullscreen, remove event listener.
|
|
if (this.isFullScreen == false) {
|
|
_V_.removeEvent(document, requestFullScreen.eventName, arguments.callee);
|
|
}
|
|
|
|
this.trigger("fullscreenchange");
|
|
}));
|
|
|
|
// Flash and other plugins get reloaded when you take their parent to fullscreen.
|
|
// To fix that we'll remove the tech, and reload it after the resize has finished.
|
|
if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) {
|
|
|
|
this.pause();
|
|
this.unloadTech();
|
|
|
|
_V_.on(document, requestFullScreen.eventName, this.proxy(function(){
|
|
_V_.removeEvent(document, requestFullScreen.eventName, arguments.callee);
|
|
this.loadTech(this.techName, { src: this.values.src });
|
|
}));
|
|
|
|
this.el[requestFullScreen.requestFn]();
|
|
|
|
} else {
|
|
this.el[requestFullScreen.requestFn]();
|
|
}
|
|
|
|
} else if (this.tech.supportsFullScreen()) {
|
|
this.trigger("fullscreenchange");
|
|
this.techCall("enterFullScreen");
|
|
|
|
} else {
|
|
this.trigger("fullscreenchange");
|
|
this.enterFullWindow();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
cancelFullScreen: function(){
|
|
var requestFullScreen = _V_.support.requestFullScreen;
|
|
|
|
this.isFullScreen = false;
|
|
|
|
// Check for browser element fullscreen support
|
|
if (requestFullScreen) {
|
|
|
|
// Flash and other plugins get reloaded when you take their parent to fullscreen.
|
|
// To fix that we'll remove the tech, and reload it after the resize has finished.
|
|
if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) {
|
|
|
|
this.pause();
|
|
this.unloadTech();
|
|
|
|
_V_.on(document, requestFullScreen.eventName, this.proxy(function(){
|
|
_V_.removeEvent(document, requestFullScreen.eventName, arguments.callee);
|
|
this.loadTech(this.techName, { src: this.values.src })
|
|
}));
|
|
|
|
document[requestFullScreen.cancelFn]();
|
|
|
|
} else {
|
|
document[requestFullScreen.cancelFn]();
|
|
}
|
|
|
|
} else if (this.tech.supportsFullScreen()) {
|
|
this.techCall("exitFullScreen");
|
|
this.trigger("fullscreenchange");
|
|
|
|
} else {
|
|
this.exitFullWindow();
|
|
this.trigger("fullscreenchange");
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
|
|
enterFullWindow: function(){
|
|
this.isFullWindow = 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_.on(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.trigger("enterFullWindow");
|
|
},
|
|
fullWindowOnEscKey: function(event){
|
|
if (event.keyCode == 27) {
|
|
if (this.isFullScreen == true) {
|
|
this.cancelFullScreen();
|
|
} else {
|
|
this.exitFullWindow();
|
|
}
|
|
}
|
|
},
|
|
|
|
exitFullWindow: function(){
|
|
this.isFullWindow = 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.trigger("exitFullWindow");
|
|
},
|
|
|
|
selectSource: function(sources){
|
|
|
|
// 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)) {
|
|
|
|
return { source: source, tech: techName };
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
// 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 sourceTech = this.selectSource(source),
|
|
source,
|
|
techName;
|
|
|
|
if (sourceTech) {
|
|
source = sourceTech.source;
|
|
techName = sourceTech.tech;
|
|
|
|
// If this technology is already loaded, set source
|
|
if (techName == this.techName) {
|
|
this.src(source); // Passing the source object
|
|
|
|
// Otherwise load this technology with chosen source
|
|
} else {
|
|
this.loadTech(techName, source);
|
|
}
|
|
} else {
|
|
_V_.log("No compatible source and playback technology were found.")
|
|
}
|
|
|
|
// Case: Source object { src: "", type: "" ... }
|
|
} else if (source instanceof Object) {
|
|
|
|
if (_V_[this.techName].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 {
|
|
// Cache for getting last set source
|
|
this.values.src = source;
|
|
|
|
if (!this.isReady) {
|
|
this.ready(function(){
|
|
this.src(source);
|
|
});
|
|
} else {
|
|
this.techCall("src", source);
|
|
if (this.options.preload == "auto") {
|
|
this.load();
|
|
}
|
|
if (this.options.autoplay) {
|
|
this.play();
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Begin loading the src data
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-load
|
|
load: function(){
|
|
this.techCall("load");
|
|
return this;
|
|
},
|
|
|
|
// http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
|
|
currentSrc: function(){
|
|
return this.techGet("currentSrc") || this.values.src || "";
|
|
},
|
|
|
|
// Attributes/Options
|
|
preload: function(value){
|
|
if (value !== undefined) {
|
|
this.techCall("setPreload", value);
|
|
this.options.preload = value;
|
|
return this;
|
|
}
|
|
return this.techGet("preload");
|
|
},
|
|
autoplay: function(value){
|
|
if (value !== undefined) {
|
|
this.techCall("setAutoplay", value);
|
|
this.options.autoplay = value;
|
|
return this;
|
|
}
|
|
return this.techGet("autoplay", value);
|
|
},
|
|
loop: function(value){
|
|
if (value !== undefined) {
|
|
this.techCall("setLoop", value);
|
|
this.options.loop = value;
|
|
return this;
|
|
}
|
|
return this.techGet("loop");
|
|
},
|
|
|
|
controls: function(){ return this.options.controls; },
|
|
poster: function(){ return this.techGet("poster"); },
|
|
error: function(){ return this.techGet("error"); },
|
|
ended: function(){ return this.techGet("ended"); }
|
|
|
|
// Methods to add support for
|
|
// networkState: function(){ return this.techCall("networkState"); },
|
|
// readyState: function(){ return this.techCall("readyState"); },
|
|
// seeking: function(){ return this.techCall("seeking"); },
|
|
// initialTime: function(){ return this.techCall("initialTime"); },
|
|
// startOffsetTime: function(){ return this.techCall("startOffsetTime"); },
|
|
// played: function(){ return this.techCall("played"); },
|
|
// seekable: function(){ return this.techCall("seekable"); },
|
|
// videoTracks: function(){ return this.techCall("videoTracks"); },
|
|
// audioTracks: function(){ return this.techCall("audioTracks"); },
|
|
// videoWidth: function(){ return this.techCall("videoWidth"); },
|
|
// videoHeight: function(){ return this.techCall("videoHeight"); },
|
|
// defaultPlaybackRate: function(){ return this.techCall("defaultPlaybackRate"); },
|
|
// playbackRate: function(){ return this.techCall("playbackRate"); },
|
|
// mediaGroup: function(){ return this.techCall("mediaGroup"); },
|
|
// controller: function(){ return this.techCall("controller"); },
|
|
// defaultMuted: function(){ return this.techCall("defaultMuted"); }
|
|
});
|
|
|
|
// RequestFullscreen API
|
|
(function(){
|
|
var requestFn,
|
|
cancelFn,
|
|
eventName,
|
|
isFullScreen,
|
|
playerProto = _V_.Player.prototype;
|
|
|
|
// Current W3C Spec
|
|
// http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
|
|
// Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
|
|
if (document.cancelFullscreen !== undefined) {
|
|
requestFn = "requestFullscreen";
|
|
cancelFn = "exitFullscreen";
|
|
eventName = "fullscreenchange";
|
|
isFullScreen = "fullScreen";
|
|
|
|
// Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementaitons
|
|
// that use prefixes and vary slightly from the new W3C spec. Specifically, using 'exit' instead of 'cancel',
|
|
// and lowercasing the 'S' in Fullscreen.
|
|
// Other browsers don't have any hints of which version they might follow yet, so not going to try to predict by loopeing through all prefixes.
|
|
} else {
|
|
|
|
_V_.each(["moz", "webkit"], function(prefix){
|
|
|
|
// https://github.com/zencoder/video-js/pull/128
|
|
if ((prefix != "moz" || document.mozFullScreenEnabled) && document[prefix + "CancelFullScreen"] !== undefined) {
|
|
requestFn = prefix + "RequestFullScreen";
|
|
cancelFn = prefix + "CancelFullScreen";
|
|
eventName = prefix + "fullscreenchange";
|
|
|
|
if (prefix == "webkit") {
|
|
isFullScreen = prefix + "IsFullScreen";
|
|
} else {
|
|
isFullScreen = prefix + "FullScreen";
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (requestFn) {
|
|
_V_.support.requestFullScreen = {
|
|
requestFn: requestFn,
|
|
cancelFn: cancelFn,
|
|
eventName: eventName,
|
|
isFullScreen: isFullScreen
|
|
};
|
|
}
|
|
|
|
})(); |