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:
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
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.
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.
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.
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.
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.
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
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
Top comments (0)