Skip to content

Commit

Permalink
Replace find/get callbacks with SegmentIndex
Browse files Browse the repository at this point in the history
This replaces find/get callbacks in Stream with a SegmentIndex.  With
the exception of DASH's SegmentTemplate+duration, all manifests were
already backed by SegmentIndex.  Now, all manifests are backed by
SegmentIndex.  This will simplify Period-flattening, in which all
tracks will be represented by a list of segments, some of which come
from different Periods.

The SegmentIndex in Stream will not be created until
createSegmentIndex is called.  Prior to this change, the find/get
callbacks could be invoked without createSegmentIndex() in some cases
(excluding DASH's SegmentBase), which some lazy tests took advantage
of.  Now that find/get are methods on SegmentIndex, createSegmentIndex
must be called in all cases.  The tests have been updated accordingly.

Making SegmentIndex generation async in all cases exposed some issues
with the parser context being modified in-place between one
Representation and the next.  So the parser now makes a shallow copy
of the context before it is bound into an async callback.

To facilitate updating the SegmentIndex for SegmentTemplate+duration
content, SegmentIndex now has a method to update its list on a timer.
Once per segment duration, the index will be updated to add and remove
SegmentReferences.

The initial expansion of SegmentTemplate+duration will be limited to a
relatively small number of segments, to avoid excessive CPU or memory
consumption.  This defaults to 1000 segments, but is configurable.

Issue shaka-project#1339

Change-Id: I99c007b1096c3b396d04a729750cd7b743cb899d
  • Loading branch information
joeyparrish authored and AnteWall committed Jul 17, 2019
1 parent 285f201 commit 1ada277
Show file tree
Hide file tree
Showing 32 changed files with 614 additions and 435 deletions.
7 changes: 6 additions & 1 deletion demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,12 @@ shakaDemo.Config = class {
.addNumberInput_('Default Presentation Delay',
'manifest.dash.defaultPresentationDelay')
.addBoolInput_('Ignore Min Buffer Time',
'manifest.dash.ignoreMinBufferTime');
'manifest.dash.ignoreMinBufferTime')
.addNumberInput_('Initial Segment Limit',
'manifest.dash.initialSegmentLimit',
/* canBeDecimal = */ false,
/* canBeZero = */ false,
/* canBeUnset = */ true);

this.addRetrySection_('manifest', 'Manifest');
}
Expand Down
53 changes: 27 additions & 26 deletions docs/tutorials/manifest-parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,20 +159,10 @@ This is called first before any other method. This allows an index to be
fetched over the network, if needed. This method should return a Promise that
will resolve when the segment index is ready. This is only ever called once.

#### findSegmentPosition(time:number):(number|null)
#### segmentIndex

This is passed in a time (in seconds) relative to the start of this Period and
should return the position of the segment that contains that time, or null
if it is not found.

*NB: This is independent of segment availability for live streams.*

#### getSegmentReference(position:number):(shaka.media.SegmentReference|null)

This is passed the position (number) of the segment and should return a
`shaka.media.SegmentReference` that is at that index, or null if not found.

*NB: This is independent of segment availability for live streams.*
This is *not* a function, but a {@link shaka.media.SegmentIndex} tracking all
available segments.

#### initSegmentReference

Expand All @@ -184,12 +174,13 @@ contains info about how to fetch the initialization segment. This can be
## shaka.media.SegmentIndex

To help in handling segment references, there is a
{@link shaka.media.SegmentIndex} type. This is given an array of references,
handles merging new segments, and has the required segment functions. All you
need to do is create an array of references and pass it to the constructor. For
updates, simply create a new array of segments and call `merge`. Any existing
segments will be updated and new segments will be added. You can also call
`evict` to remove old references to reduce the memory footprint.
{@link shaka.media.SegmentIndex} type. This is given an array of references.
It handles merging new segments, and expanding the list of segments for live
streams.

To merge updates, simply create a new array of segments and call `merge`. Any
existing segments will be updated and new segments will be added. You can also
call `evict` to remove old references to reduce the memory footprint.

```js
var references = refs.map(function(r, i) {
Expand All @@ -201,11 +192,22 @@ var references = refs.map(function(r, i) {
});

var index = new shaka.media.SegmentIndex(references);
var streamFunctions = {
createSegmentIndex: function() { return Promise.resolve(); },
findSegmentPosition: index.find.bind(index),
getSegmentReference: index.get.bind(index)
};
```

To expand the list of references on a timer, as is done for DASH's
SegmentTemplate, call `index.updateEvery` with a callback that evicts old
references and returns an array of new references.

```js
index.updateEvery(updateIntervalSeconds, () => {
// Evict old references
index.evict(windowStartTime);

// Generate new references to append to the end of the index
const references = [];
// ...
return references;
});
```


Expand Down Expand Up @@ -294,8 +296,7 @@ MyManifestParser.prototype.loadStream_ = function(type) {
return {
id: this.curId_++, // globally unique ID
createSegmentIndex: function() { return Promise.resolve(); },
findSegmentPosition: index.find.bind(index),
getSegmentReference: index.get.bind(index),
segmentIndex: index,
initSegmentReference: init,
presentationTimeOffset: 0, // seconds
mimeType: type == 'video' ?
Expand Down
39 changes: 4 additions & 35 deletions externs/shaka/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,36 +258,12 @@ shaka.extern.Variant;
shaka.extern.CreateSegmentIndexFunction;


/**
* Finds the position of the segment for the given time, in seconds, relative
* to the start of a particular Period; returns null if the position of the
* segment could not be determined. Note: the position of a segment is unique
* only among segments within the same Period.
*
* @typedef {function(number): ?number}
* @exportDoc
*/
shaka.extern.FindSegmentPositionFunction;


/**
* Gets the SegmentReference for the segment at the given position; returns
* null if no such SegmentReference exists. Note: the position of a segment is
* unique only among segments within the same Period.
*
* @typedef {function(number): shaka.media.SegmentReference}
* @exportDoc
*/
shaka.extern.GetSegmentReferenceFunction;


/**
* @typedef {{
* id: number,
* originalId: ?string,
* createSegmentIndex: shaka.extern.CreateSegmentIndexFunction,
* findSegmentPosition: shaka.extern.FindSegmentPositionFunction,
* getSegmentReference: shaka.extern.GetSegmentReferenceFunction,
* segmentIndex: shaka.media.SegmentIndex,
* initSegmentReference: shaka.media.InitSegmentReference,
* presentationTimeOffset: (number|undefined),
* mimeType: string,
Expand Down Expand Up @@ -323,17 +299,10 @@ shaka.extern.GetSegmentReferenceFunction;
* this is the "NAME" attribute.
* @property {shaka.extern.CreateSegmentIndexFunction} createSegmentIndex
* <i>Required.</i> <br>
* Creates the Stream's SegmentIndex (asynchronously).
* @property {shaka.extern.FindSegmentPositionFunction} findSegmentPosition
* <i>Required.</i> <br>
* Finds the position of the segment for the given time. The caller must call
* createSegmentIndex() and wait until the returned Promise resolves before
* calling this function.
* @property {shaka.extern.GetSegmentReferenceFunction} getSegmentReference
* Creates the Stream's segmentIndex (asynchronously).
* @property {shaka.media.SegmentIndex} segmentIndex
* <i>Required.</i> <br>
* Gets the SegmentReference for the segment at the given position. The
* caller must call createSegmentIndex() and wait until the returned Promise
* resolves before calling this function.
* May be null until createSegmentIndex() is complete.
* @property {shaka.media.InitSegmentReference} initSegmentReference
* The Stream's initialization segment metadata, or null if the segments are
* self-initializing.
Expand Down
8 changes: 6 additions & 2 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,8 @@ shaka.extern.DrmConfiguration;
* xlinkFailGracefully: boolean,
* defaultPresentationDelay: number,
* ignoreMinBufferTime: boolean,
* autoCorrectDrift: boolean
* autoCorrectDrift: boolean,
* initialSegmentLimit: number
* }}
*
* @property {shaka.extern.DashContentProtectionCallback} customScheme
Expand Down Expand Up @@ -554,7 +555,10 @@ shaka.extern.DrmConfiguration;
* the segments to determine the live edge. This allows us to play streams
* that have a lot of drift. If false, we can't play content where the
* manifest specifies segments in the future. Defaults to true.
*
* @property {number} initialSegmentLimit
* The maximum number of initial segments to generate for SegmentTemplate with
* fixed-duration segments. This is limited to avoid excessive memory
* consumption with very large timeShiftBufferDepth values.
* @exportDoc
*/
shaka.extern.DashManifestConfiguration;
Expand Down
81 changes: 31 additions & 50 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ goog.require('shaka.log');
goog.require('shaka.media.DrmEngine');
goog.require('shaka.media.ManifestParser');
goog.require('shaka.media.PresentationTimeline');
goog.require('shaka.media.SegmentReference');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.text.TextEngine');
goog.require('shaka.util.Error');
Expand Down Expand Up @@ -999,35 +999,27 @@ shaka.dash.DashParser = class {
return this.requestInitSegment_(uris, startByte, endByte);
};
if (context.representation.segmentBase) {
streamInfo = shaka.dash.SegmentBase.createStream(
streamInfo = shaka.dash.SegmentBase.createStreamInfo(
context, requestInitSegment);
} else if (context.representation.segmentList) {
streamInfo = shaka.dash.SegmentList.createStream(
streamInfo = shaka.dash.SegmentList.createStreamInfo(
context, this.segmentIndexMap_);
} else if (context.representation.segmentTemplate) {
const hasManifest = !!this.manifest_;
streamInfo = shaka.dash.SegmentTemplate.createStream(
context, requestInitSegment, this.segmentIndexMap_, hasManifest);
streamInfo = shaka.dash.SegmentTemplate.createStreamInfo(
context, requestInitSegment, this.segmentIndexMap_, hasManifest,
this.config_.dash.initialSegmentLimit);
} else {
goog.asserts.assert(isText,
'Must have Segment* with non-text streams.');

const baseUris = context.representation.baseUris;
const duration = context.periodInfo.duration || 0;
const findSegmentPosition = (time) => {
return (time >= 0 && time < duration) ? 1 : null;
};
const getSegmentReference = (ref) => {
if (ref != 1) {
return null;
}
return new shaka.media.SegmentReference(
1, 0, duration, (() => { return baseUris; }), 0, null);
};
streamInfo = {
createSegmentIndex: () => Promise.resolve(),
findSegmentPosition: findSegmentPosition,
getSegmentReference: getSegmentReference,
generateSegmentIndex: () => {
return Promise.resolve(shaka.media.SegmentIndex.forSingleSegment(
duration, baseUris));
},
initSegmentReference: null,
scaledPresentationTimeOffset: 0,
};
Expand All @@ -1048,12 +1040,16 @@ shaka.dash.DashParser = class {
contentProtectionElems, this.config_.dash.customScheme,
contentProtection, this.config_.dash.ignoreDrmInfo);

return {
/** @type {shaka.extern.Stream} */
const stream = {
id: this.globalId_++,
originalId: context.representation.id,
createSegmentIndex: streamInfo.createSegmentIndex,
findSegmentPosition: streamInfo.findSegmentPosition,
getSegmentReference: streamInfo.getSegmentReference,
createSegmentIndex: async () => {
if (!stream.segmentIndex) {
stream.segmentIndex = await streamInfo.generateSegmentIndex();
}
},
segmentIndex: null,
initSegmentReference: streamInfo.initSegmentReference,
presentationTimeOffset: streamInfo.scaledPresentationTimeOffset,
mimeType: context.representation.mimeType,
Expand All @@ -1076,6 +1072,7 @@ shaka.dash.DashParser = class {
channelsCount: context.representation.numChannels,
closedCaptions: closedCaptions,
};
return stream;
}

/**
Expand Down Expand Up @@ -1510,7 +1507,7 @@ shaka.dash.DashParser = class {
}

/**
* Makes a network request on behalf of SegmentBase.createStream.
* Makes a network request on behalf of SegmentBase.createStreamInfo.
*
* @param {!Array.<string>} uris
* @param {?number} startByte
Expand Down Expand Up @@ -1641,7 +1638,9 @@ shaka.dash.DashParser.InheritanceFrame;
* }}
*
* @description
* Contains context data for the streams.
* Contains context data for the streams. This is designed to be
* shallow-copyable, so the parser must overwrite (not modify) each key as the
* parser moves through the manifest and the parsing context changes.
*
* @property {boolean} dynamic
* True if the MPD is dynamic (not all segments available at once)
Expand Down Expand Up @@ -1725,45 +1724,27 @@ shaka.dash.DashParser.AdaptationInfo;


/**
* @typedef {{
* createSegmentIndex: shaka.extern.CreateSegmentIndexFunction,
* findSegmentPosition: shaka.extern.FindSegmentPositionFunction,
* getSegmentReference: shaka.extern.GetSegmentReferenceFunction
* }}
*
* @typedef {function():!Promise.<shaka.media.SegmentIndex>}
* @description
* Contains functions used to create and find segment references. Used as
* a return value, to temporarily store them before StreamInfo is created.
*
* @property {shaka.extern.CreateSegmentIndexFunction} createSegmentIndex
* The createSegmentIndex function.
* @property {shaka.extern.FindSegmentPositionFunction} findSegmentPosition
* The findSegmentPosition function.
* @property {shaka.extern.GetSegmentReferenceFunction} getSegmentReference
* The getSegmentReference function.
* An async function which generates and returns a SegmentIndex.
*/
shaka.dash.DashParser.SegmentIndexFunctions;
shaka.dash.DashParser.GenerateSegmentIndexFunction;


/**
* @typedef {{
* createSegmentIndex: shaka.extern.CreateSegmentIndexFunction,
* findSegmentPosition: shaka.extern.FindSegmentPositionFunction,
* getSegmentReference: shaka.extern.GetSegmentReferenceFunction,
* generateSegmentIndex: shaka.dash.DashParser.GenerateSegmentIndexFunction,
* initSegmentReference: shaka.media.InitSegmentReference,
* scaledPresentationTimeOffset: number
* }}
*
* @description
* Contains information about a Stream. This is passed from the createStream
* Contains information about a Stream. This is passed from the createStreamInfo
* methods.
*
* @property {shaka.extern.CreateSegmentIndexFunction} createSegmentIndex
* The createSegmentIndex function for the stream.
* @property {shaka.extern.FindSegmentPositionFunction} findSegmentPosition
* The findSegmentPosition function for the stream.
* @property {shaka.extern.GetSegmentReferenceFunction} getSegmentReference
* The getSegmentReference function for the stream.
* @property {shaka.dash.DashParser.GenerateSegmentIndexFunction}
* generateSegmentIndex
* An async function to create the SegmentIndex for the stream.
* @property {shaka.media.InitSegmentReference} initSegmentReference
* The init segment for the stream.
* @property {number} scaledPresentationTimeOffset
Expand Down
Loading

0 comments on commit 1ada277

Please sign in to comment.