// ------------------------------------------------------------------------------------------- // // 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 = document.cookie.match(/aviacookiesilentconsent/); if( cookie_check && ! silent_accept_cookie ) { if( ! document.cookie.match(/aviacookieconsent/) || sessionstorage.getitem( 'aviacookierefused' ) ) { 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); })();