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

captions-viewer

v0.9.0

Published

Captions that follow along to the video.

Downloads

4

Readme

<captions-viewer>

A web component to display captions as a video plays. Think Spotify or Apple Music lyrics. Includes automatic scrolling to the caption cue as the video plays, and events when a chapter is clicked.

The component takes a webvtt or srt caption file and renders to a list. Caption parsing uses the faster native browser textTracks for vtt files, or a super small srt parser. Classes are updated depending on if the cue is upcoming, active or passed, and a little JS is used to move the cue into view. Cues are sorted, and larger gaps show a progress bar.

HLS streaming files with captions are supported, but take a few extra steps to send caption cues to the component. Examples are shown below.

The component has been tested with about 3,000 cues (2hr move) for performance.

Also check out the other component that makes it easier to manage starting and ending live streams for viewers https://livestreamwrapper.vercel.app/demo/

In Development

While this remains under 1.0, things are probably going to change.

Basic Usage

Install

npm i captions-viewer
<script type="module">
  import captionsViewer from 'https://cdn.jsdelivr.net/npm/captions-viewer/+esm'
</script>
<script type="module" src="'https://cdn.jsdelivr.net/npm/captions-viewer/+esm"></script>

Quickstart

<video controls src="video.mp4" id="my_player"></video>
<captions-viewer src="caption.vtt" player="#my_player"></caption-viewer>

Guide

Add Caption File

First, provide the path to a caption file the src parameter on the element. This must be an already created vtt or srt captions file. The caption file procesesd by the browser-native caption parser for vtt with a backup lightweight parser for srt or if there is a problem.

This example will display captions, but will not follow the video yet:

<video controls src="video.mp4"></video>

<captions-viewer src="caption.vtt"></caption-viewer>

Link the Video Player and Captions Reader

To make the captions to update when the video is playing, and respond to clicks, the captions component needs to be linked to the video player. Simply use the config setup and pass the video element shown below. Most (if not all) have a property to get the native <video> or <audio> element, but if not there is a way describe later on to manually connect the listeners.

Simple example connecting a native video tag to the component:

Auto Linking to <video> Events

<video id="myVideo" controls src="video.mp4"></video>
<captions-viewer file="caption.vtt"></caption-viewer>

<script>
  const player = document.querySelector('#myVideo');
  const captions = document.querySelector('captions-viewer');
  captions.config({ player: player });
</script>

Manual Linking Events

For more fine-tunned control or if the video/audio element is not available, setup the listeners as shown below:

<video controls src="video.mp4"></video>
<captions-viewer file="caption.vtt"></caption-viewer>

<script>
const captions = document.querySelector('captions-viewer');
const player = document.querySelector('video');

// Regularly update the reader with the player's current time:
player.addEventListener('timeupdate', () => {
  captions.playhead = player.currentTime;
});
// ** note that you could also setup an interval to get a faster response ** //

// When a caption cue is clicked, seek the player.
captions.addEventListener('seek', e => {
  player.currentTime = e.detail.value;
});

// Quickly scroll to the current cue after player seek.
player.addEventListener('seeking', () => {
  captions.debounceScrolling = false;
});
</script>

HLS

HLS videos work the same way as flat mp4 files when there is a separate caption file.

If the captions/subtitles are in the HLS stream, such as during a live stream, caption cues are progressively loaded as each video chunk is downloaded. Simply pass the video element hls.js creates to the reader component:

<video></video>
<captions-viewer file="caption.vtt"></caption-viewer>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
  const captions = document.querySelector('captions-viewer');
  const player = document.querySelector('video');
  const videoSrc = 'https://mysite.com/playlist.m3u8';
  if (Hls.isSupported()) {
    const hls = new Hls();
    hls.loadSource(videoSrc);
    hls.attachMedia(player);
    hls.on(Hls.Events.MANIFEST_PARSED, () => {
      player.addEventListener('loadedmetadata', async () => {;
        captions.config({ player: player });
      });
    });
  } else if (player.canPlayType('application/vnd.apple.mpegurl')) {
    player.src = videoSrc;
    player.addEventListener('play', () => {
      captions.config({ player: player });
    });
  }
</script>

Tag Parameters

Setup

| Name | Default | Description | | - | - | - | | src | null | Location of the vtt/srt file. | | playhead | 0 | The current video/audio player time, in seconds, to keep the player and reader in-sync. (see more below) | | youtube | false | Enable true if the caption vtt track came from YouTube for some special handling. | | textTrack | undefined | Will hold the HTMLTextTrack. The native browser text track object. Pass this along for more custom player implementations. | | captions | undefined | Read-only property of the internal caption cues list. Useful for debugging. | | paused | false | The on/off button. If the reader system is enabled or not. |

Display

| Name | Default | Description | | - | - | - | | singleline | false | True will show all text for a cue on a single line. False will obey the line breaks in the caption file. | | disable | empty | Turn off displaying timecode chapters or text. Use a pipe \| between each option, such as timecode\|chapters |

Adjusting Playback

| Name | Default | Description | | - | - | - | | debounce | 5000 | Control the time between scrolling. Time in ms between the last scroll (user or automatic) | | nudge | 0.5 | Amount in seconds to adjust the cues to trigger sooner. 0.5 means the cue will show 500ms sooner than the timecode. | | spacer | 5 | Time in seconds between cues where a spacer (progress bar) will display |

Simple Theming

| Name | Default | Description | | - | - | - | | height | 300px | Height of the scrolling box. Valid CSS unit. | | color | 360 | The Hue (0-360) of the base color to use. This is put into an hsla color. | | theme | light, dark | Light theme shades the text darker for a whiter background. Dark will lighten text for a darker background. | | stylesheet | true | True/False to enable the default stylesheet. False will remove all styling, default is True. See the guide below. |

Methods

config(options)

| Name | Description | Type | Default | | - | - | - | - | | player | (Required) The video or audio element. | htmlElement | undefined | | lang | Language code that matches the tracks code. | string | en | | refresh | Time in milliseconds to update cues as player plays. | number | player timeUpdate |

Example:

const captions = document.querySelector('captions-viewer');
captions.config({
  player: document.querySelector('video'),
  lang: 'fr',
  refresh: 500,
});

pause()

A toggle to stop/start the component without removing it. All events, scrolling and updating will stop.

<button type="button">Toggle Caption Reading</button>
<script>
document.querySelector('button').addEventListener('click', () => {
  captions.pause();
});
</script>

setTheme(theme)

Calling this method with the name of the theme will immediately change the color scheme. Options are currently only dark for making the text lighter for dark backgrounds, light for dark text on ligher backgrounds, and blank/empty for user's system theme.

captions.setTheme('dark');

updateCues(textTrack.cues)

Live streaming and HLS can push text cue updates as the video plays. To update the reader with new use, this method will append those cues. This method expects a textTrack cue list of all the current cues, plus new cues. It has better performance by only rendering the new cues than pushing updates to the textTrack property as textTrack is a complete refresh and re-render of all the cues.

const tracks = player.textTracks;
const track = tracks[0]; // example, pick the first track.
track.addEventListener('cuechange', (e) => {
  component.updateCues(e.target);
});

Events

| Name | Description | | - | - | | parsed | When the caption file has been parsed. | | error | Fires when an error occurs. Returns the text description of the error. | | seek | Fires when a click/select is made on a caption cue. Returns the start of that cue in seconds. | | cuechange | Fires when cues change. Returns the internal cue object of the single active track, or the last one if there are multiple. undefined if no cue is active. |

No Captions

To display a "no captions" message, add the custom element:

<captions-viewer ...>
  <captions-viewer-empty>
    Your Custom Message
  </captions-viewer-empty>
</caption-viewer>

Complete Examples

For more examples, see the /demo folder.

Show captions for a video using the native caption tracy system, with a dynamic container height, custom color, and only showing timecode and the text. The theme is set for dark, so the background will darker with lighter captions.

<style>
  /* Hides the component until it has loaded */
  captions-viewer:not(:defined) {
    visibility: hidden
  }
  /* set some container styles */
  captions-viewer {
    font-family: arial;
    display: block;
    background: rgba(0,0,0,.2);
  }
</style>

<video controls id="demo_video">
  <source src="movie.mp4" type="video/mp4">
  <!-- Not required for the reader,
      but best for accessibility to keep them here for the actual player. -->
  <track label="English" kind="subtitles" srclang="en" src="subtitle.vtt" />
</video>

<captions-viewer
  src="subtitle.vtt"
  player="#demo_video"
  height="30vw"
  singleline="true"
  color="300"
  theme="dark"
  disable="chapters"
  >
  <captions-viewer-empty></captions-viewer-empty>
</captions-viewer>

<script type="module" src="/dist/captions-viewer.js"></script>

YouTube

YouTube captions are not accessible publicly. One way is to download the vtt option from YouTube in the Creator Studio, put it on your own server. Another is using YouTube's API.

Note when using the YouTube vtt file, one edit may be needed to the first cue. Normally there is a blank line between the timecode and cue code. Each browser handles this slightly differently, so to provide browser parity simply add text in this blank line. Any text will do, and will be removed by the parser.

WEBVTT
Kind: captions
Language: en

00:00:00.000 --> 00:00:02.810 align:start position:0%
[add any text here]
hello<00:00:00.539><c> boys</c><00:00:00.719><c> and</c><00:00:00.750><c> girls</c>

...etc
<div id="player"></div>

<captions-viewer
  src="caption.vtt"
  disable="chapters"
  debounce="1000"
  youtube="true"
  >
  <captions-viewer-empty></captions-viewer-empty>
</captions-viewer>

<script type="module" src="captions-viewer.js"></script>
<script defer src="https://www.youtube.com/iframe_api"></script>
<script>
  const captions = document.querySelector('captions-viewer');

  // From YouTube documentation
  // https://developers.google.com/youtube/iframe_api_reference
  let player;
  function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', {
      height: '390',
      width: '100%',
      videoId: 'abcdefg', // Add YouTube video ID.
      playerVars: {
        'playsinline': 1
      },
      events: {
        'onStateChange': onPlayerStateChange
      }
    });
  }

  // While playing, update the component with the current time.
  // YouTube does not have an event for when time is updating, so we have to poll with an interval.
  // The faster 200ms is to make sure the sub cues are showing on time.  Keep this above 200ms to not tax the client's CPU.
  let pingInterval;
  let playheadUpdate = 200;
  function onPlayerStateChange(event) {
    if (event.data == YT.PlayerState.PLAYING) {
      pingInterval = setInterval(() => {
        captions.playhead = player.getCurrentTime()
      }, playheadUpdate)
    } else {
       // Stop when paused.
      clearInterval(pingInterval);
    }
  }

  // On click, Seek's player to caption location.
  // seekTo is YouTube's method to seek the player.
  component.addEventListener('seek', e => {
    player.seekTo(e.detail.value);
  });
</script>