@sub37/server
v1.1.0
Published
Server component for subtitles
Downloads
1
Maintainers
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:
- Which formats of subtitles are you going to support
- Where to get and how to get the tracks you want to show
- Need to know if your raw tracks need some kind of conversion
- 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
. |