0

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


.
Daveh0
  • 952
  • 9
  • 33
  • https://stackoverflow.com/questions/7988476/listening-for-youtube-event-in-javascript-or-jquery offers a very robust solution to the general challenge of being able to listen for events with a pre-existing ` – Daveh0 Jun 30 '20 at 14:01

1 Answers1

0

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);
 }
F.Igor
  • 4,119
  • 1
  • 18
  • 26
  • the `lity:ready` event does not fire until you click the "open" text link. It has to wait until then because the `iframe` is not created until then... and the `iframe` is what is referenced when the player object is created (`player = new YT.Player("x",...` - `x` is the `ID` of the `iframe`. But yes `window.onYouTubeIframeAPIReady=function() {` gets the function to fire and thus the player created, but now I can't get either of the callbacks (`onPlayerReady` and `onPlayerStateChange`) to fire. – Daveh0 Jun 30 '20 at 00:40
  • @Daveh0 any update on this ? I've the same problem. My code works in my local enviroment i.e both the events are getting called but in Live the same code doesn't work. – DavidG Aug 10 '20 at 13:45
  • I've added some additional changes, to make the callbacks available in the global context. – F.Igor Aug 10 '20 at 16:12
  • @SohaibAhmad - see my updates in OP - let me know if that doesn't help. but also see https://stackoverflow.com/questions/7988476/listening-for-youtube-event-in-javascript-or-jquery for a very robust, working solution – Daveh0 Aug 14 '20 at 23:08