Instagram Like Play/Pause for Videos using React.js
Tags: react, javascript, webdev, css
Published: 2020-07-07 09:36:35

A study states that users browsing the internet have short attention spans.

**Videos** allow users to consume more content, at a faster pace. Users are much more apt to engage with videos than with static text.

Instagram has videos that play when in focus and pause when goes out of focus. I have been working on a similar feature to **play and pause** videos but for the web. 

In this article, I will help you to achieve this functionality and on native and any third-party web video player. 

Here, we will be using a youtube video player. But, it will work with any video player.

*TLDR;* Here is the code sandbox working example:

{% codesandbox 938i4 %}


# Tech used

I will be using Youtube API with React.js to solve the problem.
 
It can also be achieved by other JS libraries or frameworks.

**Youtube API:** 
It helps load Youtube iframe on the DOM. 
The iframe plays a youtube video and Youtube API gives the power to handle multiple video controls.

*Ref:* 
 [https://developers.google.com/youtube/iframe_api_reference]

**React.js:** It's one of the best libraries for frontend development.

*Ref:* [https://reactjs.org/]

                   
                      Enough Talks Lets Code

![Lets Code](https://media.giphy.com/media/PiQejEf31116URju4V/giphy.gif)


#Steps:

###1] Loading Youtube Script on DOM:

I have created a custom hook named `useYoutubeScript` which loads the script in our DOM and gives us a callback when done.

> Please follow the comments in the code.

[![Edit playpauseyoutube](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/playpauseyoutube-938i4?fontsize=14&hidenavigation=1&module=%2Fsrc%2FuseYoutubeScript.js&theme=dark)

```javascript

import { useState, useEffect } from "react";

// Iframe API Link
const YOUTUBE_PLAYER_API = "https://www.youtube.com/iframe_api";

export default function useYoutubeScript(scriptId) {
  // Keeping track of script loaded and error state
  const [state, setState] = useState({
    loaded: false,
    error: false
  });

  useEffect(
    () => {
      // if youtube is already available in window return.

      if (window.YT) {
        return [true, false];
      }

      // Create script tag, add ID and source to it.
      const script = document.createElement("script");
      script.id = scriptId;
      script.src = YOUTUBE_PLAYER_API;

      // Youtube promotes adding the script at the top.
      const firstScriptTag = document.getElementsByTagName("script")[0];
      firstScriptTag.parentNode.insertBefore(script, firstScriptTag);

      /* 
         Youtube API fires 'onYouTubeIframeAPIReady' when API 
         is loaded
      */

      window.onYouTubeIframeAPIReady = () => {
        // fire when script is loaded
        onScriptLoad();
      };

      const onScriptLoad = () => {
        setState({
          loaded: true,
          error: false
        });
      };

      const onScriptError = () => {
        setState({
          loaded: true,
          error: true
        });
      };

      // Listen when script has caused any error
      script.addEventListener("error", onScriptError);

      // Remove event listeners on cleanup
      return () => {
        script.removeEventListener("error", onScriptError);
      };
    },
    [scriptId] // Only re-run effect if script src changes
  );

  return [state.loaded, state.error];
}
```

###2] Embedding Youtube Iframe and Video player on DOM:

For embedding a youtube player we have copy `video ID` from youtube.

After that, we have to pass the video ID to the youtube video player instance.

You can read more about the youtube player parameters https://developers.google.com/youtube/player_parameters

> Please follow the comments in the code.

```javascript
import React, { useRef, useEffect } from "react";
import useYoutubeScript from "./useYoutubeScript";

export default function App() {
  const [loaded, error] = useYoutubeScript("sample-youtube");
  const isPlayerReady = useRef(false);
  const player = useRef(null);
  const isPlayerAlreadySet = player && player.current;
  const hasYoutubeInWindow = typeof window.YT === "object" && window.YT.Player;

  useEffect(() => {
    const playerObject = player.current;
    // destroy player when unmounting
    return () => playerObject && playerObject.destroy && playerObject.destroy();
  }, []);

  useEffect(() => {
    /* This useEffect runs when youtube script is loaded on 
       DOM.
    */
    if (!isPlayerAlreadySet && hasYoutubeInWindow) {
      /* create a Youtube player and attach it to a div with 
         ID, apply player parameters and callback events
      */
      player.current = new window.YT.Player("youtube-iframe-id", {
        videoId: "PvtI_71FrF8",
        width: 400,
        height: 350,
        events: {
          onReady: onPlayerReady
        }
      });
    }
  }, [loaded]);

  if (!loaded || error) {
    // show loading when loaded is false or error is true
    return <div>Loading...</div>;
  }
 
  // this function is fired when player is ready for playing
  const onPlayerReady = () => {
    if (isPlayerReady && !!isPlayerReady.current) {
      return;
    }
    /* 
       It's important to mute the video before playing 
       since the browser does not allow autoplay with 
       sound on
    */
    player.current.mute && player.current.mute();
    // set player ready to true
    isPlayerReady.current = true;
  };

  return (
    <div className="App">
      <div>
        <div id="youtube-iframe-id" />
      </div>
    </div>
  );
}
```

###3] Play and Pause Video based on Visibility:

Gone are the days we used to have scroll listener and complex calculation to detect if the component is in focus or not.

We have a new champ to help us out which is [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). 

In short, it enables you to detect the visibility of an element, i.e. if it’s in the current viewport, and also the relative visibility of two elements in relationship to each other. 

It does not run on the main thread hence does not hamper performance like a scroll listener used to.

Same as before we will be having a custom hook to detect the visibility of the component when a reference of the component is passed as a parameter to the hook.
 
The hook is named `useIntersectionObserver`.

> Nowadays, most browsers support intersection-observer but still, there are some unsupported ones.

![polyfill intersection observer](https://media.giphy.com/media/Lp8kVSwaSU6V9oATDM/giphy.gif)


**No worries we have you covered we have the polyfill for those browser's [https://www.npmjs.com/package/intersection-observer]**

> Please follow the comments in the code.

[![Edit playpauseyoutube](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/playpauseyoutube-938i4?fontsize=14&module=%2Fsrc%2FuseIntersectionObeserver.js&moduleview=1&theme=dark)

```javascript
import { useEffect, useRef, useState } from 'react'

export default ({ root = null, rootMargin = '0px 0px 0px 0px', threshold = 0 }) => {
        // check if it running on client
	const isClient = typeof window === 'object'
	const [entry, updateEntry] = useState({})
	const [node, setNode] = useState(null)
	let observer = null

	if (isClient) {
        /*
           the root prop is of the parent element of the 
           component, if nothing is passed it will be the 
           viewport, the threshold is visibility percentage
        */
		observer = useRef(
			new window.IntersectionObserver(([intersectionEntry]) => updateEntry(intersectionEntry), {
				root,
				rootMargin,
				threshold
			})
		)
	}

	const unObserve = () => {
		const { current: currentObserver } = observer
		currentObserver.disconnect()
	}

	useEffect(() => {
		if (!isClient) {
			return false
		}
		const { current: currentObserver } = observer
		currentObserver.disconnect()

		if (node) currentObserver.observe(node)

		return () => currentObserver.disconnect()
	}, [node])

	return [setNode, entry, unObserve]
}

```

#### Usage

```javascript
const [ref,entry] = useIntersectionObserver({ threshold: 0.8 })
// Should be 80% in the viewport 

if(entry.isIntersecting){
// returns when its equal to or more than 80% in viewport
 playVideo()
} else {
// when less than 80% in viewport
 pauseVideo()
}

<div ref={ref} />
```


## Conclusion:

Creating a feed for the user includes multiple types of multimedia components like Images and Videos, where video auto-playing saves us a client interaction. 

Intersection observer has solved multiple problems like lazy loading components, but video play pause is another really good use case for it.

Play and pause functionality can be achieved on any video player even on the native HTML5 `video` component.

Here is the code sandbox link - 
https://codesandbox.io/s/playpauseyoutube-938i4