npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@eyevinn/media-event-filter

v3.4.1

Published

HTML5 media event filter

Downloads

5,139

Readme

Media Event Filter

A dependecy free, tiny package for producing a sane default interpretation of playback events from a HTMLMediaElement.

Why

The HTML5 video element does not produce a consistent set of events that correspond to the updates required by trackers and user interfaces, like loaded, buffering or seeking.

Different player engines built on top of the Media Source Extension all have their own implementations for listening to events.

This filter aims to provide a single source of truth that can be used across player engines and browser native playback.

Adheres to the Eyevinn Player Analytics Specification.

Overview

/** Loading of stream is complete, playback is ready to start */
/** It is now safe to let the user press a play button */
"loaded";
/** A seek has started */
"seeking";
/** A seek has ended  */
"seeked";
/** Buffering has started */
/** Does not trigger during seek, cancelled by a seek */
"buffering";
/** Buffering has ended */
/** Does not trigger during seek */
"buffered";
/** A request to start playing again has been made */
"play";
/** The stream has started playing after loading completed
 *  OR the stream has started playing after the stream was previously paused */
"playing";
/** The stream has been paused */
"pause";
/** The end of the stream was reached */
"ended";
/** A timeupdate event */
"timeupdate";

Limitations

Does not support native HTML5 MSE controls (<video controls>). It works, but event sequence will not strictly follow EPAS in all browsers.

Usage

npm install @eyevinn/media-event-filter

yarn add @eyevinn/media-event-filter

Example of creating and listening to the event filter.

import {
  getMediaEventFilter,
  FilteredMediaEvent,
} from "@eyevinn/media-event-filter";

const videoElement = document.createElement("video");

// Using a switch statement

const mediaEventFilter = getMediaEventFilter({
  mediaElement: videoElement,
  callback: (event: FilteredMediaEvent) => {
    switch (event) {
      case FilteredMediaEvent.LOADED:
        // handle loaded
        break;
      case FilteredMediaEvent.BUFFERING:
        // handle buffering
        break;
      case FilteredMediaEvent.BUFFERED:
      // handle buffered
      // ...
      default:
        break;
    }
  },
});

// Call when done
mediaEventFilter.teardown();
// Object notation can also be used

const handlers = {
  [FilteredMediaEvent.LOADED]: () => {
    /* handle loaded */
  },
  [FilteredMediaEvent.BUFFERING]: () => {
    /* handle buffering */
  },
  [FilteredMediaEvent.BUFFERED]: () => {
    /* handle buffered */
  },
  // ...
};

const mediaEventFilter = getMediaEventFilter({
  mediaElement: videoElement,
  callback: (event: FilteredMediaEvent) => handlers[event]?.(),
});
// It is safe to use destructuring

const { teardown } = getMediaEventFilter({
  /* ... */
});

teardown();

Sample Shaka + React Component

The filter can be used to easily build a React UI on top of Shaka.

A barebones sample integration (see it on codepen):

import { useCallback, useEffect, useMemo, useRef, useState } from "React";
import shaka from "shaka-player";
import {
  FilteredMediaEvent,
  getMediaEventFilter,
} from "@eyevinn/media-event-filter";

const PlayerComponent = ({ videoUrl }) => {
  const videoRef = useRef(null);
  const [playing, setPlaying] = useState(false);
  const [loading, setLoading] = useState(true);
  const [blocked, setBlocked] = useState(false);

  useEffect(() => {
    if (!videoUrl || !videoRef.current) return () => {};

    const eventFilter = getMediaEventFilter({
      mediaElement: videoRef.current,
      // add your state handlers here
      callback: (event) => {
        switch (event) {
          case FilteredMediaEvent.LOADED:
            setLoading(false);

            // attempt autoplay
            videoRef.current.play().catch((e) => {
              // catch autoplay block
              if (e.name.indexOf("NotAllowedError") > -1) {
                setBlocked(true);
              }
            });
            break;
          case FilteredMediaEvent.PLAYING:
            // reset autplay blocked
            setBlocked(false);
            // we're playing!
            setPlaying(true);
            break;
          case FilteredMediaEvent.ENDED:
          case FilteredMediaEvent.PAUSE:
            setPlaying(false);
            break;
          default:
            break;
        }
      },
    });

    const player = new shaka.Player(videoRef.current);

    // Add configuration if needed
    // player.configure()

    player
      // start loading the stream
      .load(videoUrl)
      // catch errors during load
      .catch(console.error);

    // Kill player when unmounted
    return () => {
      player.destroy();
      eventFilter.teardown();
    };
  }, [videoUrl, videoRef]);

  const play = useCallback(() => {
    if (!videoRef.current) return;

    videoRef.current.play();
  }, [videoRef]);

  const pause = useCallback(() => {
    if (!videoRef.current) return;

    videoRef.current.pause();
  }, [videoRef]);

  return (
    <div style={{ width: "720px", margin: "20px auto" }}>
      {loading && <p>Video is Loading</p>}

      {!loading &&
        (playing ? (
          <button type="button" onClick={pause}>
            Pause
          </button>
        ) : (
          <button type="button" onClick={play}>
            Play
          </button>
        ))}

      {blocked && <p>Autoplay blocked, please start playback manually</p>}

      <video ref={videoRef} style={{ width: "100%", height: "auto" }} />
    </div>
  );
};
// Use it:
<PlayerComponent videoUrl="https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd" />

Benefits

Get a single source of truth for playback events regardless of engine (Shaka, Hls.js, DashJS, native) or browser (Chrome, Firefox, Safari).

Pipe the events directly into your UI state management for a reliable source of truth of playback state updates.

The event sequence map directly to popular tracking providers and playback SDKs without further filtering, like Youbora, Comscore, Yospace, or Nielsen. If yours is also supported we welcome PRs to update this list!

Compatible with EPAS.

Events

A description of events and their sequencing.

Example Sequences

LOADED; // playback is ready to start
PLAYING; // playback started
SEEKING; // seek requested
PAUSED; // manual pause
PLAY; // manual play
SEEKED; // seek finished
PLAYING; // video is rolling again
BUFFERING; // unable to continue playing due to missing buffer
BUFFERED; // buffer ended by incoming seek request
SEEKING; // seek requested
SEEKED; // seek finished
PAUSED; // manual pause
PLAY; // manual play
PLAYING; // video is rolling again
ENDED; // video reached the end

loaded

The initial load of the video has completed, and it is ready to start playing.

No other event can trigger before loaded.

seeking

Seeking has started.

If buffering is ongoing buffered will be triggered prior to seeking.

Buffer events can not trigger during a seek.

Can not trigger before loaded.

seeked

Seeking has ended.

Can not trigger before loaded.

Can not trigger without a preceding seeking event.

buffering

Buffering has started.

Can not trigger during a seek.

Can not trigger before loaded.

buffered

Buffering has ended, or was interrupted by a seek.

Can not trigger during a seek.

Can not trigger without a preceding buffering event.

play

Playback has been requested.

Can not trigger before loaded.

Can not trigger if video was not previously paused.

playing

Playback has started.

Can not trigger before loaded.

Can not trigger during seeking.

Can not trigger during buffering.

Can not trigger if video was not previously paused.

A play requested during seeking or buffering will trigger playing after the seek or buffer has finished.

pause

Playback has been paused.

Seeking or buffering do not count as pausing.

Can not trigger before loaded.

timeupdate

A timeupdate event

Can not trigger before loaded.

ended

The player has reached the end of a stream.

To allow restarting the stream after the end of stream has been reached set allowResumeAfterEnded to true.

Contributing

Contributions to improve compatibility with or add support for different engines, tracking solutions, and browsers are welcome.

Git Ways of Working

The project uses feature branches, and a rebase merge strategy.

Make sure you have git pull set to rebase mode:

git config pull.rebase true

Optionally, you can add the --global flag to the above command.

To start working on a new feature: git checkout <feature branch name>.

As the project uses semantic-release to automatically generate release notes based on commits, it is important to follow some rules when committing.

This project uses conventional commits.

Read Using Git with Discipline.

Read How to Write a Commit Message.

A commit should:

  • contain a single change set (smaller commits are better)
  • pass tests, linting, and typescript checks
  • not be broken

Along with enabling time saving automation, it enables extremely powerful debug workflows via git bisect, making bug hunting a matter of minutes instead of days. There are a number of articles out there on the magic of bisecting.

Basic structure of a commit message:

<type>[optional scope]: <title starting with verb in infinitive>

[optional body]

[optional footer]

For automated release notes to work well, try to describe what was added or changed, instead of describing what the code does. Example:

fix(seek): rewrite calculation in seek module // bad, the consumer does not know what issue this fixes

fix(seek): stop player from freezing after seek // good, the consumer understands what is now working again

Develop

To start a dev server: pnpm dev, check demo and public/index.html for details.

Familiarity with the HTML5 video standard, shaka, hlsjs, or other engines is recommended before contributing.

https://www.w3.org/TR/2011/WD-html5-20110113/video.html

https://html.spec.whatwg.org/multipage/media.html

https://html.spec.whatwg.org/multipage/media.html#mediaevents

Tested with shaka 2.5.X - 4.X.X

Tested with native video in Safari

Tested with hls.js

Tested on Safari 13.1+, Firefox, Chrome, Edge

Tested on OSX, Windows, Linux

Releasing

Releases are triggered via a github action that will automatically increment the version and write a changelog based on commits.

Manual releases can be made by running pnpm release.

Support

Join our community on Slack where you can post any questions regarding any of our open source projects. Eyevinn's consulting business can also offer you:

  • Further development of this component
  • Customization and integration of this component into your platform
  • Support and maintenance agreement

Contact [email protected] if you are interested.

About Eyevinn Technology

Eyevinn Technology is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor. As our way to innovate and push the industry forward we develop proof-of-concepts and tools. The things we learn and the code we write we share with the industry in blogs and by open sourcing the code we have written.

Want to know more about Eyevinn and how it is to work here. Contact us at [email protected]!