Using the Javascript Revealing Object Pattern with the YouTube iFrame API

YouTube iFrame APIWhen working with videos it is sometimes preferable and cost effective to utilise a service such as YouTube to serve them to users that visit your website. Recently we had to use YouTube videos that need to be updated on a button click for a project which users could vote for their favourite video.

The biggest part of our requirement was being able to serve a series of videos on the same web page and being able to pause all other playing videos when another one starts playing. In order to do this we implemented the Javascript based Revealing Object Pattern. Todd Motto describes this pattern very well here.

This meant we were able to build a reusable and configurable API to allow us to serve these videos on our web page as well as serve a single video where required. Using this object pattern allows you to call these public functions from any other objects whilst also hiding any private internal functionality within that object.

It was also important that the players needed were only loaded once and then any further videos were cued up in the players when a user selects a new set of videos. This basically allowed us to load in new videos without creating a new set of player objects each time.

The first step was to build the YouTubePlayer. This utilises jQuery but you are able to write vanilla JS if you wish. I have added comments to explain the functionality for this.

//handles all videos on the site using the YouTube iframe API
//https://developers.google.com/youtube/iframe_api_reference
var YouTubePlayer = (function () {
	//setup the player
	var player;
        //setup the video collection
	var videoCollection = Array();
        //setup a player tracker array
	var playerTracker = Array();
        //variable to track the current player
	var currentPlayer;
        //this enables us to retrieve a video by its id
	var referencePlayers = {};
        //determine how many players are ready
	var playerReadyCount = 0;
        //set total videos on the page
        var totalVideos = 5;
	//setup default player vars
	var playerVars = {
		autoplay: 0,
		controls: 0,
		modestbranding: 0,
		rel: 0,
		showinfo: 0,
		wmode: "transparent"
	};
	// initilise YT library
	var init = function ( vars ) {
	    //get the API
	    var tag = document.createElement('script');
	    tag.src = "https://www.youtube.com/iframe_api";
	    var firstScriptTag = document.getElementsByTagName('script')[0];
	    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
	};
	//listen for the iframe ready event on the window object
	window.onYouTubeIframeAPIReady = function() {
		//trigger the ready event for the API
		//cannot run anything else until this has been listened for!
		$(window).trigger("youtube_iframeapi_ready");
        };
        //function to load a single video
        var loadVideo = function( id, videoId, width, height, vars ){
	    //setup the player
	    player = new YT.Player(id, {
				height: height,
				width: width,
				playerVars: vars,
				videoId: videoId,
				events: {
				'onReady': onPlayerReady,
				'onStateChange': onPlayerStateChange
			}
		});
        };
        //function to cue the next video
        var cueVideo = function( playerId, ytId, play ){
	    //determine if we want the video to play once cued
            play = ( play == null ) ? false : play;
	    //get the player
	    var player = videoCollection[ playerId ];
	    //cue the video
	    player.cueVideoById( { videoId: ytId } );
	    //do we need to play the video
	    if ( play ){
	    	//play the video
	    	player.playVideo();
	    }
        };
        //function to pause the current player
        var pauseCurrentVideo = function(  ){
	    //pause the video
	    currentPlayer.pauseVideo();
        };
        //function to play the current player
        var playCurrentVideo = function(  ){
	    //pause the video
	    currentPlayer.playVideo();
        };
        //function to stop the current player
        var stopCurrentVideo = function(  ){
	    //pause the video
	    currentPlayer.stopVideo();
        };
        //function to get the current player
        var getCurrentPlayer = function(  ){
	    //pause the video
	    return currentPlayer;
        };
        //function to get the a player by its YouTube id
        var getPlayerById = function( id ){
	    //pause the video
	    return referencePlayers[id];
        };
        //function to get a player by its index
        var playVideoByIndex = function( i ){
	    //grab the video
		var video = videoCollection[ i ];
		//play video
		video.playVideo();
        };
        //function to clear the video collection
        var clearVideoCollection = function(){
    	    //loop through each video in the collection
	    $.each(videoCollection, function( i ){
		//grab the video
		var video = videoCollection[i];
		//destroy the video
		video.destroy();
		//destry the object
		video = null;
	    });
    	    //reset the array
    	    videoCollection = Array();  
        };
        //function to initialise multiple videos
        var initialiseVideos = function( elements, vars ){
	    //determine if vars have been passed
	    if ( vars ){
		//set the vars
                //if not set then we use the default vars
		playerVars = vars;
	    }
	    //store the players
	    var players = [];
	    //loop through and build videos
	    $( "." + elements ).each(function( ){
		    //get player id
		    var playerId = $( this ).attr("id");
		    //check if it does not exist
		    if( playerTracker.indexOf( playerId ) == -1 ){
			    //get the video id
			    var videoId = $( this ).data().id;
			    //get the width
			    var width = $( this ).data().width;
			    //get the height
			    var height = $( this ).data().height;
			    //var video = videos[i];
			    var data = { id: playerId, videoId: videoId, width: width, height: height };
			    //add data object
			    players.push( data );
			    //we store the configuration tracking so we don't add this player
			    playerTracker.push( playerId );
		    }
	    });
	    //create the players
	    createPlayers( players, vars );
        };
        //function to create multiple players
        var createPlayers = function( players, vars ) {
		//determine if vars have been passed
		if ( vars ){
			//set the vars
			playerVars = vars;
		}
		//loop through each player setup	
		$.each( players, function(i){
			//get the player data
			var data = players[i];
			//setup the player with the given data
			player = new YT.Player(data.id, {
					height: data.height,
					width: data.width,
					playerVars: playerVars,
					videoId: data.videoId,
					events: {
					'onReady': onPlayerReady,
					'onStateChange': onPlayerStateChange
				}
			});
			//add to video collection
			videoCollection.push( player );	
			//load players
			referencePlayers[data.id] = player;
		} );
        };
	//function to check when player(s) are ready
	var onPlayerReady = function( event ){
		//get the player ready
		currentPlayer = event.target;
		//increment ready players
		playerReadyCount++;
		//determine if we have reached the maximum for the page
		if ( playerReadyCount == totalVideos ){
			//trigger the ready event
			$(window).trigger( "youtube_iframeapi_all_players_ready", playerReadyCount );
		}
	};
	//function to listen for state change
	var onPlayerStateChange = function( event ){
		//determine event
		if ( event.data == YT.PlayerState.PLAYING ){
			//video is playing
			//loop through each video in the collection
			$.each(videoCollection, function( i ){
				//grab the video
				var video = videoCollection[i];
				//determine if the video clicked is not the video in the collection
				if ( video.m != event.target.m ){
					//set the current player
					currentPlayer = event.target;
					//pause the video
					video.pauseVideo();
				}
			});
		}
		else if ( event.data == YT.PlayerState.PAUSED ){
			//video is paused
			//loop through each video in the collection
			$.each(videoCollection, function( i ){
				//grab the video
				var video = videoCollection[i];
				//determine if the video clicked is not the video in the collection
				if ( video.m != event.target.m ){
					//set the current player
					currentPlayer = event.target;
				}
			});
		}			
	};
	//publicly accessible items
	return {
		init:init,
		loadVideo: loadVideo,
		initialiseVideos: initialiseVideos,
		clearVideoCollection: clearVideoCollection,
		pauseCurrentVideo: pauseCurrentVideo,
		playCurrentVideo: playCurrentVideo,
		stopCurrentVideo: stopCurrentVideo,
		getCurrentPlayer: getCurrentPlayer,
		getPlayerById: getPlayerById,
		cueVideo: cueVideo,
		playVideoByIndex: playVideoByIndex
	};
})();

We initialise the YouTube object like so:

//initialise the YouTube iFrame API
YouTubePlayer.init( );

Once we have done this, this will trigger the youtube_iframeapi_ready event which we will be able to listen for and act upon. We can then start to setup our video(s). For a single video we just need to make sure we have the following as our html:

<div id="player"></div>

At this point we will just need to listen for the ready event from YouTube and then display the video:

//on to the YT ready event
$(window).on( "youtube_iframeapi_ready", function(){
	//video vars
	var vars = {
		autoplay: 1, 
		controls: 0, 
		modestbranding: 0, 
		rel: 0, 
		showinfo: 0,
		wmode: "transparent"
	};
	YouTubePlayer.loadVideo( "player", "Q3Yc3HhSl1Q", 775, 430, vars );	    		
} );

This will simply load the video directly into that player.

But what if we have multiple videos on the page? We can utilise the createPlayers function in our object. Firstly we need to build a html list of videos we want to play. We can do this like so:

<div id="players">
    <div id="player-1" data-id="w8KQmps-Sog" data-width="286" data-height="286" class="videos"></div>
    <div id="player-2" data-id="O2IuJPh6h_A" data-width="286" data-height="286" class="videos"></div>
    <div id="player-3" data-id="Q3Yc3HhSl1Q" data-width="286" data-height="286" class="videos"></div>
    <div id="player-4" data-id="RS2WvsYFgLA" data-width="286" data-height="286" class="videos"></div>
    <div id="player-5" data-id="jD-dHbQt0iM" data-width="286" data-height="286" class="videos"></div>
</div>

Once we have our markup we can easily call the YouTubePlayer's initialiseVideos function in our object like so:

//listen for the YouTube ready event
$(window).on( "youtube_iframeapi_ready", function(){
	//create the home page video
	YouTubePlayer.initialiseVideos( "videos", { autoplay: 0, controls: 1, modestbranding: 0, rel: 0, showinfo: 0, wmode: "transparent" } );
} );

If we then wish to cue another set of videos (for example if we load another set of videos from the server) we can then just call the cueVideo function in our YouTubePlayer like so:

//listen to determine if all the players are ready
$(window).on( "youtube_iframeapi_all_players_ready", function(){
	//cue a new video
    	YouTubePlayer.cueVideo( 0, "I5sJhSNUkwQ" );
});

You will see that this will cue the video id into the first video player on the page.

Using this will allow you to easily handle all videos on your website whether it is via a single video player or multiple video players using the YouTube iFrame API.