Shaka Player Manifest Upgrade Guide (v3.0)

v3.0 introduced many changes to the shaka.extern.Manifest structure. This is a detailed guide for upgrading ManifestParser plugins or applications using Player.getManifest() to extract information about content. Any application with a custom ManifestParser or which uses Player.getManifest() MUST be upgraded for compatibility with v3.0.

Registration

In addition to registering new parsers or overriding existing ones with shaka.media.ManifestParser.registerParserByMime, you can now also unregister them with shaka.media.ManifestParser.unregisterParserByMime.

Manifest and Period-flattening

The shaka.extern.Manifest structure has changed. The concept of "Periods", based closely on DASH, has been removed. Many individual parts of the manifest structure have changed to make this possible. In this section, we will give a high-level view of the changes. In the sections below, you will find details on the changes to the various parts of the Manifest structure and the ManifestParser plugin interfaces.

If you have a custom ManifestParser plugin that parses multi-Period DASH content, you will need to consider how <Representation>s connect across <Period>s. Each shaka.extern.Stream now comprises the entire presentation, and shaka.media.SegmentReference timestamps are no longer Period-relative.

If you have a custom ManifestParser plugin that parses other formats, or if your application reads the Manifest structure, you will just need to move a few fields and make some relatively minor API updates as detailed below.

In v2.5, Manifest objects contained Periods, and each Period contained fields named variants and textStreams. These fields are now part of the top-level Manifest structure, and the Stream objects that make them up now span all DASH Periods. ManifestParser plugins MUST be updated to move these fields, and applications which read the Manifest object MUST be updated to look for them in the new location.

Filtering

The shaka.extern.ManifestParser.PlayerInterface structure has changed. Instead of filterNewPeriod and filterAllPeriods, there is now a single callback: shaka.extern.ManifestParser.PlayerInterface#filter. This should be invoked any time new text streams or variants are added to the manifest. ManifestParser plugins MUST be updated to use the new callback. Keep in mind that the new filter method is asynchronous, and thus it should probably be used in conjunction with .then() or await.

// v2.5:
// Call this after the initial parsing
this.playerInterface_.filterAllPeriods(periods);

// Call this after any new periods are added
this.playerInterface_.filterNewPeriod(someNewPeriod);

// v3.0:
// Call this after the initial parsing or after any new streams are added
await this.playerInterface_.filter(manifest);

Embedded Captions

There is another new method inside the player interface, makeTextStreamsForClosedCaptions. This method must be called on the manifest initially, and after each time new content is added, in order for embedded captions, such as CEA 608 captions, to work.

this.playerInterface_.makeTextStreamsForClosedCaptions(manifest);

PresentationTimeline

The API for shaka.media.PresentationTimeline has changed. ManifestParser plugins that use these methods MUST be updated:

// v2.4:
timeline.notifySegments(segmentList, /* periodStart= */ 0);

// v2.5:
timeline.notifySegments(segmentList, /* isFirstPeriod= */ true);

// v3.0:
timeline.notifySegments(segmentList);

DrmInfo

The shaka.extern.DrmInfo structure has changed. The keyIds field is now a Set of strings instead of an Array. ManifestParser plugins MUST be updated to return the correct type, and applications which read this part of the Manifest structure MUST be updated to interact with Set instead of an Array.

Variant

The shaka.extern.Variant structure has changed. The drmInfos field has moved to shaka.extern.Stream. ManifestParser plugins MUST be updated to output this field on the correct object.

Stream

The shaka.extern.Stream structure has changed.

The drmInfos field that used to appear in shaka.extern.Variant has been moved here. ManifestParser plugins MUST be updated to output this field on the correct object.

The findSegmentPosition and getSegmentReference callbacks have been replaced with segmentIndex, an instance of shaka.media.SegmentIndex. ManifestParser plugins MUST be updated to output a segmentIndex instead of these callbacks. (In many cases, you may have been basing these callbacks on a SegmentIndex object's find and get methods already.)

The initSegmentReference and presentationTimeOffset fields are no longer part of Stream. This information has moved to shaka.media.SegmentReference. ManifestParser plugins MUST be updated accordingly. See the section on SegmentReference for details.

The keyId field, a string, has been replaced with the keyIds field, a Set of strings. ManifestParser plugins MUST be updated to output the new field.

InitSegmentReference

The shaka.media.InitSegmentReference API has changed. The createUris methods has been removed, as it was redundant. Applications with custom ManifestParser plugins and applications which read InitSegmentReferences MUST be updated to use getUris instead.

SegmentReference

The shaka.media.SegmentReference API has changed. Applications with custom ManifestParser plugins and applications which read SegmentReferences MUST be updated to account for all changes below.

The createUris methods has also been removed, as it was redundant. Use getUris instead.

SegmentReference objects no longer have any internal concept of "position". The getPosition method has been removed, and the constructor no longer takes a position parameter.

SegmentReference timestamps are no longer relative to the Period. Instead, they are presentation timestamps. For example, if the Period starts at 100 and the segment begins at time 5 within that Period, startTime will now be 105.

SegmentReference objects now contain their InitSegmentReference. This allows the segments within a Stream to change init segments mid-stream, which is used both for multi-Period DASH and for HLS discontinuities. If two segments have the same initialization segment, their SegmentReferences MUST have the same InitSegmentReference object at the same memory address. StreamingEngine will compare InitSegmentReference objects to determine when a new init segment must be fetched, and this will NOT be a deep comparison of the contents of the object.

SegmentReference objects now contain an append window. This is used to trim content in MediaSource. For multi-Period content, this is the start and end of the Period from which the segment comes. For other content, the start is usually 0 and the end is usually Infinity.

SegmentReference objects now contain a timestamp offset. MediaSource adds this value to the timestamps in the media segment when it is parsed by the browser. This should be the value which aligns the media timestamps (in the segment) to the presentation timestamps (in the SegmentReference). This is related to the presentationTimeOffset from v2.5 and earlier. timestampOffset should be the period start time minus the presentationTimeOffset. (In v2.5, this is how MediaSource's timestampOffset field was calculated. Now, we pass the timestampOffset field from the SegmentReference directly to MediaSource.)

// v2.5
const period = {
  startTime: 100,
  /* ... */
};
const initSegmentReference = /* ... */;
const stream = {
  /* ... */
  initSegmentReference,
  presentationTimeOffset: 20,
};
const ref = new shaka.media.SegmentReference(
    /* position= */ 0,
    /* startTime= */ 0,
    /* endTime= */ 10,
    /* uris= */ () => [uri],
    /* startByte= */ 0,
    /* endByte= */ null);

// v3.0
const ref = new shaka.media.SegmentReference(
    /* startTime= */ 100,  // <-- period start 100 + period-relative timestamp 0
    /* endTime= */ 110,
    /* uris= */ () => [uri],
    /* startByte= */ 0,
    /* endByte= */ null,
    initSegmentReference,
    /* timestampOffset= */ 80,  // <-- period start 100 - PTO 20
    /* appendWindowStart= */ 100,  // <-- period start
    /* appendWindowEnd= */ Infinity);  // <-- for the last period in live stream

SegmentIndex

The shaka.media.SegmentIndex API has changed. Applications with custom ManifestParser plugins and applications which read SegmentIndexes MUST be updated to account for all changes below.

The asynchronous destroy method has been replaced with the synchronous release method. Applications which destroy SegmentIndex objects SHOULD be updated to call release instead. Backward compatibility will be removed in v4.0.

The find method still returns a position which can be passed to get, but this position is no longer part of the underlying SegmentReference object. (See changes to SegmentReference in the section above.) The value returned from find is now abstract. This should be completely compatible with v2.5, but the value can no longer be tied to a SegmentReference.

The merge method has been simplified. It was already documented to only support extending the reference array without replacing anything or interleaving new references into the old ones. Now it is actually a true statement about the implementation. If your custom ManifestParser plugin was relying on the old merge behavior rather than its documentation, you MUST update your plugin.

The evict method now takes a presentation timestamp. See related changes in the SegmentReference section above.

The fit method now takes a window start and end time instead of a period duration. See related changes in the SegmentReference section above.

The updateEvery method has been added. This allows ManifestParser plugins to schedule callbacks to calculate additional SegmentReferences to be appended to the reference array. This is useful, for example, in generating references for <SegmentTemplate> content in DASH.

The static forSingleSegment method has been added. It will generate a SegmentIndex for a single segment based on a start time, duration, and URIs. This is useful for ManifestParser plugins creating non-segmented text Streams.

Finally, SegmentIndex now implements the Iterable protocol. This means applications can now iterate through the index or convert it to an Array. For example:

for (const reference of stream.segmentIndex) {
  // ...
}

// OR

const listOfReferences = Array.from(stream.segmentIndex);

// OR

const firstReference = Array.from(stream.segmentIndex)[0];

This could be useful to applications which read the contents of a Manifest.