Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support multi-period live DASH with changing periods #144

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,19 @@ const manifestUri = 'https://example.com/dash.xml';
const res = await fetch(manifestUri);
const manifest = await res.text();

var parsedManifest = mpdParser.parse(manifest, { manifestUri });
const { manifest: parsedManifest } = mpdParser.parse(manifest, { manifestUri });
```

If dealing with a live stream, then on subsequent calls to parse, the last parsed manifest
object should be provided as an option to `parse` using the `lastMpd` option:

```js
const parsedManifest = mpdParser.parse(manifest, { manifestUri, lastMpd });
```

### Parsed Output

The parser ouputs a plain javascript object with the following structure:
The parser ouputs a parsed manifest as a plain javascript object with the following structure:

```js
Manifest {
Expand All @@ -61,6 +68,8 @@ Manifest {
Manifest
}
],
playlistToExclude: [],
mediaGroupPlaylistsToExclude: []
mediaGroups: {
AUDIO: {
'GROUP-ID': {
Expand Down Expand Up @@ -113,6 +122,12 @@ Manifest {
}
```

For live DASH playlists where a `lastMpd` object was provided, the returned manifest object
may contain `playlistsToExclude` and `mediaGroupPlaylistsToExclude`. These are arrays
containing playlists that were found in the `lastMpd` (if provided) but could not be
matched in the latest MPD. To continue playback uninterrupted, they should be excluded by
the playback engine.

## Including the Parser

To include mpd-parser on your website or web application, use any of the following methods.
Expand All @@ -124,8 +139,8 @@ This is the simplest case. Get the script in whatever way you prefer and include
```html
<script src="//path/to/mpd-parser.min.js"></script>
<script>
var mpdParser = window['mpd-parser'];
var parsedManifest = mpdParser.parse(manifest, manifestUrl);
const mpdParser = window['mpd-parser'];
const parsedManifest = mpdParser.parse(manifest, { manifestUri });
</script>
```

Expand All @@ -134,16 +149,15 @@ This is the simplest case. Get the script in whatever way you prefer and include
When using with Browserify, install mpd-parser via npm and `require` the parser as you would any other module.

```js
var mpdParser = require('mpd-parser');

var parsedManifest = mpdParser.parse(manifest, manifestUrl);
const mpdParser = require('mpd-parser');
const parsedManifest = mpdParser.parse(manifest, { manifestUri });
```

With ES6:
```js
import { parse } from 'mpd-parser';

const parsedManifest = parse(manifest, manifestUrl);
const parsedManifest = parse(manifest, { manifestUri });
```

### RequireJS/AMD
Expand All @@ -152,7 +166,7 @@ When using with RequireJS (or another AMD library), get the script in whatever w

```js
require(['mpd-parser'], function(mpdParser) {
var parsedManifest = mpdParser.parse(manifest, manifestUrl);
const parsedManifest = mpdParser.parse(manifest, { manifestUri });
});
```

Expand Down
22 changes: 20 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,33 @@ import { toPlaylists } from './toPlaylists';
import { inheritAttributes } from './inheritAttributes';
import { stringToMpdXml } from './stringToMpdXml';
import { parseUTCTimingScheme } from './parseUTCTimingScheme';
import {addSidxSegmentsToPlaylist} from './segment/segmentBase.js';
import { addSidxSegmentsToPlaylist } from './segment/segmentBase.js';

const VERSION = version;

/*
* Given a DASH manifest string and options, parses the DASH manifest into a manifest
* object.
*
* For live DASH manifests, if `lastMpd` is provided in options, then the newly parsed
* DASH manifest will have its media and discontinuity sequence values updated to reflect
* its position relative to the prior manifest.
*
* @param {string} manifestString - the DASH manifest as a string
* @param {options} [options] - any options
*
* @return {Object} the manifest object
*/
const parse = (manifestString, options = {}) => {
const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
const playlists = toPlaylists(parsedManifestInfo.representationInfo);

return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping);
return toM3u8({
dashPlaylists: playlists,
locations: parsedManifestInfo.locations,
sidxMapping: options.sidxMapping,
lastMpd: options.lastMpd
});
};

/**
Expand Down
18 changes: 14 additions & 4 deletions src/inheritAttributes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import window from 'global/window';
import { flatten } from './utils/list';
import { merge } from './utils/object';
import { findChildren, getContent } from './utils/xml';
Expand Down Expand Up @@ -386,9 +385,20 @@ export const toRepresentations =
*/
export const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
const parsedPeriodId = parseInt(period.attributes.id, 10);
// fallback to mapping index if Period@id is not a number
const periodIndex = window.isNaN(parsedPeriodId) ? index : parsedPeriodId;
// periodIndex used to always use Period@id, and was used as the period's timeline (the
// discontinuity sequence for the manifest). This helped address dynamic playlists, as
// Period@id was often a number, and it often increased. However, the DASH specification
// states that Period@id is a string, and says nothing about increasing values. This led
// to problems with dynamic playlists where the Period@id would not be a number and
// would not increase for each added period. In addition, when it was a number, it
// potentially increased dramatically, making the manifest object harder to understand.
//
// Now, the index of the period is used, and for overlapping values on refreshes, the
// playlist merging logic should handle any increase. This should handle all the
// necessary cases we used to support, but may be problematic when it comes time to
// handle multiperiod live playlists with SIDX segments (using SegmentBase), as the
// merging logic can't yet handle that case. This should be a TODO for the future.
const periodIndex = index;
const periodAttributes = merge(mpdAttributes, {
periodIndex,
periodStart: period.attributes.start
Expand Down
Loading