Skip to content

Commit

Permalink
Merge branch 'main' into segment-sequence-representation
Browse files Browse the repository at this point in the history
# Conflicts:
#	lib/dash/dash_parser.js
  • Loading branch information
avelad committed Oct 23, 2023
2 parents 2e9180b + f81a16a commit 85e14dd
Show file tree
Hide file tree
Showing 19 changed files with 705 additions and 356 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Alugha GmbH <*@alugha.com>
Alvaro Velad Galvan <[email protected]>
Amila Sampath <[email protected]>
Anthony Stansbridge <[email protected]>
Armand Zangue <[email protected]>
Benjamin Wallberg <[email protected]>
Bonnier Broadcasting <*@bonnierbroadcasting.com>
Bryan Huh <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Alvaro Velad Galvan <[email protected]>
Amila Sampath <[email protected]>
Andy Hochhaus <[email protected]>
Anthony Stansbridge <[email protected]>
Armand Zangue <[email protected]>
Ashutosh Kumar Mukhiya <[email protected]>
Benjamin Wallberg <[email protected]>
Benjamin Wallberg <[email protected]>
Expand Down
2 changes: 2 additions & 0 deletions demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ shakaDemo.Config = class {
.addBoolInput_('Enable HLS sequence mode', 'manifest.hls.sequenceMode')
.addBoolInput_('Ignore Manifest Timestamps in Segments Mode',
'manifest.hls.ignoreManifestTimestampsInSegmentsMode')
.addBoolInput_('Disable codec guessing',
'manifest.hls.disableCodecGuessing')
.addNumberInput_('Availability Window Override',
'manifest.availabilityWindowOverride',
/* canBeDecimal= */ true,
Expand Down
10 changes: 9 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,8 @@ shaka.extern.DashManifestConfiguration;
* useSafariBehaviorForLive: boolean,
* liveSegmentsDelay: number,
* sequenceMode: boolean,
* ignoreManifestTimestampsInSegmentsMode: boolean
* ignoreManifestTimestampsInSegmentsMode: boolean,
* disableCodecGuessing: boolean
* }}
*
* @property {boolean} ignoreTextStreamFailures
Expand Down Expand Up @@ -973,6 +974,13 @@ shaka.extern.DashManifestConfiguration;
* to the SourceBuffer, even if the manifest and segment times disagree.
* Only applies when sequenceMode is <code>false</code>.
* <i>Defaults to <code>false</code>.</i>
* @property {boolean} disableCodecGuessing
* If set to true, the HLS parser won't automatically guess or assume default
* codec for playlists with no "CODECS" attribute. Instead, it will attempt to
* extract the missing information from the media segment.
* As a consequence, lazy-loading media playlists won't be possible for this
* use case, which may result in longer video startup times.
* <i>Defaults to <code>false</code>.</i>
* @exportDoc
*/
shaka.extern.HlsManifestConfiguration;
Expand Down
78 changes: 39 additions & 39 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,40 +870,29 @@ shaka.dash.DashParser = class {
}
}

const audioSets = this.config_.disableAudio ? [] :
this.getSetsOfType_(playbackAdaptationSets, ContentType.AUDIO);
const videoSets = this.config_.disableVideo ? [] :
this.getSetsOfType_(playbackAdaptationSets, ContentType.VIDEO);
const textSets = this.config_.disableText ? [] :
this.getSetsOfType_(playbackAdaptationSets, ContentType.TEXT);
const imageSets = this.config_.disableThumbnails ? [] :
this.getSetsOfType_(playbackAdaptationSets, ContentType.IMAGE);

if (!videoSets.length && !audioSets.length) {
const audioStreams = this.getStreamsFromSets_(
this.config_.disableAudio,
playbackAdaptationSets,
ContentType.AUDIO);
const videoStreams = this.getStreamsFromSets_(
this.config_.disableVideo,
playbackAdaptationSets,
ContentType.VIDEO);
const textStreams = this.getStreamsFromSets_(
this.config_.disableText,
playbackAdaptationSets,
ContentType.TEXT);
const imageStreams = this.getStreamsFromSets_(
this.config_.disableThumbnails,
playbackAdaptationSets,
ContentType.IMAGE);

if (videoStreams.length === 0 && audioStreams.length === 0) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.DASH_EMPTY_PERIOD);
}

const audioStreams = [];
for (const audioSet of audioSets) {
audioStreams.push(...audioSet.streams);
}

const videoStreams = [];
for (const videoSet of videoSets) {
videoStreams.push(...videoSet.streams);
}

const textStreams = [];
for (const textSet of textSets) {
textStreams.push(...textSet.streams);
}

const imageStreams = [];
for (const imageSet of imageSets) {
imageStreams.push(...imageSet.streams);
shaka.util.Error.Code.DASH_EMPTY_PERIOD,
);
}

return {
Expand All @@ -916,15 +905,26 @@ shaka.dash.DashParser = class {
}

/**
* Gets the streams from the given sets or returns an empty array if disabled
* or no streams are found.
* @param {boolean} disabled
* @param {!Array.<!shaka.dash.DashParser.AdaptationInfo>} adaptationSets
* @param {string} type
* @return {!Array.<!shaka.dash.DashParser.AdaptationInfo>}
* @private
*/
getSetsOfType_(adaptationSets, type) {
return adaptationSets.filter((as) => {
return as.contentType == type;
});
* @param {string} contentType
@private
*/
getStreamsFromSets_(disabled, adaptationSets, contentType) {
if (disabled || !adaptationSets.length) {
return [];
}

return adaptationSets.reduce((all, part) => {
if (part.contentType != contentType) {
return all;
}

all.push(...part.streams);
return all;
}, []);
}

/**
Expand Down
66 changes: 39 additions & 27 deletions lib/dash/mpd_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,30 +322,46 @@ shaka.dash.MpdUtils = class {
}

/**
* Searches the inheritance for a Segment* with the given attribute.
*
* Parses common attributes for Representation, AdaptationSet, and Period.
* @param {shaka.dash.DashParser.Context} context
* @param {function(?shaka.dash.DashParser.InheritanceFrame):Element} callback
* Gets the Element that contains the attribute to inherit.
* @param {string} attribute
* @return {?string}
*/
static inheritAttribute(context, callback, attribute) {
* @return {!Array.<!Element>}
*/
static getNodes(context, callback) {
const Functional = shaka.util.Functional;
goog.asserts.assert(
callback(context.representation),
'There must be at least one element of the given type');
'There must be at least one element of the given type',
);

/** @type {!Array.<!Element>} */
const nodes = [
return [
callback(context.representation),
callback(context.adaptationSet),
callback(context.period),
].filter(Functional.isNotNull);
}

return nodes
.map((s) => { return s.getAttribute(attribute); })
.reduce((all, part) => { return all || part; });
/**
* Searches the inheritance for a Segment* with the given attribute.
*
* @param {shaka.dash.DashParser.Context} context
* @param {function(?shaka.dash.DashParser.InheritanceFrame):Element} callback
* Gets the Element that contains the attribute to inherit.
* @param {string} attribute
* @return {?string}
*/
static inheritAttribute(context, callback, attribute) {
const MpdUtils = shaka.dash.MpdUtils;
const nodes = MpdUtils.getNodes(context, callback);

let result = null;
for (const node of nodes) {
result = node.getAttribute(attribute);
if (result) {
break;
}
}
return result;
}

/**
Expand All @@ -358,22 +374,18 @@ shaka.dash.MpdUtils = class {
* @return {Element}
*/
static inheritChild(context, callback, child) {
const Functional = shaka.util.Functional;
goog.asserts.assert(
callback(context.representation),
'There must be at least one element of the given type');

/** @type {!Array.<!Element>} */
const nodes = [
callback(context.representation),
callback(context.adaptationSet),
callback(context.period),
].filter(Functional.isNotNull);
const MpdUtils = shaka.dash.MpdUtils;
const nodes = MpdUtils.getNodes(context, callback);

const XmlUtils = shaka.util.XmlUtils;
return nodes
.map((s) => { return XmlUtils.findChild(s, child); })
.reduce((all, part) => { return all || part; });
let result = null;
for (const node of nodes) {
result = XmlUtils.findChild(node, child);
if (result) {
break;
}
}
return result;
}

/**
Expand Down
67 changes: 57 additions & 10 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ shaka.hls.HlsParser = class {
*/
this.streamsFinalized_ = false;

/**
* Whether the manifest informs about the codec to use.
*
* @private
*/
this.codecInfoInManifest_ = false;

/**
* This timer is used to trigger the start of a manifest update. A manifest
* update is async. Once the update is finished, the timer will be restarted
Expand Down Expand Up @@ -806,6 +813,24 @@ shaka.hls.HlsParser = class {
type: shaka.media.ManifestParser.HLS,
serviceDescription: null,
};

// If there is no 'CODECS' attribute in the manifest and codec guessing is
// disabled, we need to create the segment indexes now so that missing info
// can be parsed from the media data and added to the stream objects.
if (!this.codecInfoInManifest_ && this.config_.hls.disableCodecGuessing) {
const createIndexes = [];
for (const variant of this.manifest_.variants) {
if (variant.audio && variant.audio.codecs === '') {
createIndexes.push(variant.audio.createSegmentIndex());
}
if (variant.video && variant.video.codecs === '') {
createIndexes.push(variant.video.createSegmentIndex());
}
}

await Promise.all(createIndexes);
}

this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
}

Expand Down Expand Up @@ -1293,17 +1318,24 @@ shaka.hls.HlsParser = class {
* @private
*/
getCodecsForVariantTag_(tag) {
// These are the default codecs to assume if none are specified.
const defaultCodecsArray = [];
if (!this.config_.disableVideo) {
defaultCodecsArray.push(this.config_.hls.defaultVideoCodec);
}
if (!this.config_.disableAudio) {
defaultCodecsArray.push(this.config_.hls.defaultAudioCodec);
let codecsString = tag.getAttributeValue('CODECS') || '';

this.codecInfoInManifest_ = codecsString.length > 0;

if (!this.codecInfoInManifest_ && !this.config_.hls.disableCodecGuessing) {
// These are the default codecs to assume if none are specified.
const defaultCodecsArray = [];

if (!this.config_.disableVideo) {
defaultCodecsArray.push(this.config_.hls.defaultVideoCodec);
}
if (!this.config_.disableAudio) {
defaultCodecsArray.push(this.config_.hls.defaultAudioCodec);
}

codecsString = defaultCodecsArray.join(',');
}
const defaultCodecs = defaultCodecsArray.join(',');

const codecsString = tag.getAttributeValue('CODECS', defaultCodecs);
// Strip out internal whitespace while splitting on commas:
/** @type {!Array.<string>} */
const codecs = codecsString.split(/\s*,\s*/);
Expand Down Expand Up @@ -1829,11 +1861,25 @@ shaka.hls.HlsParser = class {
const playlist = this.manifestTextParser_.parsePlaylist(
response.data, absoluteMediaPlaylistUri);

let mimeType = undefined;

// If no codec info was provided in the manifest and codec guessing is
// disabled we try to get necessary info from the media data.
if (!this.codecInfoInManifest_ && this.config_.hls.disableCodecGuessing) {
const basicInfo = await this.getMediaPlaylistBasicInfo_(playlist);

goog.asserts.assert(
type === basicInfo.type, 'Media types should match!');

mimeType = basicInfo.mimeType;
codecs = basicInfo.codecs;
}

const wasLive = this.isLive_();
const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
playlist, verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs,
type, languageValue, primary, name, channelsCount, closedCaptions,
characteristics, forced, sampleRate, spatialAudio);
characteristics, forced, sampleRate, spatialAudio, mimeType);
if (abortSignal.aborted) {
return;
}
Expand Down Expand Up @@ -1864,6 +1910,7 @@ shaka.hls.HlsParser = class {
stream.keyIds = realStream.keyIds;
stream.mimeType = realStream.mimeType;
stream.bandwidth = realStream.bandwidth;
stream.codecs = realStream.codecs || stream.codecs;

// Since we lazy-loaded this content, the player may need to create new
// sessions for the DRM info in this stream.
Expand Down
16 changes: 7 additions & 9 deletions lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2045,15 +2045,13 @@ shaka.media.DrmEngine = class {
// audio adaptations, so we shouldn't have to worry about checking
// robustness.
if (drm1.keySystem == drm2.keySystem) {
/** @type {Array<shaka.extern.InitDataOverride>} */
let initData = [];
initData = initData.concat(drm1.initData || []);
initData = initData.concat(drm2.initData || []);
initData = initData.filter((d, i) => {
return d.keyId === undefined || i === initData.findIndex((d2) => {
return d2.keyId === d.keyId;
});
});
const initDataMap = new Map();
const bothInitDatas = (drm1.initData || [])
.concat(drm2.initData || []);
for (const d of bothInitDatas) {
initDataMap.set(d.keyId, d);
}
const initData = Array.from(initDataMap.values());

const keyIds = drm1.keyIds && drm2.keyIds ?
new Set([...drm1.keyIds, ...drm2.keyIds]) :
Expand Down
Loading

0 comments on commit 85e14dd

Please sign in to comment.