-
Notifications
You must be signed in to change notification settings - Fork 2
Migrating from v1 to v2
Releasing a new major version took me very long time so a very well explained migration guide deserves to be written.
I'm very happy presenting you the v2 of sub37. This release focused mostly on the introduction to the support of TTML as an
adapter and adding support for its demands in the protocols, other than improving what was already there.
If you are a consumer of the library, there aren't very much breaking changes for you.
A new .connect() method has been added to @sub37/caption-renderer in order to made automatic the linking of events listeners. This doesn't deprecate the current usage of event listeners but make their handling easier and automatic.
In fact, .connect() includes support to two new events added in this version: USER_PAUSE and USER_RESUME. They are mostly needed for animation running synchronization with play and pause of the video tag.
- server.addEventListener("cuestart", (cues) => { ... });
- server.addEventListener("cuestop", () => { ... });
- server.addEventListener("cueerror", () => { ... });
+
+ const presenter = document.getElementsByTagName("captions-renderer")[0];
+
+ presenter.connect(server, (parseError) => {
+ console.warn("Error happened:", parseError);
+ });Adapter protocol changed a lot. I warned you.
In v1, BaseAdapter was imported from @sub37/server while now it is imported from a new package you'll need to use: @sub37/adapter-utils:
- import { BaseAdapter } from "@sub37/server";
+ import { BaseAdapter } from "@sub37/adapter-utils/BaseAdapter";This separates the adapter contract from the server implementation, so adapters no longer need to take a dependency on the server package.
This is the most significant change. In v1, parse was a regular method that returned a ParseResult. It was a container with a data array and an errors array, both populated upfront:
// v1
public override parse(content: unknown): BaseAdapter.ParseResult {
const failures: BaseAdapter.ParseError[] = [];
let result: CueNode[];
try {
result = convertToCueNodes(content);
} catch (err) {
return BaseAdapter.ParseResult(undefined, [
{ error: new CriticalError(), failedChunk: "...", isCritical: true },
]);
}
return BaseAdapter.ParseResult(result, failures);
}In v2, parse is a generator method (function*) that yields batches of CueNode[] or ParseError[] incrementally, instead of returning everything at once. This allows the server to implement parsing strategies, like not making the main thread busy all the time to parse the whole track.
// v2
import type { BaseAdapter, ParseGenerator } from "@sub37/adapter-utils/BaseAdapter";
import { MissingContentError } from "@sub37/adapter-utils/MissingContentError";
import { CueNode } from "@sub37/adapter-utils/CueNode.js";
...
public *parse(content: unknown): ParseGenerator {
if (!content) {
return [{
error: new MissingContentError(),
isCritical: true,
failedChunk: ""
}];
}
while (/* there is content to process */) {
const nextCues: CueNode[] = parseNextBatch(content);
if (nextCues.length) {
yield nextCues;
}
}
}Therefore BaseAdapter.ParseResult (the factory function) and the related static namespace on BaseAdapter no longer exist. There is no equivalent to import — errors and cues are yielded directly as plain arrays matching the ParseError[] or CueNode[] types.
ParseError interface is still the same.
Key differences:
| v1 | v2 | |
|---|---|---|
| Returns |
ParseResult (all at once) |
Yields CueNode[] or ParseError[] in batches |
| Critical error | Return early with isCritical: true error |
return (not yield) a ParseError[] with isCritical: true
|
| Non-critical error | Accumulate in errors array, return at end |
yield a ParseError[] alongside normal processing |
| Import source |
BaseAdapter.ParseResult, BaseAdapter.ParseError from @sub37/server
|
ParseGenerator, ParseError from @sub37/adapter-utils/BaseAdapter
|
In v1, failing to override both the static and instance toString methods would cause the adapter to be rejected entirely. In v2 this is no longer enforced: BaseAdapter provides a default implementation that reads this.name / this.constructor.name. Overriding it is still recommended to guard against minification renaming your class to a single letter in production builds.
Entities.Style was removed / deprecated in v1 and has now been reintroduced in v2, split into two distinct types to give the renderer the scope information it needs:
-
LocalStyleEntity(Type.LOCAL_STYLE) — styles scoped to the inline span -
LineStyleEntity(Type.LINE_STYLE) — styles scoped to the line container (the<p>-equivalent)
Use createLocalStyleEntity and createLineStyleEntity from @sub37/adapter-utils to produce them.
The setStyles method on tag entities no longer exists. Styles are now expressed through dedicated LocalStyleEntity or LineStyleEntity as per above example.
In v1, each entity carried offset and length fields indicating which character range of the cue's text it applied to. These have been removed entirely.
Adapters are now responsible for emitting entities in the order they should be applied. The renderer will no longer re-order them. This means the entity list on a CueNode is treated as an ordered sequence, and the adapter must ensure that order reflects the intended rendering — for example, opening tags before closing tags, styles before the elements they affect.
As it happened in v1, entities list order determines how the CueNode is rendered in regions. Two different orders of entities may lead to very different outputs.
Region lost the possibility to specify a number width or height. It now only accepts strings, which must have units.
Generally speaking, you can now follow Guidelines and rules (v2), a document containing Guidelines and rules for the v2.
If you have any feedback, question or whatever about docs, open an issue and you'll be answered as soon as possible!
Thank you for using sub37!