Skip to content

Commit

Permalink
feat(DASH): Add support for service descriptions (shaka-project#5394)
Browse files Browse the repository at this point in the history
  • Loading branch information
adgllorente committed Jul 5, 2023
1 parent c378e10 commit 693abd5
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 5 deletions.
26 changes: 25 additions & 1 deletion externs/shaka/manifest.js
Expand Up @@ -20,7 +20,8 @@
* minBufferTime: number,
* sequenceMode: boolean,
* ignoreManifestTimestampsInSegmentsMode: boolean,
* type: string
* type: string,
* serviceDescription: ?shaka.extern.ServiceDescription
* }}
*
* @description
Expand Down Expand Up @@ -89,6 +90,9 @@
* @property {string} type
* Indicates the type of the manifest. It can be <code>'HLS'</code> or
* <code>'DASH'</code>.
* @property {?shaka.extern.ServiceDescription} serviceDescription
* The service description for the manifest. Used to adapt playbackRate to
* decrease latency.
*
* @exportDoc
*/
Expand Down Expand Up @@ -118,6 +122,26 @@ shaka.extern.Manifest;
*/
shaka.extern.InitDataOverride;

/**
* @typedef {{
* maxLatency: ?number,
* maxPlaybackRate: ?number
* }}
*
* @description
* Maximum latency and playback rate for a manifest. When max latency is reached
* playbackrate is updated to maxPlaybackRate to decrease latency.
* More information {@link https://dashif.org/docs/CR-Low-Latency-Live-r8.pdf here}.
*
* @property {?number} maxLatency
* Maximum latency in seconds.
* @property {?number} maxPlaybackRate
* Maximum playback rate.
*
* @exportDoc
*/
shaka.extern.ServiceDescription;


/**
* @typedef {{
Expand Down
34 changes: 34 additions & 0 deletions lib/dash/dash_parser.js
Expand Up @@ -509,6 +509,7 @@ shaka.dash.DashParser = class {
sequenceMode: this.config_.dash.sequenceMode,
ignoreManifestTimestampsInSegmentsMode: false,
type: shaka.media.ManifestParser.DASH,
serviceDescription: this.parseServiceDescription_(mpd),
};

// We only need to do clock sync when we're using presentation start
Expand Down Expand Up @@ -547,6 +548,39 @@ shaka.dash.DashParser = class {
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
}

/**
* Reads maxLatency and maxPlaybackRate properties from service
* description element.
*
* @param {!Element} mpd
* @return {?shaka.extern.ServiceDescription}
* @private
*/
parseServiceDescription_(mpd) {
const XmlUtils = shaka.util.XmlUtils;
const elem = XmlUtils.findChild(mpd, 'ServiceDescription');

if (!elem ) {
return null;
}

const latencyNode = XmlUtils.findChild(elem, 'Latency');
const playbackRateNode = XmlUtils.findChild(elem, 'PlaybackRate');

if ((latencyNode && latencyNode.getAttribute('max')) || playbackRateNode) {
const maxLatency = latencyNode && latencyNode.getAttribute('max') ?
parseInt(latencyNode.getAttribute('max'), 10) / 1000 :
null;
const maxPlaybackRate = playbackRateNode ?
parseFloat(playbackRateNode.getAttribute('max')) :
null;

return {maxLatency, maxPlaybackRate};
}

return null;
}

/**
* Reads and parses the periods from the manifest. This first does some
* partial parsing so the start and duration is available when parsing
Expand Down
1 change: 1 addition & 0 deletions lib/hls/hls_parser.js
Expand Up @@ -801,6 +801,7 @@ shaka.hls.HlsParser = class {
ignoreManifestTimestampsInSegmentsMode:
this.config_.hls.ignoreManifestTimestampsInSegmentsMode,
type: shaka.media.ManifestParser.HLS,
serviceDescription: null,
};
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
}
Expand Down
1 change: 1 addition & 0 deletions lib/mss/mss_parser.js
Expand Up @@ -376,6 +376,7 @@ shaka.mss.MssParser = class {
sequenceMode: this.config_.mss.sequenceMode,
ignoreManifestTimestampsInSegmentsMode: false,
type: shaka.media.ManifestParser.MSS,
serviceDescription: null,
};

// This is the first point where we have a meaningful presentation start
Expand Down
1 change: 1 addition & 0 deletions lib/offline/manifest_converter.js
Expand Up @@ -91,6 +91,7 @@ shaka.offline.ManifestConverter = class {
sequenceMode: manifestDB.sequenceMode || false,
ignoreManifestTimestampsInSegmentsMode: false,
type: manifestDB.type || shaka.media.ManifestParser.UNKNOWN,
serviceDescription: null,
};
}

Expand Down
29 changes: 25 additions & 4 deletions lib/player.js
Expand Up @@ -2278,7 +2278,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
'arbitrary language initially');
}

if (this.isLive() && this.config_.streaming.liveSync) {
if (this.isLive() && (this.config_.streaming.liveSync ||
this.manifest_.serviceDescription)) {
const onTimeUpdate = () => this.onTimeUpdate_();
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
}
Expand Down Expand Up @@ -5728,8 +5729,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
// Bad stream?
return;
}
const liveSyncMaxLatency = this.config_.streaming.liveSyncMaxLatency;
const liveSyncPlaybackRate = this.config_.streaming.liveSyncPlaybackRate;

let liveSyncMaxLatency;
let liveSyncPlaybackRate;
if (this.config_.streaming.liveSync) {
liveSyncMaxLatency = this.config_.streaming.liveSyncMaxLatency;
liveSyncPlaybackRate = this.config_.streaming.liveSyncPlaybackRate;
} else {
// serviceDescription must override if it is defined in the MPD and
// liveSync configuration is not set.
if (this.manifest_ && this.manifest_.serviceDescription) {
liveSyncMaxLatency = this.manifest_.serviceDescription.maxLatency ||
this.config_.streaming.liveSyncMaxLatency;
liveSyncPlaybackRate =
this.manifest_.serviceDescription.maxPlaybackRate ||
this.config_.streaming.liveSyncPlaybackRate;
}
}

const playbackRate = this.video_.playbackRate;
const latency = seekRange.end - this.video_.currentTime;
let offset = 0;
Expand All @@ -5745,8 +5762,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
}

if ((latency - offset) > liveSyncMaxLatency) {
if (liveSyncMaxLatency && liveSyncPlaybackRate &&
(latency - offset) > liveSyncMaxLatency) {
if (playbackRate != liveSyncPlaybackRate) {
shaka.log.debug('Latency (' + latency + 's) ' +
'is greater than liveSyncMaxLatency (' + liveSyncMaxLatency + 's). ' +
'Updating playbackRate to ' + liveSyncPlaybackRate);
this.trickPlay(liveSyncPlaybackRate);
}
} else if (playbackRate !== 1 && playbackRate !== 0) {
Expand Down
22 changes: 22 additions & 0 deletions test/dash/dash_parser_manifest_unit.js
Expand Up @@ -2556,4 +2556,26 @@ describe('DashParser Manifest', () => {
expect(segments[0][1].startTime).toBe(15);
expect(segments[1][1].startTime).toBe(15);
});

describe('Parses ServiceDescription', () => {
it('with PlaybackRate and Latency', async () => {
const source = [
'<MPD minBufferTime="PT75S" type="dynamic"',
' availabilityStartTime="1970-01-01T00:00:00Z">',
' <ServiceDescription id="0">',
' <Latency max="2000" min="2000" referenceId="0" target="4000" />',
' <PlaybackRate max="1.10" min="0.96" />',
' </ServiceDescription>',
'</MPD>',
].join('\n');

fakeNetEngine.setResponseText('dummy://foo', source);

/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);

expect(manifest.serviceDescription.maxLatency).toBe(2);
expect(manifest.serviceDescription.maxPlaybackRate).toBe(1.1);
});
});
});
1 change: 1 addition & 0 deletions test/media/playhead_unit.js
Expand Up @@ -136,6 +136,7 @@ describe('Playhead', () => {
sequenceMode: false,
ignoreManifestTimestampsInSegmentsMode: false,
type: 'UNKNOWN',
serviceDescription: null,
};

config = shaka.util.PlayerConfiguration.createDefault().streaming;
Expand Down
1 change: 1 addition & 0 deletions test/media/streaming_engine_integration.js
Expand Up @@ -597,6 +597,7 @@ describe('StreamingEngine', () => {
sequenceMode: false,
ignoreManifestTimestampsInSegmentsMode: false,
type: 'UNKNOWN',
serviceDescription: null,
variants: [{
id: 1,
video: {
Expand Down
3 changes: 3 additions & 0 deletions test/test/util/manifest_generator.js
Expand Up @@ -101,6 +101,9 @@ shaka.test.ManifestGenerator.Manifest = class {
this.ignoreManifestTimestampsInSegmentsMode = false;
/** @type {string} */
this.type = 'UNKNOWN';
/** @type {?shaka.extern.ServiceDescription} */
this.serviceDescription = null;


/** @type {shaka.extern.Manifest} */
const foo = this;
Expand Down
1 change: 1 addition & 0 deletions test/test/util/streaming_engine_util.js
Expand Up @@ -286,6 +286,7 @@ shaka.test.StreamingEngineUtil = class {
sequenceMode: false,
ignoreManifestTimestampsInSegmentsMode: false,
type: 'UNKNOWN',
serviceDescription: null,
};

/** @type {shaka.extern.Variant} */
Expand Down

0 comments on commit 693abd5

Please sign in to comment.