﻿/*!
* jQuery TubePlayer Plugin
* 
* version: 1.0.2 (31-MAY-2011)
* @requires v1.3.2 or later
*
* @imports SWFObject - http://code.google.com/p/swfobject/
*	- Degrades to flash based player if not HTML5/iframe option 
*
* Author: Nirvana Tikku (@ntikku / ntikku@gmail.com)
* Documentation:
* 		http://www.tikku.com/jquery-youtube-tubeplayer-plugin
* 
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html 
*/
; (function ($) {

    // tubeplayer namespace

    var TUBEPLAYER = ".tubeplayer",

		TUBE_PLAYER_CLASS = "jquery-youtube-tubeplayer",

		OPTS = "opts" + TUBEPLAYER;

    //	
    //
    // TubePlayer package 
    //
    //
    var TubePlayer = {};

    // all the instances that exist
    TubePlayer.ytplayers = {};

    // local init functions for multiple iframe players
    TubePlayer.inits = [];

    // no need to import the iframe script multiple times
    TubePlayer.iframeScriptInited = false;

    // tubeplayer inited flag - for destroy/re-init
    TubePlayer.inited = false;

    //
    //
    // public facing defaults
    //
    //

    $.tubeplayer = {};

    /**
    * These are all the events that are bound to the YouTube Player
    * the events can be overridden as they are public.
    * 
    * There are several functions that serve as wrappers to be utilized
    * internally - stateChange, onError, qualityChange. Change them at your 
    * own risk.
    */
    $.tubeplayer.defaults = {

        afterReady: function ($player) { },

        stateChange: function (player) {

            var _ret = this.onPlayer;

            return function (state) {

                if (typeof (state) == "object")
                    state = state.data;

                switch (state) {

                    case -1: return _ret.unstarted[player]();

                    case 0: return _ret.ended[player]();

                    case 1: return _ret.playing[player]();

                    case 2: return _ret.paused[player]();

                    case 3: return _ret.buffering[player]();

                    case 5: return _ret.cued[player]();

                    default: return null;

                }
            }

        },

        onError: function (player) {

            var _ret = this.onErr;

            return function (errorCode) {

                if (typeof (errorCode) == "object")
                    errorCode = errorCode.data;

                switch (errorCode) {

                    case 2: return _ret.invalidParameter[player]();

                    case 100: return _ret.notFound[player]();

                    case 101:
                    case 150: return _ret.notEmbeddable[player]();

                    default: return null;

                }

            }

        },

        qualityChange: function (player) {

            var _this = this;

            return function (suggested) {

                if (typeof (suggested) == "object")
                    suggested = suggested.data;

                return _this.onQualityChange[player](suggested);

            }

        },

        onQualityChange: {},

        onPlayer: { unstarted: {}, ended: {}, playing: {}, paused: {}, buffering: {}, cued: {} },

        onErr: { notFound: {}, notEmbeddable: {}, invalidParameter: {} }

    };

    /**
    * These are the internal defaults for the TubePlayer
    * plugin to work without providing any parameters. They
    * are merged with the users options.
    */
    var defaults = {

        // public facing
        width: 425,
        height: 355,
        allowFullScreen: "true",
        initialVideo: "J_zMVAswVQQ",
        preferredQuality: "default",
        showControls: 0,
        showRelated: 0,
        showInfo: 1,
        hideBranding: 1,
        autoPlay: 0,
        swfobjectURL: "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js",

        // privately used
        allowScriptAccess: "always",
        playerID: "tubeplayer-player-container",
        loadSWFObject: true, // if already available, allow them to bypass

        // html5 specific attrs
        iframed: true,

        // trigger fn's
        onPlay: function (id) { },
        onPause: function () { },
        onStop: function () { },
        onSeek: function (time) { },
        onMute: function () { },
        onUnMute: function () { },

        // player fn's
        onPlayerUnstarted: function () { },
        onPlayerEnded: function () { },
        onPlayerPlaying: function () { },
        onPlayerPaused: function () { },
        onPlayerBuffering: function () { },
        onPlayerCued: function () { },
        onQualityChange: function () { },

        // error fn's
        onErrorNotFound: function () { },
        onErrorNotEmbeddable: function () { },
        onErrorInvalidParameter: function () { }

    };

    /**
    * The TubePlayer plugin bound to the jQuery object's prototype. 
    * This method acts as an interface to instantiate a TubePlayer, 
    * as well as invoke events that are attached - typically getters/setters
    */
    $.fn.tubeplayer = function (input, xtra) {

        var $this = $(this);

        var type = typeof input;

        if (arguments.length == 0 || type == "object")

            return $this.each(function () {

                TubePlayer.init($(this), input);

            });

        else if (type == "string")

            return $this.triggerHandler(input + TUBEPLAYER, xtra || null);

    };


    /**
    * This method is the base method for all the events
    * that are bound to the TubePlayer. 
    */
    var buildFN = function (fn, after) {

        return function (evt, param) {

            var p = TubePlayer.getPkg(evt);

            if (p.ytplayer) {

                var ret = fn(evt, param, p);

                if (typeof (ret) == "undefined")
                    ret = p.$player;

                return ret;

            }

            return p.$player;

        };

    };

    /**
    * Public method to get all the player instances
    */
    $.tubeplayer.getPlayers = function () {

        return TubePlayer.ytplayers;

    };


    /**
    * Initialize a YouTube player; 
    *
    *	First check to see if TubePlayer has been init'd
    *	if it has then return, otherwise:
    *		> add the tubeplayer class (used to denote a player)
    *		> provide local data access to the options and store it
    *		> initialize the default events on the jQuery instance
    *		> create the container for the player
    *		> initialize the player (iframe/HTML5 vs flash based)
    * 
    *	@param $player - the instance being created on
    *	@param opts - the user's options
    */
    TubePlayer.init = function ($player, opts) {

        if ($player.hasClass(TUBE_PLAYER_CLASS))
            return $player;

        var o = $.extend({}, defaults, opts);

        o.playerID = o.playerID + (new Date().valueOf());

        $player.addClass(TUBE_PLAYER_CLASS).data(OPTS, o);

        for (e in PLAYER)
            $player.bind(e + TUBEPLAYER, $player, PLAYER[e]);

        // initialize the default event methods
        TubePlayer.initDefaults($.tubeplayer.defaults, o);

        // insert the player container
        jQuery("<div></div>").attr("id", o.playerID).appendTo($player);

        // append the player into the container
        TubePlayer.initPlayer($player, o);

        return $player;

    };

    /**
    * Every method needs these items
    */
    TubePlayer.getPkg = function (evt) {

        var $player = evt.data;

        var opts = $player.data(OPTS);

        var ytplayer = TubePlayer.ytplayers[opts.playerID];

        return {

            $player: $player,

            opts: opts,

            ytplayer: ytplayer

        }

    };

    /**
    * This method handles the player init. Since 
    * onYouTubePlayerReady is called when the script
    * has been evaluated, we want all the instances
    * to get init'd. For this we have a init queue.
    * If the script has been init'd, we automatically
    * pop the method off the queue and init the player.
    */
    TubePlayer.iframeReady = function (o) {

        TubePlayer.inits.push(function () {

            new YT.Player(o.playerID, {

                videoId: o.initialVideo,

                width: o.width,

                height: o.height,

                playerVars: {

                    'autoplay': o.autoPlay,

                    'controls': o.showControls,

                    'rel': o.showRelated,

                    'showinfo': o.showInfo,

                    'modestbranding': o.hideBranding,

                    'fs': (o.allowFullScreen ? 1 : 0)
                },

                events: {

                    'onReady': function (evt) {

                        TubePlayer.ytplayers[o.playerID] = evt.target;

                        var $player = $(evt.target).parents("." + TUBE_PLAYER_CLASS);

                        $.tubeplayer.defaults.afterReady($player);

                    },

                    'onPlaybackQualityChange': $.tubeplayer.defaults.qualityChange(o.playerID),

                    'onStateChange': $.tubeplayer.defaults.stateChange(o.playerID),

                    'onError': $.tubeplayer.defaults.onError(o.playerID)

                }

            });

        });

        // stacked init method
        if (TubePlayer.inits.length == 1 && !TubePlayer.inited) {

            return function () {

                for (var i = 0; i < TubePlayer.inits.length; i++) {

                    TubePlayer.inits[i]();

                }

                TubePlayer.inited = true;

            };

        }

        // if we've inited already, just call the init fn
        if (TubePlayer.inited) {

            (TubePlayer.inits.pop())();

        }

        return onYouTubePlayerAPIReady;

    };

    /**
    * check to see if iframe option is plausible
    */
    TubePlayer.supportsHTML5 = function () {

        return !!document.createElement('video').canPlayType;

    };

    /**
    * @param d - the defaults
    * @param o - the options w/ methods to attach
    */
    TubePlayer.initDefaults = function (d, o) {

        var ID = o.playerID;

        // default onPlayer events
        var dp = d.onPlayer;
        dp.unstarted[ID] = o.onPlayerUnstarted;
        dp.ended[ID] = o.onPlayerEnded;
        dp.playing[ID] = o.onPlayerPlaying;
        dp.paused[ID] = o.onPlayerPaused;
        dp.buffering[ID] = o.onPlayerBuffering;
        dp.cued[ID] = o.onPlayerCued;

        // default onQualityChange
        d.onQualityChange[ID] = o.onQualityChange;

        // default onError events
        var de = d.onErr;
        de.notFound[ID] = o.onErrorNotFound;
        de.notEmbeddable[ID] = o.onErrorNotEmbeddable;
        de.invalidParameter[ID] = o.onErrorInvalidParameter;

    };

    /**
    * init the iframed option if its requested and supported
    * otherwise initialize the flash based player
    * @param $player - the player that the tubeplayer binds to
    * @param o - the init options
    */
    TubePlayer.initPlayer = function ($player, o) {

        if (o.iframed && TubePlayer.supportsHTML5())

            TubePlayer.initIframePlayer($player, o);

        else

            TubePlayer.initFlashPlayer($player, o);

    };

    /**
    * Initialize an iframe player
    */
    TubePlayer.initIframePlayer = function ($player, o) {

        if (!TubePlayer.iframeScriptInited) {

            // write the api script tag
            var tag = document.createElement('script');

            tag.src = "http://www.youtube.com/player_api";

            var firstScriptTag = document.getElementsByTagName('script')[0];

            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

            TubePlayer.iframeScriptInited = true;

        };

        // init the iframe player
        onYouTubePlayerAPIReady = TubePlayer.iframeReady(o);

    };

    /**
    * Flash player initialization
    * @imports swfobject automatically
    */
    TubePlayer.initFlashPlayer = function ($player, o) {

        if (o.loadSWFObject) {
            $.getScript(o.swfobjectURL, TubePlayer.initFlashPlayerFN(o));
        } else {
            TubePlayer.initFlashPlayerFN(o)();
        }

    };

    TubePlayer.initFlashPlayerFN = function (o) {
        return function () {

            var url = ["//www.youtube.com/v/"]
            url.push(o.initialVideo);
            url.push("?fs=" + (o.allowFullScreen ? 1 : 0));
            url.push("&enablejsapi=1&version=3");
            url.push("&playerapiid=" + o.playerID);
            url.push("&rel= " + o.showRelated);
            url.push("&showinfo= " + o.showInfo);
            url.push("&modestbranding= " + o.hideBranding);
            url.push("&wmode=opaque");
            url.push("&autoplay=" + o.autoPlay);
            url.push("&controls=" + o.showControls);

            swfobject.embedSWF(url.join(""), o.playerID,
				o.width,
				o.height,
				"8", null, null,
				{
				    allowScriptAccess: o.allowScriptAccess,
				    wmode: 'transparent',
				    allowFullScreen: o.allowFullScreen
				},
				{ id: o.playerID }
			);

            // init the player ready fn
            onYouTubePlayerReady = function (playerId) {

                var player = document.getElementById(playerId);

                TubePlayer.ytplayers[playerId] = player;

                player.addEventListener("onStateChange", "$.tubeplayer.defaults.stateChange('" + playerId + "')");

                player.addEventListener("onError", "$.tubeplayer.defaults.onError('" + playerId + "')");

                player.addEventListener("onPlaybackQualityChange", "$.tubeplayer.defaults.qualityChange('" + playerId + "')");

                var $player = $(player).parents("." + TUBE_PLAYER_CLASS);

                $.tubeplayer.defaults.afterReady($player);

            };

        };
    }

    // fmt: youtube.com/watch?v=[desired-token]&
    TubePlayer.getVideoIDFromURL = function (sURL) {

        var pattern = 'youtube.com/watch?v=';

        var start = sURL.indexOf(pattern) + pattern.length;

        var end = sURL.indexOf('&', start) || sURL.length;

        if (start > end)
            return "";

        return sURL.substring(start, end);

    };

    /**
    * All the events that are bound to a TubePlayer instance
    */
    var PLAYER = {

        cue: buildFN(function (evt, param, p) {

            p.ytplayer.cueVideoById(param, p.opts.preferredQuality);

        }),

        play: buildFN(function (evt, param, p) {

            if (typeof (param) == 'object')
                p.ytplayer.loadVideoById(param.id, param.time, p.opts.preferredQuality);

            else if (param)
                p.ytplayer.loadVideoById(param, 0, p.opts.preferredQuality);

            else
                p.ytplayer.playVideo();

            p.opts.onPlay(param);

        }),

        pause: buildFN(function (evt, param, p) {

            p.ytplayer.pauseVideo();

            p.opts.onPause();

        }),

        stop: buildFN(function (evt, param, p) {

            p.ytplayer.stopVideo();

            p.opts.onStop();

        }),

        seek: buildFN(function (evt, param, p) {

            p.ytplayer.seekTo(param, true);

            p.opts.onSeek(param);

        }),

        mute: buildFN(function (evt, param, p) {

            p.$player.attr("data-prev-mute-volume", p.ytplayer.getVolume());

            p.ytplayer.mute();

            p.opts.onMute();

        }),

        unmute: buildFN(function (evt, param, p) {

            p.ytplayer.unMute();

            p.ytplayer.setVolume((p.$player.attr("data-prev-mute-volume") || 50));

            p.opts.onUnMute();

        }),

        isMuted: buildFN(function (evt, param, p) {

            return p.ytplayer.isMuted();

        }),

        volume: buildFN(function (evt, param, p) {

            if (param) {

                p.ytplayer.setVolume(param);

                p.$player.attr("data-prev-mute-volume", p.ytplayer.getVolume());

            } else {

                return p.ytplayer.getVolume() || 0; // 0 because iframe's currently in labs

            }

        }),

        quality: buildFN(function (evt, param, p) {

            if (param)
                p.ytplayer.setPlaybackQuality(param);

            else
                return p.ytplayer.getPlaybackQuality();

        }),

        data: buildFN(function (evt, param, p) {

            var ret = {};

            var P = p.ytplayer;

            ret.bytesLoaded = P.getVideoBytesLoaded();

            ret.bytesTotal = P.getVideoBytesTotal();

            ret.startBytes = P.getVideoStartBytes();

            ret.state = P.getPlayerState();

            ret.currentTime = P.getCurrentTime();

            ret.availableQualityLevels = P.getAvailableQualityLevels();

            ret.duration = P.getDuration();

            ret.videoURL = P.getVideoUrl();

            ret.getVideoEmbedCode = P.getVideoEmbedCode();

            ret.videoID = TubePlayer.getVideoIDFromURL(ret.videoURL);

            return ret;

        }),

        videoId: buildFN(function (evt, param, p) {

            return TubePlayer.getVideoIDFromURL(p.ytplayer.getVideoUrl());

        }),

        size: buildFN(function (evt, param, p) {

            if (param.width && param.height) {

                p.ytplayer.setSize(param.width, param.height);

                $(p.ytplayer).css(param);

            }

        }),

        destroy: buildFN(function (evt, param, p) {

            p.$player.removeClass(TUBE_PLAYER_CLASS)
				.data(OPTS, null)
				.unbind(TUBEPLAYER)
				.html("");

            delete TubePlayer.ytplayers[p.opts.playerID];

            $(p.ytplayer).remove();

            return null;

        }),

        player: buildFN(function (evt, param, p) {

            return p.ytplayer;

        })

    };

})(jQuery);
