1
0
mirror of https://github.com/videojs/video.js.git synced 2025-04-07 07:19:54 +02:00
video.js/tech/vimeo/vimeo.js
Steve Heffernan 428203c466 Continued updating all library pieces to support closure compiler.
Renamed internal lib var to vjs instead of _V_.
2013-01-04 16:58:23 -08:00

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);
})();