mirror of
https://github.com/videojs/video.js.git
synced 2025-04-07 07:19:54 +02:00
604 lines
18 KiB
JavaScript
604 lines
18 KiB
JavaScript
|
|
var VimeoState = {
|
|
UNSTARTED: -1,
|
|
ENDED: 0,
|
|
PLAYING: 1,
|
|
PAUSED: 2,
|
|
BUFFERING: 3
|
|
};
|
|
|
|
/* VideoJS-Vimeo - Vimeo iFrame Wrapper
|
|
================================================================================ */
|
|
_V_.vimeo = _V_.PlaybackTech.extend({
|
|
|
|
init: function(player, options){
|
|
this.player = player;
|
|
|
|
var source = options.source;
|
|
|
|
// Extract the Vimeo videoID from the source
|
|
var videoId = this.getVideoId(source.src);
|
|
|
|
// Which element to embed in
|
|
var parentEl = options.parentEl;
|
|
|
|
// Generate ID for iFrame
|
|
var objId = player.el.id+"_vimeo_api";
|
|
|
|
// Create an iFrame for the Vimeo player
|
|
var iFrm = this.el = _V_.createElement("iframe", {
|
|
id: objId + "_iframe",
|
|
name: objId + "_iframe",
|
|
className: "vjs-tech",
|
|
scrolling: "no",
|
|
marginWidth: 0,
|
|
marginHeight: 0,
|
|
frameBorder: 0,
|
|
webkitAllowFullScreen: "",
|
|
mozallowfullscreen: "",
|
|
allowFullScreen: ""
|
|
});
|
|
|
|
var playerOptions = player.options;
|
|
var optionsParams = options.params || {};
|
|
|
|
// Setup player parameters
|
|
this.player.apiArgs = {
|
|
api: 1,
|
|
byline: 0,
|
|
portrait: 0,
|
|
show_title: 0,
|
|
show_byline: 0,
|
|
show_portrait: 0,
|
|
fullscreen: 1,
|
|
player_id: objId + "_iframe",
|
|
color: optionsParams.color || playerOptions.color || 'ffffff',
|
|
autoplay: this.toBoolInt(optionsParams.autoplay || playerOptions.autoplay),
|
|
loop: this.toBoolInt(optionsParams.loop || playerOptions.loop)
|
|
};
|
|
|
|
// Create a dummy object to hold Vimeo state in case any info is requested
|
|
// before the iFrame loads
|
|
this.player.vimeoInfo = {};
|
|
|
|
// Add iFrame to player div
|
|
_V_.insertFirst(iFrm, parentEl);
|
|
|
|
this.loadPlayer(videoId);
|
|
},
|
|
|
|
loadPlayer: function(videoId){
|
|
var baseUrl = (document.location.protocol == 'https:') ?
|
|
'https://secure.vimeo.com/video/' :
|
|
'http://player.vimeo.com/video/';
|
|
|
|
// Wait until iFrame has loaded to initialize the API
|
|
_V_.addEvent(this.el, "load", _V_.proxy(this, function(){
|
|
// Initialize the Vimeo Player object
|
|
this.vimeo = $f(this.el);
|
|
|
|
// Create an object to track the state of the Vimeo player, since we can
|
|
// only fetch information from the iFrame asynchronously
|
|
this.player.vimeoInfo = {
|
|
state: VimeoState.UNSTARTED,
|
|
volume: 1,
|
|
muted: false,
|
|
muteVolume: 1,
|
|
time: 0,
|
|
duration: 0,
|
|
buffered: 0,
|
|
url: baseUrl + videoId,
|
|
error: null
|
|
};
|
|
|
|
// Register Vimeo event handlers
|
|
this.vimeo.addEvent('ready', _V_.vimeo.onReady);
|
|
this.vimeo.addEvent('loadProgress', _V_.vimeo.onLoadProgress);
|
|
this.vimeo.addEvent('playProgress', _V_.vimeo.onPlayProgress);
|
|
this.vimeo.addEvent('play', _V_.vimeo.onPlay);
|
|
this.vimeo.addEvent('pause', _V_.vimeo.onPause);
|
|
this.vimeo.addEvent('finish', _V_.vimeo.onFinish);
|
|
this.vimeo.addEvent('seek', _V_.vimeo.onSeek);
|
|
}));
|
|
|
|
// Set the iFrame URL to start loading the video
|
|
this.el.src = baseUrl + videoId + "?" + this.makeQueryString(this.player.apiArgs);
|
|
},
|
|
|
|
destroy: function(){
|
|
this.vimeo.api("unload");
|
|
delete this.vimeo;
|
|
this.el.parentNode.removeChild(this.el);
|
|
},
|
|
|
|
play: function(){ this.vimeo.api("play"); },
|
|
pause: function(){ this.vimeo.api("pause"); },
|
|
paused: function(){
|
|
var state = this.player.vimeoInfo.state;
|
|
return state !== VimeoState.PLAYING &&
|
|
state !== VimeoState.BUFFERING;
|
|
},
|
|
|
|
src: function(src){
|
|
var videoId = this.getVideoId(src);
|
|
this.loadPlayer(videoId);
|
|
},
|
|
load: function(){ },
|
|
poster: function(){
|
|
// We could fetch the poster image using JSONP, but it would need to be
|
|
// done ahead of time (and probably enabled only through a player param)
|
|
return null;
|
|
},
|
|
|
|
currentTime: function(){ return this.player.vimeoInfo.time || 0; },
|
|
setCurrentTime: function(seconds){ this.vimeo.api("seekTo", seconds); },
|
|
|
|
duration: function(){ return this.player.vimeoInfo.duration || 0; },
|
|
buffered: function(){
|
|
return _V_.createTimeRange(0, this.player.vimeoInfo.buffered || 0);
|
|
},
|
|
|
|
volume: function(){
|
|
return (this.player.vimeoInfo.muted) ?
|
|
this.player.vimeoInfo.muteVolume :
|
|
this.player.vimeoInfo.volume;
|
|
},
|
|
setVolume: function(percentAsDecimal){
|
|
this.vimeo.api("setVolume", percentAsDecimal);
|
|
this.player.vimeoInfo.volume = percentAsDecimal;
|
|
this.player.triggerEvent("volumechange");
|
|
},
|
|
muted: function(){ return this.player.vimeoInfo.muted || false; },
|
|
setMuted: function(muted){
|
|
if (muted) {
|
|
this.player.vimeoInfo.muteVolume = this.player.vimeoInfo.volume;
|
|
this.setVolume(0);
|
|
} else {
|
|
this.setVolume(this.player.vimeoInfo.muteVolume);
|
|
}
|
|
this.player.vimeoInfo.muted = muted;
|
|
this.player.triggerEvent("volumechange");
|
|
},
|
|
|
|
width: function(){ return this.el.offsetWidth; },
|
|
height: function(){ return this.el.offsetHeight; },
|
|
|
|
currentSrc: function(){ return this.player.vimeoInfo.url; },
|
|
|
|
preload: function(){ return false; },
|
|
setPreload: function(val){ },
|
|
autoplay: function(){ return !!this.player.apiArgs.autoplay; },
|
|
setAutoplay: function(val){ },
|
|
loop: function(){ return !!this.player.apiArgs.loop; },
|
|
setLoop: function(val){
|
|
this.player.apiArgs.loop = (val ? 1 : 0);
|
|
// We handle looping manually
|
|
//this.vimeo.api("setLoop", val);
|
|
},
|
|
|
|
supportsFullScreen: function(){ return false; },
|
|
enterFullScreen: function(){ return false; },
|
|
|
|
error: function(){ return this.player.vimeoInfo.error; },
|
|
seeking: function(){ return false; },
|
|
ended: function(){ return this.player.vimeoInfo.state === VimeoState.ENDED; },
|
|
videoWidth: function(){ return this.width(); },
|
|
videoHeight: function(){ return this.height(); },
|
|
controls: function(){ return this.player.options.controls; },
|
|
defaultMuted: function(){ return false; },
|
|
|
|
// Helpers ------------------------------------------------------------------
|
|
|
|
makeQueryString: function(args) {
|
|
var array = [];
|
|
for (var key in args) {
|
|
if (args.hasOwnProperty(key))
|
|
array.push(encodeURIComponent(key) + "=" + encodeURIComponent(args[key]));
|
|
}
|
|
return array.join("&");
|
|
},
|
|
|
|
getVideoId: function(url) {
|
|
return url.match(/vimeo\.com\/(?:.*#|.*\/videos\/)?([0-9]+)/)[1];
|
|
},
|
|
|
|
toBoolInt: function(val) {
|
|
return val ? 1 : 0;
|
|
}
|
|
});
|
|
|
|
// Event callbacks ------------------------------------------------------------
|
|
|
|
_V_.vimeo.onReady = function(id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
player.tech.triggerReady();
|
|
player.triggerReady();
|
|
player.triggerEvent("canplay");
|
|
_V_.vimeo.hideOverlay(player);
|
|
|
|
// Hide our playback controls since we have no good way of hiding the Vimeo
|
|
// controls
|
|
player.controlBar.hide();
|
|
};
|
|
|
|
_V_.vimeo.onLoadProgress = function(data, id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
var durationUpdate = !player.vimeoInfo.duration;
|
|
player.vimeoInfo.duration = data.duration;
|
|
player.vimeoInfo.buffered = data.percent;
|
|
player.triggerEvent("progress");
|
|
if (durationUpdate) player.triggerEvent("durationchange");
|
|
};
|
|
|
|
_V_.vimeo.onPlayProgress = function(data, id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
player.vimeoInfo.time = data.seconds;
|
|
player.triggerEvent("timeupdate");
|
|
};
|
|
|
|
_V_.vimeo.onPlay = function(id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
player.vimeoInfo.state = VimeoState.PLAYING;
|
|
player.triggerEvent("play");
|
|
};
|
|
|
|
_V_.vimeo.onPause = function(id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
player.vimeoInfo.state = VimeoState.PAUSED;
|
|
player.triggerEvent("pause");
|
|
};
|
|
|
|
_V_.vimeo.onFinish = function(id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
player.vimeoInfo.state = VimeoState.ENDED;
|
|
player.triggerEvent("ended");
|
|
_V_.vimeo.hideOverlay(player);
|
|
|
|
// Vimeo looping doesn't seem to play well with VideoJS, so we need to
|
|
// implement it manually here
|
|
if (player.apiArgs.loop) {
|
|
//player.tech.vimeo.api("seekTo", 0);
|
|
player.tech.vimeo.api("play");
|
|
} else {
|
|
// Reset the video
|
|
player.tech.vimeo.api("seekTo", 0);
|
|
player.tech.vimeo.api("play");
|
|
player.tech.vimeo.api("pause");
|
|
}
|
|
};
|
|
|
|
_V_.vimeo.onSeek = function(data, id) {
|
|
var player = _V_.el(id).parentNode.player;
|
|
player.vimeoInfo.time = data.seconds;
|
|
player.triggerEvent("timeupdate");
|
|
player.triggerEvent("seeked");
|
|
};
|
|
|
|
// Helpers --------------------------------------------------------------------
|
|
|
|
_V_.vimeo.hideOverlay = function(player) {
|
|
// Hide the big play button and poster since Vimeo provides these. Using
|
|
// our own prevents the video from playing on the first click in mobile
|
|
// devices
|
|
player.bigPlayButton.hide();
|
|
player.posterImage.hide();
|
|
};
|
|
|
|
// Support testing ------------------------------------------------------------
|
|
|
|
_V_.vimeo.isSupported = function(){
|
|
return true;
|
|
};
|
|
|
|
_V_.vimeo.canPlaySource = function(srcObj){
|
|
return srcObj.type == "video/vimeo";
|
|
};
|
|
|
|
_V_.vimeo.prototype.support = {
|
|
formats: {
|
|
"video/vimeo": "VIM"
|
|
},
|
|
|
|
// Optional events that we can manually mimic with timers
|
|
progressEvents: true,
|
|
timeupdateEvents: true,
|
|
|
|
//fullscreen: true,
|
|
// In iOS, if you move a video element in the DOM, it breaks video playback.
|
|
movingElementInDOM: !_V_.isIOS(),
|
|
|
|
fullscreenResize: true,
|
|
parentResize: true
|
|
};
|
|
|
|
// Froogaloop API -------------------------------------------------------------
|
|
|
|
// From https://github.com/vimeo/player-api/blob/master/javascript/froogaloop.js
|
|
var Froogaloop = (function(){
|
|
// Define a local copy of Froogaloop
|
|
function Froogaloop(iframe) {
|
|
// The Froogaloop object is actually just the init constructor
|
|
return new Froogaloop.fn.init(iframe);
|
|
}
|
|
|
|
var eventCallbacks = {},
|
|
hasWindowEvent = false,
|
|
isReady = false,
|
|
slice = Array.prototype.slice,
|
|
playerDomain = '';
|
|
|
|
Froogaloop.fn = Froogaloop.prototype = {
|
|
element: null,
|
|
|
|
init: function(iframe) {
|
|
if (typeof iframe === "string") {
|
|
iframe = document.getElementById(iframe);
|
|
}
|
|
|
|
this.element = iframe;
|
|
|
|
// Register message event listeners
|
|
playerDomain = getDomainFromUrl(this.element.getAttribute('src'));
|
|
|
|
return this;
|
|
},
|
|
|
|
/*
|
|
* Calls a function to act upon the player.
|
|
*
|
|
* @param {string} method The name of the Javascript API method to call. Eg: 'play'.
|
|
* @param {Array|Function} valueOrCallback params Array of parameters to pass when calling an API method
|
|
* or callback function when the method returns a value.
|
|
*/
|
|
api: function(method, valueOrCallback) {
|
|
if (!this.element || !method) {
|
|
return false;
|
|
}
|
|
|
|
var self = this,
|
|
element = self.element,
|
|
target_id = element.id !== '' ? element.id : null,
|
|
params = !isFunction(valueOrCallback) ? valueOrCallback : null,
|
|
callback = isFunction(valueOrCallback) ? valueOrCallback : null;
|
|
|
|
// Store the callback for get functions
|
|
if (callback) {
|
|
storeCallback(method, callback, target_id);
|
|
}
|
|
|
|
postMessage(method, params, element);
|
|
return self;
|
|
},
|
|
|
|
/*
|
|
* Registers an event listener and a callback function that gets called when the event fires.
|
|
*
|
|
* @param eventName (String): Name of the event to listen for.
|
|
* @param callback (Function): Function that should be called when the event fires.
|
|
*/
|
|
addEvent: function(eventName, callback) {
|
|
if (!this.element) {
|
|
return false;
|
|
}
|
|
|
|
var self = this,
|
|
element = self.element,
|
|
target_id = element.id !== '' ? element.id : null;
|
|
|
|
|
|
storeCallback(eventName, callback, target_id);
|
|
|
|
// The ready event is not registered via postMessage. It fires regardless.
|
|
if (eventName != 'ready') {
|
|
postMessage('addEventListener', eventName, element);
|
|
}
|
|
else if (eventName == 'ready' && isReady) {
|
|
callback.call(null, target_id);
|
|
}
|
|
|
|
return self;
|
|
},
|
|
|
|
/*
|
|
* Unregisters an event listener that gets called when the event fires.
|
|
*
|
|
* @param eventName (String): Name of the event to stop listening for.
|
|
*/
|
|
removeEvent: function(eventName) {
|
|
if (!this.element) {
|
|
return false;
|
|
}
|
|
|
|
var self = this,
|
|
element = self.element,
|
|
target_id = element.id !== '' ? element.id : null,
|
|
removed = removeCallback(eventName, target_id);
|
|
|
|
// The ready event is not registered
|
|
if (eventName != 'ready' && removed) {
|
|
postMessage('removeEventListener', eventName, element);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles posting a message to the parent window.
|
|
*
|
|
* @param method (String): name of the method to call inside the player. For api calls
|
|
* this is the name of the api method (api_play or api_pause) while for events this method
|
|
* is api_addEventListener.
|
|
* @param params (Object or Array): List of parameters to submit to the method. Can be either
|
|
* a single param or an array list of parameters.
|
|
* @param target (HTMLElement): Target iframe to post the message to.
|
|
*/
|
|
function postMessage(method, params, target) {
|
|
if (!target.contentWindow.postMessage) {
|
|
return false;
|
|
}
|
|
|
|
var url = target.getAttribute('src').split('?')[0],
|
|
data = JSON.stringify({
|
|
method: method,
|
|
value: params
|
|
});
|
|
|
|
if (url.substr(0, 2) === '//') {
|
|
url = window.location.protocol + url;
|
|
}
|
|
|
|
target.contentWindow.postMessage(data, url);
|
|
}
|
|
|
|
/**
|
|
* Event that fires whenever the window receives a message from its parent
|
|
* via window.postMessage.
|
|
*/
|
|
function onMessageReceived(event) {
|
|
var data, method;
|
|
|
|
try {
|
|
data = JSON.parse(event.data);
|
|
method = data.event || data.method;
|
|
}
|
|
catch(e) {
|
|
//fail silently... like a ninja!
|
|
}
|
|
|
|
if (method == 'ready' && !isReady) {
|
|
isReady = true;
|
|
}
|
|
|
|
// Handles messages from moogaloop only
|
|
if (event.origin != playerDomain) {
|
|
return false;
|
|
}
|
|
|
|
var value = data.value,
|
|
eventData = data.data,
|
|
target_id = target_id === '' ? null : data.player_id,
|
|
|
|
callback = getCallback(method, target_id),
|
|
params = [];
|
|
|
|
if (!callback) {
|
|
return false;
|
|
}
|
|
|
|
if (value !== undefined) {
|
|
params.push(value);
|
|
}
|
|
|
|
if (eventData) {
|
|
params.push(eventData);
|
|
}
|
|
|
|
if (target_id) {
|
|
params.push(target_id);
|
|
}
|
|
|
|
return params.length > 0 ? callback.apply(null, params) : callback.call();
|
|
}
|
|
|
|
|
|
/**
|
|
* Stores submitted callbacks for each iframe being tracked and each
|
|
* event for that iframe.
|
|
*
|
|
* @param eventName (String): Name of the event. Eg. api_onPlay
|
|
* @param callback (Function): Function that should get executed when the
|
|
* event is fired.
|
|
* @param target_id (String) [Optional]: If handling more than one iframe then
|
|
* it stores the different callbacks for different iframes based on the iframe's
|
|
* id.
|
|
*/
|
|
function storeCallback(eventName, callback, target_id) {
|
|
if (target_id) {
|
|
if (!eventCallbacks[target_id]) {
|
|
eventCallbacks[target_id] = {};
|
|
}
|
|
eventCallbacks[target_id][eventName] = callback;
|
|
}
|
|
else {
|
|
eventCallbacks[eventName] = callback;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves stored callbacks.
|
|
*/
|
|
function getCallback(eventName, target_id) {
|
|
if (target_id) {
|
|
return eventCallbacks[target_id][eventName];
|
|
}
|
|
else {
|
|
return eventCallbacks[eventName];
|
|
}
|
|
}
|
|
|
|
function removeCallback(eventName, target_id) {
|
|
if (target_id && eventCallbacks[target_id]) {
|
|
if (!eventCallbacks[target_id][eventName]) {
|
|
return false;
|
|
}
|
|
eventCallbacks[target_id][eventName] = null;
|
|
}
|
|
else {
|
|
if (!eventCallbacks[eventName]) {
|
|
return false;
|
|
}
|
|
eventCallbacks[eventName] = null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns a domain's root domain.
|
|
* Eg. returns http://vimeo.com when http://vimeo.com/channels is sbumitted
|
|
*
|
|
* @param url (String): Url to test against.
|
|
* @return url (String): Root domain of submitted url
|
|
*/
|
|
function getDomainFromUrl(url) {
|
|
if (url.substr(0, 2) === '//') {
|
|
url = window.location.protocol + url;
|
|
}
|
|
|
|
var url_pieces = url.split('/'),
|
|
domain_str = '';
|
|
|
|
for(var i = 0, length = url_pieces.length; i < length; i++) {
|
|
if(i<3) {domain_str += url_pieces[i];}
|
|
else {break;}
|
|
if(i<2) {domain_str += '/';}
|
|
}
|
|
|
|
return domain_str;
|
|
}
|
|
|
|
function isFunction(obj) {
|
|
return !!(obj && obj.constructor && obj.call && obj.apply);
|
|
}
|
|
|
|
function isArray(obj) {
|
|
return toString.call(obj) === '[object Array]';
|
|
}
|
|
|
|
// Give the init function the Froogaloop prototype for later instantiation
|
|
Froogaloop.fn.init.prototype = Froogaloop.fn;
|
|
|
|
// Listens for the message event.
|
|
// W3C
|
|
if (window.addEventListener) {
|
|
window.addEventListener('message', onMessageReceived, false);
|
|
}
|
|
// IE
|
|
else {
|
|
window.attachEvent('onmessage', onMessageReceived);
|
|
}
|
|
|
|
// Expose froogaloop to the global object
|
|
return (window.Froogaloop = window.$f = Froogaloop);
|
|
|
|
})();
|