Written by
Amit Singh
12 minutes

Key Takeaways

  • Adding features like auto-pause when switching tabs ensures seamless learning and prevents interruptions, making video-based education more effective.
  • Using Shaka Player allows educators to store videos directly on the platform, eliminating dependency on third-party tools while enabling advanced playback customizations.
  • Implementing features like smart playback controls and watch-time tracking creates a smoother, more engaging experience for both students and administrators.

Introduction

For visual learning, videos are a game-changer. They bring concepts to life, making understanding and remembering information easier. Imagine how awesome it would be to have your own video player, where teachers can upload their videos and students can easily watch them. This not only enhances the learning experience but also makes it more engaging and interactive.

What is it lacking?

Video learning is great, but we can make it even better with a few customizations. Currently, when users switch tabs, the video keeps playing in the background. This can be inconvenient, as it defeats the purpose of video learning and users might forget where they left off, having to manually find their place again.

One way to solve this problem is to automatically pause the video when the user switches tabs and resume playing when they return to the video tab. This way, users can seamlessly pick up right where they left off without any hassle.

In EdisonOS, we want to store videos made by admins and display them within our platform to reduce dependency on third-party services. While talking about how we can improve the learning experience for students, our customers have informed us that the current video player in EdisonOS lacks the capability to pause and play based on user activity, such as switching browser tabs, and to track watch time. Implementing these features will enhance the overall learning experience and ensure seamless video playback for users.

Why Shaka Player?

We have chosen Shaka Player for this purpose. While there are many other tools available, we selected Shaka Player because it is free and offers excellent support. Now that we are building our own video player using Shaka Player, we can implement the customizations that users need and have full control over its features. One such customization is automatically pausing the video when the user switches tabs. This enhancement will improve the user experience by ensuring that video playback is consistent with user activity.

In this blog, we’ll explore why advanced video players like Shaka Player are essential for creating a seamless and secure video learning environment. We’ll look at how they work, their key features, and why they’re the perfect tool for educators and learners alike.

Why Do We Need Smart Playback Control?

One key feature to consider is smart playback control. When users switch tabs, videos should automatically stop playing. This prevents videos from playing in the background, which can be annoying. Without this feature, users have to switch back to the tab to pause the video, which disrupts their workflow. By stopping the video automatically, we ensure a smoother, more user-friendly experience.

How I started execution?

Now, let’s suppose you are a student watching a video on trigonometry. Suddenly, a term called sinθ appears on the screen, and you become interested in it. The video does not go in-depth on this topic. Naturally, you would open a new tab to search for sinθ and its use cases to learn more about it. In this case, it would be better if the video automatically paused when you switched tabs and resumed playing when you returned. This way, you can focus solely on learning and gain a better, more in-depth understanding of trigonometry. 😎

Implementation

Technologies You Need to Know Before Implementing VideoJS

  • Next.js / React
  • Shaka Player (You can learn Shaka Player along the way)

First, we need to set up Shaka Player and ensure the video is running before adding any features.

Step 1: Set Up React App and Install Shaka Player

Create a new React app:

Copy code

1npx create-react-app my-video-app
2cd my-video-app

Install Shaka Player:

Copy code

1npm install shaka-player

Now that the React app is up and running, we need to clean it up.

Remove unnecessary files to keep the project clean. Delete the following files from the src directory:

  • App.test.js
  • logo.svg
  • reportWebVitals.js
  • setupTests.js

Copy code

1import React from 'react';
2import ReactDOM from 'react-dom';
3import './index.css';
4import App from './App';
5
6ReactDOM.render(
7  <React.StrictMode>
8    <App />
9  </React.StrictMode>,
10  document.getElementById('root')
11);

Step 2: Set Up the Video Player Component

Start by importing the necessary modules and setting up the component structure. Use useRef to create references for the video element and the Shaka Player instance.

javascript

1import React, { useEffect, useRef, useCallback } from 'react';
2import shaka from 'shaka-player';
3
4const Player = ({ url }) => {
5  const videoRef = useRef(null);
6  const playerRef = useRef(null);
7
8  // Error handling callbacks
9  const onError = useCallback((error) => {
10    console.error('Error code', error.code, 'object', error);
11  }, []);
12
13  const onErrorEvent = useCallback((event) => {
14    onError(event.detail);
15  }, [onError]);
16
17  // Initialization and cleaning up the player
18  useEffect(() => {
19    shaka.polyfill.installAll();
20
21    if (shaka.Player.isBrowserSupported()) {
22      playerRef.current = new shaka.Player(videoRef.current);
23      playerRef.current.addEventListener('error', onErrorEvent);
24    } else {
25      console.error('Browser not supported!');
26    }
27
28    return () => {
29      if (playerRef.current) {
30        playerRef.current.destroy();
31      }
32    };
33  }, [onErrorEvent]);
34
35  // Loading the video
36  useEffect(() => {
37    if (url !== '' && playerRef.current) {
38      playerRef.current.load(url)
39        .then(() => {
40          console.log('The video has now been loaded! ' + url);
41        })
42        .catch(onError);
43    }
44  }, [url, onError]);
45
46  // Rendering the video element
47  return (
48    <div>
49      <h2>Player</h2>
50      <video
51        ref={videoRef}
52        width="640"
53        poster="//shaka-player-demo.appspot.com/assets/poster.jpg"
54        controls
55        autoPlay
56      ></video>
57    </div>
58  );
59};
60
61export default Player;

Step 3: Implement Smart Playback Control

To enhance the user experience, you can add a feature that automatically pauses the video when the user switches tabs. This can prevent the video from playing in the background.

1useEffect(() => {
2  const handleVisibilityChange = () => {
3    if(document.hidden && !videoRef.current.paused) {
4      videoRef.current.pause();
5    }
6  };
7  
8 document.addEventListener('visibilitychange', handleVisibilityChange);
9 return () => {
10  document.removeEventListener('visibilitychange', handleVisibilityChange);
11  };
12}, []);

Things to consider:

Now that we are implementing this pausing functionality, we need to consider all possible scenarios to make it more user-friendly. Otherwise, it might feel like we are taking control away from the user just to implement a feature we like, which we don’t want to do. Therefore, we will take all cases into account and fix any bugs that might arise.

Case 1:  Manually pausing video

When the user manually pauses the video and switches to another tab, the video will automatically start playing when the user returns.

To fix the issue of the video automatically starting to play when the user returns to the tab, we can introduce an additional state variable to keep track of whether the user manually paused the video.

1const [isManuallyPaused, setIsManuallyPaused] = useState(false)
2const handleVisibilityChange = () => { 
3  if (document.visibilityState === 'visible') {
4	if (!isManuallyPaused) { 
5	  videoRef.current.play(); 
6		setIsPlaying(true); 
7	}
8} else { 
9	videoRef.current.pause(); 
10    } 
11};

Also need to change and add dependency in useEffect

1useEffect(() => {
2 document.addEventListener('visibilitychange', handleVisibilityChange); 
3  return () => {
4  document.removeEventListener('visibilitychange', handleVisibilityChange);		
5   };
6  }, 
7[]);

Case 2: First loading page

When we first load the video and then switch page and come back to original page the video will start playing.

So we will need one more additional state for this and instead of creating a new separate one for this we will combine it with previous state variable as they both are related to each other

Code:

1const [videoState, setVideoState] = useState({
2    userPaused: false,
3    isFirstPlay: false,
4});
5
6useEffect(() => {
7    const initPlayer = async () => {
8      const shakaPlayer = new shaka.Player(videoRef.current);
9      setPlayer(shakaPlayer);
10
11      shakaPlayer.configure({
12        streaming: {
13          bufferingGoal: 20,
14          alwaysStreamText: true,
15        },
16      });
17      videoRef.current.addEventListener("pause", () => {
18        if (!document.hidden) {
19          setVideoState((prevState) => ({
20            ...prevState,
21            userPaused: true,
22          }));
23        }
24      });
25      videoRef.current.addEventListener("play", () => {
26        setVideoState({
27          userPaused: false,
28          isFirstPlay: true,
29        });
30      });
31
32      shakaPlayer.addEventListener("error", (event) => {
33        console.error("Error in Shaka Player:", event.detail);
34      });
35
36
37      return () => {
38        shakaPlayer.destroy();
39        if (ui) {
40          ui.destroy();
41          setUi(null);
42        }
43      };
44    };
45
46    initPlayer();
47
48    // Clean up on unmount
49    return () => {
50      if (player) {
51        player.destroy();
52        setPlayer(null);
53      }
54      if (ui) {
55        ui.destroy();
56        setUi(null);
57      }
58    };
59  }, [chromeLess]);

Two things to consider here:

Using Cleanup in useEffect: Including cleanup functions in useEffect is a good practice to prevent memory leaks and ensure that any event listeners or subscriptions are properly removed when the component unmounts or dependencies change.

Avoiding Unnecessary Dependencies: Be cautious about adding unnecessary dependencies to the dependency array of useEffect, as this can lead to unexpected behaviors. For example, previously, I added videoState.isFirstPlay as a dependency, which caused issues and took me two days to figure out the actual problem. It's important to include only the necessary dependencies to avoid such problems.

And we will add use effect to handle visibility change.

1useEffect(() => {
2    const handleVisibilityChange = () => {
3      if (isFirstPlay && playerRef.current && isUser) {
4        if (document.hidden) {
5          playerRef.current.pause();
6        } else if (!userPaused) {
7          playerRef.current.play();
8        }
9      }
10    };
11    document.addEventListener("visibilitychange", handleVisibilityChange);
12    return () => {
13      document.removeEventListener("visibilitychange",  handleVisibilityChange);
14    };
15  }, [playerRef, userPaused, isUser, isFirstPlay]);

That's it! Your video is now up and running with the cool new feature you added.

Next Steps

Measuring user watch time can provide administrators with insights into which video sections students find engaging or challenging. This data helps improve content and engagement strategies.

Table of Content