Lose context of this when youtube constructor method is called - javascript

I have created a youtube player using the youtube iframe api, I am listening for the ENDED event but I've realised that I lose the reference to this which becomes the window but I'm really unsure how to resolve this. I've tried binding this to the contsructor etc but with no joy whatesoever so could really do with you guys help.
JS
startPlayer: function (videoId) {
var instance = this;
console.log('startPlayer', instance);
if( instance.flags.isPlaying ) {
instance.selectors.playerCtn.empty();
instance.flags.isPlaying = false;
}
instance.selectors.playerCtn.append('<div id="player"></div>');
instance.player = new YT.Player('player', {
height: '390',
width: '640',
videoId: videoId,
events: {
'onReady': this.onPlayerReady,
'onStateChange': this.onPlayerStateChange
}
});
instance.flags.isPlaying = true;
},
onPlayerStateChange: function (event) {
console.log('onPlayerStateChange');
var instance = this;
console.log(instance); //undefined??
if (event.data == YT.PlayerState.PLAYING) {
console.log('PLAYING...');
}
if (event.data == YT.PlayerState.PAUSED) {
console.log('PAUSED...');
}
if (event.data == YT.PlayerState.ENDED) {
console.log('what is this', instance);
// if instance.counter === instance.playlist
if (instance.counter === instance.playlist) {
console.log('you\'ve come to the end of your playlist');
// Display message or go back to first?
return;
}
// Increase the counter
instance.counter++
// Set the new current element
instance.current = instance.selectors.listItems[instance.counter];
console.log(instance.counter);
console.log(instance.current);
// Get the new current element data-id
var videoId = instance.current.attr('data-id');
// Start the player
startPlayer(videoId);
}
if (event.data == YT.PlayerState.BUFFERING) {
console.log('BUFFERING...');
}
}
Test page http://go.shr.lc/1lh2dmu

events: {
'onReady': this.onPlayerReady.bind(this),
'onStateChange': this.onPlayerStateChange.bind(this)
}
Besides, why var instance = this;? this is quite shorter to type and you aren't using instance in any closure.

Related

using YouTube iFrame API within jQuery event

I have a page in which clicking a link opens a lightbox and embeds a YouTube video in an <iframe>. The lightbox and <iframe> markup are generate on the fly by Lity.
Following the example right out of the documentation, where the <iframe> is hard-coded into the page, it works as expected.
<iframe id="existing-iframe-example"
width="640" height="360"
src="https://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1"
frameborder="0"
style="border: solid 4px #37474F">
</iframe>
<script type="text/javascript">
var tag = document.createElement('script');
tag.id = 'iframe-demo';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubeIframeAPIReady() {
console.log('api ready');
player = new YT.Player('existing-iframe-example', {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
document.getElementById('existing-iframe-example').style.borderColor = '#FF6D00';
}
function changeBorderColor(playerStatus) {
var color;
if (playerStatus == -1) {
color = "#37474F"; // unstarted = gray
} else if (playerStatus == 0) {
color = "#FFFF00"; // ended = yellow
} else if (playerStatus == 1) {
color = "#33691E"; // playing = green
} else if (playerStatus == 2) {
color = "#DD2C00"; // paused = red
} else if (playerStatus == 3) {
color = "#AA00FF"; // buffering = purple
} else if (playerStatus == 5) {
color = "#FF6DOO"; // video cued = orange
}
if (color) {
document.getElementById('existing-iframe-example').style.borderColor = color;
}
}
function onPlayerStateChange(event) {
changeBorderColor(event.data);
}
</script>
See this Codepen
I'm trying to modify it to work with a dynamically generated <iframe> (which is how Lity handles YouTube videos). I can see that the YouTube API script is being added to the page, but the function onYouTubeIframeAPIReady does not ever seem to be called, which according to documentation is supposed to fire as soon as API script loads.
HTML (to open lightbox)
<a href="https://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1" class="lity" data-lity>open</a>
JavaScript (fires after lightbox object is available in DOM)
$(document).on("lity:ready", function (event, instance) {
console.log("Lightbox ready");
$(".lity-youtube iframe").attr("id", "x");
if ($("iframe#x").length) {
console.log("ID added to <iframe>");
} else {
console.log("ID NOT added to <iframe>");
}
var tag = document.createElement("script");
tag.id = "iframe-demo";
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
if ($("#iframe-demo").length) {
console.log("api script added");
} else {
console.log("api script NOT added");
}
var player;
function onYouTubeIframeAPIReady() {
console.log("api ready");
player = new YT.Player("x", {
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
}
function onPlayerReady(event) {
console.log("player ready");
document.getElementById("x").style.borderColor = "#FF6D00";
}
function changeBorderColor(playerStatus) {
var color;
if (playerStatus == -1) {
color = "#37474F"; // unstarted = gray
} else if (playerStatus == 0) {
color = "#FFFF00"; // ended = yellow
} else if (playerStatus == 1) {
color = "#33691E"; // playing = green
} else if (playerStatus == 2) {
color = "#DD2C00"; // paused = red
} else if (playerStatus == 3) {
color = "#AA00FF"; // buffering = purple
} else if (playerStatus == 5) {
color = "#FF6DOO"; // video cued = orange
}
if (color) {
document.getElementById("x").style.borderColor = color;
}
}
function onPlayerStateChange(event) {
console.log('state change');
changeBorderColor(event.data);
}
});
See this Codepen.
I tried pulling everything out of the lity:ready event handler with the same results....
Can anyone see what's going wrong?
EDIT/UPDATE
I tried running onYouTubeIframeAPIReady as a deferred object
var YTdeferred = $.Deferred();
window.onYouTubeIframeAPIReady = function() {
YTdeferred.resolve(window.YT);
};
and using it in the callback
var player;
YTdeferred.done(function (YT) {
console.log("api ready");
player = new YT.Player("x", {
events: {
//onReady: onPlayerReady,
//onStateChange: onPlayerStateChange
onReady: function () {
console.log("player ready from ananonymous");
},
onStateChange: onPlayerStateChange
}
});
console.log(player);
});
Now the YT.Player object is created (presumably on the iframe created by lity), but neither of the events (onReady and onStateChange) seem to be triggering. Also, it doesn't have access to any of the methods or properties that it should:
player.playVideo()
produces an error: player.playVideo is not a function`
Still stumped.
Codepen
ANOTHER EDIT/UPDATE
Now I'm very confused because i created a version that replicates what lity does (I think) and the events (onReady and onStateChange) do in fact fire as expected.
Codepen
Solution to a STUPID mistake
I can' believe I beat my head into the wall over this for so long... G'AAAAAAH!!
It's definitely a lity thing
When lity receives an argument (YouTube URL) formatted like
https://www.youtube.com/embed/XXXXXXXXX
or
http://youtu.be/XXXXXXXXX
it converts it to
https://www.youtube.com/embed/XXXXXXXXX?autoplay=1
If you try to pass query string arguments to to a similarly formatted URL (such as: https://www.youtube.com/embed/XXXXXXXXX?enablejsapi=1 (which is required for the API calls to work), it URLencodes the ?enablejsapi=1 and adds it to the end of the converted URL, so you get:
https://www.youtube.com/embed/XXXXXXXX?autoplay=1&%3Fenablejsapi=1
As a result the enablejsapi=1 is never read in and therefore the YT.Player is never attached to the <iframe> DOM element.
** SOLUTION **
Pass the URL to lity in a format that passes the video ID as a query string argument (and append enablejsapi=1):
https://www.youtube.com/watch?v=XXXXXXXX&enablejsapi=1
lity will now keep your argument(s) intact and convert to
https://www.youtube.com/embed/XXXXXXXX?autoplay=1&enablejsapi=1
Now the YT.Player call can read enablejsapi=1 and properly attach to the <iframe> element, and all is good in the universe.
<a href="https://www.youtube.com/watch?v=TJU-tBquzcM&enablejsapi=1" class="lity" data-lity>open lightbox with video</a>
Final Working CodePen
.
It looks like the "lity:ready" event is not fired in my case.
I changed
$(document).on("lity:ready", function (event, instance) {
to
$(document).ready(function (event, instance) {
using the jQuery ready event.
And most important, the function onYouTubeIframeAPIReady must be global (on window scope) to be detected by the youtube iFrame API (actually it was created inside an anonymous function() ). So I changed
function onYouTubeIframeAPIReady() {
to
window.onYouTubeIframeAPIReady=function() {
Codepen
EDIT:
To work on a global context inside an anonymous function. Other local functions created and referenced (onPlayerReady and onPlayerStateChange) must be declared in the global context using the window object.
window.onPlayerReady=function(event) {
console.log("player ready");
document.getElementById("x").style.borderColor = "#FF6D00";
}
window.changeBorderColor=function(playerStatus) {
var color;
if (playerStatus == -1) {
color = "#37474F"; // unstarted = gray
} else if (playerStatus == 0) {
color = "#FFFF00"; // ended = yellow
} else if (playerStatus == 1) {
color = "#33691E"; // playing = green
} else if (playerStatus == 2) {
color = "#DD2C00"; // paused = red
} else if (playerStatus == 3) {
color = "#AA00FF"; // buffering = purple
} else if (playerStatus == 5) {
color = "#FF6DOO"; // video cued = orange
}
if (color) {
document.getElementById("x").style.borderColor = color;
}
}
window.onPlayerStateChange=function(event) {
console.log('state change');
changeBorderColor(event.data);
}

Add/loop multiple videos, player names and events via YouTube API dynamically?

I'm trying to make an array of players to pass to new YT.Player. I keep getting 'player1' undefined, and the iFrame never gets added to the stated 'playerInfo.id'.
My code, without player2 or player3 included for simplicity:
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var playerInfoList = [
{playerName: 'player1', id: 'container1', videoId: 'WPvGqX-TXP0', eventId: 'playerOneReady'},
{playerName: 'player2', id: 'container2', videoId: 'Yj0G5UdBJZw', eventId: 'playerTwoReady'},
{playerName: 'player3', id: 'container3', videoId: '9gTw2EDkaDQ', eventId: 'playerThreeReady'}];
var players = new Array();
function onYouTubeIframeAPIReady() {
if (typeof playerInfoList === 'undefined') return;
for (var i = 0; i < playerInfoList.length; i++) {
var generatePlayers = createPlayer(playerInfoList[i]);
players[i] = generatePlayers;
}
}
function createPlayer(playerInfo) {
playerInfo.playerName = new YT.Player(playerInfo.id, {
height: '390',
width: '640',
videoId: playerInfo.videoId,
events: {
'onReady': playerInfo.eventId
}
});
}
// When the Players are ready.
var duration1;
function playerOneReady() {
duration1 = player1.getDuration();
}
function play(playerNum) {
playerNum.seekTo(0);
playerNum.playVideo();
}
$('#player1-trigger').click(function(){
play(player1);
}
I'm not sure why it keeps coming back as undefined. I have a simpler structure that works fine, but it involves adding each player manually, and I'm trying to make it more dynamic/efficient. Although this is more efficient than dynamic.
from what i'm seeing you made a mistake in the function createPlayer. while you're trying to make a player with the name player1 you're actually saving the new YT.player on the location of playerInfo.playername. it would be better to save in te players list directly like this:
function onYouTubeIframeAPIReady() {
if (typeof playerInfoList === 'undefined') return;
for (var i = 0; i < playerInfoList.length; i++) {
var generatePlayers = createPlayer(playerInfoList[i],i);
}
}
function createPlayer(playerInfo, locationInList) {
players[locationInList] = new YT.Player(playerInfo.id, {
height: '390',
width: '640',
videoId: playerInfo.videoId,
events: {
'onReady': playerInfo.eventId
}
});
}
and then when calling for the players use:
$('#player1-trigger').click(function(){
play(players[1]);
}
For the future I would suggest when making code more efficient/dynamic/readable to make multiple smaller changes and to test after every change if the code still works. That way it's easier to find what has broken.

YouTube API loadVideoById some times not working?

I am working on this code. Some times the videos play well but some times it's showing a white screen and I am getting this type of error message:
player.loadVideoById is not a function.
My HTML code:
<i class="fa fa-play-circle-o" id="play_btn" onclick="playyoutubevideo('<?php echo $topic_names1['youtube_video_id']?>');"></i>
This is my javascript code:
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
videoId: $('#video_id').val(),
playerVars: { 'fs':'0' },
events: {
'onStateChange': onPlayerStateChange
}
});
}
// when video ends
function onPlayerStateChange(event) {
if(event.data === 0) {
$('#popup_close').click();
}
}
function playyoutubevideo(video_id)
{
$("body").css("position", "fixed");
if(player)
{
var fn = function(){ player.loadVideoById(video_id); }
setTimeout(fn, 500);
}
$(".pos_popup").removeClass("none1");
$(".pos_popup1").removeClass("none1");
}
If anyone knows the mistake, please help me to solve this issue.

How to use an iframe as input for YT.Player

The docs say we should use a div container where the <iframe> element will be appended.
That works:
new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
But I should have an element <div id="player"></div> on the page.
Is it possible that if I already have the iframe element with the embedded video to use that iframe in the YT.Player constructor?
I would like to have something like this:
new YT.Player('iframeId').playVideo();
But the playVideo isn't there because the player is not loading (I guess that's happening because we are providing the iframe id).
Is there a way to connect an existing iframe with the YT.Player instance?
I wrote a quick hack for it: get the video id from the <iframe> url, replace the iframe with a div and initialize the player:
YTPlayerIframe = (function () {
function youtube_parser(url){
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var match = url.match(regExp);
return (match&&match[7].length==11)? match[7] : false;
}
var __oldPlayer = YT.Player;
var player = function (id, options) {
var el = document.getElementById(id);
options.videoId = youtube_parser(el.getAttribute("src"))
el.outerHTML = "<div id='" + id + "'>test</div>";
return new __oldPlayer(id, options);
};
return player;
})();
Then you can use it this way:
var myPlayer = YTPlayerIframe("iframe-id", {
// The videoId option will be taken from the iframe src.
events: {
onReady: function () {...}
}
});

Using an array in Jquery

I'm not so experienced with Javascript and I have been struggling with this one pretty much all day.
I'm using Jquery to create and array of the ids of embedded youtube videos:
$(function() {
$('li').on("click",function(){
alert($(this).attr('data-pile'));
var pilename = $(this).attr('data-pile');
var videoIDs = [];
$("li[data-pile='"+pilename+"']").each(function(index){
videoIDs.push($(this).attr('id'));
});
$.each(videoIDs,function(){
});
});
});
And I need to use the array in this JS:
<script src="//www.youtube.com/iframe_api"></script>
<script>
/**
* Put your video IDs in this array
*/
var videoIDs = [
//my array of Ids here
];
var player, currentVideoId = 0;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '350',
width: '425',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
event.target.loadVideoById(videoIDs[currentVideoId]);
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED) {
currentVideoId++;
if (currentVideoId < videoIDs.length) {
player.loadVideoById(videoIDs[currentVideoId]);
}
}
}
</script>
In each div where embedded videos are I'm applying an id with same id as video.
How should I make the two scripts work?
I'll really appreciate if someone can point me in the right direction.
You're declaring your videoIDs array twice, once in your click events and again in your second
script.
The one inside your click events is local to that function whereas the other one is global. Javascript has function scope, so that click event one gets discarded once that function ends.
If you remove the one inside your click events, I believe it should work. You should also remove the $.each... as I don't think it's going to help (you're trying to make a playlist, right?).
It should be noted that it's considered bad practice to pollute the global namespace by using global variables. If this is all the code you have on your page, it's probably not an issue.
Try doing it this way: add a custom listener after "click" event. Didn't check your array forming section, tested with a custom array, hope you won't have issues with it.
<script>
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '350',
width: '425',
});
}
$(function(){
$(document.body).on("click",".play", function(){
player.stopVideo();
var pilename = $(this).attr('data-pile');
var videoIDs = [];
$("li[data-pile='"+pilename+"']").each(function(index){
videoIDs.push($(this).attr('id'));
});
if(videoIDs.length > 0){
currentVideoId = 0;
player.loadVideoById(videoIDs[0]);
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED) {
currentVideoId++;
if (currentVideoId < videoIDs.length) {
player.loadVideoById(videoIDs[currentVideoId]);
}
}
}
player.addEventListener("onStateChange", onPlayerStateChange)
player.playVideo();
}
});
});
</script>

Categories

Resources