// ------------------------------------------------------------------------------------------- // // AVIA VIDEO API - make sure that youtube, vimeo and html 5 use the same interface // // requires froogaloop vimeo library and youtube iframe api (yt can be loaded async) // // ------------------------------------------------------------------------------------------- (function($) { "use strict"; $.AviaVideoAPI = function(options, video, option_container) { this.videoElement = video; // actual video element. either iframe or video this.$video = $( video ); // container where the AviaVideoAPI object will be stored as data, and that can receive events like play, pause etc // also the container that allows to overwrite javacript options by adding html data- attributes this.$option_container = option_container ? $( option_container ) : this.$video; // button to click to actually start the loading process of the video this.load_btn = this.$option_container.find('.av-click-to-play-overlay'); // contains video list this.video_wrapper = this.$video.parents('ul').eq(0); //check if we should load immediately or on click this.lazy_load = this.video_wrapper.hasClass('av-show-video-on-click') ? true : false; //mobile device? this.isMobile = $.avia_utilities.isMobile; //iamge fallback use this.fallback = this.isMobile ? this.$option_container.is('.av-mobile-fallback-image') : false; if(this.fallback) return; // set up the whole api object this._init( options ); } $.AviaVideoAPI.defaults = { loop: false, mute: false, controls: false, events: 'play pause mute unmute loop toggle reset unload' }; $.AviaVideoAPI.apiFiles = { youtube : {loaded: false, src: 'https://www.youtube.com/iframe_api' } } $.AviaVideoAPI.players = { } $.AviaVideoAPI.prototype = { _init: function( options ) { // set slider options this.options = this._setOptions(options); // info which video service we are using: html5, vimeo or youtube this.type = this._getPlayerType(); this.player = false; // store the player object to the this.player variable created by one of the APIs (mediaelement, youtube, vimeo) this._bind_player(); // set to true once the events are bound so it doesnt happen a second time by accident or racing condition this.eventsBound = false; // info if the video is playing this.playing = false; //set css class that video is currently not playing this.$option_container.addClass('av-video-paused'); //play pause indicator this.pp = $.avia_utilities.playpause(this.$option_container); }, //set the video options by first merging the default options and the passed options, then checking the video element if any data attributes overwrite the option set _setOptions: function(options) { var newOptions = $.extend( true, {}, $.AviaVideoAPI.defaults, options ), htmlData = this.$option_container.data(), i = ""; //overwritte passed option set with any data properties on the html element for (i in htmlData) { if (htmlData.hasOwnProperty(i) && (typeof htmlData[i] === "string" || typeof htmlData[i] === "number" || typeof htmlData[i] === "boolean")) { newOptions[i] = htmlData[i]; } } return newOptions; }, //get the player type _getPlayerType: function() { var vid_src = this.$video.get(0).src || this.$video.data('src'); if(this.$video.is('video')) return 'html5'; if(this.$video.is('.av_youtube_frame')) return 'youtube'; if(vid_src.indexOf('vimeo.com') != -1 ) return 'vimeo'; if(vid_src.indexOf('youtube.com') != -1) return 'youtube'; }, _bind_player: function() { var _self = this; // check if videos are disabled by user setting via cookie - or user must opt in. var cookie_check = $('html').hasClass('av-cookies-needs-opt-in') || $('html').hasClass('av-cookies-can-opt-out'); var allow_continue = true; var silent_accept_cookie = $('html').hasClass('av-cookies-user-silent-accept'); var self_hosted = 'html5' == this.type; if( cookie_check && ! silent_accept_cookie && ! self_hosted ) { if( ! document.cookie.match(/aviaCookieConsent/) || $('html').hasClass('av-cookies-session-refused') ) { allow_continue = false; } else { if( ! document.cookie.match(/aviaPrivacyRefuseCookiesHideBar/) ) { allow_continue = false; } else if( ! document.cookie.match(/aviaPrivacyEssentialCookiesEnabled/) ) { allow_continue = false; } else if( document.cookie.match(/aviaPrivacyVideoEmbedsDisabled/) ) { allow_continue = false; } } } if( ! allow_continue ) { this._use_external_link(); return; } if(this.lazy_load && this.load_btn.length && this.type != "html5") { this.$option_container.addClass('av-video-lazyload'); this.load_btn.on('click', function() { _self.load_btn.remove(); _self._setPlayer(); }); } else { this.lazy_load = false; this._setPlayer(); } }, //if the user has disabled video slides via cookie a click event will open the video externally _use_external_link: function() { //display the play button no matter what this.$option_container.addClass('av-video-lazyload'); this.load_btn.on('click', function(e) { if (e.originalEvent === undefined) return; //human click only var src_url = $(this).parents('.avia-slide-wrap').find('div[data-original_url]').data('original_url'); if( src_url ) window.open(src_url , '_blank'); }); }, //get the player object _setPlayer: function() { var _self = this; switch(this.type) { case "html5": this.player = this.$video.data('mediaelementplayer'); //apply fallback. sometimes needed for safari if(!this.player) { this.$video.data('mediaelementplayer', $.AviaVideoAPI.players[ this.$video.attr('id').replace(/_html5/,'') ] ); this.player = this.$video.data('mediaelementplayer'); } this._playerReady(); break; case "vimeo": //we nedd to create an iframe and append it to the html first var ifrm = document.createElement("iframe"); var $ifrm = $( ifrm ); //note: unmuted vimeo videos in chrome do often times not work due to chrome blocking them ifrm.onload = function() { _self.player = Froogaloop( ifrm ); _self._playerReady(); _self.$option_container.trigger('av-video-loaded'); }; ifrm.setAttribute("src", this.$video.data('src') ); //we replace the old html structure with the iframe $ifrm.insertAfter( this.$video ); this.$video.remove(); this.$video = ifrm; break; case "youtube": this._getAPI(this.type); $('body').on('av-youtube-iframe-api-loaded', function(){ _self._playerReady(); }); break; } }, _getAPI: function( api ) { //make sure the api file is loaded only once if($.AviaVideoAPI.apiFiles[api].loaded === false) { $.AviaVideoAPI.apiFiles[api].loaded = true; //load the file async var tag = document.createElement('script'), first = document.getElementsByTagName('script')[0]; tag.src = $.AviaVideoAPI.apiFiles[api].src; first.parentNode.insertBefore(tag, first); } }, //wait for player to be ready, then bind events _playerReady: function() { var _self = this; this.$option_container.on('av-video-loaded', function(){ _self._bindEvents(); }); switch(this.type) { case "html5": this.$video.on('av-mediajs-loaded', function(){ _self.$option_container.trigger('av-video-loaded'); }); this.$video.on('av-mediajs-ended' , function(){ _self.$option_container.trigger('av-video-ended'); }); break; case "vimeo": //finish event must be applied after ready event for firefox _self.player.addEvent('ready', function(){ _self.$option_container.trigger('av-video-loaded'); _self.player.addEvent('finish', function(){ _self.$option_container.trigger('av-video-ended'); }); }); break; case "youtube": var params = _self.$video.data(); if(_self._supports_video()) params.html5 = 1; _self.player = new YT.Player(_self.$video.attr('id'), { videoId: params.videoid, height: _self.$video.attr('height'), width: _self.$video.attr('width'), playerVars: params, events: { 'onReady': function(){ _self.$option_container.trigger('av-video-loaded'); }, 'onError': function(player){ $.avia_utilities.log('YOUTUBE ERROR:', 'error', player); }, 'onStateChange': function(event){ if (event.data === YT.PlayerState.ENDED) { var command = _self.options.loop != false ? 'loop' : 'av-video-ended'; _self.$option_container.trigger(command); } } } }); break; } //fallback always trigger after 2 seconds setTimeout(function() { if(_self.eventsBound == true || typeof _self.eventsBound == 'undefined' || _self.type == 'youtube' ) { return; } $.avia_utilities.log('Fallback Video Trigger "'+_self.type+'":', 'log', _self); _self.$option_container.trigger('av-video-loaded'); },2000); }, //bind events we should listen to, to the player _bindEvents: function() { if(this.eventsBound == true || typeof this.eventsBound == 'undefined') { return; } var _self = this, volume = 'unmute'; this.eventsBound = true; this.$option_container.on(this.options.events, function(e) { _self.api(e.type); }); if(!_self.isMobile) { //set up initial options if(this.options.mute != false) { volume = "mute"; } if(this.options.loop != false) { _self.api('loop'); } _self.api(volume); } //set timeout to prevent racing conditions with other scripts setTimeout(function() { _self.$option_container.trigger('av-video-events-bound').addClass('av-video-events-bound'); },50); }, _supports_video: function() { return !!document.createElement('video').canPlayType; }, /************************************************************************ PUBLIC Methods *************************************************************************/ api: function( action ) { //commands on mobile can not be executed if the player was not started manually if(this.isMobile && !this.was_started()) return; // prevent calling of unbound function if(this.options.events.indexOf(action) === -1) return; // broadcast that the command was executed this.$option_container.trigger('av-video-'+action+'-executed'); // console.log("video player api action: " + action); // calls the function based on action. eg: _html5_play() if(typeof this[ '_' + this.type + '_' + action] == 'function') { this[ '_' + this.type + '_' + action].call(this); } //call generic function eg: _toggle() or _play() if(typeof this[ '_' + action] == 'function') { this[ '_' + action].call(this); } }, was_started: function() { if(!this.player) return false; switch(this.type) { case "html5": if(this.player.getCurrentTime() > 0) return true; break; case "vimeo": if(this.player.api('getCurrentTime') > 0) return true; break; case "youtube": if(this.player.getPlayerState() !== -1) return true; break; } return false; }, /************************************************************************ Generic Methods, are always executed and usually set variables *************************************************************************/ _play: function() { this.playing = true; this.$option_container.addClass('av-video-playing').removeClass('av-video-paused'); }, _pause: function() { this.playing = false; this.$option_container.removeClass('av-video-playing').addClass('av-video-paused'); }, _loop: function() { this.options.loop = true; }, _toggle: function( ) { var command = this.playing == true ? 'pause' : 'play'; this.api(command); this.pp.set(command); }, /************************************************************************ VIMEO Methods *************************************************************************/ _vimeo_play: function( ) { this.player.api('play'); }, _vimeo_pause: function( ) { this.player.api('pause'); }, _vimeo_mute: function( ) { this.player.api('setVolume', 0); }, _vimeo_unmute: function( ) { this.player.api('setVolume', 0.7); }, _vimeo_loop: function( ) { // currently throws error, must be set in iframe // this.player.api('setLoop', true); }, _vimeo_reset: function( ) { this.player.api('seekTo',0); }, _vimeo_unload: function() { this.player.api('unload'); }, /************************************************************************ YOUTUBE Methods *************************************************************************/ _youtube_play: function( ) { this.player.playVideo(); }, _youtube_pause: function( ) { this.player.pauseVideo() }, _youtube_mute: function( ) { this.player.mute(); }, _youtube_unmute: function( ) { this.player.unMute(); }, _youtube_loop: function( ) { // does not work properly with iframe api. needs to manual loop on "end" event // this.player.setLoop(true); if(this.playing == true) this.player.seekTo(0); }, _youtube_reset: function( ) { this.player.stopVideo(); }, _youtube_unload: function() { this.player.clearVideo(); }, /************************************************************************ HTML5 Methods *************************************************************************/ _html5_play: function( ) { //disable stoping of other videos in case the user wants to run section bgs if(this.player) { this.player.options.pauseOtherPlayers = false; this.player.play(); } }, _html5_pause: function( ) { if(this.player) this.player.pause(); }, _html5_mute: function( ) { if(this.player) this.player.setMuted(true); }, _html5_unmute: function( ) { if(this.player) this.player.setVolume(0.7); }, _html5_loop: function( ) { if(this.player) this.player.options.loop = true; }, _html5_reset: function( ) { if(this.player) this.player.setCurrentTime(0); }, _html5_unload: function() { this._html5_pause(); this._html5_reset(); } } //simple wrapper to call the api. makes sure that the api data is not applied twice $.fn.aviaVideoApi = function( options , apply_to_parent) { return this.each(function() { // by default save the object as data to the initial video. // in the case of slideshows its more benefitial to save it to a parent element (eg: the slide) var applyTo = this; if(apply_to_parent) { applyTo = $(this).parents(apply_to_parent).get(0); } var self = $.data( applyTo, 'aviaVideoApi' ); if(!self) { self = $.data( applyTo, 'aviaVideoApi', new $.AviaVideoAPI( options, this, applyTo ) ); } }); } })( jQuery ); window.onYouTubeIframeAPIReady = function(){ jQuery('body').trigger('av-youtube-iframe-api-loaded'); }; // Init style shamelessly stolen from jQuery http://jquery.com // min version: https://f.vimeocdn.com/js/froogaloop2.min.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, playerOrigin = '*'; Froogaloop.fn = Froogaloop.prototype = { element: null, init: function(iframe) { if (typeof iframe === "string") { iframe = document.getElementById(iframe); } this.element = iframe; 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 data = JSON.stringify({ method: method, value: params }); target.contentWindow.postMessage(data, playerOrigin); } /** * 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 the vimeo player only if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) { return false; } if (playerOrigin === '*') { playerOrigin = event.origin; } 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) { /*modified by kriesi - removing this will result in a js error. */ if (target_id && eventCallbacks[target_id] && eventCallbacks[target_id][eventName]) { 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; } 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); })();