Making the YouTube API adaptive and modular with ES6 syntax and prototypal inheritance.
The YouTube iframe API requires a pretty opinionated setup so I want to take a moment to walk through how we managed this integration with our own modern JavaScript setup. The developer docs for the YouTube embed API are very good with one caveat, they require that the onYouTubeIframeReady() be applied to the global window object. For small applications, this is probably fine, but if you're using ES6 modules and Webpack to compile your JS then instantiating methods on the global object is not optimal. Here's an example of how we approached this problem.
First, we assign our custom YouTube class to a variable. Then with that variable bound to the method found in our custom class, we reassign the global onYouTubeIframeAPIReady() that YouTube API is listening for, with the method we instantiating in our class.
var YT = new YouTubeScript(); window.onYouTubeIframeAPIReady = YT.onYouTubeIframeAPIReady(YT);
Now lets look at our custom onYouTubeIframeAPIReady()
Here things get interesting... We have to pass a parameter of 'instance' into our function and then assign the global YT method back to that instance.
onYouTubeIframeAPIReady(instance) { window.YT = instance; return function() { jQuery('.yt_player').each(function(index) { YT.YTplayer[index] = new YT.Player(jQuery(this)[0], { events: { 'onReady' : YT.onPlayerReady, 'onStateChange': YT.onPlayerStateChange } }); jQuery(this).parent().find('.you_tube_play_button').on('click', function() { YT.YTplayer[index].playVideo(); }); }); } };
We can actually refactor this bit to make it a little easier to read with the use of the bind method in JS. The above code is really just a workaround for the way that JavaScript binds methods to the global object. We can be more explicit by refactoring the code in this way.
this.onYouTubeIframeAPIReady = this.onYouTubeIframeAPIReady.bind(this); this.exitHandler = this.exitHandler.bind(this); this.onPlayerReady = this.onPlayerReady.bind(this); this.onPlayerStateChange = this.onPlayerStateChange.bind(this);
Here we are specifying the scope of the 'this' keyword by binding it to the appropriate function.
Now we can access the players' lifecycle methods inside our own class and manipulate the video depending on what state it is in.
onPlayerStateChange(e) { let iframe = e.target.h; if (( e.data == 1 || e.data == -1 || e.data == 3 )&& !YT.wasFullScreen) { var requestFullScreen = iframe.requestFullScreen || iframe.mozRequestFullScreen || iframe.webkitRequestFullScreen; if (requestFullScreen) { iframe.style.display = 'initial'; requestFullScreen.bind(iframe)(); } if (document.addEventListener) { document.addEventListener('webkitfullscreenchange', YT.exitHandler, false); document.addEventListener('mozfullscreenchange', YT.exitHandler, false); document.addEventListener('fullscreenchange', YT.exitHandler, false); document.addEventListener('MSFullscreenChange', YT.exitHandler, false); } } }
At this point, we're pretty much off to the races! We can manipulate the player however we see fit and have integrated the YouTube API without too much pollution to the global state object. This setup also keeps things nice and organized and modular!