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

@sub37/server

v1.1.0

Published

Server component for subtitles

Downloads

1

Readme

@sub37/server

This is the core package, where magic happens.

It is a client side library with the role of making common interfaces for adapters and renderers available and keeping the tracks for cues distribution session (when a content starts).

What you need to know

Before start using sub37, you need to know the following aspects:

  1. Which formats of subtitles are you going to support
  2. Where to get and how to get the tracks you want to show
  3. Need to know if your raw tracks need some kind of conversion
  4. Need to know how to connect it with the player states

sub37 doesn't perform any kind of conversion of your tracks before sending them to the adapters. It is suggested to ensure yourself that tracks are, if a text, in UTF-8.

Importing

server exposes a class that will let you associate the renderers you might want to use.

import { Server, Events, Errors } from "@sub37/server";

const server = new Server(...adapters);

Provided adapters will be available as long as a Server instance is available.

Starting a new session

Server works with sessions. Once the instance is created, it will be possible to create and start a distribution session.

A session is described by the tracks that get specified. User will have to specify them with the following properties:

interface TrackRecord {
	lang: string /** arbitrary language id */;
	content: unknown;
	mimeType: `${string}/${string}`;
	active?: boolean;
}

server.createSession([
	{
		lang: "ita",
		content: "WEBVTT ...",
		mimeType: `text/vtt`,
		active: true;
	},
	...
]);

By default no track is considered active if not determined with the active parameter. Not specifying an active track and starting the server, might cause an ActiveTrackMissingError when attempting to start the server.

Once created the session, you can start it. In order to be synchronized with your video element, when starting the session, server requires you to specify a callback that will get invoked at every check (a "tick"; every tick happens once an amount of time - specified by the second parameter - passes). That callback is expected to return the current position.

Tick's default time is 250ms.

server.start(() => {
	return myVideoElement.currentTime;
}, 300);

Attempting to start a server before creating a session, will cause a SessionNotInitializedError to be fired.

Changing server states

Server can be paused by using the .suspend() method and resumed by using the .resume() method.

When server is suspended, the callback passed to start will stop being invoked. Therefore, if a seek happens, subtitles won't be updated.

To prevent this issue, there's a method that can be called only when the server is suspended: .updateTime(). It accepts an amount of time in milliseconds as a parameter.

Using it, the server will perform an update of cues and send them to the renderer.

videoElement.addEventListener("pause", () => {
	if (server.isRunning) {
		server.suspend();
	}
});

videoElement.addEventListener("playing", () => {
	if (!server.isRunning) {
		server.resume();
	}
});

videoElement.addEventListener("seeking", () => {
	server.updateTime(videoElement.currentTime * 1000);
});

videoElement.pause();
videoElement.currentTime = 12; /** seconds **/

Attaching a renderer

In order to distribute the subtitles, the server must be attached to the captions-renderer.

This can be done through .addEventListener. A listener can be removed through .removeEventListener. These two methods accept the event name and the callback to be invoked, just like an EventTarget.

The events available are exposed from the package. Two events are required to be listened to so that everything works fine: Events.CUE_START and Events.CUE_STOP.

  • When CUE_START is fired, a new set of cues to be shown will be sent to the renderer.
  • When CUE_STOP is fired, no cues will be shown and the renderer will remove them all.

A third optional event can be listened to: CUE_ERROR. This is an event that gets fired when some non-critical errors get fired, like a malformed cue. You can do whatever you want to do with it. The callback will contain the error.

import { Events } from "@sub37/server";

const renderer = document.getElementsByTagName("captions-renderer")[0];

server.addEventListener(Events.CUE_START, renderer.setCue);
server.addEventListener(Events.CUE_STOP, renderer.setCue);
server.addEventListener(Events.CUE_ERROR, (/** @type {Error} */ error) => {
	console.log(error);
});

Tracks Management

When a session gets created and raw tracks get provided, they get converted into Tracks. A Track has the following attributes:

  • Can be active or inactive;
  • Knows which is adapter has been used;
  • Can be augmented with more content;

When a track is active, its cues will be actively served.

It might be in your interest to always have an active track to show subtitles instead of captions.

Captions vs Subtitles

Althought captions and subtitles might feel to be the same concept, the edge is really thin:

  • "subtitles" are for people that can hear but cannot understand a language. It is common for films to have a single active track dedicated to translations.

  • "captions" are for people that cannot hear audio and hence require the written words.

Translation tracks are usually included inside caption tracks, but you can also have two or more different tracks to toggle.

Toggling a track

To activate a track, you can retrieve it and activate it with its getter as follows:

const tracks = server.tracks;
/** Language is specified inside the raw track */
const italianTrack = tracks.find((track) => track.lang === "ita");
italianTrack.active = true;

@sub37/server also owns a getter as a shortcut just to retrieve all the tracks languages loaded:

const langs = server.availableLanguages; // e.g. ["ita", "eng", "de"]

Adding content to a track

Raw content can be added to tracks whenever needed, to achieve a live-captioning effect.

Please note that, as per v1.0.0, the only requirement is that each new raw chunk that you might want to add, should be available to be used as a standalone track.

For example, for WebVTT tracks, the sheet is required to start with the uppercase characters sequence "WEBVTT". So, new chunks will require it too.

const italianTrack = server.tracks.find((track) => track.lang === "ita");
italianTrack.addChunk(`
WEBVTT

00:00:05.500 --> 00:00:10.000
Hey mom, look at me, I'm having fun!
`);

Error handling

All sub37 functions are as safe as possible and, when things go wrong, throw explicative errors so that issues can be diagnosed easily. All the exposed errors can be used importing the Errors namespace as shown below.

try {
	...
} catch (err) {
	if (err instance of Errors.ParsingError) {
		/** do something */
	}
}

These are the errors that might get fired. Some might just get logged in console. Others might get fired when attempting to use methods.

| Error class name | When happens | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | ActiveTrackMissingError | When server is started but, of the provided ones, no track has been set as active. | | AdapterNotExtendingPrototypeError | When a provided adapter doesn't respect prototype requirements. | | AdapterNotOverridingSupportedTypesError | When a provided adapter doesn't specify which mimeType it supports. Hence, it cannot be used. | | AdapterNotOverridingToStringError | When a provided adapter doesn't specify it's name. | | AdaptersMissingError | When Server didn't receive any adapter when the instance gets created. | | NoAdaptersFoundError | When none of the provided adapters satisfy the requirements, so none can be used. | | OutOfRangeFrequencyError | When creating a server, the frequency parameter is not a number or is lower than 1 or is falsy | | ParsingError | When an adapter crashed or throw a critical error. It is not possible to go on. Will wrap other errors. | | SessionNotInitializedError | When an operation expects a session to have already been created through createSession, but it wasn't. | | SessionNotStartedError | When an operation expects a session to have already been started through start but it wasn't. | | ServerAlreadyRunningError | When an operation expects a session to be paused before continuing. | | ServerNotRunningError | When an operation expects a session to be running before continuing. | | UncaughtParsingExceptionError | An unknown blocking error happened inside an adapter while attempting to parse. | | UnexpectedDataFormatError | An adapter didn't return the expected format for a cue. The cue gets ignored. | | UnexpectedParsingOutputFormatError  | When an adapter didn't return the expected data structure when it ended the parsing phase. Data cannot be read an it is not possible to proceed. | | UnparsableContentError | The chosen adapter wasn't able to parse the provided content. Something is wrong here, either the adapter support or the track format. | | UnsupportedContentError | When looking for a suitable Adapter, Server wasn't able to find an adapter that matches content's mimetype with adapters supportedTypes. |