[SOLVED] How to sync Youtube Iframe Player API with Meteor reactive system?

Hello,

I have added the following code to template onRendered. The following creates an script tag and inserts it into the dom to load the javascript code for the youtube player 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);

The Youtube API will call the following function when the video player is ready, the function is called but
on local env Template.instance() is undefined there.

window.onYouTubeIframeAPIReady = function() {
  console.log('onYouTubeIframeAPIReady');
  new YT.Player('player', {
    height: '390',
    width: '640',
    videoId: '4GMU08J98MQ',
    playerVars: {
      'color': 'white',
      'modestbranding': 1,
      'controls': 0,
      'origin': Meteor.absoluteUrl()
    },
    events: {
      'onReady': onPlayerReady,
      'onStateChange': onPlayerStateChange
    }
  });
  console.log('Template', Template.instance()); //Returns undefined
}

I also call a Meteor method to get documents from a collection at template onRendered, locally the Meteor method resolves before the onYouTubeIframeAPIReady function.

instance.state.set({ isLoading: true });
getSongsByPage.call(
  {},
  (err, result) => {
    if (result) {
      instance.state.set({
        isLoading: false,
        songs: result.songs
      });
    }
  }
);

I don’t know which one is going to resolve first, the onYouTubeIframeAPIReady function or the Meteor method.

I need both to be resolve so I can create the youtube players.
I have no idea how to sync them, the player api is outside meteor system.
How can I trigger a function call when both are done?

Thanks,

First, I would put both async actions into onCreated.
I would also keep track of both ready states in your reactive state:

Template.example.onCreated(function() {
    this.state = new ReactiveDict({
        youtubeReady: false,
        songsReady: false,
        songs: undefined,
    });
    getSongsByPage.call({}, (err, result) => {
        if (result) {
            this.state.set({
                songsReady: true,
                songs: result.songs,
            });
        }
    });
    window.onYouTubeIframeAPIReady = function() {
        this.state.set('youtubeReady', true);
    };
    const tag = document.createElement('script');
    tag.src = 'https://www.youtube.com/iframe_api';
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
});

Then in onRendered, I would use autorun to react to changes in state.
I’d use a guard clause where if either ready state is false, we return from the function, and if they are both ready we continue.

Template.example.onRendered(function() {
    this.autorun(() => {
        if (
            this.state.get('youtubeReady') === false ||
            this.state.get('songsReady') === false
        ) {
            return;
        }

        // Do what you need to do here
    });
});

Which is where you can be sure that both the youtube API and the meteor method are ready, and because you’re in onRendered, you can be sure that the DOM for your template exists too.

Using reactive state also makes it easy to control rendering in the template:

Template.example.helpers({
  loaded() {
    const inst = Template.instance();
    return !!(inst.state.get('youtubeReady') && inst.state.get('songsReady'));
  }
});
<template name="example">
  {{#if loaded}}
    <iframe src="youtube-link"> </iframe>
  {{/if}}
</template>
1 Like

Yes, that was it. Thanks a lot.
I only had to use an arrow function to save the this reference.

window.onYouTubeIframeAPIReady = () => {
  this.state.set('youtubeReady', true);
};

After that another problem show up that I could fix on my own by using Meteor.defer “to wait” until the div elements are on dom to call the youtube api to replace them with the player.

1 Like