justinribeiro / lite-youtube

The fastest little YouTube web component on this side of the internet. The shadow dom web component version of Paul's lite-youtube-embed.
https://www.npmjs.com/package/@justinribeiro/lite-youtube
MIT License
966 stars 70 forks source link

Add javascript event dispatching #37

Closed RoofTurkey closed 3 years ago

RoofTurkey commented 3 years ago

@justinribeiro+lite-youtube+0.9.1.patch.zip

When using this package I was missing events, therefore I wrote this patch that adds event dispatching when a YouTube iframe has finished loading (then you can start using the YouTube JS API for example)

Would be nice if you could add this to the codebase so I no longer need this patch and other developers can also use this functionality.

justinribeiro commented 3 years ago

I'm not exactly sure how that patch works for you; passing the frame id out of the component won't allow you to access it with the JS API. The iframe is within the shadow dom.

To resolve this, I patched the event in the above PR and if you pass the iframe ref from the shadow to the YT.Player() method, you'll end up with a working solution (full example in demo/index.html):

<lite-youtube
      id="test-jsapi"
      videoid="guJLfqTFfIw"
      params="enablejsapi=1">
</lite-youtube>
<script>
let player;
// do stuff with the callbacks and what not
document.addEventListener('liteYoutubeIframeLoaded', () => {

  // There is only ever one iframe, and you know it's in the ShadowRoot once the event is thrown
  const iframe = document.querySelector('#test-jsapi').shadowRoot.querySelector('iframe');

  player = new YT.Player(iframe, {
      events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange
      }
  });
}, false);
</script>
RoofTurkey commented 3 years ago

Ah, the reason I also added in a (unique) id was to be able to load multiple videos on one page. With the event I can easily add the YouTube JS api once an iframe has loaded. My JS code handling this looks like this (and is currently running in our production environment):

let youTubeIframeScriptLoaded = false;
let youTubePlayerLoaded = false;
document.addEventListener('youTubeIframeLoaded', function (event) {
    if (!youTubeIframeScriptLoaded) {
        // See: https://developers.google.com/youtube/iframe_api_reference
        const tag = document.createElement('script');
        tag.id = 'youtube-iframe-api';
        tag.src = 'https://www.youtube.com/iframe_api';
        const firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
        youTubeIframeScriptLoaded = true;
    }

    let player;
    function onYouTubeIframeAPIReady() {
        let youTubeIframe = document.getElementById(event.detail.id + '-root').shadowRoot.getElementById(event.detail.id);
        player = new YT.Player(youTubeIframe, {
            events: {}
        });
        youTubePlayerLoaded = true;
    }

    if (!youTubePlayerLoaded) {
        window.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
    } else {
        onYouTubeIframeAPIReady();
    }

    const photoGalleryModal = $('#photogallery');
    photoGalleryModal.on('hide.bs.modal', function () {
        pausePlayerIfPlaying(player);
    });
    photoGalleryModal.on('show.bs.modal', function () {
        pausePlayerIfPlaying(player);
    });

    $('#carouselBig').on('slide.bs.carousel', function () {
        pausePlayerIfPlaying(player);
    });
    $('#carouselSmall').on('slide.bs.carousel', function () {
        pausePlayerIfPlaying(player);
    });

    function pausePlayerIfPlaying(player) {
        if (player === undefined) {
            return;
        }

        if (player.getPlayerState() === YT.PlayerState.PLAYING) {
            player.pauseVideo();
        }
    }
});

With your solution multiple videos on one page would not be supported, while (as you can see here) it could be done using the frame id.