2 * jquery.youtube.js v0.1a1 - a jquery youtube player
3 * Copyright (c) 2010 Richard Willis
4 * MIT license : http://www.opensource.org/licenses/mit-license.php
5 * Project : http://github.com/badsyntax/jquery-youtube-player
6 * Contact : willis.rh@gmail.com | badsyntax.co.uk
9 (function($, window, document, undefined){
11 $.fn.player = function(method){
13 var pluginName = 'jquery-youtube-player', args = arguments, val = undefined;
17 // get plugin reference
18 var obj = $.data( this, pluginName );
20 if ( obj && obj[method]) {
22 // execute a public method, store the return value
23 val = obj[method].apply( obj, Array.prototype.slice.call( args, 1 ) );
25 // only the 'plugin' public method is allowed to return a value
26 val = (method == 'plugin') ? val : undefined;
28 else if ( !obj && ( typeof method === 'object' || ! method ) ) {
30 // initiate the plugin
31 $.data( this, pluginName, new player(this, method, pluginName) );
35 // return the value from a method, or the jQuery object
40 function player(element, options, pluginName){
44 this._pluginName = pluginName;
46 this.element = $( element ).addClass('ui-widget');
48 this.options = $.extend({
49 width: 425, // player width (integer or string)
50 height: 356, // player height (integer or string)
51 swfobject: window.swfobject, // swfobject object
52 playlist: {}, // playlist object (object literal)
53 showPlaylist: 1, // show playlist on plugin init (boolean)
54 showTime: 1, // show current time and duration in toolbar (boolean)
55 videoThumbs: 0, // show videos as thumbnails in the playlist area (boolean) (experimental)
56 randomStart: 0, // show random video on plugin init (boolean)
57 autoStart: 0, // auto play the video after the player as been built (boolean)
58 autoPlay: 0, // auto play the video when loading it via the playlist or toolbar controls (boolean)
59 repeat: 1, // repeat videos (boolean)
60 repeatPlaylist: 0, // repeat the playlist (boolean)
61 shuffle: 0, // shuffle the play list (boolean)
62 chromeless: 1, // chromeless player (boolean)
63 highDef: 0, // high definition quality or normal quality (boolean)
64 playlistHeight: 5, // height of the playlist (integer) (N * playlist item height)
65 playlistBuilder: null, // custom playlist builder function (null or function) see http://github.com/badsyntax/jquery-youtube-player/wiki/Installation-and-usage#fn9
66 playlistBuilderClickHandler: null, // custom playlist video click event handler, useful if you want to prevent default click event (null or function)
71 playlistSpeed: 550, // speed of playlist show/hide animate
72 toolbarAppendTo: null, // element to append the toolbar to (selector or null)
73 playlistAppendTo: null, // element to append the playlist to (selector or null)
74 timeAppendTo: null, // elemend to append to time to (selector or null)
75 videoParams: { // video <object> params (object literal)
76 allowfullscreen: 'true',
77 allowScriptAccess: 'always',
80 showToolbar: null, // show or hide the custom toolbar (null, true or false)
81 toolbarButtons: {}, // custom toolbar buttons
82 toolbar: 'play,prev,next,shuffle,repeat,mute,playlistToggle', // comma separated list of toolbar buttons
89 if (!this.options.chromeless && this.options.showToolbar != true) this.options.showToolbar = 0;
91 // these initial states are the youtube player states
92 // button states will be added to this object
102 // munge youtube video id from any url
103 this._youtubeIdExp = /^[^v]+v.(.{11}).*/;
105 // extend the default toolbar buttons with user specified buttons (specified buttons will override default)
106 this.buttons = $.extend({}, this.defaultToolbarButtons, this.options.toolbarButtons);
108 // convert inks to vidoes
109 if (this.element.is('a')) {
111 var anchor = this.element;
113 this.element = $('<div class="youtube-player"></div>');
115 playerVideo = $('<div class="youtube-player-video"></div>').appendTo(this.element),
116 playerObject = $('<div class="youtube-player-object"></div>').appendTo(playerVideo),
117 playlist = $('<ol class="youtube-player-playlist"><li></li></ol>')
119 .append( anchor.clone() )
121 .appendTo(this.element);
123 anchor.after( this.element ).hide();
126 // store common elements
128 player: this.element,
129 playerVideo: this.element.find('.youtube-player-video'),
130 playerObject: this.element.find('.youtube-player-object')
133 // swfobject will destroy the video object <div>, so we clone it to use it to restore it when destroy()ing the plugin
134 this.elements.playerObjectClone = this.elements.playerObject.clone();
136 this.keys = { video: 0 };
138 // swfobject requires the video object <div> to have an id set
141 id = 'jqueryyoutubeplayer' + Math.floor( Math.random() * 101 ).toString();
143 } while( document.getElementById(id) );
145 this.elements.playerObject[0].id = id;
147 (this.options.swfobject.getFlashPlayerVersion().major >= 8)
149 && this.loadPlaylist(null, null, null, function(){
151 // build everything and set event handlers
153 ._bindYoutubeEventHandlers()
164 _activeStates: [], timer: {}, videoIds: [],
166 _trigger : function(scope, callback, arg){
168 var type = typeof callback;
172 if ( type === 'string' && this.options[ callback ] && $.isFunction(this.options[ callback ]) ) {
174 return this.options[ callback ].apply( scope, arg );
176 } else if ( type === 'function' ) {
178 callback.apply( scope, arg );
182 _bindYoutubeEventHandlers : function(){
188 self.youtubePlayer = document.getElementById(id);
190 self._trigger(self, 'onPlayerReady', [ id ]);
192 self.loadVideo(false, true);
194 self.elements.toolbar.container
195 .animate(self.options.toolbarAnimation, self.options.toolbarSpeed, function(){
197 self._trigger(self, 'onReady', [ id ]);
200 self._showPlaylist( self.options.showPlaylist );
202 ( self.keys.play ) && self.playVideo();
205 function videoPaused(){
207 self._trigger(this, 'onVideoPaused', [ self._getVideo() ]);
210 function videoEnded(){
212 self.buttons.play.element && self.buttons.play.element.trigger( 'off' );
214 if (self.options.repeat) {
220 function error(state){
224 msg = 'This video has been removed from Youtube.';
228 msg = 'This video does not allow playback outside of Youtube.';
231 msg = 'Unknown error';
233 if (self._trigger(this, 'onError', [msg]) === undefined){
235 alert( 'There was an error loading this video. ' + msg );
239 function videoCued(){
241 self._updatePlaylist();
243 self.elements.toolbar.updateStates();
245 self._trigger(this, 'onVideoCue', arguments);
248 function videoBuffer(){
250 self._trigger(this, 'onBuffer', [ self._getVideo() ]);
253 function videoPlay(){
255 self._updatePlaylist();
257 self._addState('play');
259 self.elements.toolbar.updateStates();
263 // update the location hash
265 self._trigger(this, 'onVideoPlay', [ self._getVideo() ]);
268 var id = this.elements.playerObject[0].id;
270 window['onytplayerStateChange' + id] = function(state){
272 // reset the youtube player states every time an event is executed
273 self._removeStates([ -1, 0, 1, 2, 3, 5, 100, 101, 150, 9 ]);
275 // add a new youtube state
276 self._addState(state, true);
279 case 0 : videoEnded(); break;
280 case 1 : videoPlay(); break;
281 case 2 : videoPaused(); break;
282 case 3 : videoBuffer(); break;
283 case 9 : ready(id); break;
284 case 5 : videoCued(); break;
285 case 100: case 101: case 150: error( state ); break;
288 self._trigger(self, 'onYoutubeStateChange', [ state ]);
291 if ( !window.onYouTubePlayerReady ){
293 window.onYouTubePlayerReady = function(id){
295 var player = document.getElementById(id);
297 player.addEventListener("onStateChange", 'onytplayerStateChange' + id);
299 player.addEventListener('onError', 'onytplayerStateChange' + id);
301 window['onytplayerStateChange' + id](9);
308 _createPlayer : function(){
310 // set the player dimensions
311 this.elements.player.width( this.options.width );
312 this.elements.playerVideo.height( this.options.height );
315 id = this.options.playlist.videos[this.keys.video].id,
316 apiid = this.elements.playerObject[0].id,
318 this.options.chromeless
319 ? 'http://www.youtube.com/apiplayer?enablejsapi=1&version=3&playerapiid='+apiid+'&hd=' + this.options.highDef + '&showinfo=0'
320 : 'http://www.youtube.com/v/' + id + '?enablejsapi=1&playerapiid='+apiid+'&version=3';
322 this._trigger(this, 'onBeforePlayerBuild');
324 // embed the youtube player
325 this.options.swfobject.embedSWF( swfpath, this.elements.playerObject[0].id, '100%', '100%', '8', null, null, this.options.videoParams);
330 _createToolbar : function(){
334 this.elements.toolbar = {
335 container: $('<ul />')
336 .addClass('youtube-player-toolbar ui-widget ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all')
338 updateStates : function(){
340 self.elements.toolbar.container.find('li').each(function(){
342 var button = $(this).removeClass('ui-state-active').data('button');
344 if (!button) return true;
346 (self._state(button.val)) &&
348 $(this).addClass('ui-state-active');
350 (self._state(button.val) && button.toggleButton) && $(this).trigger('on');
355 ( this.options.showToolbar != null && !this.options.showToolbar )
356 && this.elements.toolbar.container.hide();
358 $.each(this.options.toolbar.split(','), function(key, val) {
360 var button = self.buttons[val];
362 if (!button || !button.text) return true;
366 self._states[val] = self._states[val] || val;
369 .addClass('ui-state-default ui-corner-all')
370 .append('<span class="ui-icon ' + button.icon + '"></span>')
371 .attr('title', button.text)
372 .data('button', button)
373 .bind('mouseenter mouseleave', function(){
375 $(this).toggleClass('ui-state-hover');
377 .bind('off', function(){
379 var elem = $(this), button = elem.data('button'), toggle = 1;
381 elem.data('toggle', toggle);
383 self._removeState(val);
385 elem.find('.ui-icon')
386 .removeClass( button.toggleButton.icon )
387 .addClass( button.icon )
389 .attr('title', button.text);
391 self._trigger(self, button.toggleButton.action, [ button ] );
393 .bind('on', function(){
395 var elem = $(this), button = elem.data('button'), toggle = 0;
397 elem.data('toggle', toggle);
403 .removeClass( button.icon )
404 .addClass( button.toggleButton.icon )
406 .attr('title', button.toggleButton.text)
408 self._trigger(self, button.action, [ button ] );
410 .bind('toggle', function(){
412 var toggle = $(this).data('toggle');
414 ( toggle || toggle == undefined) ? $(this).trigger('on') : $(this).trigger('off');
418 var button = $(this).data('button'),
419 state = self._state(val);
421 if (button.toggleButton) {
423 $(this).trigger('toggle');
426 self._trigger(self, button.action, [ button ] );
428 ( !button.toggle || ( button.toggle && state ) )
429 ? self._removeState(val)
430 : self._addState(val);
432 self.elements.toolbar.updateStates();
435 .appendTo(self.elements.toolbar.container);
438 (this.options.toolbarAppendTo) ?
439 this.elements.toolbar.container.appendTo( this.options.toolbarAppendTo ) :
440 this.elements.playerVideo.after( this.elements.toolbar.container );
445 _createTimeArea : function(){
447 this.elements.toolbar.time =
448 this.options.timeAppendTo
449 ? $('<span />').appendTo( this.options.timeAppendTo )
450 : $('<li />').addClass('youtube-player-time').appendTo( this.elements.toolbar.container );
452 this.elements.toolbar.timeCurrent = $('<span />').html('0:00').appendTo(this.elements.toolbar.time);
454 this.elements.toolbar.timeDuration = $('<span />').appendTo(this.elements.toolbar.time);
459 _createPlaylist : function(){
463 function videoClickHandler(){
465 var videoData = $(this).data('video');
467 if (!videoData) return;
469 self.keys.video = $.inArray( videoData.id, self.videoIds );
471 self._removeState('play');
473 self._updatePlaylist();
480 function buildPlaylist(){
482 self.elements.playlist = self.elements.player.find('.youtube-player-playlist').length
483 ? self.elements.player.find('.youtube-player-playlist')
484 : $('<ol />').addClass('youtube-player-playlist ui-helper-reset');
486 self.elements.playlistContainer =
488 .addClass('youtube-player-playlist-container ui-widget-content ui-corner-all')
489 .append( self.elements.playlist );
492 this._addVideosToPlaylist = function(cue){
494 // get this list of vidoes to add to the playlist
495 // if cueing, we only want to add 1 video, so we find the last video added to playlist
497 ? [ self.options.playlist.videos[self.options.playlist.videos.length - 1] ]
498 : self.options.playlist.videos;
500 (!cue) && self.elements.playlist.empty();
504 $.each(videos, function(){
506 self.videoIds.push(this.id);
510 .append( self.options.videoThumbs ? '<img alt="' + this.title + '" title="' + this.title + '" src="http://img.youtube.com/vi/' + this.id + '/2.jpg" />' : this.title)
511 .addClass('ui-state-default')
512 .addClass( self.options.videoThumbs ? 'youtube-player-thumb' : '' )
513 .bind('mouseenter mouseleave', function(){
515 $( this ).toggleClass( 'ui-state-hover' );
517 .appendTo(self.elements.playlist);
520 self._trigger(self, 'onAfterVideosAddedToPlaylist');
523 if (!$.isFunction( this.options.playlistBuilder )) {
527 this._addVideosToPlaylist();
529 (this.options.playlistAppendTo)
530 // append playlist to specified element
531 ? this.elements.playlistContainer.appendTo( this.options.playlistAppendTo )
532 // insert playlist after the toolbar
533 : this.elements.toolbar.container.after( this.elements.playlistContainer );
535 this.options.playlistBuilder = function(){
537 items: self.elements.playlist.find('li'),
538 container: self.elements.playlistContainer
543 $.each(this.options.playlist.videos, function(){
545 self.videoIds.push( this.id );
548 var playlist = this.options.playlistBuilder.call(this, this.options.playlist.videos);
554 self._trigger(this, videoClickHandler, arguments);
556 self._trigger(self, 'playlistBuilderClickHandler', arguments);
559 this.elements.playlistContainer = playlist.container;
564 _updateTime : function(){
566 if (!this.options.showTime) return;
568 var self = this, duration = Number( this.youtubePlayer.getDuration() );
570 function timeFormat(seconds) {
572 var m = Math.floor( seconds / 60), s = (seconds % 60).toFixed(0);
574 return m + ':' + ( s < 10 ? '0' + s : s);
577 this.elements.toolbar.timeDuration.html( ' / ' + timeFormat( duration ));
579 this.elements.toolbar.time.fadeIn();
581 this.timeInterval = setInterval(function(){
583 ( !self.youtubePlayer.getCurrentTime )
584 ? clearInterval( self.timeInterval )
585 : self.elements.toolbar.timeCurrent.html( timeFormat( self.youtubePlayer.getCurrentTime() ) );
589 _removeStates : function(states){
593 $.each(this._activeStates, function(key, value){
595 ($.inArray(value, states) === -1
596 && $.inArray(value, newArray) === -1)
597 && newArray.push(value);
600 this._activeStates = newArray;
603 _removeState : function(state){
605 state = typeof state === 'string' ? this._states[ state ] : state;
607 this._removeStates([ state ]);
610 _state : function(state, remove){
612 state = this._states[ state ];
614 return $.inArray(state, this._activeStates) !== -1 ? true : false;
617 _addState : function(state, stateID){
621 $.inArray(state, this._activeStates) === -1
622 && this._activeStates.push( state );
626 this._states[ state ]
627 && $.inArray(this._states[ state ], this._activeStates) === -1
628 && this._activeStates.push( this._states[ state ] );
632 _setVideoKey : function(val){
634 this.keys.video = this.options.shuffle ? this.options.randomVideo() : val || 0;
637 _getVideo : function(){
639 return this.options.playlist.videos[ this.keys.video ];
642 _findVideo : function(id){
646 $.each(this.options.playlist.videos, function(key, val){
652 return false; // break
659 _getPlaylistData : function(success, error){
661 var self = this, playlist = this.options.playlist;
663 if (playlist.user || playlist.playlist) {
665 function ajaxSuccess(json){
672 // replace playlist ID with json array
673 self.options.playlist = {
674 title: json.feed.title.$t,
679 $.each(json.feed.entry, function(key, vid){
680 self.options.playlist.videos.push({
681 id: vid.link[0].href.replace(self._youtubeIdExp, '$1'), // munge video id from href
686 self.elements.playerObject.fadeOut(180, function(){ success.call( self ); });
689 var url = playlist.user
690 ? 'http://gdata.youtube.com/feeds/api/videos'
691 : 'http://gdata.youtube.com/feeds/api/playlists/' + playlist.playlist;
693 url += '?callback=?';
695 var data = { alt: 'json', format: '5' };
697 if (playlist.user){ data.author = playlist.user; }
699 this._trigger(this, 'onBeforePlaylistLoaded', [ playlist ]);
708 self._trigger(self, 'onAfterPlaylistLoaded', [ playlist ]);
710 self._trigger(self, error);
714 self._trigger(self, 'onAfterPlaylistLoaded', [ playlist ]);
716 self._trigger(self, ajaxSuccess, arguments);
722 } else if (!playlist.videos){
724 var videos = this.elements.player.find('.youtube-player-playlist li a');
728 self.options.playlist.videos = [];
730 videos.each(function(){
731 self.options.playlist.videos.push({
732 id: this.href.replace(self._youtubeIdExp, '$1'),
733 title: $(this).html(),
740 self._trigger(self, 'onAfterPlaylistLoaded', [ playlist ]);
742 self._trigger(self, success);
745 _updatePlaylist : function(){
749 (this.elements.playlist) &&
751 this.elements.playlist
753 .removeClass('ui-state-active')
756 if ( self.options.playlist.videos[self.keys.video].id == $(this).data('video').id) {
758 var height = $( this ).addClass('ui-state-active').outerHeight();
760 if ( !self.options.videoThumbs ){
762 var pos = (key * height) - ( Math.floor(self.options.playlistHeight / 2) * height);
764 self.elements.playlist.scrollTop( pos );
772 _showPlaylist : function(show) {
774 show = show === undefined ? true : show;
776 ( show ) && this.elements.playlistContainer.show();
779 oldHeight = this.elements.playlist.height(),
780 scrollerHeight = this.elements.playlist.css('height', 'auto').height(),
781 videoHeight = this.elements.playlist.find('li:first').outerHeight(),
782 newHeight = videoHeight * this.options.playlistHeight,
783 height = newHeight < scrollerHeight ? newHeight : scrollerHeight;
785 ( show ) && this.elements.playlistContainer.hide();
787 if ( !this.elements.playlist.children().length ) {
789 this.elements.playlistContainer.hide();
791 } else if ( height ) {
793 this.elements.playlist.height( height );
795 (this.options.showPlaylist || show)
797 && this.elements.playlistContainer.animate(this.options.playlistAnimation, this.options.playlistSpeed);
801 loadVideo : function(video, cue){
805 function load(videoID){
808 ? self.cueVideo(videoID)
809 : self.youtubePlayer.loadVideoById(videoID, 0);
811 self._trigger(self, 'onVideoLoad', [ self._getVideo() ]);
814 if (video && video.id) {
821 this.videoIds = cue ? this.videoIds : [];
825 // append video to video list
826 this.options.playlist.videos.push(video);
829 // add video to video list only if a title is present
830 this.options.playlist.videos = video.title ? [ video ] : [];
833 // add video/s to playlist
834 this._addVideosToPlaylist(cue);
836 // update the height of the playlist, but don't explicidly show it
837 this._showPlaylist(false);
840 // load and play the video
845 // you can't load videos that aren't in the current playlist
847 var index = this._findVideo( video );
851 this.keys.video = index;
858 // try load the next video
859 load( this.options.playlist.videos[this.keys.video].id );
863 loadPlaylist: function(playlist, play, show, success){
867 this.options.playlist = playlist;
870 this._getPlaylistData(
871 function(){ // success
873 this.keys.video = this.options.randomStart ? this.randomVideo() : 0;
875 // has the flash object been built?
876 if (this.youtubePlayer) {
878 // reset the playlist
879 this._addVideosToPlaylist();
881 // play or cue the video
882 (play) ? this.loadVideo() : this.cueVideo();
884 this._showPlaylist(show);
887 this._trigger(this, success);
891 var msg = 'There was an error loading the playlist.';
893 this.elements.playerObject.html( msg );
895 this._trigger(this, 'onError', [msg]);
900 pauseVideo : function(){
902 this.youtubePlayer.pauseVideo();
905 shufflePlaylist : function(){
912 muteVideo : function(button){
914 this._state('mute') ? this.youtubePlayer.unMute() : this.youtubePlayer.mute();
920 this.options.repeat = 1;
923 playVideo : function(){
925 this.youtubePlayer.playVideo();
928 cueVideo : function(videoID, startTime){
930 return this.youtubePlayer.cueVideoById( videoID || this.options.playlist.videos[this.keys.video].id, startTime || 0);
933 randomVideo : function(){
935 this.keys.video = Math.floor(Math.random() * this.options.playlist.videos.length);
937 return this.keys.video;
940 prevVideo : function(){
942 if (this.keys.video > 0) {
944 this._setVideoKey( --this.keys.video );
946 } else if ( this.options.repeatPlaylist ) {
948 this._setVideoKey( this.videoIds.length - 1 );
952 this.loadVideo(null, this._state('play') || this.options.autoPlay ? false : true);
955 nextVideo : function(){
957 if (this.keys.video < this.options.playlist.videos.length-1) {
959 this._setVideoKey( ++this.keys.video );
961 } else if ( this.options.repeatPlaylist ) {
963 this._trigger(this, 'onEndPlaylist');
965 this._setVideoKey( 0 );
969 this.loadVideo(null, this._state('play') || this.options.autoPlay ? false : true);
972 playlistToggle : function(button){
974 (this.elements.playlistContainer.find('li').length) &&
981 }, this.options.playlistSpeed);
984 // return the plugin object
985 plugin : function(){ return this; },
987 // return an array of current player states
990 var self = this, states = [];
992 $.each(this._activeStates, function(key, val){
994 $.each(self._states, function(k, v){
996 (val === v) && states.push(k);
1003 videos : function(){
1005 return this.options.playlist.videos;
1008 videoIndex : function(){
1010 return this.keys.video;
1013 destroy: function(){
1015 clearInterval( this.timeInterval );
1017 this.element.removeClass('ui-widget').removeAttr('style');
1019 this.elements.playerVideo.removeAttr('style');
1021 this.elements.playlistContainer.remove();
1023 this.elements.toolbar.container.remove();
1025 this.options.swfobject.removeSWF(this.elements.playerObject[0].id);
1027 this.elements.playerObjectClone.appendTo( this.elements.playerVideo );
1029 $.removeData( this.element[0], this._pluginName );
1033 player.prototype.defaultToolbarButtons = {
1036 icon: 'ui-icon-play',
1039 icon: 'ui-icon-pause',
1052 icon: 'ui-icon-seek-prev',
1060 icon: 'ui-icon-seek-next',
1067 text: 'Shuffle/Random',
1068 icon: 'ui-icon-shuffle',
1072 this.shufflePlaylist();
1076 text: 'Repeat playlist',
1077 icon: 'ui-icon-refresh',
1086 icon: 'ui-icon-volume-on',
1088 action: function(button){
1090 this.muteVideo(button);
1094 text: 'Toggle playlist',
1095 icon: 'ui-icon-script',
1098 this.playlistToggle();
1103 })(this.jQuery, this, document);