Posted by Neil Young on September 08, 2015
When 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.