/** * $media jQuery plugin (v.2.1.1) * * 2012. Created by Oscar Otero (http://oscarotero.com / http://anavallasuiza.com) * * $media is released under the GNU Affero GPL version 3. * More information at http://www.gnu.org/licenses/agpl-3.0.html */ (function ($) { 'use strict'; //Support detection var support = { video: document.createElement('video'), isAndroid: (navigator.userAgent.indexOf('Android') >= 0), isIpad: (navigator.userAgent.indexOf('iPad') >= 0), isIPhone: (navigator.userAgent.indexOf('iPhone') >= 0), tests: {}, check: function (property) { if (this.tests[property] === undefined) { switch (property) { case 'fullscreen': this.tests.fullscreen = ((this.video.requestFullScreen || this.video.mozRequestFullScreen || this.video.webkitRequestFullScreen)); case 'volume': this.video.volume = 0.5; this.tests.volume = (this.video.volume === 0.5); break; case 'muted': case 'autoplay': this.video[property] = true; this.tests[property] = (this.video[property] === true); break; case 'multiInstances': this.tests[property] = !(this.isAndroid || this.isIPhone || this.isIpad); default: this.tests[property] = ((property in this.video)); } } return this.tests[property]; } }; //$media constructor window.$media = function (element) { this.element = element; this.$element = $(element); if (this.$element.is('video')) { this.type = 'video'; } else if (this.$element.is('audio')) { this.type = 'audio'; } //Unify the preload property in all browsers if (!this.$element.attr('preload')) { this.preload('metadata'); } }; //Support detection window.$media.support = function (property) { return support.check(property); }; /** * Returns the html audio/video element * * @return html element */ window.$media.prototype.get = function () { return this.element; }; /** * Returns the jquery object with the audio/video element * * @return jQuery object */ window.$media.prototype.$get = function () { return this.$element; }; /** * Check if the browser can play the source or a specific codec * canPlayType(); * canPlayType('ogg'); * canPlayType('video/mp4'); * * @param source A specific source to check. If it's not defined, checks the element sources * * @return false If the browser doesn't support audio/video element * @return 0 If the browser supports audio/video element but can't play the sources/specific codec * @return 1 If the browser maybe can play * @return 2 If the browser probably can play */ window.$media.prototype.canPlayType = function (source) { var length, result, r, i; if (!(this.element.canPlayType)) { return false; } if (source === undefined) { if (this.source()) { return this.canPlayType(this.source()); } source = this.source(true); } if ($.isArray(source)) { length = source.length; result = 0; for (i = 0; i < length; ++i) { r = this.canPlayType(source[i]); result = (r > result) ? r : result; } return result; } if (source.indexOf('.') !== -1) { source = (source.substring(source.lastIndexOf('.') + 1)).toLowerCase().split('#', 2)[0]; } if (/^[a-z0-9]+$/i.test(source)) { source = this.mimeType(source); } if (source) { switch (this.element.canPlayType(source)) { case 'probably': return 2; case 'maybe': return 1; } } return 0; }; /** * Alias of readyState(1, fn), execute a function when the media element is ready to start playing * * @param function fn A function to execute when the media element is ready * * @return this */ window.$media.prototype.ready = function (fn) { return this.readyState(1, fn); }; /** * Returns the current readyState property or binds a function when some the readyState becomes * * @param number state One of the four available states * @param function fn A function to bind to the event * * @return this (for bind event) * @return boolean/number (for getters) */ window.$media.prototype.readyState = function (state, fn) { if (arguments.length === 0) { return this.element.readyState; } if (arguments.length === 1) { return (this.element.readyState >= state) ? true : false; } if (this.element.readyState >= state) { $.proxy(fn, this)(); } else { var that = this; setTimeout(function () { that.readyState(state, fn); }, 100); } return this; }; /** * Returns the current networkState property * * @param number state One of the three available states * * @return this (for bind event) * @return boolean/number (for getters) */ window.$media.prototype.networkState = function (state) { if (arguments.length === 0) { return this.element.networkState; } return (this.element.networkState === state) ? true : false; }; /** * Get/set the playback rate or bind a function to ratechange event * If the browser doesn't support playbackRate property does nothing * * playbackRate() * playbackRate(0.5) * playbackRate(fn) * * @param float/function fn The new playback rate or the function to bind to ratechange event * * @return this (for setters) * @return float (for getters) */ $media.prototype.playbackRate = function (fn) { if (support.check('playbackRate') === false) { return (arguments.length === 0) ? 1 : this; } if ($.isFunction(fn)) { return this.on('ratechange', fn); } if (fn === undefined) { return this.element.playbackRate; } if (this.element.playbackRate !== fn) { this.element.playbackRate = fn; if (this.element.playbackRate !== fn) { support.playbackRate = false; } else { return this.triggerHandler('ratechange'); } } return this; } /** * Gets the source type for a specific extension * * @param string ext The extension (for example 'ogg', 'mp3', etc) * * @return string The mimetype of the extension (for example 'video/ogg'). * @return undefined If the extension is not valid. */ window.$media.prototype.mimeType = function (ext) { switch (ext) { case 'mp4': case 'acc': return this.type + '/mp4'; case 'ogg': case 'ogv': case 'oga': return this.type + '/ogg'; case 'webm': return this.type + '/webm'; case 'mp3': return this.type + '/mpeg'; case 'wav': return this.type + '/wav'; } }; /** * Get/set the sources for the media element * * source(); * source('my-video.ogv'); * source(['my-video.ogv', 'my-video.mp4']); * source({src: 'my-video.ogv', type: 'video/ogv'}); * source([{src: 'my-video.ogv', type: 'video/ogv'}, {src: 'my-video.mp4', type: 'video/mp4'}]); * * @param string/array/object sources The new sources for the element. * * @return string/array (for getter) * @return this (for setter) */ window.$media.prototype.source = function (sources) { var $media = this.$element; //Getter if (sources === undefined) { return this.element.currentSrc; } if (sources === true) { var src = []; if ($media.attr('src')) { src.push($media.attr('src')); } $media.find('source').each(function () { src.push($(this).attr('src')); }); return src; } //Setter $media.find('source').remove(); if (typeof sources === 'string') { $media.attr('src', sources); } else { if (!$.isArray(sources)) { sources = [sources]; } $media.removeAttr('src'); var that = this; $.each(sources, function (k, source) { if (typeof source !== 'object') { source = {src: source}; } if (!source.type) { var ext = (source.src.substring(source.src.lastIndexOf('.') + 1)).toLowerCase().split('#', 2)[0]; source.type = that.mimeType(ext); } $('', source).appendTo($media); }); } this.element.load(); return this; }; /** * Get or set loop property. * * loop(); * loop(true); * loop(false); * * @param boolean True for activate loop, false for don't * * @return boolean (for getter) * @return this (for setter) */ window.$media.prototype.loop = function (value) { if (arguments.length === 0) { return this.element.loop; } this.element.loop = value; return this; }; /** * Get or set autoplay property. * * autoplay(); * autoplay(true); * autoplay(false); * * @param boolean True for activate autoplay, false for don't * * @return boolean (for getter) * @return this (for setter) */ window.$media.prototype.autoplay = function (value) { if (support.check('autoplay') === false) { return (arguments.length === 0) ? false : this; } if (arguments.length === 0) { return this.element.autoplay; } this.element.autoplay = value; return this; }; /** * Get or set controls property. * * controls(); * controls(true); * controls(false); * * @param boolean True for activate controls, false for don't * * @return boolean (for getter) * @return this (for setter) */ window.$media.prototype.controls = function (value) { if (arguments.length === 0) { return this.element.controls; } this.element.controls = value; return this; }; /** * Get or set poster attribute. * * poster(); * poster('new-poster.jpg'); * poster(''); * * @param string The new value for poster attribute * * @return string (for getter) * @return this (for setter) */ window.$media.prototype.poster = function (value) { if (arguments.length === 0) { return this.element.poster; } this.element.poster = value; return this; }; /** * Get or set preload attribute. * * preload(); * preload('metadata'); * preload(''); * * @param string The new value for preload attribute * * @return string (for getter) * @return this (for setter) */ window.$media.prototype.preload = function (value) { if (arguments.length === 0) { return this.element.preload; } this.element.preload = value; return this; }; /** * Get or set the width of the media element * * width(); * width(true); * width(234); * * @param bool/int videoWidth Set true to return the real width of the video. Set number to change the width of the video * * @return integer (for getter) * @return this (for setter) */ window.$media.prototype.width = function (videoWidth) { if (videoWidth === true) { return this.element.videoWidth; } if (arguments.length === 0) { return this.$element.width(); } this.$element.width(videoWidth); return this; }; /** * Get or set the height of the media element * * height(); * height(true); * height(234); * * @param bool/int videoHeight Set true to return the real height of the video. Set number to change the height of the video * * @return integer (for getter) * @return this (for setter) */ window.$media.prototype.height = function (videoHeight) { if (videoHeight === true) { return this.element.videoHeight; } if (arguments.length === 0) { return this.$element.height(); } this.$element.height(videoHeight); return this; }; /** * Plays the media or adds a play event listener * * play (fn) * play () * * @param function fn The function to the event listener * * @return this */ window.$media.prototype.play = function (fn) { if ($.isFunction(fn)) { this.on('play', fn); } else if (this.element.paused) { this.element.play(); } return this; }; /** * Return if the media is playing or bind a function to playing event * * playing (fn) * playing () * * @param function fn The function to the event listener * * @return bool True if the media is playing, false if not * @return this On bind event */ window.$media.prototype.playing = function (fn) { if ($.isFunction(fn)) { this.on('playing', fn); return this; } return (this.element.paused || this.element.ended) ? false : true; }; /** * Toggles fullscreen mode or binds a function to fullscreen event * This method works only in webkit and mozilla platforms * * function fullScreen (fn) * function fullScreen (true) * function fullScreen (false) * function fullScreen () * * @param function/bool fn The function to the event listener. Set true or false to enter or exit of fullscreen * * @return this */ window.$media.prototype.fullScreen = function (fn) { if ($.isFunction(fn)) { this.on('fullScreen', fn); return this; } if (fn === false) { if ($.isFunction(document.webkitCancelFullScreen)) { document.webkitCancelFullScreen(); } else if ($.isFunction(document.mozCancelFullScreen)) { document.mozCancelFullScreen(); } } else if (fn === true) { if ($.isFunction(this.element.webkitRequestFullScreen)) { this.element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); } else if ($.isFunction(this.element.mozRequestFullScreen)) { this.element.mozRequestFullScreen(); } } else { if (document.mozFullScreen || document.webkitIsFullScreen) { this.fullScreen(false); } else { this.fullScreen(true); } } return this; }; /** * Return if the media is waiting or bind a function to waiting event * * waiting (fn) * waiting () * * @param function fn The function to the event listener * * @return bool True if the media is waiting, false if not * @return this On bind event */ window.$media.prototype.waiting = function (fn) { if ($.isFunction(fn)) { this.on('waiting', fn); return this; } return this.readyState(3) ? false : true; }; /** * Pauses the media or adds a play event listener * * pause (fn) * pause () * * @param function fn The function to the event listener * * @return this */ window.$media.prototype.pause = function (fn) { if ($.isFunction(fn)) { return this.on('pause', fn); } if (!this.element.paused) { this.element.pause(); } return this; }; /** * Toggle between play and pause, or bind a function to playPause event * playPause event is not the same than play or pause events (separately) * * function playPause (fn) * function playPause () * * @param function fn The function to the event listener * * @return this */ window.$media.prototype.playPause = function (fn) { if ($.isFunction(fn)) { return this.on('playpause', fn); } if (this.element.paused) { this.play(); } else { this.pause(); } return this.triggerHandler('playpause'); }; /** * Stops media (pauses, goes to start and stops loading the sources) or bind a function to stop event * * function stop (fn) * function stop () * * @param function fn The function to the event listener * * @return this */ window.$media.prototype.stop = function (fn) { if ($.isFunction(fn)) { return this.on('stop', fn); } return this.pause().reload().trigger('stop'); }; /** * Returns if it has reached the end of media or adds a ended event listener * * ended (fn) * ended () * * @param function fn The function to the event listener * * @return bool True if the media is ended, false if not * @return this On bind event */ window.$media.prototype.ended = function (fn) { if ($.isFunction(fn)) { return this.on('ended', fn); } return this.element.ended; }; /** * Removes the video/audio element or bind a function to remove event * * remove (fn) * remove () * * @param function fn The function to the event listener * * @return this On bind event */ window.$media.prototype.remove = function (fn) { if ($.isFunction(fn)) { return this.on('remove', fn); } this.triggerHandler('remove').$element.remove(); var prop; for (prop in this) { if (this.hasOwnProperty(prop)) { this[prop] = null; } } }; /** * Seek for specific point of media or adds a seek event listener * * seek (fn) * seek (23) * seek ('+23') * seek ('05:04') * seek ('50%') * ... * * @param function/string/int fn The function to the event listener or the point to seek * * @return this */ window.$media.prototype.seek = function (fn) { if ($.isFunction(fn)) { return this.on('seeked', fn); } var time = fn; this.readyState(1, function () { time = this.time(time); if (this.element.currentTime !== time) { var element = this.element; var fn = function () { try { element.currentTime = time; } catch(err) { window.setTimeout(fn, 100); } }; fn(); } }); return this; }; /** * Return if the media is seeking or bind a function to seeking event * * seeking (fn) * seeking () * * @param function fn The function to the event listener * * @return bool True if the media is seeking, false if not * @return this On bind event */ window.$media.prototype.seeking = function (fn) { if ($.isFunction(fn)) { this.on('seeking', fn); return this; } return this.element.seeking; }; /** * Set/set the volume value of media or bind a function to volume event * * volume (fn) * volume (0.5) * volume () * * @param function/float fn The function to the event listener or the new volume value (0-1 range) * * @return float (for getter) * @return this (for setter / on bind event) */ window.$media.prototype.volume = function (fn) { if (support.check('volume') === false) { return (arguments.length === 0) ? 1 : this; } if (arguments.length === 0) { return this.element.volume; } if ($.isFunction(fn)) { return this.on('volumechange', fn); } if (typeof fn === 'string') { if (fn.indexOf('+') === 0) { fn = this.element.volume + parseFloat(fn); } else if (fn.indexOf('-') === 0) { fn = this.element.volume - parseFloat(fn); } } this.element.volume = (fn < 0) ? 0 : ((fn > 1) ? 1 : fn); return this; }; /** * Get/set mute to the media or bind a function to mute event * * muted (fn) * muted (true) * muted (false) * muted () * * @param function/bool fn The function to the event listener. True to mute, false to unmute and void to toggle * * @return this */ window.$media.prototype.muted = function (fn) { if (support.check('muted') === false) { return (arguments.length === 0) ? false : this; } if (fn === undefined) { return this.element.muted; } if ($.isFunction(fn)) { return this.on('muted', fn); } if (typeof fn === 'boolean' && (this.element.muted !== fn)) { this.element.muted = fn; return this.triggerHandler('muted'); } return this; }; /** * Adds an event listener to media element * * on('click', fn) * * @param string event The event name. You can set more than one event space separated * @param function fn The function to execute on trigger the event * * @return this */ window.$media.prototype.on = function (event, fn) { var registeredEvents = this.$element.data('events') || {}, events = event.toLowerCase().split(' '), i, length = events.length, that = this; fn = $.proxy(fn, this); for (i = 0; i < length; i++) { switch (events[i]) { case 'fullScreen': if (!registeredEvents[events[i]]) { $(document).on('mozfullscreenchange', function (e) { if (document.mozFullScreenElement && that.$element.is(document.mozFullScreenElement)) { that.$element.data('fullScreen', true); } else if (that.$element.data('fullScreen')) { that.$element.data('fullScreen', false); } else { return; } that.trigger('fullScreen', [that.$element.data('fullScreen')]); }); this.on('webkitfullscreenchange', function (e) { this.trigger('fullScreen', [document.webkitIsFullScreen]); }); } this.$element.bind('fullScreen', fn); break; default: this.$element.bind(events[i], fn); } } return this; }; /** * Removes one or more event listener to media element * * off('click', fn) * off('click') * off() * * @param string event The event name. You can set more than one event space separated * @param function fn The function to delete in the event * * @return this */ window.$media.prototype.off = function (event, fn) { var events = event.toLowerCase().split(' '), i, length = events.length; if (fn) { fn = $.proxy(fn, this); } for (i = 0; i < length; i++) { switch (events[i]) { case 'end': this.$element.unbind('ended', fn); break; case 'seek': this.$element.unbind('seeked', fn); break; default: this.$element.unbind(events[i], fn); } } return this; }; /** * Trigger an event * * trigger('click') * * @param string event The event name to trigger. * @param array data Optional arguments to pass to function events * * @return this */ window.$media.prototype.trigger = function (event, data) { switch (event) { case 'seek': this.$element.trigger('seeked', data); break; default: this.$element.trigger(event, data); } return this; }; /** * Execute all handles attached to the media for an event * * triggerHandler('click') * * @param string event The event name to trigger. * @param array data Optional arguments to pass to function events * * @return this */ window.$media.prototype.triggerHandler = function (event, data) { switch (event) { case 'seek': this.$element.triggerHandler('seeked', data); break; default: this.$element.triggerHandler(event, data); } return this; }; /** * Returns the current time of the media or a specific point in seconds or add a listener to timeupdate event * * time() * time('+10') * time('10%') * * @param string time The point of the media you get * * @return float The time in seconds */ window.$media.prototype.time = function (time) { if (arguments.length === 0) { return this.element.currentTime.toSeconds(); } if ($.isFunction(time)) { this.on('timeupdate', time); } if (isNaN(parseInt(time, 10))) { return 0; } if (typeof time === 'string') { if (time.indexOf('+') === 0) { time = this.time() + this.time(time.substr(1)); } else if (time.indexOf('-') === 0) { time = this.time() - this.time(time.substr(1)); } else if (time.indexOf('%') === -1) { time = time.toSeconds(); } else { time = ((this.duration()/100) * parseFloat(time)).toSeconds(); } } else { time = time.toSeconds(); } if (time < 0) { return 0; } if (this.element.duration && (time > this.element.duration)) { return this.duration(); } return time; }; /** * Returns the media duration in seconds or bind a function on the durationchange event * * duration() * duration(fn) * * @param function fn The function to the event listener * * @return float The duration of the media * @return this On bind event */ window.$media.prototype.duration = function (fn) { if ($.isFunction(fn)) { return this.on('durationchange', fn); } return (this.element.duration || 0).toSeconds(); }; /** * Reload the media element (back to inital state) * * @return this */ window.$media.prototype.reload = function () { this.source(this.source()); return this; }; /** * Get/set arbitrary data associated with this element. * * @param string name Name of the data * @param value Value of the data. Set this value to create/edit the data * * @return this (on set) or mixed (on get) */ window.$media.prototype.data = function (name, value) { if (value === undefined) { return this.$element.data(name); } this.$element.data(name, value); return this; } /** * Extends this media instance with other functions and properties * * extend('myFunction', fn); * * @param string/object name The name of the property or an object with all new properties * @param mixed value The value of the property */ window.$media.prototype.extend = function (name, value) { if (typeof name !== 'object') { var k = name; name = {}; name[k] = value; } $.extend(this, name); }; /** * Extends the $media prototype with other functions or properties * * $media.extend('myFunction', fn); * * @param string/object name The name of the property or an object with all new properties * @param mixed value The value of the property */ window.$media.extend = function (name, value) { if (typeof name !== 'object') { var k = name; name = {}; name[k] = value; } $.each(name, function (k, v) { window.$media.prototype[k] = v; }); }; /** * Creates and returns a new $media object * * @param string/html/jQuery selector The selector of the media element * @param string/object properties The properties of the media element * * @return The $media intance. */ $.media = function (selector, properties) { if (properties) { var src; if (properties.src) { src = properties.src; delete(properties.src); } selector = $(selector, properties); var media = new window.$media(selector.get(0)); if (src) { media.source(src); } return media; } selector = $(selector); if (!selector.is('video, audio')) { selector = selector.find('video, audio'); } return new window.$media(selector.get(0)); }; /** * Creates a new html video element and returns a $media object with it * * @param string/object The src or an object with all properties of the media element * * @return The $media instance */ $.mediaVideo = function (properties) { if (typeof properties === 'string' || $.isArray(properties)) { properties = {src: properties}; } return $.media('