An Exercise in Granting Access to Content By Watching a YouTube Video
Tags: javascript, youtube, exercise, codenewbie
Published: 2020-12-09 04:34:08


A few evenings ago, I saw someone post a request for technical help on twitter. I responded that I thought I could hack something together. Nothing came of that but I couldn't get the the thought out of my head once it was there. To clear my head, I'll build a proof of concept in this post as a writing/coding exercise. I'll go through my steps of researching the feature and building out a working example. The question was.

>Is there a way that I can create a way for people to have to watch a short video before they can access a resource? 
How can I do this with very little effort?


Assuming it's public website, the answer is a little bit of JavaScript and html. It'll have to be adapted to whatever framework the site is using but, for the purposes of simplicity, I'm going to stick to vanilla JavaScript.

Let's say you have a web page that you want people to watch a video before watching

```html
<html>
  <body>
    This is my website that requires you to watch through a video before accessing
  </body>
</html>
```

First, lets arrange the html and css so that the content isn't visible on page load. I'm putting the css in a `<style>` block to keep the page concise but you can put in in a separate css file.  The below will result in a completely blank page.

```html
<html>
    <style>
      .content {display:none;}
    </style>
  <body>
    <div class="content">
      This is my website that requires you to watch through a video before accessing
    </div>
  </body>
</html>
```

I like to figure out what is going to be the last state as soon as possible. Too often, you'll write a program and wait to long to confirm your plan is going to work. You'll end up doing a lot of work on the logic whether to do a thing or not but you haven't established that you CAN do that thing. Trying to think through in a linear fashion without figuring out the destination will get you lost quick.

I've thought through the logic that I want to apply here. Sometimes a flow chart is helpful to plan this out. I'm going to advise against using pseudo code too early to avoid creating a solution before understanding the problem.

1. Page content is not available
2. Program checks a condition to see if the user has watched video
3. If the user has seen the video, move to step 5 
4. Video appears and user is prompted to watch it
5. When the video is finished, the program records a condition so that the next time the user visits, the don't have to.
6. Content is made visible

The logic all seems to be all in steps two through five. I'm going to make sure that the non logic steps one and six are working before tackling the logic. This isn't a terribly complicated issue, but it's a good idea to isolate the most complicated parts of a program so that you can focus on it when working on it.

Let's get step six taken care of and add some javascript that will take the initial state of not being visible and change that to being visible after the page loads.

```html
<html>
    <style>
      .content { display:none; }
    </style>
  <body>
    <div class="content">
      This is my website that requires you to watch through a video before accessing
    </div>
  </body>
  <script>
    window.onload = (event) => {
      const content = document.querySelector('.content');
      content.style.display = "inline"
    };
  </script>
</html>
```
Now, I'll start adding logic. I'm confident that I can use [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to persist something that will track if a user has watched the video so I'll work on that last. I'm also going to make an assumption that the questioner is not going to host their own video and will use [youtube](https://www.youtube.com). Some googling has lead me to [this stackoverflow article](https://stackoverflow.com/questions/10773322/event-when-youtube-video-finished) as a potential solution. I'll do a little more reading and attempt to use it as a reference to embedded a [youtube](https://www.youtube.com) video. 

Stack overflow is one of the few places on the internet where reading the comments is useful. In the linked article, there are notes on api changes to be aware of. The answer also links to the docs directly. Spending an extra minute on reading through the links has saved me some time. My assumption was that all I needed to do was copy the standard embedded url for a [youtube](https://www.youtube.com) video and then listen for an event. 

The [example from google](https://developers.google.com/youtube/iframe_api_reference) shows that I need to use the [youtube iframe api](https://www.youtube.com/iframe_api) and create a `YT.Player` object to interact with the video. The same site give me a set of docs that I'm sure I'll have to refer to for enhancements or debugging. Stack overflow answers with links to docs are so much more helpful that ones without. Here's the example code that would instantiate a video.

```javascript
  function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', {
      height: '390',
      width: '640',
      videoId: 'M7lc1UVf-VE',
      events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange
      }
    });
  }

```

I've read through the example and it's doing a bit more than I need. It looks to be auto starting the the video and stopping it six seconds after it starts. I think my next incremental step is to nothing but to get the youtube video on the page using the api instead of the embedded html. So I'll trim the example code, add it to my page and make sure I can get a video loaded. Here's the code to get a video to display using the api. 

```html
<html>
    <style>
      .content {display:none;}
    </style>
  <body>
    <div class="content">
      This is my website that requires you to watch through a video before accessing
    </div>
    <div id="info-video"></div>
  </body>
  <script>
     // This loads the youtube iframe api
    const tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    // This function needs to be global
    function onYouTubeIframeAPIReady() {
      player = new YT.Player('info-video', {
        height: '390',
        width: '640',
        videoId: <Your video id>,
        events: {
        }
      });
    }
    window.onload = (event) => {
      const content = document.querySelector('.content');
      content.style.display = "inline"
    };
  </script>
</html>

```

Now I can focus on the logic parts of the code now that I know the basics work.

After some debugging and refactoring, this is what I have. I've added an onStateChange function to the YouTube player that will make the site visible after the video has ended. debugging was easier at this point because I knew the code to load the player was working and could focus on the new parts.

```html
<html>
    <style>
      .content {display:none;}
    </style>
  <body>
    <div class="content">
      This is my website that requires you to watch through a video before accessing
    </div>
    <div id="no-access-view">
      <h4>Please watch this video before entering this site</h4>
      <div id="info-video"></div>
    </div>
  </body>
  <script>
     // This loads the YouTube iframe api
    const tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    // This function needs to be global to be consumed by the iframe_api
    function onYouTubeIframeAPIReady() {
      let player;
      function onStateChange(event){
        if(event.data == YT.PlayerState.ENDED){
          const noAccessMessage =  document.querySelector('#no-access-view');
          noAccessMessage.style.display = "none";
          const content = document.querySelector('.content');
          content.style.display = "inline";
          if (player) player.destroy()
        }
      }
      player = new YT.Player('info-video', {
        height: '390',
        width: '640',
        videoId: '<your video id>',
        events: {
          onStateChange
        }
      });
    }
  </script>
</html>
```

My last step is to add the logic around noting that the video has been played in a previous session to skip showing it on reload. Think I'll use [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) for that and put the code for displaying the site in a function to DRY things out.

```html
<html>
<style>
  .content {
    display: none;
  }
</style>

<body>
  <div class="content">
    This is my website that requires you to watch through a video before accessing
  </div>
  <div id="no-access-view">
    <h4>Please watch this video before entering this site</h4>
    <div id="info-video"></div>
  </div>
</body>
<script>
  function openUpSite() {
    const noAccessMessage = document.querySelector('#no-access-view');
    noAccessMessage.style.display = "none";
    const content = document.querySelector('.content');
    content.style.display = "inline";
  }
  if (localStorage.getItem('accessVideoPlayed')) {
    openUpSite();
  } else {
    // This loads the youtube iframe api
    const tag = document.createElement('script');
    tag.src = "https://www.youtube.com/iframe_api";
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  }
  // This function needs to be global to be consumed by the iframe_api
  function onYouTubeIframeAPIReady() {
    let player;
    function onStateChange(event) {
      if (event.data == YT.PlayerState.ENDED) {
        localStorage.setItem('accessVideoPlayed', 'true');
        openUpSite();
        if (player) player.destroy()
      }
    }
    player = new YT.Player('info-video', {
      height: '390',
      width: '640',
      videoId: '<your video id>',
      events: {
        onStateChange
      }
    });
  }
</script>

</html>
```

And there you have it. The user has to watch the video and the next time they visit the site, they don't have to watch it. I worked through my example as I wrote this. You can see the code working at [https://benpatterson.io/force-a-video](https://benpatterson.io/force-a-video.html).

This is not meant to be an implementation of anything secure.  It's more or less a velvet rope for honest people/average user.