} periods
+ * @param {shaka.extern.Manifest} manifest
* @private
*/
- static filterForAVVariants_(periods) {
+ static filterForAVVariants_(manifest) {
const isAVVariant = (variant) => {
// Audio-video variants may include both streams separately or may be
// single multiplexed streams with multiple codecs.
return (variant.video && variant.audio) ||
(variant.video && variant.video.codecs.includes(','));
};
- const hasAVVariant = periods.some((period) => {
- return period.variants.some(isAVVariant);
- });
- if (hasAVVariant) {
+ if (manifest.variants.some(isAVVariant)) {
shaka.log.debug('Found variant with audio and video content, ' +
- 'so filtering out audio-only content in all periods.');
- for (const period of periods) {
- period.variants = period.variants.filter(isAVVariant);
- }
+ 'so filtering out audio-only content.');
+ manifest.variants = manifest.variants.filter(isAVVariant);
}
}
@@ -2156,11 +2136,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
goog.asserts.assert(this.video_, 'Must have video element');
- // Create the period observer. This will allow us to notify the app when we
- // transition between periods.
- const periodObserver = new shaka.media.PeriodObserver(this.manifest_);
- periodObserver.setListeners((period) => this.onChangePeriod_());
-
// Create the region observer. This will allow us to notify the app when we
// move in and out of timeline regions.
const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
@@ -2182,7 +2157,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
// Now that we have all our observers, create a manager for them.
const manager = new shaka.media.PlayheadObserverManager(this.video_);
- manager.manage(periodObserver);
manager.manage(regionObserver);
return manager;
@@ -2315,8 +2289,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
mediaSourceEngine: this.mediaSourceEngine_,
netEngine: this.networkingEngine_,
- onChooseStreams: (period) => this.onChooseStreams_(period),
- onCanSwitch: () => this.canSwitch_(),
onError: (error) => this.onError_(error),
onEvent: (event) => this.dispatchEvent(event),
onManifestUpdate: () => this.onManifestUpdate_(),
@@ -2403,33 +2375,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
if (this.streamingEngine_) {
this.streamingEngine_.configure(this.config_.streaming);
- // Need to apply the restrictions to every period.
+ // Need to apply the restrictions.
try {
- // this.filterNewPeriod_() may throw.
- for (const period of this.manifest_.periods) {
- this.filterNewPeriod_(period);
- }
+ // this.filterManifest_() may throw.
+ this.filterManifest_(this.manifest_);
} catch (error) {
this.onError_(error);
}
- // If the stream we are playing is restricted, we need to switch.
- const activeAudio = this.streamingEngine_.getBufferingAudio();
- const activeVideo = this.streamingEngine_.getBufferingVideo();
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
- const activeVariant = shaka.util.StreamUtils.getVariantByStreams(
- activeAudio, activeVideo, period.variants);
- if (this.abrManager_ && activeVariant &&
- activeVariant.allowedByApplication &&
- activeVariant.allowedByKeySystem) {
+ if (this.abrManager_) {
// Update AbrManager variants to match these new settings.
- this.chooseVariant_(period.variants);
- } else {
- shaka.log.debug('Choosing new streams after changing configuration');
- this.chooseStreamsAndSwitch_(period);
+ this.updateAbrManagerVariants_();
+ }
+
+ // If the streams we are playing are restricted, we need to switch.
+ const activeVariant = this.streamingEngine_.getCurrentVariant();
+ if (activeVariant) {
+ if (!activeVariant.allowedByApplication ||
+ !activeVariant.allowedByKeySystem) {
+ shaka.log.debug('Choosing new variant after changing configuration');
+ this.chooseVariantAndSwitch_();
+ }
}
}
+
if (this.mediaSourceEngine_) {
const textDisplayerFactory = this.config_.textDisplayFactory;
if (this.lastTextFactory_ != textDisplayerFactory) {
@@ -2448,7 +2417,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.abrManager_.configure(this.config_.abr);
// Simply enable/disable ABR with each call, since multiple calls to these
// methods have no effect.
- if (this.config_.abr.enabled && !this.switchingPeriods_) {
+ if (this.config_.abr.enabled) {
this.abrManager_.enable();
} else {
this.abrManager_.disable();
@@ -2617,12 +2586,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
*/
isAudioOnly() {
if (this.manifest_) {
- const periods = this.manifest_.periods;
- if (!periods.length) {
- return false;
- }
-
- const variants = this.manifest_.periods[0].variants;
+ const variants = this.manifest_.variants;
if (!variants.length) {
return false;
}
@@ -2816,9 +2780,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Return a list of variant tracks that can be switched to in the current
- * period. If there are multiple periods, you must seek to the period in order
- * to get variants from that period.
+ * Return a list of variant tracks that can be switched to.
*
*
* If the player has not loaded content, this will return an empty list.
@@ -2827,13 +2789,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* @export
*/
getVariantTracks() {
- if (this.manifest_ && this.playhead_) {
- const currentVariant = this.getPresentationVariant_();
+ if (this.manifest_) {
+ const currentVariant = this.streamingEngine_ ?
+ this.streamingEngine_.getCurrentVariant() : null;
const tracks = [];
// Convert each variant to a track.
- for (const variant of this.getSelectableVariants_()) {
+ for (const variant of this.manifest_.variants) {
+ if (!shaka.util.StreamUtils.isPlayable(variant)) {
+ continue;
+ }
+
const track = shaka.util.StreamUtils.variantToTrack(variant);
track.active = variant == currentVariant;
@@ -2855,9 +2822,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Return a list of text tracks that can be switched to in the current period.
- * If there are multiple periods, you must seek to a period in order to get
- * text tracks from that period.
+ * Return a list of text tracks that can be switched to.
*
*
* If the player has not loaded content, this will return an empty list.
@@ -2866,14 +2831,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* @export
*/
getTextTracks() {
- if (this.manifest_ && this.playhead_) {
- const currentText = this.getPresentationText_();
+ if (this.manifest_) {
+ const currentTextStream = this.streamingEngine_ ?
+ this.streamingEngine_.getCurrentTextStream() : null;
const tracks = [];
// Convert all selectable text streams to tracks.
- for (const text of this.getSelectableText_()) {
+ for (const text of this.manifest_.textStreams) {
const track = shaka.util.StreamUtils.textStreamToTrack(text);
- track.active = text == currentText;
+ track.active = text == currentTextStream;
tracks.push(track);
}
@@ -2889,10 +2855,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Select a specific text track from the current period. track
- * should come from a call to getTextTracks
. If the track is not
- * found in the current period, this will be a no-op. If the player has not
- * loaded content, this will be a no-op.
+ * Select a specific text track. track
should come from a call to
+ * getTextTracks
. If the track is not found, this will be a
+ * no-op. If the player has not loaded content, this will be a no-op.
*
*
* Note that AdaptationEvents
are not fired for manual track
@@ -2903,20 +2868,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
*/
selectTextTrack(track) {
if (this.manifest_ && this.streamingEngine_) {
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
- const stream = period.textStreams.find((stream) => stream.id == track.id);
+ const stream = this.manifest_.textStreams.find(
+ (stream) => stream.id == track.id);
if (!stream) {
shaka.log.error('No stream with id', track.id);
return;
}
- // Add entries to the history.
- this.addTextStreamToSwitchHistory_(
- period, stream, /* fromAdaptation= */ false);
+ if (stream == this.streamingEngine_.getCurrentTextStream()) {
+ shaka.log.debug('Text track already selected.');
+ return;
+ }
- this.switchTextStream_(stream);
+ // Add entries to the history.
+ this.addTextStreamToSwitchHistory_(stream, /* fromAdaptation= */ false);
+ this.streamingEngine_.switchTextStream(stream);
+ this.onTextChanged_();
// Workaround for https://github.com/google/shaka-player/issues/1299
// When track is selected, back-propagate the language to
@@ -2939,11 +2907,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Select a specific variant track to play from the current period.
- * track
should come from a call to
- * getVariantTracks
. If track
cannot be found in the
- * current variant, this will be a no-op. If the player has not loaded
- * content, this will be a no-op.
+ * Select a specific variant track to play. track
should come
+ * from a call to getVariantTracks
. If track
cannot
+ * be found, this will be a no-op. If the player has not loaded content, this
+ * will be a no-op.
*
*
* Changing variants will take effect once the currently buffered content has
@@ -2967,11 +2934,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* safeMargin value.
* @export
*/
- selectVariantTrack(track, clearBuffer, safeMargin = 0) {
+ selectVariantTrack(track, clearBuffer = false, safeMargin = 0) {
if (this.manifest_ && this.streamingEngine_) {
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
-
if (this.config_.abr.enabled) {
shaka.log.alwaysWarn('Changing tracks while abr manager is enabled ' +
'will likely result in the selected track ' +
@@ -2979,7 +2943,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
'calling selectVariantTrack().');
}
- const variant = period.variants.find((variant) => variant.id == track.id);
+ const variant = this.manifest_.variants.find(
+ (variant) => variant.id == track.id);
if (!variant) {
shaka.log.error('No variant with id', track.id);
return;
@@ -2994,10 +2959,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return;
}
+ if (variant == this.streamingEngine_.getCurrentVariant()) {
+ shaka.log.debug('Variant already selected.');
+ return;
+ }
+
// Add entries to the history.
- this.addVariantToSwitchHistory_(period, variant,
- /* fromAdaptation= */ false);
- this.switchVariant_(variant, clearBuffer, safeMargin);
+ this.addVariantToSwitchHistory_(variant, /* fromAdaptation= */ false);
+ this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
+ // Dispatch a 'variantchanged' event
+ this.onVariantChanged_();
// Workaround for https://github.com/google/shaka-player/issues/1299
// When track is selected, back-propagate the language to
@@ -3006,7 +2977,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
variant);
// Update AbrManager variants to match these new settings.
- this.chooseVariant_(period.variants);
+ this.updateAbrManagerVariants_();
} else if (this.video_ && this.video_.audioTracks) {
// Safari's native HLS won't let you choose an explicit variant, though
// you can choose audio languages this way.
@@ -3022,9 +2993,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Return a list of audio language-role combinations available for the current
- * period. If the player has not loaded any content, this will return an empty
- * list.
+ * Return a list of audio language-role combinations available. If the
+ * player has not loaded any content, this will return an empty list.
*
* @return {!Array.}
* @export
@@ -3034,9 +3004,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Return a list of text language-role combinations available for the current
- * period. If the player has not loaded any content, this will be return an
- * empty list.
+ * Return a list of text language-role combinations available. If the player
+ * has not loaded any content, this will be return an empty list.
*
* @return {!Array.}
* @export
@@ -3046,8 +3015,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Return a list of audio languages available for the current period. If the
- * player has not loaded any content, this will return an empty list.
+ * Return a list of audio languages available. If the player has not loaded
+ * any content, this will return an empty list.
*
* @return {!Array.}
* @export
@@ -3057,8 +3026,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Return a list of text languages available for the current period. If the
- * player has not loaded any content, this will return an empty list.
+ * Return a list of text languages available. If the player has not loaded
+ * any content, this will return an empty list.
*
* @return {!Array.}
* @export
@@ -3080,14 +3049,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
const LanguageUtils = shaka.util.LanguageUtils;
if (this.manifest_ && this.playhead_) {
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
-
this.currentAdaptationSetCriteria_ =
new shaka.media.PreferenceBasedCriteria(language, role || '',
/* channelCount= */ 0, /* label= */ '', /* type= */ 'audio');
- this.chooseVariantAndSwitch_(period);
+ this.chooseVariantAndSwitch_();
} else if (this.video_ && this.video_.audioTracks) {
const audioTracks = Array.from(this.video_.audioTracks);
const selectedLanguage = LanguageUtils.normalize(language);
@@ -3115,18 +3081,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
const LanguageUtils = shaka.util.LanguageUtils;
if (this.manifest_ && this.playhead_) {
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
-
this.currentTextLanguage_ = language;
this.currentTextRole_ = role || '';
- const chosenText = this.chooseTextStream_(period.textStreams);
+ const chosenText = this.chooseTextStream_();
if (chosenText) {
+ if (chosenText == this.streamingEngine_.getCurrentTextStream()) {
+ shaka.log.debug('Text track already selected.');
+ return;
+ }
+
this.addTextStreamToSwitchHistory_(
- period, chosenText, /* fromAdaptation= */ false);
+ chosenText, /* fromAdaptation= */ false);
if (this.shouldStreamText_()) {
- this.switchTextStream_(chosenText);
+ this.streamingEngine_.switchTextStream(chosenText);
+ this.onTextChanged_();
}
}
} else {
@@ -3152,11 +3121,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
*/
selectVariantsByLabel(label) {
if (this.manifest_ && this.playhead_) {
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
-
let firstVariantWithLabel = null;
- for (const variant of this.getSelectableVariants_()) {
+ for (const variant of this.manifest_.variants) {
if (variant.audio.label == label) {
firstVariantWithLabel = variant;
break;
@@ -3177,7 +3143,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
new shaka.media.PreferenceBasedCriteria(
firstVariantWithLabel.language, '', 0, label);
- this.chooseVariantAndSwitch_(period);
+ this.chooseVariantAndSwitch_();
}
}
@@ -3212,10 +3178,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* state, the request will be applied next time content is loaded.
*
* @param {boolean} isVisible
- * @return {!Promise}
* @export
*/
- async setTextTrackVisibility(isVisible) {
+ setTextTrackVisibility(isVisible) {
const oldVisibilty = this.isTextVisible_;
// Convert to boolean in case apps pass 0/1 instead false/true.
const newVisibility = !!isVisible;
@@ -3239,15 +3204,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
if (!this.config_.streaming.alwaysStreamText) {
if (newVisibility) {
// Find the text stream that best matches the user's preferences.
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
const streams = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
- period.textStreams, this.currentTextLanguage_,
+ this.manifest_.textStreams,
+ this.currentTextLanguage_,
this.currentTextRole_);
// It is possible that there are no streams to play.
if (streams.length > 0) {
- await this.streamingEngine_.loadNewTextStream(streams[0]);
+ this.streamingEngine_.switchTextStream(streams[0]);
+ this.onTextChanged_();
}
} else {
this.streamingEngine_.unloadTextStream();
@@ -3406,9 +3371,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
// Event through we are loaded, it is still possible that we don't have a
- // presentation variant yet because we set the load mode before we select
- // the first variant to stream.
- const variant = this.getPresentationVariant_();
+ // variant yet because we set the load mode before we select the first
+ // variant to stream.
+ const variant = this.streamingEngine_.getCurrentVariant();
if (variant) {
const rate = this.playRateController_ ?
@@ -3433,11 +3398,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Adds the given text track to the current Period. load()
must
- * resolve before calling. The current Period or the presentation must have a
- * duration.
- * This returns a Promise that will resolve with the track that was created,
- * when that track can be switched to.
+ * Adds the given text track to the loaded manifest. load()
must
+ * resolve before calling. The presentation must have a duration.
+ *
+ * This returns the created track, which can immediately be selected by the
+ * application. The track will not be automatically selected.
*
* @param {string} uri
* @param {string} language
@@ -3445,10 +3410,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* @param {string} mime
* @param {string=} codec
* @param {string=} label
- * @return {!Promise.}
+ * @return {shaka.extern.Track}
* @export
*/
- async addTextTrack(uri, language, kind, mime, codec, label) {
+ addTextTrack(uri, language, kind, mime, codec, label) {
// TODO: Add an actual error for this.
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
shaka.log.error('Cannot add text when loaded with src=');
@@ -3461,23 +3426,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
throw new Error('State error!');
}
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
-
const ContentType = shaka.util.ManifestParserUtils.ContentType;
- // Get the Period duration.
- /** @type {number} */
- const periodIndex = this.manifest_.periods.indexOf(period);
- /** @type {number} */
- const nextPeriodIndex = periodIndex + 1;
- /** @type {number} */
- const nextPeriodStart = nextPeriodIndex >= this.manifest_.periods.length ?
- this.manifest_.presentationTimeline.getDuration() :
- this.manifest_.periods[nextPeriodIndex].startTime;
- /** @type {number} */
- const periodDuration = nextPeriodStart - period.startTime;
- if (periodDuration == Infinity) {
+ const duration = this.manifest_.presentationTimeline.getDuration();
+ if (duration == Infinity) {
throw new shaka.util.Error(
shaka.util.Error.Severity.RECOVERABLE,
shaka.util.Error.Category.MANIFEST,
@@ -3490,14 +3442,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
originalId: null,
createSegmentIndex: () => Promise.resolve(),
segmentIndex: shaka.media.SegmentIndex.forSingleSegment(
- /* startTime= */ period.startTime,
- /* duration= */ periodDuration,
+ /* startTime= */ 0,
+ /* duration= */ duration,
/* uris= */ [uri]),
mimeType: mime,
codecs: codec || '',
kind: kind,
encrypted: false,
- keyId: null,
+ keyIds: [],
language: language,
label: label || null,
type: ContentType.TEXT,
@@ -3510,28 +3462,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
closedCaptions: null,
};
- // Add the stream to the loading list to ensure it isn't switched to while
- // it is initializing.
- this.loadingTextStreams_.add(stream);
- period.textStreams.push(stream);
-
- await this.streamingEngine_.loadNewTextStream(stream);
- goog.asserts.assert(period, 'The period should still be non-null here.');
-
- const activeText = this.streamingEngine_.getBufferingText();
- if (activeText) {
- // If this was the first text stream, StreamingEngine will start streaming
- // it in loadNewTextStream. To reflect this, update the active stream.
- this.activeStreams_.useText(period, activeText);
- }
-
- // Remove the stream from the loading list.
- this.loadingTextStreams_.delete(stream);
-
- shaka.log.debug('Choosing new streams after adding a text stream');
- this.chooseStreamsAndSwitch_(period);
+ this.manifest_.textStreams.push(stream);
this.onTracksChanged_();
-
return shaka.util.StreamUtils.textStreamToTrack(stream);
}
@@ -3594,25 +3526,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * @param {shaka.extern.Period} period
* @param {shaka.extern.Variant} variant
* @param {boolean} fromAdaptation
* @private
*/
- addVariantToSwitchHistory_(period, variant, fromAdaptation) {
- this.activeStreams_.useVariant(period, variant);
+ addVariantToSwitchHistory_(variant, fromAdaptation) {
const switchHistory = this.stats_.getSwitchHistory();
switchHistory.updateCurrentVariant(variant, fromAdaptation);
}
/**
- * @param {shaka.extern.Period} period
* @param {shaka.extern.Stream} textStream
* @param {boolean} fromAdaptation
* @private
*/
- addTextStreamToSwitchHistory_(period, textStream, fromAdaptation) {
- this.activeStreams_.useText(period, textStream);
+ addTextStreamToSwitchHistory_(textStream, fromAdaptation) {
const switchHistory = this.stats_.getSwitchHistory();
switchHistory.updateCurrentText(textStream, fromAdaptation);
}
@@ -3658,161 +3586,90 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
/**
* For CEA closed captions embedded in the video streams, create dummy text
* stream.
- * @param {!Array.} periods
+ * @param {!Array.} variants
* @private
*/
- createTextStreamsForClosedCaptions_(periods) {
+ createTextStreamsForClosedCaptions_(variants) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const TextStreamKind = shaka.util.ManifestParserUtils.TextStreamKind;
- for (const period of periods) {
- // A map of the closed captions id and the new dummy text stream.
- const closedCaptionsMap = new Map();
- for (const variant of period.variants) {
- if (variant.video && variant.video.closedCaptions) {
- const video = variant.video;
- for (const id of video.closedCaptions.keys()) {
- if (!closedCaptionsMap.has(id)) {
- const textStream = {
- id: this.nextExternalStreamId_++, // A globally unique ID.
- originalId: id, // The CC ID string, like 'CC1', 'CC3', etc.
- createSegmentIndex: () => Promise.resolve(),
- segmentIndex: null,
- mimeType: shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE,
- codecs: '',
- kind: TextStreamKind.CLOSED_CAPTION,
- encrypted: false,
- keyId: null,
- language: video.closedCaptions.get(id),
- label: null,
- type: ContentType.TEXT,
- primary: false,
- trickModeVideo: null,
- emsgSchemeIdUris: null,
- roles: video.roles,
- channelsCount: null,
- audioSamplingRate: null,
- closedCaptions: null,
- };
- closedCaptionsMap.set(id, textStream);
- }
+ // A map of the closed captions id and the new dummy text stream.
+ const closedCaptionsMap = new Map();
+ for (const variant of this.manifest_.variants) {
+ if (variant.video && variant.video.closedCaptions) {
+ const video = variant.video;
+ for (const id of video.closedCaptions.keys()) {
+ if (!closedCaptionsMap.has(id)) {
+ const textStream = {
+ id: this.nextExternalStreamId_++, // A globally unique ID.
+ originalId: id, // The CC ID string, like 'CC1', 'CC3', etc.
+ createSegmentIndex: () => Promise.resolve(),
+ segmentIndex: null,
+ mimeType: shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE,
+ codecs: '',
+ kind: TextStreamKind.CLOSED_CAPTION,
+ encrypted: false,
+ keyIds: [],
+ language: video.closedCaptions.get(id),
+ label: null,
+ type: ContentType.TEXT,
+ primary: false,
+ trickModeVideo: null,
+ emsgSchemeIdUris: null,
+ roles: video.roles,
+ channelsCount: null,
+ audioSamplingRate: null,
+ closedCaptions: null,
+ };
+ closedCaptionsMap.set(id, textStream);
}
}
}
- for (const textStream of closedCaptionsMap.values()) {
- period.textStreams.push(textStream);
- }
+ }
+ for (const textStream of closedCaptionsMap.values()) {
+ this.manifest_.textStreams.push(textStream);
}
}
/**
- * Filters a list of periods.
- * @param {!Array.} periods
+ * Filters a manifest, removing unplayable streams/variants.
+ *
+ * @param {?shaka.extern.Manifest} manifest
* @private
*/
- filterAllPeriods_(periods) {
+ filterManifest_(manifest) {
+ goog.asserts.assert(manifest, 'Manifest should exist!');
goog.asserts.assert(this.video_, 'Must not be destroyed');
- const ArrayUtils = shaka.util.ArrayUtils;
const StreamUtils = shaka.util.StreamUtils;
- /** @type {?shaka.extern.Stream} */
- const activeAudio = this.streamingEngine_ ?
- this.streamingEngine_.getBufferingAudio() :
- null;
- /** @type {?shaka.extern.Stream} */
- const activeVideo = this.streamingEngine_ ?
- this.streamingEngine_.getBufferingVideo() :
- null;
+ /** @type {?shaka.extern.Variant} */
+ const currentVariant = this.streamingEngine_ ?
+ this.streamingEngine_.getCurrentVariant() : null;
- for (const period of periods) {
- StreamUtils.filterNewPeriod(
- this.drmEngine_, activeAudio, activeVideo, period);
- }
+ StreamUtils.filterManifest(
+ this.drmEngine_, currentVariant, manifest);
- const validPeriodsCount = ArrayUtils.count(periods, (period) => {
- return period.variants.some(StreamUtils.isPlayable);
- });
+ const valid = manifest.variants.some(StreamUtils.isPlayable);
- // If none of the periods are playable, throw
+ // If none of the variants are playable, throw
// CONTENT_UNSUPPORTED_BY_BROWSER.
- if (validPeriodsCount == 0) {
+ if (!valid) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MANIFEST,
shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER);
}
- // If only some of the periods are playable, throw UNPLAYABLE_PERIOD.
- if (validPeriodsCount < periods.length) {
- throw new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.UNPLAYABLE_PERIOD);
- }
-
- for (const period of periods) {
- const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
- period.variants, this.config_.restrictions, this.maxHwRes_);
- if (tracksChanged && this.streamingEngine_ &&
- this.getPresentationPeriod_() == period) {
- this.onTracksChanged_();
- }
-
- this.checkRestrictedVariants_(period.variants);
- }
- }
-
- /**
- * Filters a new period.
- * @param {shaka.extern.Period} period
- * @private
- */
- filterNewPeriod_(period) {
- goog.asserts.assert(this.video_, 'Must not be destroyed');
- const StreamUtils = shaka.util.StreamUtils;
-
- /** @type {?shaka.extern.Stream} */
- const activeAudio = this.streamingEngine_ ?
- this.streamingEngine_.getBufferingAudio() :
- null;
- /** @type {?shaka.extern.Stream} */
- const activeVideo = this.streamingEngine_ ?
- this.streamingEngine_.getBufferingVideo() :
- null;
-
- StreamUtils.filterNewPeriod(
- this.drmEngine_, activeAudio, activeVideo, period);
-
- /** @type {!Array.} */
- const variants = period.variants;
-
- // Check for playable variants before restrictions, so that we can give a
- // special error when there were tracks but they were all filtered.
- const hasPlayableVariant = variants.some(StreamUtils.isPlayable);
- if (!hasPlayableVariant) {
- throw new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.UNPLAYABLE_PERIOD);
- }
-
- this.checkRestrictedVariants_(period.variants);
-
const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
- variants, this.config_.restrictions, this.maxHwRes_);
-
- // Trigger the track change event if the restrictions now prevent use from
- // using a variant that we previously thought we could use.
- if (tracksChanged && this.streamingEngine_ &&
- this.getPresentationPeriod_() == period) {
+ manifest.variants, this.config_.restrictions, this.maxHwRes_);
+ if (tracksChanged && this.streamingEngine_) {
this.onTracksChanged_();
}
- // For new Periods, we may need to create new sessions for any new init
- // data.
+ // We may need to create new sessions for any new init data.
const curDrmInfo = this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
if (curDrmInfo) {
- for (const variant of variants) {
+ for (const variant of manifest.variants) {
for (const drmInfo of variant.drmInfos) {
// Ignore any data for different key systems.
if (drmInfo.keySystem == curDrmInfo.keySystem) {
@@ -3824,138 +3681,46 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
}
}
- }
-
- /**
- * Switches to the given variant, deferring if needed.
- * @param {shaka.extern.Variant} variant
- * @param {boolean=} clearBuffer
- * @param {number=} safeMargin
- * @return {boolean}
- * @private
- */
- switchVariant_(variant, clearBuffer = false, safeMargin = 0) {
- if (this.switchingPeriods_) {
- // Store this action for later.
- this.deferredVariant_ = variant;
- this.deferredVariantClearBuffer_ = clearBuffer;
- this.deferredVariantClearBufferSafeMargin_ = safeMargin;
- return true;
- } else {
- // Act now.
- const changed =
- this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
- if (changed) {
- // Dispatch a 'variantchanged' event
- this.onVariantChanged_();
- }
- return changed;
- }
- }
- /**
- * Switches to the given text stream, deferring if needed.
- * @param {shaka.extern.Stream} textStream
- * @return {boolean}
- * @private
- */
- switchTextStream_(textStream) {
- if (this.switchingPeriods_) {
- // Store this action for later.
- this.deferredTextStream_ = textStream;
- return true;
- } else {
- // Act now.
- const changed = this.streamingEngine_.switchTextStream(textStream);
- if (changed) {
- this.onTextChanged_();
- }
- return changed;
- }
- }
-
- /**
- * Verifies that the active streams according to the player match those in
- * StreamingEngine.
- * @private
- */
- assertCorrectActiveStreams_() {
- if (!this.streamingEngine_ || !this.manifest_ || !goog.DEBUG) {
- return;
- }
-
- const activePeriod = this.streamingEngine_.getBufferingPeriod();
- /** @type {shaka.extern.Period} */
- const currentPeriod = this.getPresentationPeriod_();
- if (activePeriod == null || activePeriod != currentPeriod) {
- return;
- }
-
- const activeAudio = this.streamingEngine_.getBufferingAudio();
- const activeVideo = this.streamingEngine_.getBufferingVideo();
- const activeText = this.streamingEngine_.getBufferingText();
-
- // If we have deferred variants/text we want to compare against those rather
- // than what we are actually streaming.
- const expectedAudio = this.deferredVariant_ ?
- this.deferredVariant_.audio :
- activeAudio;
-
- const expectedVideo = this.deferredVariant_ ?
- this.deferredVariant_.video :
- activeVideo;
-
- const expectedText = this.deferredTextStream_ || activeText;
-
- const actualVariant = this.activeStreams_.getVariant(currentPeriod);
- const actualText = this.activeStreams_.getText(currentPeriod);
-
- goog.asserts.assert(
- actualVariant.audio == expectedAudio,
- 'Inconsistent active audio stream');
- goog.asserts.assert(
- actualVariant.video == expectedVideo,
- 'Inconsistent active video stream');
-
- // Because we always set a text stream to be active in the active stream
- // map, regardless of whether or not we are actually streaming text, it is
- // possible for these to be out of line.
- goog.asserts.assert(
- expectedText == null || actualText == expectedText,
- 'Inconsistent active text stream');
+ this.checkRestrictedVariants_(manifest);
}
/**
+ * @param {shaka.extern.Variant} initialVariant
* @param {number} time
- * @return {number}
+ * @return {!Promise.}
* @private
*/
- adjustStartTime_(time) {
+ async adjustStartTime_(initialVariant, time) {
/** @type {?shaka.extern.Stream} */
- const activeAudio = this.streamingEngine_.getBufferingAudio();
+ const activeAudio = initialVariant.audio;
/** @type {?shaka.extern.Stream} */
- const activeVideo = this.streamingEngine_.getBufferingVideo();
- /** @type {shaka.extern.Period} */
- const period = this.getPresentationPeriod_();
+ const activeVideo = initialVariant.video;
- // This method is called after StreamingEngine.init resolves, which means
- // that all the active streams have had createSegmentIndex called.
- function getAdjustedTime(stream, time) {
+ /**
+ * @param {?shaka.extern.Stream} stream
+ * @param {number} time
+ * @return {!Promise.}
+ */
+ const getAdjustedTime = async (stream, time) => {
if (!stream) {
return null;
}
- const ref = stream.segmentIndex[Symbol.iterator]().seek(
- time - period.startTime);
+
+ await stream.createSegmentIndex();
+ const ref = stream.segmentIndex[Symbol.iterator]().seek(time);
if (!ref) {
return null;
}
- const refTime = ref.startTime + period.startTime;
- goog.asserts.assert(refTime <= time, 'Segment should start before time');
+
+ const refTime = ref.startTime;
+ goog.asserts.assert(refTime <= time,
+ 'Segment should start before target time!');
return refTime;
- }
+ };
- const audioStartTime = getAdjustedTime(activeAudio, time);
- const videoStartTime = getAdjustedTime(activeVideo, time);
+ const audioStartTime = await getAdjustedTime(activeAudio, time);
+ const videoStartTime = await getAdjustedTime(activeVideo, time);
// If we have both video and audio times, pick the larger one. If we picked
// the smaller one, that one will download an entire segment to buffer the
@@ -3997,14 +3762,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.dispatchEvent(this.makeEvent_(eventName, {'buffering': isBuffering}));
}
- /**
- * Callback from PlayheadObserver.
- * @private
- */
- onChangePeriod_() {
- this.onTracksChanged_();
- }
-
/**
* A callback for when the playback rate changes. We need to watch the
* playback rate so that if the playback rate on the media element changes
@@ -4087,31 +3844,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Chooses a variant from all possible variants while taking into account
- * restrictions, preferences, and ABR.
+ * Update AbrManager with variants while taking into account restrictions,
+ * preferences, and ABR.
*
- * On error, this dispatches an error event and returns null.
+ * On error, this dispatches an error event and returns false.
*
- * @param {!Array.} allVariants
- * @return {?shaka.extern.Variant}
+ * @return {boolean} True if successful.
* @private
*/
- chooseVariant_(allVariants) {
- goog.asserts.assert(this.config_, 'Must not be destroyed');
-
+ updateAbrManagerVariants_() {
try {
- // |variants| are the filtered variants, use |period.variants| so we know
- // why they we restricted.
- this.checkRestrictedVariants_(allVariants);
+ goog.asserts.assert(this.manifest_, 'Manifest should exist by now!');
+ this.checkRestrictedVariants_(this.manifest_);
} catch (e) {
this.onError_(e);
- return null;
+ return false;
}
- goog.asserts.assert(
- allVariants.length, 'Should have thrown for no Variants.');
-
- const playableVariants = allVariants.filter((variant) => {
+ const playableVariants = this.manifest_.variants.filter((variant) => {
return shaka.util.StreamUtils.isPlayable(variant);
});
@@ -4119,213 +3869,98 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
const adaptationSet = this.currentAdaptationSetCriteria_.create(
playableVariants);
this.abrManager_.setVariants(Array.from(adaptationSet.values()));
- return this.abrManager_.chooseVariant();
+ return true;
+ }
+
+ /**
+ * Chooses a variant from all possible variants while taking into account
+ * restrictions, preferences, and ABR.
+ *
+ * On error, this dispatches an error event and returns null.
+ *
+ * @return {?shaka.extern.Variant}
+ * @private
+ */
+ chooseVariant_() {
+ if (this.updateAbrManagerVariants_()) {
+ return this.abrManager_.chooseVariant();
+ } else {
+ return null;
+ }
}
/**
* Choose a text stream from all possible text streams while taking into
* account user preference.
*
- * @param {!Array.} textStreams
* @return {?shaka.extern.Stream}
* @private
*/
- chooseTextStream_(textStreams) {
+ chooseTextStream_() {
const subset = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
- textStreams,
+ this.manifest_.textStreams,
this.currentTextLanguage_,
this.currentTextRole_);
-
return subset[0] || null;
}
/**
- * Chooses streams from the given Period and switches to them.
- * Called after a config change, a new text stream, a key status event, or an
- * explicit language change.
+ * Chooses a new Variant. If the new variant differs from the old one, it
+ * adds the new one to the switch history and switches to it.
*
- * @param {!shaka.extern.Period} period
- * @private
- */
- chooseStreamsAndSwitch_(period) {
- // Because we know we will change both variants and text, tell
- // the switch methods not to through separate onAdaptation events,
- // so UI doesn't update twice in a row. Switch everything, then throw
- // a single event from this method with onAdaptation_().
- const variantChanged =
- this.chooseVariantAndSwitch_(period, /* fireAdaptationEvent= */ false);
- const textChanged = this.chooseTextAndSwitch_(period);
- if (variantChanged || textChanged) {
- this.onAdaptation_();
- }
- }
-
-
- /**
- * Chooses a variant from the given Period and switches to it.
+ * Called after a config change, a key status event, or an explicit language
+ * change.
*
- * @param {!shaka.extern.Period} period
- * @param {boolean=} fireAdaptationEvent
- * @return {boolean}
* @private
*/
- chooseVariantAndSwitch_(period, fireAdaptationEvent = true) {
+ chooseVariantAndSwitch_() {
goog.asserts.assert(this.config_, 'Must not be destroyed');
// Because we're running this after a config change (manual language
- // change), a new text stream, or a key status event, and because switching
- // to an active stream is a no-op, it is always okay to clear the buffer
+ // change) or a key status event, it is always okay to clear the buffer
// here.
- const chosenVariant = this.chooseVariant_(period.variants);
- let changed = false;
+ const chosenVariant = this.chooseVariant_();
if (chosenVariant) {
- this.addVariantToSwitchHistory_(
- period, chosenVariant, /* fromAdaptation= */ true);
- changed = this.switchVariant_(chosenVariant, /* clearBuffers= */ true);
- }
-
- if (fireAdaptationEvent && changed) {
- // Send an adaptation event so that the UI can show the new
- // language/tracks.
- this.onAdaptation_();
- }
- return changed;
- }
-
-
- /**
- * If text should be streamed, chooses a text stream from
- * the given Period and switches to it.
- *
- * @param {!shaka.extern.Period} period
- * @return {boolean}
- * @private
- */
- chooseTextAndSwitch_(period) {
- goog.asserts.assert(this.config_, 'Must not be destroyed');
+ if (chosenVariant == this.streamingEngine_.getCurrentVariant()) {
+ shaka.log.debug('Variant already selected.');
+ return;
+ }
- // Only switch text if we should be streaming text right now.
- const chosenText = this.chooseTextStream_(period.textStreams);
- let changed = false;
- if (chosenText && this.shouldStreamText_()) {
- this.addTextStreamToSwitchHistory_(
- period, chosenText, /* fromAdaptation= */ true);
- changed = this.switchTextStream_(chosenText);
+ this.addVariantToSwitchHistory_(
+ chosenVariant, /* fromAdaptation= */ true);
+ this.streamingEngine_.switchVariant(
+ chosenVariant, /* clearBuffers= */ true, /* safeMargin= */ 0);
+ // Dispatch a 'variantchanged' event
+ this.onVariantChanged_();
}
- return changed;
- }
-
- /**
- * Callback from StreamingEngine, invoked when a period starts. This method
- * must always "succeed" so it may not throw an error. Any errors must be
- * routed to |onError|.
- *
- * @param {!shaka.extern.Period} period
- * @return {shaka.media.StreamingEngine.ChosenStreams}
- * An object containing the chosen variant and text stream.
- * @private
- */
- onChooseStreams_(period) {
- shaka.log.debug('onChooseStreams_', period);
-
- goog.asserts.assert(this.config_, 'Must not be destroyed');
-
- try {
- shaka.log.v2('onChooseStreams_, choosing variant from ', period.variants);
- shaka.log.v2('onChooseStreams_, choosing text from ', period.textStreams);
-
- const chosen = this.chooseStreams_(period);
-
- shaka.log.v2('onChooseStreams_, chose variant ', chosen.variant);
- shaka.log.v2('onChooseStreams_, chose text ', chosen.text);
-
- return chosen;
- } catch (e) {
- this.onError_(e);
- return {variant: null, text: null};
- }
+ // Send an adaptation event so that the UI can show the new
+ // language/tracks.
+ this.onAdaptation_();
}
/**
- * This is the internal logic for |onChooseStreams_|. This separation is done
- * to allow this implementation to throw errors without consequence.
- *
- * @param {shaka.extern.Period} period
- * The period that we are selecting streams from.
- * @return {shaka.media.StreamingEngine.ChosenStreams}
- * An object containing the chosen variant and text stream.
+ * Decide during startup if text should be streamed/shown.
* @private
*/
- chooseStreams_(period) {
- // We are switching Periods, so the AbrManager will be disabled. But if we
- // want to abr.enabled, we do not want to call AbrManager.enable before
- // canSwitch_ is called.
- this.switchingPeriods_ = true;
- this.abrManager_.disable();
- this.onAbrStatusChanged_();
-
- shaka.log.debug('Choosing new streams after period changed');
-
- let chosenVariant = this.chooseVariant_(period.variants);
- let chosenText = this.chooseTextStream_(period.textStreams);
-
- // Ignore deferred variant or text streams only if we are starting a new
- // period. In this case, any deferred switches were from an older period,
- // so they do not apply. We can still have deferred switches from the
- // current period in the case of an early call to select*Track while we are
- // setting up the first period. This can happen with the 'streaming' event.
- if (this.deferredVariant_) {
- if (period.variants.includes(this.deferredVariant_)) {
- chosenVariant = this.deferredVariant_;
- }
- this.deferredVariant_ = null;
- }
-
- if (this.deferredTextStream_) {
- if (period.textStreams.includes(this.deferredTextStream_)) {
- chosenText = this.deferredTextStream_;
- }
- this.deferredTextStream_ = null;
- }
-
- if (chosenVariant) {
- this.addVariantToSwitchHistory_(
- period, chosenVariant, /* fromAdaptation= */ true);
- }
-
- if (chosenText) {
- this.addTextStreamToSwitchHistory_(
- period, chosenText, /* fromAdaptation= */ true);
- }
-
+ setInitialTextState_(initialVariant, initialTextStream) {
// Check if we should show text (based on difference between audio and text
- // languages). Only check this during startup so we don't "pop-up" captions
- // mid playback.
- const startingUp = !this.streamingEngine_.getBufferingPeriod();
- const chosenAudio = chosenVariant ? chosenVariant.audio : null;
- if (startingUp && chosenText) {
- if (chosenAudio && this.shouldShowText_(chosenAudio, chosenText)) {
+ // languages).
+ if (initialTextStream) {
+ if (initialVariant.audio && this.shouldInitiallyShowText_(
+ initialVariant.audio, initialTextStream)) {
this.isTextVisible_ = true;
}
if (this.isTextVisible_) {
// If the cached value says to show text, then update the text displayer
- // since it defaults to not shown. Note that returning the |chosenText|
- // below will make StreamingEngine stream the text.
+ // since it defaults to not shown.
this.mediaSourceEngine_.getTextDisplayer().setTextVisibility(true);
goog.asserts.assert(this.shouldStreamText_(),
'Should be streaming text');
}
this.onTextTrackVisibility_();
- }
-
- // Don't fire a tracks-changed event since we aren't inside the new Period
- // yet.
- // Don't initialize with a text stream unless we should be streaming text.
- if (this.shouldStreamText_()) {
- return {variant: chosenVariant, text: chosenText};
} else {
- return {variant: chosenVariant, text: null};
+ this.isTextVisible_ = false;
}
}
@@ -4350,7 +3985,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
* @return {boolean}
* @private
*/
- shouldShowText_(audioStream, textStream) {
+ shouldInitiallyShowText_(audioStream, textStream) {
const LanguageUtils = shaka.util.LanguageUtils;
/** @type {string} */
@@ -4366,37 +4001,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
!LanguageUtils.areLanguageCompatible(audioLocale, textLocale));
}
- /**
- * Callback from StreamingEngine, invoked when the period is set up.
- *
- * @private
- */
- canSwitch_() {
- shaka.log.debug('canSwitch_');
- goog.asserts.assert(this.config_, 'Must not be destroyed');
-
- this.switchingPeriods_ = false;
-
- if (this.config_.abr.enabled) {
- this.abrManager_.enable();
- this.onAbrStatusChanged_();
- }
-
- // If we still have deferred switches, switch now.
- if (this.deferredVariant_) {
- this.streamingEngine_.switchVariant(
- this.deferredVariant_, this.deferredVariantClearBuffer_,
- this.deferredVariantClearBufferSafeMargin_);
- this.onVariantChanged_();
- this.deferredVariant_ = null;
- }
- if (this.deferredTextStream_) {
- this.streamingEngine_.switchTextStream(this.deferredTextStream_);
- this.onTextChanged_();
- this.deferredTextStream_ = null;
- }
- }
-
/**
* Callback from StreamingEngine.
*
@@ -4437,26 +4041,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
shaka.log.debug('switch_');
goog.asserts.assert(this.config_.abr.enabled,
'AbrManager should not call switch while disabled!');
- goog.asserts.assert(!this.switchingPeriods_,
- 'AbrManager should not call switch while transitioning between ' +
- 'Periods!');
goog.asserts.assert(this.manifest_, 'We need a manifest to switch ' +
'variants.');
- const period = this.findPeriodWithVariant_(variant);
- goog.asserts.assert(period, 'A period should contain the variant.');
-
- this.addVariantToSwitchHistory_(
- period, variant, /* fromAdaptation= */ true);
-
if (!this.streamingEngine_) {
// There's no way to change it.
return;
}
- if (this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin)) {
- this.onAdaptation_();
+ if (variant == this.streamingEngine_.getCurrentVariant()) {
+ // This isn't a change.
+ return;
}
+
+ this.addVariantToSwitchHistory_(variant, /* fromAdaptation= */ true);
+ this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
+ this.onAdaptation_();
}
/**
@@ -4628,12 +4228,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return;
}
- const restrictedStatuses = shaka.Player.restrictedStatuses_;
-
- /** @type {shaka.extern.Period} */
- const currentPeriod = this.getPresentationPeriod_();
- let tracksChanged = false;
-
const keyIds = Object.keys(keyStatusMap);
if (keyIds.length == 0) {
shaka.log.warning(
@@ -4646,7 +4240,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
// byte). In this case, it is only used to report global success/failure.
// See note about old platforms in: https://bit.ly/2tpez5Z
const isGlobalStatus = keyIds.length == 1 && keyIds[0] == '00';
-
if (isGlobalStatus) {
shaka.log.warning(
'Got a synthetic key status event, so we don\'t know the real key ' +
@@ -4654,48 +4247,48 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
'restrictions so we don\'t select those tracks.');
}
+ const restrictedStatuses = shaka.Player.restrictedStatuses_;
+ let tracksChanged = false;
+
// Only filter tracks for keys if we have some key statuses to look at.
if (keyIds.length) {
- for (const period of this.manifest_.periods) {
- for (const variant of period.variants) {
- const streams = shaka.util.StreamUtils.getVariantStreams(variant);
-
- for (const stream of streams) {
- const originalAllowed = variant.allowedByKeySystem;
-
- // Only update if we have a key ID for the stream.
- // If the key isn't present, then we don't have that key and the
- // track should be restricted.
- if (stream.keyId) {
- const keyStatus =
- keyStatusMap[isGlobalStatus ? '00' : stream.keyId];
- variant.allowedByKeySystem =
+ for (const variant of this.manifest_.variants) {
+ const streams = shaka.util.StreamUtils.getVariantStreams(variant);
+
+ for (const stream of streams) {
+ const originalAllowed = variant.allowedByKeySystem;
+
+ // Only update if we have key IDs for the stream. If the keys aren't
+ // all present, then the track should be restricted.
+ if (stream.keyIds.length) {
+ variant.allowedByKeySystem = true;
+
+ for (const keyId of stream.keyIds) {
+ const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
+ variant.allowedByKeySystem = variant.allowedByKeySystem &&
!!keyStatus && !restrictedStatuses.includes(keyStatus);
}
+ }
- if (originalAllowed != variant.allowedByKeySystem) {
- tracksChanged = true;
- }
- } // for (const stream of streams)
- } // for (const variant of period.variants)
- } // for (const period of this.manifest_.periods)
+ if (originalAllowed != variant.allowedByKeySystem) {
+ tracksChanged = true;
+ }
+ } // for (const stream of streams)
+ } // for (const variant of this.manifest_.variants)
} // if (keyIds.length)
- // TODO: Get StreamingEngine to track variants and create
- // getBufferingVariant()
- const activeAudio = this.streamingEngine_.getBufferingAudio();
- const activeVideo = this.streamingEngine_.getBufferingVideo();
- const activeVariant = shaka.util.StreamUtils.getVariantByStreams(
- activeAudio, activeVideo, currentPeriod.variants);
+ if (tracksChanged) {
+ this.updateAbrManagerVariants_();
+ }
- if (activeVariant && !activeVariant.allowedByKeySystem) {
- shaka.log.debug('Choosing new variants after key status changed');
- this.chooseVariantAndSwitch_(currentPeriod);
+ const currentVariant = this.streamingEngine_.getCurrentVariant();
+ if (currentVariant && !currentVariant.allowedByKeySystem) {
+ shaka.log.debug('Choosing new streams after key status changed');
+ this.chooseVariantAndSwitch_();
}
if (tracksChanged) {
this.onTracksChanged_();
- this.chooseVariant_(currentPeriod.variants);
}
}
@@ -4758,13 +4351,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
/**
- * Checks the given variants and if they are all restricted, throw an
- * appropriate exception.
+ * Checks if the variants are all restricted, and throw an appropriate
+ * exception if so.
+ *
+ * @param {shaka.extern.Manifest} manifest
*
- * @param {!Array.} variants
* @private
*/
- checkRestrictedVariants_(variants) {
+ checkRestrictedVariants_(manifest) {
const restrictedStatuses = shaka.Player.restrictedStatuses_;
const keyStatusMap =
this.drmEngine_ ? this.drmEngine_.getKeyStatuses() : {};
@@ -4772,11 +4366,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
const isGlobalStatus = keyIds.length && keyIds[0] == '00';
let hasPlayable = false;
- let hasAppRestrict = false;
- const missingKeys = [];
- const badKeyStatuses = [];
+ let hasAppRestrictions = false;
+
+ /** @type {!Set.} */
+ const missingKeys = new Set();
+
+ /** @type {!Set.} */
+ const badKeyStatuses = new Set();
- for (const variant of variants) {
+ for (const variant of manifest.variants) {
// TODO: Combine with onKeyStatus_.
const streams = [];
if (variant.audio) {
@@ -4787,22 +4385,20 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}
for (const stream of streams) {
- if (stream.keyId) {
- const keyStatus = keyStatusMap[isGlobalStatus ? '00' : stream.keyId];
- if (!keyStatus) {
- if (!missingKeys.includes(stream.keyId)) {
- missingKeys.push(stream.keyId);
- }
- } else if (restrictedStatuses.includes(keyStatus)) {
- if (!badKeyStatuses.includes(keyStatus)) {
- badKeyStatuses.push(keyStatus);
+ if (stream.keyIds.length) {
+ for (const keyId of stream.keyIds) {
+ const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
+ if (!keyStatus) {
+ missingKeys.add(keyId);
+ } else if (restrictedStatuses.includes(keyStatus)) {
+ badKeyStatuses.add(keyStatus);
}
}
- }
+ } // if (stream.keyIds.length)
}
if (!variant.allowedByApplication) {
- hasAppRestrict = true;
+ hasAppRestrictions = true;
} else if (variant.allowedByKeySystem) {
hasPlayable = true;
}
@@ -4811,9 +4407,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
if (!hasPlayable) {
/** @type {shaka.extern.RestrictionInfo} */
const data = {
- hasAppRestrictions: hasAppRestrict,
- missingKeys: missingKeys,
- restrictedKeyStatuses: badKeyStatuses,
+ hasAppRestrictions,
+ missingKeys: Array.from(missingKeys),
+ restrictedKeyStatuses: Array.from(badKeyStatuses),
};
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
@@ -4915,134 +4511,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return pairings;
}
- /**
- * Get the variants that the user can select. The variants will be based on
- * the period that the playhead is in and what variants are playable.
- *
- * @return {!Array.}
- * @private
- */
- getSelectableVariants_() {
- // Use the period that is currently playing, allowing the change to affect
- // the "now".
- /** @type {shaka.extern.Period} */
- const currentPeriod = this.getPresentationPeriod_();
-
- // If we have been called before we load content or after we have unloaded
- // content, then we should return no variants.
- if (currentPeriod == null) {
- return [];
- }
-
- this.assertCorrectActiveStreams_();
-
- return currentPeriod.variants.filter((variant) => {
- return shaka.util.StreamUtils.isPlayable(variant);
- });
- }
-
- /**
- * Get the text streams that the user can select. The streams will be based on
- * the period that the playhead is in and what streams have finished loading.
- *
- * @return {!Array.}
- * @private
- */
- getSelectableText_() {
- // Use the period that is currently playing, allowing the change to affect
- // the "now".
- /** @type {shaka.extern.Period} */
- const currentPeriod = this.getPresentationPeriod_();
-
- // If we have been called before we load content or after we have unloaded
- // content, then we should return no streams.
- if (currentPeriod == null) {
- return [];
- }
-
- this.assertCorrectActiveStreams_();
-
- // Don't show return streams that are still loading.
- return currentPeriod.textStreams.filter((stream) => {
- return !this.loadingTextStreams_.has(stream);
- });
- }
-
- /**
- * Get the period that is on the screen. This will return |null| if nothing
- * is loaded.
- *
- * @return {shaka.extern.Period}
- * @private
- */
- getPresentationPeriod_() {
- goog.asserts.assert(this.manifest_ && this.playhead_,
- 'Only ask for the presentation period when loaded with media source.');
-
- const presentationTime = this.playhead_.getTime();
-
- let lastPeriod = null;
-
- // Periods are ordered by |startTime|. If we always keep the last period
- // that started before our presentation time, it means we will have the
- // best guess at which period we are presenting.
- for (const period of this.manifest_.periods) {
- if (period.startTime <= presentationTime) {
- lastPeriod = period;
- }
- }
-
- goog.asserts.assert(lastPeriod, 'Should have found a period.');
- return lastPeriod;
- }
-
- /**
- * Get the variant that we are currently presenting to the user. If we are not
- * showing anything, then we will return |null|.
- *
- * @return {?shaka.extern.Variant}
- * @private
- */
- getPresentationVariant_() {
- /** @type {shaka.extern.Period} */
- const currentPeriod = this.getPresentationPeriod_();
- return this.activeStreams_.getVariant(currentPeriod);
- }
-
- /**
- * Get the text stream that we are either currently presenting to the user or
- * will be presenting will captions are enabled. If we have no text to
- * display, this will return |null|.
- *
- * @return {?shaka.extern.Stream}
- * @private
- */
- getPresentationText_() {
- /** @type {shaka.extern.Period} */
- const currentPeriod = this.getPresentationPeriod_();
-
- // Can't have a text stream when there is no period.
- if (currentPeriod == null) {
- return null;
- }
-
- // This is a workaround for the demo page to be able to display the list of
- // text tracks. If no text track is currently active, pick the one that's
- // going to be streamed when captions are enabled and mark it as active.
- if (!this.activeStreams_.getText(currentPeriod)) {
- const textStreams = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
- currentPeriod.textStreams,
- this.currentTextLanguage_,
- this.currentTextRole_);
-
- if (textStreams.length) {
- this.activeStreams_.useText(currentPeriod, textStreams[0]);
- }
- }
-
- return this.activeStreams_.getText(currentPeriod);
- }
-
/**
* Assuming the player is playing content with media source, check if the
* player has buffered enough content to make it to the end of the
@@ -5122,24 +4590,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return bufferEnd >= this.video_.duration - fudge;
}
- /**
- * Find the period in |this.manifest_| that contains |variant|. If no period
- * contains |variant| this will return |null|.
- *
- * @param {shaka.extern.Variant} variant
- * @return {?shaka.extern.Period}
- * @private
- */
- findPeriodWithVariant_(variant) {
- for (const period of this.manifest_.periods) {
- if (period.variants.includes(variant)) {
- return period;
- }
- }
-
- return null;
- }
-
/**
* Create an error for when we purposely interrupt a load operation.
*
diff --git a/lib/util/error.js b/lib/util/error.js
index 365c3e98ef..0fe4036787 100644
--- a/lib/util/error.js
+++ b/lib/util/error.js
@@ -491,12 +491,7 @@ shaka.util.Error.Code = {
/** The DASH Manifest specifies conflicting key IDs. */
'DASH_CONFLICTING_KEY_IDS': 4010,
- /**
- * The manifest contains a period with no playable streams.
- * Either the period was originally empty, or the streams within cannot be
- * played on this browser or platform.
- */
- 'UNPLAYABLE_PERIOD': 4011,
+ // RETIRED: 'UNPLAYABLE_PERIOD': 4011,
/**
* There exist some streams that could be decoded, but restrictions imposed
@@ -517,11 +512,7 @@ shaka.util.Error.Code = {
// RETIRED: 'INTERNAL_ERROR_KEY_STATUS': 4013,
- /**
- * No valid periods were found in the manifest. Please check that your
- * manifest is correct and free of typos.
- */
- 'NO_PERIODS': 4014,
+ // RETIRED: 'NO_PERIODS': 4014,
/**
* HLS playlist doesn't start with a mandory #EXTM3U tag.
@@ -644,22 +635,22 @@ shaka.util.Error.Code = {
*/
'HLS_INTERNAL_SKIP_STREAM': 4035,
+ /** The Manifest contained no Variants. */
+ 'NO_VARIANTS': 4036,
+
+
// RETIRED: 'INCONSISTENT_BUFFER_STATE': 5000,
// RETIRED: 'INVALID_SEGMENT_INDEX': 5001,
// RETIRED: 'SEGMENT_DOES_NOT_EXIST': 5002,
// RETIRED: 'CANNOT_SATISFY_BYTE_LIMIT': 5003,
// RETIRED: 'BAD_SEGMENT': 5004,
+ // RETIRED: 'INVALID_STREAMS_CHOSEN': 5005,
/**
- * The StreamingEngine called onChooseStreams() but the callback receiver
- * did not return the correct number or type of Streams.
- *
- * This can happen when there is multi-Period content where one Period is
- * video+audio and another is video-only or audio-only. We don't support this
- * case because it is incompatible with MSE. When the browser reaches the
- * transition, it will pause, waiting for the audio stream.
+ * This would only happen if StreamingEngine were not started correctly, and
+ * should not be seen in production.
*/
- 'INVALID_STREAMS_CHOSEN': 5005,
+ 'STREAMING_ENGINE_STARTUP_INVALID_STATE': 5006,
/**
diff --git a/lib/util/manifest_filter.js b/lib/util/manifest_filter.js
index e19b1f50ee..0a5211987d 100644
--- a/lib/util/manifest_filter.js
+++ b/lib/util/manifest_filter.js
@@ -5,8 +5,6 @@
goog.provide('shaka.util.ManifestFilter');
-goog.require('goog.asserts');
-
/**
* This utility class contains all the functions used to filter manifests
@@ -22,12 +20,10 @@ shaka.util.ManifestFilter = class {
* @param {{width: number, height:number}} maxHwResolution
*/
static filterByRestrictions(manifest, restrictions, maxHwResolution) {
- for (const period of manifest.periods) {
- period.variants = period.variants.filter((variant) => {
- return shaka.util.StreamUtils.meetsRestrictions(
- variant, restrictions, maxHwResolution);
- });
- }
+ manifest.variants = manifest.variants.filter((variant) => {
+ return shaka.util.StreamUtils.meetsRestrictions(
+ variant, restrictions, maxHwResolution);
+ });
}
@@ -40,20 +36,18 @@ shaka.util.ManifestFilter = class {
static filterByMediaSourceSupport(manifest) {
const MediaSourceEngine = shaka.media.MediaSourceEngine;
- for (const period of manifest.periods) {
- period.variants = period.variants.filter((variant) => {
- let supported = true;
- if (variant.audio) {
- supported =
- supported && MediaSourceEngine.isStreamSupported(variant.audio);
- }
- if (variant.video) {
- supported =
- supported && MediaSourceEngine.isStreamSupported(variant.video);
- }
- return supported;
- });
- }
+ manifest.variants = manifest.variants.filter((variant) => {
+ let supported = true;
+ if (variant.audio) {
+ supported =
+ supported && MediaSourceEngine.isStreamSupported(variant.audio);
+ }
+ if (variant.video) {
+ supported =
+ supported && MediaSourceEngine.isStreamSupported(variant.video);
+ }
+ return supported;
+ });
}
/**
@@ -64,196 +58,8 @@ shaka.util.ManifestFilter = class {
* @param {!shaka.media.DrmEngine} drmEngine
*/
static filterByDrmSupport(manifest, drmEngine) {
- for (const period of manifest.periods) {
- period.variants = period.variants.filter((variant) => {
- return drmEngine.supportsVariant(variant);
- });
- }
- }
-
- /**
- * Filter the variants in |manifest| to only include those that use codecs
- * that will be supported in each variant. This ensures playback from the
- * first period to the last period by "jumping between" compatible variants.
- *
- * @param {shaka.extern.Manifest} manifest
- */
- static filterByCommonCodecs(manifest) {
- goog.asserts.assert(manifest.periods.length > 0,
- 'There should be at least be one period');
-
- const ManifestFilter = shaka.util.ManifestFilter;
-
- // Create a set of summaries that occur in each period.
- /** @type {!shaka.util.ManifestFilter.VariantCodecSummarySet} */
- const common = new shaka.util.ManifestFilter.VariantCodecSummarySet();
-
- let first = true;
- for (const period of manifest.periods) {
- /** @type {!shaka.util.ManifestFilter.VariantCodecSummarySet} */
- const next = ManifestFilter.VariantCodecSummarySet.fromVariants(
- period.variants);
-
- if (first) {
- common.includeAll(next);
- first = false;
- } else {
- common.onlyKeep(next);
- }
- }
-
- // Filter the variants in the period by whether they match a summary that
- // occurs in every period.
- for (const period of manifest.periods) {
- period.variants = period.variants.filter((variant) => {
- const summary = new ManifestFilter.VariantCodecSummary(variant);
- return common.contains(summary);
- });
- }
- }
-
- /**
- * Go through each period and apply the filter to the set of variants.
- * |filter| will only be given the set of variants in the current period that
- * are compatible with at least one variant in the previous period.
- *
- * @param {shaka.extern.Manifest} manifest
- * @param {function(shaka.extern.Period):!Promise} filter
- * @return {!Promise}
- */
- static async rollingFilter(manifest, filter) {
- // Store a reference to the variants so that the next period can easily
- // reference them too.
- /** @type {shaka.util.ManifestFilter.VariantCodecSummarySet} */
- let previous = null;
-
- for (const period of manifest.periods) {
- // Remove all variants that don't have a compatible variant in the
- // previous period. If we were to only use the first variant, we would
- // risk a variant being removed from a later period that would break that
- // path across all periods.
- if (previous) {
- period.variants = period.variants.filter((variant) => {
- const summary =
- new shaka.util.ManifestFilter.VariantCodecSummary(variant);
- return previous.contains(summary);
- });
- }
-
- // eslint-disable-next-line no-await-in-loop
- await filter(period);
-
- // Use the results of filtering this period as the "previous" for the
- // next period.
- previous = shaka.util.ManifestFilter.VariantCodecSummarySet.fromVariants(
- period.variants);
- }
- }
-};
-
-
-/**
- * The variant codec summary is a summary of the codec information for a given
- * codec. This can be used to test the compatibility between variants by
- * checking that their summaries contain the same information.
- *
- * @final
- */
-shaka.util.ManifestFilter.VariantCodecSummary = class {
- /**
- * @param {shaka.extern.Variant} variant
- */
- constructor(variant) {
- // We summarize a variant based on the basic mime type and the basic
- // codec because they must match for two variants to be compatible. For
- // example, we can't adapt between WebM and MP4, nor can we adapt between
- // mp4a.* to ec-3.
-
- const audio = variant.audio;
- const video = variant.video;
-
- /** @private {?string} */
- this.audioMime_ = audio ? audio.mimeType : null;
- /** @private {?string} */
- this.audioCodec_ = audio ? audio.codecs.split('.')[0] : null;
- /** @private {?string} */
- this.videoMime_ = video ? video.mimeType : null;
- /** @private {?string} */
- this.videoCodec_ = video ? video.codecs.split('.')[0] : null;
- }
-
- /**
- * Check if this summaries is equal to another.
- *
- * @param {!shaka.util.ManifestFilter.VariantCodecSummary} other
- * @return {boolean}
- */
- equals(other) {
- return this.audioMime_ == other.audioMime_ &&
- this.audioCodec_ == other.audioCodec_ &&
- this.videoMime_ == other.videoMime_ &&
- this.videoCodec_ == other.videoCodec_;
- }
-};
-
-
-/**
- * @final
- */
-shaka.util.ManifestFilter.VariantCodecSummarySet = class {
- constructor() {
- /** @private {!Array.} */
- this.all_ = [];
- }
-
- /**
- * @param {!shaka.util.ManifestFilter.VariantCodecSummary} summary
- */
- add(summary) {
- if (!this.contains(summary)) {
- this.all_.push(summary);
- }
- }
-
- /**
- * Add all items from |other| to |this|.
- * @param {!shaka.util.ManifestFilter.VariantCodecSummarySet} other
- */
- includeAll(other) {
- for (const item of other.all_) {
- this.add(item);
- }
- }
-
- /**
- * Remove all items from |this| that are not in |other|.
- * @param {!shaka.util.ManifestFilter.VariantCodecSummarySet} other
- */
- onlyKeep(other) {
- this.all_ = this.all_.filter((x) => other.contains(x));
- }
-
- /**
- * @param {!shaka.util.ManifestFilter.VariantCodecSummary} summary
- * @return {boolean}
- */
- contains(summary) {
- return this.all_.some((x) => summary.equals(x));
- }
-
- /**
- * Create a set of variant codec summaries for a list of variants. The set
- * may have fewer elements than the list if there are variants with similar
- * codecs.
- *
- * @param {!Array.} variants
- * @return {!shaka.util.ManifestFilter.VariantCodecSummarySet}
- */
- static fromVariants(variants) {
- const set = new shaka.util.ManifestFilter.VariantCodecSummarySet();
- for (const variant of variants) {
- set.add(new shaka.util.ManifestFilter.VariantCodecSummary(variant));
- }
- return set;
+ manifest.variants = manifest.variants.filter((variant) => {
+ return drmEngine.supportsVariant(variant);
+ });
}
};
diff --git a/lib/util/periods.js b/lib/util/periods.js
index afa6e73add..9f0158b5df 100644
--- a/lib/util/periods.js
+++ b/lib/util/periods.js
@@ -5,7 +5,6 @@
goog.provide('shaka.util.Periods');
-
/**
* This is a collection of period-focused utility methods.
*
@@ -13,45 +12,44 @@ goog.provide('shaka.util.Periods');
*/
shaka.util.Periods = class {
/**
- * Get all the variants across all periods.
+ * Stitch together variants across periods.
*
- * @param {!Iterable.} periods
+ * @param {!Array.>} variantsPerPeriod
* @return {!Array.}
*/
- static getAllVariantsFrom(periods) {
- const found = [];
-
- for (const period of periods) {
- for (const variant of period.variants) {
- found.push(variant);
- }
+ static stitchVariants(variantsPerPeriod) {
+ if (variantsPerPeriod.length == 1) {
+ return variantsPerPeriod[0];
}
- return found;
+ // FIXME(#1339): implement
+ return variantsPerPeriod[0];
}
/**
- * Find our best guess at which period contains the given time. If
- * |timeInSeconds| starts before the first period, then |null| will be
- * returned.
+ * Stitch together text streams across periods.
*
- * @param {!Iterable.} periods
- * @param {number} timeInSeconds
- * @return {?shaka.extern.Period}
+ * @param {!Array.>} textStreamsPerPeriod
+ * @return {!Array.}
*/
- static findPeriodForTime(periods, timeInSeconds) {
- let bestGuess = null;
-
- // Go period-by-period and see if the period started before our current
- // time. If so, we could be in that period. Since periods are supposed to be
- // in order by start time, we can allow later periods to override our best
- // guess.
- for (const period of periods) {
- if (timeInSeconds >= period.startTime) {
- bestGuess = period;
- }
+ static stitchTextStreams(textStreamsPerPeriod) {
+ if (textStreamsPerPeriod.length == 1) {
+ return textStreamsPerPeriod[0];
}
- return bestGuess;
+ // FIXME(#1339): implement
+ return textStreamsPerPeriod[0];
+ }
+
+ /**
+ * Stitch together DB streams across periods, taking a mix of stream types.
+ * The offline database does not separate these by type.
+ *
+ * @param {!Array.>} streamDBsPerPeriod
+ * @return {!Array.}
+ */
+ static stitchStreamDBs(streamDBsPerPeriod) {
+ // FIXME(#1339): implement
+ return streamDBsPerPeriod[0];
}
};
diff --git a/lib/util/stream_utils.js b/lib/util/stream_utils.js
index a82a63aa2c..c5e05653cf 100644
--- a/lib/util/stream_utils.js
+++ b/lib/util/stream_utils.js
@@ -31,18 +31,13 @@ shaka.util.StreamUtils = class {
static chooseCodecsAndFilterManifest(manifest, preferredAudioChannelCount) {
const MimeUtils = shaka.util.MimeUtils;
- // Collect a list of variants for all periods.
- /** @type {!Array.} */
- let variants = manifest.periods.reduce(
- (variants, period) => variants.concat(period.variants), []);
-
// To start, consider a subset of variants based on audio channel
// preferences.
// For some content (#1013), surround-sound variants will use a different
// codec than stereo variants, so it is important to choose codecs **after**
// considering the audio channel config.
- variants = shaka.util.StreamUtils.filterVariantsByAudioChannelCount(
- variants, preferredAudioChannelCount);
+ const variants = shaka.util.StreamUtils.filterVariantsByAudioChannelCount(
+ manifest.variants, preferredAudioChannelCount);
function variantCodecs(variant) {
// Only consider the base of the codec string. For example, these should
@@ -93,17 +88,15 @@ shaka.util.StreamUtils = class {
// Filter out any variants that don't match, forcing AbrManager to choose
// from the most efficient variants possible.
- for (const period of manifest.periods) {
- period.variants = period.variants.filter((variant) => {
- const codecs = variantCodecs(variant);
- if (codecs == bestCodecs) {
- return true;
- }
+ manifest.variants = manifest.variants.filter((variant) => {
+ const codecs = variantCodecs(variant);
+ if (codecs == bestCodecs) {
+ return true;
+ }
- shaka.log.debug('Dropping Variant (better codec available)', variant);
- return false;
- });
- }
+ shaka.log.debug('Dropping Variant (better codec available)', variant);
+ return false;
+ });
}
/**
@@ -190,28 +183,17 @@ shaka.util.StreamUtils = class {
/**
- * Alters the given Period to filter out any unplayable streams.
+ * Alters the given Manifest to filter out any unplayable streams.
*
* @param {shaka.media.DrmEngine} drmEngine
- * @param {?shaka.extern.Stream} activeAudio
- * @param {?shaka.extern.Stream} activeVideo
- * @param {shaka.extern.Period} period
+ * @param {?shaka.extern.Variant} currentVariant
+ * @param {shaka.extern.Manifest} manifest
*/
- static filterNewPeriod(drmEngine, activeAudio, activeVideo, period) {
+ static filterManifest(drmEngine, currentVariant, manifest) {
const StreamUtils = shaka.util.StreamUtils;
- if (activeAudio) {
- goog.asserts.assert(StreamUtils.isAudio(activeAudio),
- 'Audio streams must have the audio type.');
- }
-
- if (activeVideo) {
- goog.asserts.assert(StreamUtils.isVideo(activeVideo),
- 'Video streams must have the video type.');
- }
-
// Filter variants.
- period.variants = period.variants.filter((variant) => {
+ manifest.variants = manifest.variants.filter((variant) => {
if (drmEngine && drmEngine.initialized()) {
if (!drmEngine.supportsVariant(variant)) {
shaka.log.debug('Dropping variant - not compatible with key system',
@@ -235,22 +217,22 @@ shaka.util.StreamUtils = class {
return false;
}
- if (audio && activeAudio) {
- if (!StreamUtils.areStreamsCompatible_(audio, activeAudio)) {
+ if (audio && currentVariant && currentVariant.audio) {
+ if (!StreamUtils.areStreamsCompatible_(audio, currentVariant.audio)) {
shaka.log.debug('Droping variant - not compatible with active audio',
'active audio',
- StreamUtils.getStreamSummaryString_(activeAudio),
+ StreamUtils.getStreamSummaryString_(currentVariant.audio),
'variant.audio',
StreamUtils.getStreamSummaryString_(audio));
return false;
}
}
- if (video && activeVideo) {
- if (!StreamUtils.areStreamsCompatible_(video, activeVideo)) {
+ if (video && currentVariant && currentVariant.video) {
+ if (!StreamUtils.areStreamsCompatible_(video, currentVariant.video)) {
shaka.log.debug('Droping variant - not compatible with active video',
'active video',
- StreamUtils.getStreamSummaryString_(activeVideo),
+ StreamUtils.getStreamSummaryString_(currentVariant.video),
'variant.video',
StreamUtils.getStreamSummaryString_(video));
return false;
@@ -261,7 +243,7 @@ shaka.util.StreamUtils = class {
});
// Filter text streams.
- period.textStreams = period.textStreams.filter((stream) => {
+ manifest.textStreams = manifest.textStreams.filter((stream) => {
const fullMimeType = shaka.util.MimeUtils.getFullType(
stream.mimeType, stream.codecs);
const keep = shaka.text.TextEngine.isTypeSupported(fullMimeType);
@@ -728,38 +710,6 @@ shaka.util.StreamUtils = class {
}
- /**
- * Finds a Variant with given audio and video streams.
- * Returns null if no such Variant was found.
- *
- * @param {?shaka.extern.Stream} audio
- * @param {?shaka.extern.Stream} video
- * @param {!Array.} variants
- * @return {?shaka.extern.Variant}
- */
- static getVariantByStreams(audio, video, variants) {
- if (audio) {
- goog.asserts.assert(
- shaka.util.StreamUtils.isAudio(audio),
- 'Audio streams must have the audio type.');
- }
-
- if (video) {
- goog.asserts.assert(
- shaka.util.StreamUtils.isVideo(video),
- 'Video streams must have the video type.');
- }
-
- for (const variant of variants) {
- if (variant.audio == audio && variant.video == video) {
- return variant;
- }
- }
-
- return null;
- }
-
-
/**
* Checks if the given stream is an audio stream.
*
diff --git a/test/abr/simple_abr_manager_unit.js b/test/abr/simple_abr_manager_unit.js
index ba7d5b02e5..38503327e9 100644
--- a/test/abr/simple_abr_manager_unit.js
+++ b/test/abr/simple_abr_manager_unit.js
@@ -25,46 +25,44 @@ describe('SimpleAbrManager', () => {
// Keep unsorted.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(100, (variant) => {
- variant.bandwidth = 4e5; // 400 kbps
- variant.addAudio(0);
- variant.addVideo(1);
- });
- period.addVariant(101, (variant) => {
- variant.bandwidth = 1e6; // 1000 kbps
- variant.addAudio(2);
- variant.addVideo(3);
- });
- period.addVariant(102, (variant) => {
- variant.bandwidth = 5e5; // 500 kbps
- variant.addAudio(4);
- variant.addVideo(5);
- });
- period.addVariant(103, (variant) => {
- variant.bandwidth = 2e6;
- variant.addAudio(6);
- variant.addVideo(7);
- });
- period.addVariant(104, (variant) => {
- variant.bandwidth = 2e6; // Identical on purpose.
- variant.addAudio(8);
- variant.addVideo(9);
- });
- period.addVariant(105, (variant) => {
- variant.bandwidth = 6e5;
- variant.addAudio(10);
- variant.addVideo(11);
- });
- period.addTextStream(20);
- period.addTextStream(21);
+ manifest.addVariant(100, (variant) => {
+ variant.bandwidth = 4e5; // 400 kbps
+ variant.addAudio(0);
+ variant.addVideo(1);
+ });
+ manifest.addVariant(101, (variant) => {
+ variant.bandwidth = 1e6; // 1000 kbps
+ variant.addAudio(2);
+ variant.addVideo(3);
+ });
+ manifest.addVariant(102, (variant) => {
+ variant.bandwidth = 5e5; // 500 kbps
+ variant.addAudio(4);
+ variant.addVideo(5);
+ });
+ manifest.addVariant(103, (variant) => {
+ variant.bandwidth = 2e6;
+ variant.addAudio(6);
+ variant.addVideo(7);
+ });
+ manifest.addVariant(104, (variant) => {
+ variant.bandwidth = 2e6; // Identical on purpose.
+ variant.addAudio(8);
+ variant.addVideo(9);
});
+ manifest.addVariant(105, (variant) => {
+ variant.bandwidth = 6e5;
+ variant.addAudio(10);
+ variant.addVideo(11);
+ });
+ manifest.addTextStream(20);
+ manifest.addTextStream(21);
});
config = shaka.util.PlayerConfiguration.createDefault().abr;
config.defaultBandwidthEstimate = defaultBandwidthEstimate;
- variants = manifest.periods[0].variants;
+ variants = manifest.variants;
abrManager = new shaka.abr.SimpleAbrManager();
abrManager.init(shaka.test.Util.spyFunc(switchCallback));
@@ -97,19 +95,17 @@ describe('SimpleAbrManager', () => {
it('can choose from audio only variants', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 4e5;
- variant.addAudio(0);
- });
- period.addVariant(1, (variant) => {
- variant.bandwidth = 1e6;
- variant.addAudio(2);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 4e5;
+ variant.addAudio(0);
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 1e6;
+ variant.addAudio(2);
});
});
- abrManager.setVariants(manifest.periods[0].variants);
+ abrManager.setVariants(manifest.variants);
const chosen = abrManager.chooseVariant();
expect(chosen).not.toBe(null);
expect(chosen.audio).not.toBe(null);
@@ -118,19 +114,17 @@ describe('SimpleAbrManager', () => {
it('can choose from video only variants', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 4e5;
- variant.addVideo(0);
- });
- period.addVariant(1, (variant) => {
- variant.bandwidth = 1e6;
- variant.addVideo(2);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 4e5;
+ variant.addVideo(0);
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 1e6;
+ variant.addVideo(2);
});
});
- abrManager.setVariants(manifest.periods[0].variants);
+ abrManager.setVariants(manifest.variants);
const chosen = abrManager.chooseVariant();
expect(chosen).not.toBe(null);
expect(chosen.audio).toBe(null);
@@ -319,23 +313,21 @@ describe('SimpleAbrManager', () => {
it('will respect restrictions', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(10, (variant) => {
- variant.bandwidth = 1e5;
- variant.addVideo(0, (stream) => {
- stream.size(50, 50);
- });
+ manifest.addVariant(10, (variant) => {
+ variant.bandwidth = 1e5;
+ variant.addVideo(0, (stream) => {
+ stream.size(50, 50);
});
- period.addVariant(11, (variant) => {
- variant.bandwidth = 2e5;
- variant.addVideo(1, (stream) => {
- stream.size(200, 200);
- });
+ });
+ manifest.addVariant(11, (variant) => {
+ variant.bandwidth = 2e5;
+ variant.addVideo(1, (stream) => {
+ stream.size(200, 200);
});
});
});
- abrManager.setVariants(manifest.periods[0].variants);
+ abrManager.setVariants(manifest.variants);
let chosen = abrManager.chooseVariant();
expect(chosen.id).toBe(11);
@@ -348,23 +340,21 @@ describe('SimpleAbrManager', () => {
it('uses lowest-bandwidth variant when restrictions cannot be met', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(10, (variant) => {
- variant.bandwidth = 1e5;
- variant.addVideo(0, (stream) => {
- stream.size(50, 50);
- });
+ manifest.addVariant(10, (variant) => {
+ variant.bandwidth = 1e5;
+ variant.addVideo(0, (stream) => {
+ stream.size(50, 50);
});
- period.addVariant(11, (variant) => {
- variant.bandwidth = 2e5;
- variant.addVideo(1, (stream) => {
- stream.size(200, 200);
- });
+ });
+ manifest.addVariant(11, (variant) => {
+ variant.bandwidth = 2e5;
+ variant.addVideo(1, (stream) => {
+ stream.size(200, 200);
});
});
});
- abrManager.setVariants(manifest.periods[0].variants);
+ abrManager.setVariants(manifest.variants);
let chosen = abrManager.chooseVariant();
expect(chosen.id).toBe(11);
diff --git a/test/dash/dash_parser_content_protection_unit.js b/test/dash/dash_parser_content_protection_unit.js
index 0eb6ee3bf7..5022086706 100644
--- a/test/dash/dash_parser_content_protection_unit.js
+++ b/test/dash/dash_parser_content_protection_unit.js
@@ -30,16 +30,16 @@ describe('DashParser ContentProtection', () => {
config.dash.ignoreDrmInfo = ignoreDrmInfo || false;
dashParser.configure(config);
- const playerEvents = {
+ const playerInterface = {
networkingEngine: netEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: (manifest) => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: fail,
onError: fail,
};
- const actual = await dashParser.start('http://example.com', playerEvents);
+ const actual = await dashParser.start(
+ 'http://example.com', playerInterface);
expect(actual).toEqual(expected);
}
@@ -97,19 +97,15 @@ describe('DashParser ContentProtection', () => {
const variant = jasmine.objectContaining({
drmInfos: drmInfos,
video: jasmine.objectContaining({
- keyId: keyIds[i] || null,
+ keyIds: keyIds[i] ? [keyIds[i]] : [],
}),
});
variants.push(variant);
}
return jasmine.objectContaining({
- periods: [
- jasmine.objectContaining({
- variants: variants,
- textStreams: [],
- }),
- ], // periods
+ variants: variants,
+ textStreams: [],
});
}
diff --git a/test/dash/dash_parser_live_unit.js b/test/dash/dash_parser_live_unit.js
index a780bd14c7..7139b8dc64 100644
--- a/test/dash/dash_parser_live_unit.js
+++ b/test/dash/dash_parser_live_unit.js
@@ -25,8 +25,7 @@ describe('DashParser Live', () => {
parser.configure(shaka.util.PlayerConfiguration.createDefault().manifest);
playerInterface = {
networkingEngine: fakeNetEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: (manifest) => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: fail,
onError: fail,
@@ -148,18 +147,13 @@ describe('DashParser Live', () => {
fakeNetEngine.setResponseText('dummy://foo', firstManifest);
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
ManifestParser.verifySegmentIndex(stream, firstReferences);
- expect(manifest.periods.length).toBe(1);
fakeNetEngine.setResponseText('dummy://foo', secondManifest);
await updateManifest();
ManifestParser.verifySegmentIndex(stream, secondReferences);
- // In https://github.com/google/shaka-player/issues/963, we
- // duplicated periods during the first update. This check covers
- // this case.
- expect(manifest.periods.length).toBe(1);
}
it('basic support', async () => {
@@ -195,7 +189,7 @@ describe('DashParser Live', () => {
const manifest = await parser.start('dummy://foo', playerInterface);
expect(manifest).toBeTruthy();
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
expect(stream).toBeTruthy();
await stream.createSegmentIndex();
@@ -215,7 +209,8 @@ describe('DashParser Live', () => {
ManifestParser.verifySegmentIndex(stream, basicRefs.slice(1));
});
- it('evicts old references for multi-period live stream', async () => {
+ // FIXME(#1339): re-enable this test!
+ xit('evicts old references for multi-period live stream', async () => {
const template = [
' {
/** @const {!Array.} */
const period2Refs = cloneRefs(basicRefs);
for (const ref of period2Refs) {
+ ref.timestampOffset = pStart;
ref.startTime += pStart;
ref.endTime += pStart;
}
+ /** @const {!Array.} */
+ const allRefs = period1Refs.concat(period2Refs);
- const stream1 = manifest.periods[0].variants[0].video;
- const stream2 = manifest.periods[1].variants[0].video;
+ const stream1 = manifest.variants[0].video;
await stream1.createSegmentIndex();
- await stream2.createSegmentIndex();
- ManifestParser.verifySegmentIndex(stream1, period1Refs);
- ManifestParser.verifySegmentIndex(stream2, period2Refs);
+ ManifestParser.verifySegmentIndex(stream1, allRefs);
// The 60 second availability window is initially full in all cases
// (SegmentTemplate+Timeline, etc.) The first segment is always 10
@@ -280,14 +275,12 @@ describe('DashParser Live', () => {
Date.now = () => 11 * 1000;
await updateManifest();
// The first reference should have been evicted.
- ManifestParser.verifySegmentIndex(stream1, period1Refs.slice(1));
- ManifestParser.verifySegmentIndex(stream2, period2Refs);
+ ManifestParser.verifySegmentIndex(stream1, allRefs.slice(1));
// Same as above, but 1 period length later
Date.now = () => (11 + pStart) * 1000;
await updateManifest();
- ManifestParser.verifySegmentIndex(stream1, []);
- ManifestParser.verifySegmentIndex(stream2, period2Refs.slice(1));
+ ManifestParser.verifySegmentIndex(stream1, period2Refs.slice(1));
});
it('sets infinite duration for single-period live streams', async () => {
@@ -313,7 +306,6 @@ describe('DashParser Live', () => {
Date.now = () => 0;
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
const timeline = manifest.presentationTimeline;
expect(timeline.getDuration()).toBe(Infinity);
});
@@ -349,59 +341,76 @@ describe('DashParser Live', () => {
Date.now = () => 0;
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(2);
- expect(manifest.periods[1].startTime).toBe(60);
const timeline = manifest.presentationTimeline;
expect(timeline.getDuration()).toBe(Infinity);
});
}
- it('can add Periods', async () => {
- const lines = [
- '',
- ];
- const template = [
+ // FIXME(#1339): re-enable this test!
+ xit('can add Periods', async () => {
+ const template1 = [
'',
- ' ',
+ ' ',
' ',
- ' ',
+ ' ',
' http://example.com',
- '%(contents)s',
+ ' ',
' ',
' ',
' ',
'',
].join('\n');
- const secondManifest =
- sprintf(template, {updateTime: updateTime, contents: lines.join('\n')});
- const firstManifest = makeSimpleLiveManifestText(lines, updateTime);
-
- /** @type {!jasmine.Spy} */
- const filterNewPeriod = jasmine.createSpy('filterNewPeriod');
- playerInterface.filterNewPeriod = Util.spyFunc(filterNewPeriod);
-
- /** @type {!jasmine.Spy} */
- const filterAllPeriods = jasmine.createSpy('filterAllPeriods');
- playerInterface.filterAllPeriods = Util.spyFunc(filterAllPeriods);
+ const template2 = [
+ '',
+ ' ',
+ ' ',
+ ' ',
+ ' http://example.com',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' http://example.com',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ '',
+ ].join('\n');
+ const firstManifest = sprintf(template1, {updateTime: updateTime});
+ const secondManifest = sprintf(template2, {updateTime: updateTime});
fakeNetEngine.setResponseText('dummy://foo', firstManifest);
+ // First two segments should exist
+ Date.now = () => 5;
+
const manifest = await parser.start('dummy://foo', playerInterface);
+ const variant = manifest.variants[0];
+ const stream = variant.video;
+ await stream.createSegmentIndex();
- expect(manifest.periods.length).toBe(1);
- // Should call filterAllPeriods for parsing the first manifest
- expect(filterNewPeriod).not.toHaveBeenCalled();
- expect(filterAllPeriods).toHaveBeenCalledTimes(1);
+ // First two segments exist, but not the third.
+ expect(stream.segmentIndex.find(3)).not.toBe(null);
+ expect(stream.segmentIndex.find(5)).toBe(null);
fakeNetEngine.setResponseText('dummy://foo', secondManifest);
+ // First period (10s) is complete, plus first two segments of next period
+ Date.now = () => 15;
+
await updateManifest();
- // Should update the same manifest object.
- expect(manifest.periods.length).toBe(2);
- // Should call filterNewPeriod for parsing the new manifest
- expect(filterAllPeriods).toHaveBeenCalledTimes(1);
- expect(filterNewPeriod).toHaveBeenCalledTimes(1);
+ // The update should have affected the same variant object we captured
+ // before. Now the entire first period should exist (10s), plus the next
+ // two segments.
+ expect(stream.segmentIndex.find(9)).not.toBe(null);
+ expect(stream.segmentIndex.find(13)).not.toBe(null);
});
it('uses redirect URL for manifest BaseURL and updates', async () => {
@@ -445,7 +454,7 @@ describe('DashParser Live', () => {
// Since the manifest request was redirected, the segment refers to
// the redirected base.
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
const pos = stream.segmentIndex.find(0);
const segmentUri = stream.segmentIndex.get(pos).getUris()[0];
@@ -1043,8 +1052,7 @@ describe('DashParser Live', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
const liveEdge =
@@ -1271,7 +1279,7 @@ describe('DashParser Live', () => {
// This should be seen as in-progress.
expect(manifest.presentationTimeline.isInProgress()).toBe(true);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
expect(stream).toBeTruthy();
await stream.createSegmentIndex();
diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js
index 061bea5cab..6d4a671a87 100644
--- a/test/dash/dash_parser_manifest_unit.js
+++ b/test/dash/dash_parser_manifest_unit.js
@@ -30,8 +30,7 @@ describe('DashParser Manifest', () => {
onEventSpy = jasmine.createSpy('onEvent');
playerInterface = {
networkingEngine: fakeNetEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: (manifest) => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: shaka.test.Util.spyFunc(onEventSpy),
onError: fail,
@@ -131,55 +130,54 @@ describe('DashParser Manifest', () => {
shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
manifest.minBufferTime = 75;
- manifest.addPeriod(null, (period) => {
- period.addPartialVariant((variant) => {
- variant.language = 'en';
- variant.bandwidth = 200;
- variant.primary = true;
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.bandwidth = 100;
- stream.frameRate = 1000000 / 42000;
- stream.size(768, 576);
- stream.mime('video/mp4', 'avc1.4d401f');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.bandwidth = 100;
- stream.primary = true;
- stream.roles = ['main'];
- stream.mime('audio/mp4', 'mp4a.40.29');
- });
- });
- period.addPartialVariant((variant) => {
- variant.language = 'en';
- variant.bandwidth = 150;
- variant.primary = true;
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.bandwidth = 50;
- stream.frameRate = 1000000 / 42000;
- stream.size(576, 432);
- stream.mime('video/mp4', 'avc1.4d401f');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.bandwidth = 100;
- stream.primary = true;
- stream.roles = ['main'];
- stream.mime('audio/mp4', 'mp4a.40.29');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'en';
+ variant.bandwidth = 200;
+ variant.primary = true;
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.bandwidth = 100;
+ stream.frameRate = 1000000 / 42000;
+ stream.size(768, 576);
+ stream.mime('video/mp4', 'avc1.4d401f');
});
- period.addPartialTextStream((stream) => {
- stream.language = 'es';
- stream.label = 'spanish';
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.bandwidth = 100;
stream.primary = true;
- stream.mimeType = 'text/vtt';
+ stream.roles = ['main'];
+ stream.mime('audio/mp4', 'mp4a.40.29');
+ });
+ });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'en';
+ variant.bandwidth = 150;
+ variant.primary = true;
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.bandwidth = 50;
+ stream.frameRate = 1000000 / 42000;
+ stream.size(576, 432);
+ stream.mime('video/mp4', 'avc1.4d401f');
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
stream.bandwidth = 100;
- stream.kind = 'caption';
- stream.roles = ['caption', 'main'];
+ stream.primary = true;
+ stream.roles = ['main'];
+ stream.mime('audio/mp4', 'mp4a.40.29');
});
});
+ manifest.addPartialTextStream((stream) => {
+ stream.language = 'es';
+ stream.label = 'spanish';
+ stream.primary = true;
+ stream.mimeType = 'text/vtt';
+ stream.bandwidth = 100;
+ stream.kind = 'caption';
+ stream.roles = ['caption', 'main'];
+ });
}));
});
- it('skips any periods after one without duration', async () => {
+ // TODO(#1339): Update this test not to rely on manifest.periods
+ xit('skips any periods after one without duration', async () => {
const periodContents = [
' ',
' ',
@@ -218,7 +216,7 @@ describe('DashParser Manifest', () => {
' ',
].join('\n');
const template = [
- '',
+ '',
' ',
'%(periodContents)s',
' ',
@@ -235,10 +233,8 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', source);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(3);
- expect(manifest.periods[0].startTime).toBe(10);
- expect(manifest.periods[1].startTime).toBe(20);
- expect(manifest.periods[2].startTime).toBe(30);
+ const timeline = manifest.presentationTimeline;
+ expect(timeline.getDuration()).toBe(40);
});
it('defaults to SegmentBase with multiple Segment*', async () => {
@@ -256,7 +252,7 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseValue('http://example.com', mp4Index);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
const pos = stream.segmentIndex.find(0);
const ref = stream.segmentIndex.get(pos);
@@ -281,7 +277,7 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', source);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
const position = stream.segmentIndex.find(0);
const ref = stream.segmentIndex.get(position);
@@ -310,7 +306,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].textStreams[0];
+ const stream = manifest.textStreams[0];
await stream.createSegmentIndex();
const pos = stream.segmentIndex.find(0);
const ref = stream.segmentIndex.get(pos);
@@ -361,10 +357,9 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
// First Representation should be dropped.
- const period = manifest.periods[0];
- const stream1 = period.variants[0].video;
- const stream2 = period.variants[1].video;
- const stream3 = period.variants[2].video;
+ const stream1 = manifest.variants[0].video;
+ const stream2 = manifest.variants[1].video;
+ const stream3 = manifest.variants[2].video;
const expectedClosedCaptions = new Map(
[['CC1', shaka.util.LanguageUtils.normalize('eng')],
@@ -395,7 +390,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].audio;
+ const stream = manifest.variants[0].audio;
expect(stream.mimeType).toBe('audio/eac3-joc');
});
@@ -418,7 +413,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
const expectedClosedCaptions = new Map(
[['CC1', shaka.util.LanguageUtils.normalize('eng')],
['CC3', shaka.util.LanguageUtils.normalize('swe')]]
@@ -445,7 +440,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
const expectedClosedCaptions = new Map([['CC1', 'und']]);
expect(stream.closedCaptions).toEqual(expectedClosedCaptions);
});
@@ -469,8 +464,8 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const variant = manifest.periods[0].variants[0];
- const stream = manifest.periods[0].variants[0].audio;
+ const variant = manifest.variants[0];
+ const stream = variant.audio;
await stream.createSegmentIndex();
const position = stream.segmentIndex.find(0);
const segment = stream.segmentIndex.get(position);
@@ -675,9 +670,8 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
// First Representation should be dropped.
- const period = manifest.periods[0];
- expect(period.variants.length).toBe(1);
- expect(period.variants[0].bandwidth).toBe(200);
+ expect(manifest.variants.length).toBe(1);
+ expect(manifest.variants[0].bandwidth).toBe(200);
});
describe('allows missing Segment* elements for text', () => {
@@ -701,7 +695,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods[0].textStreams.length).toBe(1);
+ expect(manifest.textStreams.length).toBe(1);
});
it('specified via AdaptationSet@mimeType', async () => {
@@ -724,7 +718,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods[0].textStreams.length).toBe(1);
+ expect(manifest.textStreams.length).toBe(1);
});
it('specified via Representation@mimeType', async () => {
@@ -747,7 +741,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods[0].textStreams.length).toBe(1);
+ expect(manifest.textStreams.length).toBe(1);
});
});
@@ -901,11 +895,10 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- expect(manifest.periods[0].variants.length).toBe(1);
- expect(manifest.periods[0].textStreams.length).toBe(0);
+ expect(manifest.variants.length).toBe(1);
+ expect(manifest.textStreams.length).toBe(0);
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
const trickModeVideo = variant && variant.video &&
variant.video.trickModeVideo;
expect(trickModeVideo).toEqual(jasmine.objectContaining({
@@ -936,14 +929,13 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
// The bogus EssentialProperty did not result in a variant.
- expect(manifest.periods[0].variants.length).toBe(1);
- expect(manifest.periods[0].textStreams.length).toBe(0);
+ expect(manifest.variants.length).toBe(1);
+ expect(manifest.textStreams.length).toBe(0);
// The bogus EssentialProperty did not result in a trick mode track.
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
const trickModeVideo = variant && variant.video &&
variant.video.trickModeVideo;
expect(trickModeVideo).toBe(null);
@@ -977,12 +969,11 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- expect(manifest.periods[0].textStreams.length).toBe(2);
+ expect(manifest.textStreams.length).toBe(2);
// At one time, these came out as 'application' rather than 'text'.
const ContentType = shaka.util.ManifestParserUtils.ContentType;
- expect(manifest.periods[0].textStreams[0].type).toBe(ContentType.TEXT);
- expect(manifest.periods[0].textStreams[1].type).toBe(ContentType.TEXT);
+ expect(manifest.textStreams[0].type).toBe(ContentType.TEXT);
+ expect(manifest.textStreams[1].type).toBe(ContentType.TEXT);
});
it('handles text with mime and codecs on different levels', async () => {
@@ -1007,13 +998,12 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
// In #875, this was an empty list.
- expect(manifest.periods[0].textStreams.length).toBe(1);
- if (manifest.periods[0].textStreams.length) {
+ expect(manifest.textStreams.length).toBe(1);
+ if (manifest.textStreams.length) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
- expect(manifest.periods[0].textStreams[0].type).toBe(ContentType.TEXT);
+ expect(manifest.textStreams[0].type).toBe(ContentType.TEXT);
}
});
@@ -1051,11 +1041,10 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', source);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- expect(manifest.periods[0].variants.length).toBe(2);
+ expect(manifest.variants.length).toBe(2);
- const variant1 = manifest.periods[0].variants[0];
- const variant2 = manifest.periods[0].variants[1];
+ const variant1 = manifest.variants[0];
+ const variant2 = manifest.variants[1];
await variant1.video.createSegmentIndex();
await variant2.video.createSegmentIndex();
@@ -1092,14 +1081,13 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', source);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- expect(manifest.periods[0].variants.length).toBe(2);
+ expect(manifest.variants.length).toBe(2);
- const variant1 = manifest.periods[0].variants[0];
+ const variant1 = manifest.variants[0];
expect(isNaN(variant1.bandwidth)).toBe(false);
expect(variant1.bandwidth).toBeGreaterThan(0);
- const variant2 = manifest.periods[0].variants[1];
+ const variant2 = manifest.variants[1];
expect(isNaN(variant2.bandwidth)).toBe(false);
expect(variant2.bandwidth).toBeGreaterThan(0);
});
@@ -1145,10 +1133,9 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', source);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- expect(manifest.periods[0].variants.length).toBe(1);
+ expect(manifest.variants.length).toBe(1);
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
expect(variant.audio.channelsCount).toBe(expectedNumChannels);
}
@@ -1253,8 +1240,8 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const variant = manifest.periods[0].variants[0];
- const textStream = manifest.periods[0].textStreams[0];
+ const variant = manifest.variants[0];
+ const textStream = manifest.textStreams[0];
expect(variant.audio.originalId).toBe('audio-en');
expect(variant.video.originalId).toBe('video-sd');
expect(textStream.originalId).toBe('text-en');
@@ -1287,7 +1274,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
expect(variant.audio).toBe(null);
expect(variant.video).toBeTruthy();
});
@@ -1319,7 +1306,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
expect(variant.audio).toBeTruthy();
expect(variant.video).toBe(null);
});
@@ -1356,7 +1343,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].textStreams[0];
+ const stream = manifest.textStreams[0];
expect(stream).toBeUndefined();
});
@@ -1549,7 +1536,7 @@ describe('DashParser Manifest', () => {
fakeNetEngine.setResponseText('dummy://foo', manifestText);
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const textStream = manifest.periods[0].textStreams[0];
+ const textStream = manifest.textStreams[0];
expect(textStream.roles).toEqual(['captions', 'foo']);
expect(textStream.kind).toBe('caption');
});
@@ -1580,7 +1567,7 @@ describe('DashParser Manifest', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
expect(variant.audio).toBeTruthy();
expect(variant.video).toBeTruthy();
});
diff --git a/test/dash/dash_parser_segment_base_unit.js b/test/dash/dash_parser_segment_base_unit.js
index dd135a5fe1..34d27cd9bd 100644
--- a/test/dash/dash_parser_segment_base_unit.js
+++ b/test/dash/dash_parser_segment_base_unit.js
@@ -27,8 +27,7 @@ describe('DashParser SegmentBase', () => {
playerInterface = {
networkingEngine: fakeNetEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: (manifest) => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: fail,
onError: fail,
@@ -70,9 +69,9 @@ describe('DashParser SegmentBase', () => {
// Call createSegmentIndex() on each stream to make the requests, but expect
// failure from the actual parsing, since the data is bogus.
- const stream1 = manifest.periods[0].variants[0].video;
+ const stream1 = manifest.variants[0].video;
await expectAsync(stream1.createSegmentIndex()).toBeRejected();
- const stream2 = manifest.periods[0].variants[1].video;
+ const stream2 = manifest.variants[1].video;
await expectAsync(stream2.createSegmentIndex()).toBeRejected();
expect(fakeNetEngine.request).toHaveBeenCalledTimes(5);
@@ -286,7 +285,7 @@ describe('DashParser SegmentBase', () => {
/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex(); // real data, should succeed
const pos = video.segmentIndex.find(0);
diff --git a/test/dash/dash_parser_segment_template_unit.js b/test/dash/dash_parser_segment_template_unit.js
index 01855e51ed..3a34eee17e 100644
--- a/test/dash/dash_parser_segment_template_unit.js
+++ b/test/dash/dash_parser_segment_template_unit.js
@@ -36,8 +36,7 @@ describe('DashParser SegmentTemplate', () => {
playerInterface = {
networkingEngine: fakeNetEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: (manifest) => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: fail,
onError: fail,
@@ -86,10 +85,9 @@ describe('DashParser SegmentTemplate', () => {
fakeNetEngine.setResponseText('dummy://foo', source);
const manifest = await parser.start('dummy://foo', playerInterface);
- expect(manifest.periods.length).toBe(1);
- expect(manifest.periods[0].variants.length).toBe(1);
+ expect(manifest.variants.length).toBe(1);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
expect(stream).toBeTruthy();
await stream.createSegmentIndex();
@@ -376,7 +374,7 @@ describe('DashParser SegmentTemplate', () => {
const actual = await parser.start('dummy://foo', playerInterface);
expect(actual).toBeTruthy();
- const variants = actual.periods[0].variants;
+ const variants = actual.variants;
expect(variants.length).toBe(3);
await variants[0].video.createSegmentIndex();
@@ -427,7 +425,7 @@ describe('DashParser SegmentTemplate', () => {
const actual = await parser.start('dummy://foo', playerInterface);
expect(actual).toBeTruthy();
- const variants = actual.periods[0].variants;
+ const variants = actual.variants;
expect(variants.length).toBe(3);
await variants[0].video.createSegmentIndex();
await variants[1].video.createSegmentIndex();
diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js
index 9ac708ccfb..7f68a8bd50 100644
--- a/test/hls/hls_live_unit.js
+++ b/test/hls/hls_live_unit.js
@@ -103,8 +103,7 @@ describe('HlsParser live', () => {
config = shaka.util.PlayerConfiguration.createDefault().manifest;
playerInterface = {
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: () => {},
networkingEngine: fakeNetEngine,
onError: fail,
onEvent: fail,
@@ -151,9 +150,7 @@ describe('HlsParser live', () => {
const manifest = await parser.start('test:/master', playerInterface);
- /** @type {!Array.} */
- const variants = manifest.periods[0].variants;
- await Promise.all(variants.map(async (variant) => {
+ await Promise.all(manifest.variants.map(async (variant) => {
await variant.video.createSegmentIndex();
ManifestParser.verifySegmentIndex(variant.video, initialReferences);
if (variant.audio) {
@@ -170,7 +167,7 @@ describe('HlsParser live', () => {
.setResponseText('test:/audio', updatedMedia);
await delayForUpdatePeriod();
- for (const variant of variants) {
+ for (const variant of manifest.variants) {
ManifestParser.verifySegmentIndex(variant.video, updatedReferences);
if (variant.audio) {
ManifestParser.verifySegmentIndex(variant.audio, updatedReferences);
@@ -282,7 +279,7 @@ describe('HlsParser live', () => {
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [ref1]);
@@ -532,13 +529,13 @@ describe('HlsParser live', () => {
.setResponseText('test:/main.vtt', vtt);
const manifest = await parser.start('test:/master', playerInterface);
- const textStream = manifest.periods[0].textStreams[0];
+ const textStream = manifest.textStreams[0];
await textStream.createSegmentIndex();
let ref = Array.from(textStream.segmentIndex)[0];
expect(ref).not.toBe(null);
expect(ref.startTime).not.toBeLessThan(rolloverOffset);
- const videoStream = manifest.periods[0].variants[0].video;
+ const videoStream = manifest.variants[0].video;
await videoStream.createSegmentIndex();
ref = Array.from(videoStream.segmentIndex)[0];
expect(ref).not.toBe(null);
@@ -602,7 +599,7 @@ describe('HlsParser live', () => {
expectedRef.timestampOffset = 0;
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [expectedRef]);
});
@@ -622,7 +619,7 @@ describe('HlsParser live', () => {
segmentDataStartTime + 4);
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [ref1, ref2]);
@@ -661,7 +658,7 @@ describe('HlsParser live', () => {
expectedRef.timestampOffset = 0;
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [expectedRef]);
});
@@ -685,7 +682,7 @@ describe('HlsParser live', () => {
expectedEndByte); // Complete segment reference
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [expectedRef]);
diff --git a/test/hls/hls_parser_unit.js b/test/hls/hls_parser_unit.js
index 495a6476dc..9249f3ddf8 100644
--- a/test/hls/hls_parser_unit.js
+++ b/test/hls/hls_parser_unit.js
@@ -77,8 +77,7 @@ describe('HlsParser', () => {
config = shaka.util.PlayerConfiguration.createDefault().manifest;
playerInterface = {
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: () => {},
networkingEngine: fakeNetEngine,
onError: fail,
onEvent: fail,
@@ -148,32 +147,30 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.language = 'en';
- variant.bandwidth = 200;
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.frameRate = 60;
- stream.mime('video/mp4', 'avc1');
- stream.size(960, 540);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'en';
- stream.channelsCount = 2;
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'en';
+ variant.bandwidth = 200;
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.frameRate = 60;
+ stream.mime('video/mp4', 'avc1');
+ stream.size(960, 540);
});
- period.addPartialTextStream((stream) => {
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
stream.language = 'en';
- stream.kind = TextStreamKind.SUBTITLE;
- stream.mime('text/vtt', '');
- });
- period.addPartialTextStream((stream) => {
- stream.language = 'es';
- stream.kind = TextStreamKind.SUBTITLE;
- stream.mime('text/vtt', '');
+ stream.channelsCount = 2;
+ stream.mime('audio/mp4', 'mp4a');
});
});
+ manifest.addPartialTextStream((stream) => {
+ stream.language = 'en';
+ stream.kind = TextStreamKind.SUBTITLE;
+ stream.mime('text/vtt', '');
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.language = 'es';
+ stream.kind = TextStreamKind.SUBTITLE;
+ stream.mime('text/vtt', '');
+ });
});
fakeNetEngine
@@ -209,11 +206,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1.4d001e');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1.4d001e');
});
});
});
@@ -240,11 +235,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
});
});
});
@@ -270,11 +263,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
});
});
});
@@ -300,11 +291,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -333,14 +322,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -369,14 +356,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', '');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', '');
});
});
});
@@ -408,15 +393,13 @@ describe('HlsParser', () => {
const closedCaptions = new Map([['CC1', 'en']]);
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.closedCaptions = closedCaptions;
- stream.mime('video/mp4', 'avc1');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.closedCaptions = closedCaptions;
+ stream.mime('video/mp4', 'avc1');
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -447,14 +430,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -482,11 +463,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -534,7 +513,7 @@ describe('HlsParser', () => {
const manifest = await parser.start('test:/master', playerInterface);
const presentationTimeline = manifest.presentationTimeline;
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
const pos = stream.segmentIndex.find(0);
@@ -567,11 +546,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1,mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1,mp4a');
});
});
});
@@ -598,11 +575,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
});
});
});
@@ -631,14 +606,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
});
});
});
@@ -666,11 +639,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', /** @type {?} */ (jasmine.any(String)));
});
});
});
@@ -698,11 +669,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', /** @type {?} */ (jasmine.any(String)));
});
});
});
@@ -736,24 +705,22 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.bandwidth = 200;
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(960, 540);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'en';
- });
+ manifest.addPartialVariant((variant) => {
+ variant.bandwidth = 200;
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(960, 540);
});
- period.addPartialVariant((variant) => {
- variant.bandwidth = 300;
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(960, 540);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'fr';
- });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'en';
+ });
+ });
+ manifest.addPartialVariant((variant) => {
+ variant.bandwidth = 300;
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(960, 540);
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'fr';
});
});
});
@@ -784,20 +751,18 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.language = 'en';
- variant.addPartialStream(ContentType.VIDEO);
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'en';
- });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'en';
+ variant.addPartialStream(ContentType.VIDEO);
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'en';
});
- period.addPartialVariant((variant) => {
- variant.language = 'fr';
- variant.addPartialStream(ContentType.VIDEO);
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'fr';
- });
+ });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'fr';
+ variant.addPartialStream(ContentType.VIDEO);
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'fr';
});
});
});
@@ -805,7 +770,7 @@ describe('HlsParser', () => {
await testHlsParser(master, media, manifest);
});
- it('should call filterAllPeriods for parsing', async () => {
+ it('should call filter during parsing', async () => {
const master = [
'#EXTM3U\n',
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
@@ -830,11 +795,11 @@ describe('HlsParser', () => {
.setResponseValue('test:/main.mp4', segmentData);
/** @type {!jasmine.Spy} */
- const filterAllPeriods = jasmine.createSpy('filterAllPeriods');
- playerInterface.filterAllPeriods = Util.spyFunc(filterAllPeriods);
+ const filter = jasmine.createSpy('filter');
+ playerInterface.filter = Util.spyFunc(filter);
await parser.start('test:/master', playerInterface);
- expect(filterAllPeriods).toHaveBeenCalledTimes(1);
+ expect(filter).toHaveBeenCalledTimes(1);
});
it('fetch the start time for one audio/video stream and reuse for the others',
@@ -908,11 +873,9 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
});
});
});
@@ -962,26 +925,24 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
});
- period.addPartialTextStream((stream) => {
- stream.language = 'en';
- stream.kind = TextStreamKind.SUBTITLE;
- stream.mime('text/vtt', '');
- });
- period.addPartialTextStream((stream) => {
- stream.language = 'es';
- stream.kind = TextStreamKind.SUBTITLE;
- stream.mime('text/vtt', '');
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
+ manifest.addPartialTextStream((stream) => {
+ stream.language = 'en';
+ stream.kind = TextStreamKind.SUBTITLE;
+ stream.mime('text/vtt', '');
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.language = 'es';
+ stream.kind = TextStreamKind.SUBTITLE;
+ stream.mime('text/vtt', '');
+ });
});
fakeNetEngine
@@ -1032,19 +993,17 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO);
- variant.addPartialStream(ContentType.AUDIO);
- });
- period.addPartialTextStream((stream) => {
- stream.kind = TextStreamKind.SUBTITLE;
- stream.mime('text/vtt', '');
- });
- period.addPartialTextStream((stream) => {
- stream.kind = TextStreamKind.SUBTITLE;
- stream.mime('text/vtt', '');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO);
+ variant.addPartialStream(ContentType.AUDIO);
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.kind = TextStreamKind.SUBTITLE;
+ stream.mime('text/vtt', '');
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.kind = TextStreamKind.SUBTITLE;
+ stream.mime('text/vtt', '');
});
});
@@ -1119,11 +1078,10 @@ describe('HlsParser', () => {
const timeline = actual.presentationTimeline;
expect(timeline.getDuration()).toBe(10);
- const period = actual.periods[0];
- expect(period.textStreams.length).toBe(1);
- expect(period.variants.length).toBe(1);
- expect(period.variants[0].audio).toBeTruthy();
- expect(period.variants[0].video).toBeTruthy();
+ expect(actual.textStreams.length).toBe(1);
+ expect(actual.variants.length).toBe(1);
+ expect(actual.variants[0].audio).toBeTruthy();
+ expect(actual.variants[0].video).toBeTruthy();
});
it('Disable audio does not create audio streams', async () => {
@@ -1182,7 +1140,7 @@ describe('HlsParser', () => {
parser.configure(config);
const actual = await parser.start('test:/master', playerInterface);
- const variant = actual.periods[0].variants[0];
+ const variant = actual.variants[0];
expect(variant.audio).toBe(null);
expect(variant.video).toBeTruthy();
});
@@ -1243,7 +1201,7 @@ describe('HlsParser', () => {
parser.configure(config);
const actual = await parser.start('test:/master', playerInterface);
- const variant = actual.periods[0].variants[0];
+ const variant = actual.variants[0];
expect(variant.audio).toBeTruthy();
expect(variant.video).toBe(null);
});
@@ -1304,7 +1262,7 @@ describe('HlsParser', () => {
parser.configure(config);
const actual = await parser.start('test:/master', playerInterface);
- const stream = actual.periods[0].textStreams[0];
+ const stream = actual.textStreams[0];
expect(stream).toBeUndefined();
});
@@ -1329,14 +1287,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO);
- });
- period.addPartialTextStream((stream) => {
- stream.language = 'en';
- stream.mime('application/mp4', 'stpp.ttml.im1t');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO);
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.language = 'en';
+ stream.mime('application/mp4', 'stpp.ttml.im1t');
});
});
@@ -1381,13 +1337,11 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO);
- });
- period.addPartialTextStream((stream) => {
- stream.mime('text/vtt', 'vtt');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO);
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.mime('text/vtt', 'vtt');
});
});
@@ -1425,13 +1379,11 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(/** @type {?} */ (jasmine.any(Number)), (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO);
- });
- period.addPartialTextStream((stream) => {
- stream.kind = TextStreamKind.SUBTITLE;
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO);
+ });
+ manifest.addPartialTextStream((stream) => {
+ stream.kind = TextStreamKind.SUBTITLE;
});
});
@@ -1467,10 +1419,8 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO);
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO);
});
});
@@ -1506,15 +1456,13 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.frameRate = 60;
- stream.mime('video/mp4', 'avc1');
- stream.size(960, 540);
- });
- variant.addPartialStream(ContentType.AUDIO);
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.frameRate = 60;
+ stream.mime('video/mp4', 'avc1');
+ stream.size(960, 540);
});
+ variant.addPartialStream(ContentType.AUDIO);
});
});
@@ -1549,8 +1497,8 @@ describe('HlsParser', () => {
const actual =
await parser.start('test:/host/master.m3u8', playerInterface);
- const video = actual.periods[0].variants[0].video;
- const audio = actual.periods[0].variants[0].audio;
+ const video = actual.variants[0].video;
+ const audio = actual.variants[0].audio;
await video.createSegmentIndex();
await audio.createSegmentIndex();
@@ -1596,14 +1544,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.mime('video/mp4', 'avc1');
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.mime('video/mp4', 'avc1');
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -1642,7 +1588,7 @@ describe('HlsParser', () => {
.setResponseValue('test:/main2.mp4', segmentData);
const actualManifest = await parser.start('test:/master', playerInterface);
- const actualVideo = actualManifest.periods[0].variants[0].video;
+ const actualVideo = actualManifest.variants[0].video;
await actualVideo.createSegmentIndex();
// Verify that the stream contains two segment references, each of the
@@ -1691,15 +1637,13 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.bandwidth = 200;
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(960, 540);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'en';
- });
+ manifest.addPartialVariant((variant) => {
+ variant.bandwidth = 200;
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(960, 540);
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'en';
});
});
});
@@ -1748,14 +1692,12 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addDrmInfo('com.widevine.alpha', (drmInfo) => {
- drmInfo.addCencInitData(initDataBase64);
- });
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.encrypted = true;
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addDrmInfo('com.widevine.alpha', (drmInfo) => {
+ drmInfo.addCencInitData(initDataBase64);
+ });
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.encrypted = true;
});
});
});
@@ -2064,7 +2006,7 @@ describe('HlsParser', () => {
expectedRef.timestampOffset = -segmentDataStartTime;
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [expectedRef]);
@@ -2096,7 +2038,7 @@ describe('HlsParser', () => {
expectedRef.timestampOffset = -segmentDataStartTime;
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
ManifestParser.verifySegmentIndex(video, [expectedRef]);
@@ -2158,7 +2100,7 @@ describe('HlsParser', () => {
const manifest = await parser.start('test:/master', playerInterface);
const presentationTimeline = manifest.presentationTimeline;
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
await video.createSegmentIndex();
const refs = Array.from(video.segmentIndex);
expect(refs.length).toBe(1);
@@ -2249,7 +2191,7 @@ describe('HlsParser', () => {
.setResponseValue('test:/main.mp4', segmentData);
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
expect(video.mimeType).toBe('video/mp4');
});
@@ -2277,7 +2219,7 @@ describe('HlsParser', () => {
.setResponseValue('test:/main.mp4?foo=bar', segmentData);
const manifest = await parser.start('test:/master', playerInterface);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
expect(video.mimeType).toBe('video/mp4');
});
@@ -2312,9 +2254,9 @@ describe('HlsParser', () => {
.setResponseValue('test:/main.mp4', segmentData);
const manifest = await parser.start('test:/master', playerInterface);
- expect(manifest.periods[0].variants.length).toBe(2);
- const audio0 = manifest.periods[0].variants[0].audio;
- const audio1 = manifest.periods[0].variants[1].audio;
+ expect(manifest.variants.length).toBe(2);
+ const audio0 = manifest.variants[0].audio;
+ const audio1 = manifest.variants[1].audio;
// These should be the exact same memory address, not merely equal.
// Otherwise, the parser will only be replacing one of the SegmentIndexes
// on update, which will lead to live streaming issues.
@@ -2361,7 +2303,7 @@ describe('HlsParser', () => {
// would still be wrong.
const manifest =
await parser.start('media/master', playerInterface);
- expect(manifest.periods[0].variants.length).toBe(1);
+ expect(manifest.variants.length).toBe(1);
});
// https://github.com/google/shaka-player/issues/1908
@@ -2391,42 +2333,40 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.language = 'en';
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(1280, 720);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'en';
- });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'en';
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(1280, 720);
});
- period.addPartialVariant((variant) => {
- variant.language = 'fr';
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(1280, 720);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'fr';
- });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'en';
});
- period.addPartialVariant((variant) => {
- variant.language = 'en';
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(1920, 1080);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'en';
- });
+ });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'fr';
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(1280, 720);
});
- period.addPartialVariant((variant) => {
- variant.language = 'fr';
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.size(1920, 1080);
- });
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.language = 'fr';
- });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'fr';
+ });
+ });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'en';
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(1920, 1080);
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'en';
+ });
+ });
+ manifest.addPartialVariant((variant) => {
+ variant.language = 'fr';
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.size(1920, 1080);
+ });
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.language = 'fr';
});
});
});
@@ -2496,8 +2436,8 @@ describe('HlsParser', () => {
shaka.log.alwaysWarn = shaka.test.Util.spyFunc(alwaysWarnSpy);
const manifest = await parser.start('test:/master', playerInterface);
- expect(manifest.periods[0].variants.length).toBe(1);
- expect(manifest.periods[0].variants[0].audio).toBe(null);
+ expect(manifest.variants.length).toBe(1);
+ expect(manifest.variants[0].audio).toBe(null);
// We should log a warning when this happens.
expect(alwaysWarnSpy).toHaveBeenCalled();
@@ -2531,14 +2471,11 @@ describe('HlsParser', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.anyTimeline();
- manifest.addPeriod(0, (period) => {
- const anyVariantId = /** @type {?} */(jasmine.any(Number));
- period.addVariant(anyVariantId, (variant) => {
- variant.bandwidth = 200;
- variant.language = 'und';
- variant.addPartialStream(ContentType.AUDIO, (stream) => {
- stream.mime('audio/mp4', 'mp4a');
- });
+ manifest.addPartialVariant((variant) => {
+ variant.bandwidth = 200;
+ variant.language = 'und';
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
+ stream.mime('audio/mp4', 'mp4a');
});
});
});
@@ -2552,7 +2489,7 @@ describe('HlsParser', () => {
.setResponseValue('test:/main.mp4', segmentData);
const actual = await parser.start('test:/master', playerInterface);
- expect(actual.periods[0].variants.length).toBe(1);
+ expect(actual.variants.length).toBe(1);
expect(actual).toEqual(manifest);
});
});
diff --git a/test/media/adaptation_set_criteria_unit.js b/test/media/adaptation_set_criteria_unit.js
index 286c27ccea..bb87c84db6 100644
--- a/test/media/adaptation_set_criteria_unit.js
+++ b/test/media/adaptation_set_criteria_unit.js
@@ -5,520 +5,490 @@
describe('AdaptationSetCriteria', () => {
describe('preference based selection', () => {
- function variants(manifest) {
- return manifest.periods[0].variants;
- }
-
it('chooses variants in user\'s preferred language', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'es';
- });
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- });
- period.addVariant(3, (variant) => {
- variant.language = 'en';
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'es';
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'en';
});
});
const builder = new shaka.media.PreferenceBasedCriteria('en', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[1],
- manifest.periods[0].variants[2],
+ manifest.variants[1],
+ manifest.variants[2],
]);
});
it('prefers primary variants', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.primary = true;
- });
- period.addVariant(2);
- period.addVariant(3);
- period.addVariant(4, (variant) => {
- variant.primary = true;
- });
+ manifest.addVariant(1, (variant) => {
+ variant.primary = true;
+ });
+ manifest.addVariant(2);
+ manifest.addVariant(3);
+ manifest.addVariant(4, (variant) => {
+ variant.primary = true;
});
});
const builder = new shaka.media.PreferenceBasedCriteria('en', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[3],
+ manifest.variants[0],
+ manifest.variants[3],
]);
});
it('chooses variants in preferred language and role', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.addAudio(10, (stream) => {
- stream.roles = ['main', 'commentary'];
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(10, (stream) => {
+ stream.roles = ['main', 'commentary'];
});
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.addAudio(20, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(20, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(3, (variant) => {
- variant.language = 'es';
- variant.addAudio(30, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'es';
+ variant.addAudio(30, (stream) => {
+ stream.roles = ['main'];
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('en', 'main', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
+ manifest.variants[0],
]);
});
it('chooses only one role, even if none is preferred', () => {
// Regression test for https://github.com/google/shaka-player/issues/949
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.addAudio(10, (stream) => {
- stream.roles = ['commentary'];
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(10, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.addAudio(20, (stream) => {
- stream.roles = ['commentary'];
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(20, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(3, (variant) => {
- variant.language = 'en';
- variant.addAudio(30, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(30, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(4, (variant) => {
- variant.language = 'en';
- variant.addAudio(40, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(40, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(5, (variant) => {
- variant.language = 'en';
- variant.addAudio(50, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(5, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(50, (stream) => {
+ stream.roles = ['main'];
});
- period.addVariant(6, (variant) => {
- variant.language = 'en';
- variant.addAudio(60, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(6, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(60, (stream) => {
+ stream.roles = ['main'];
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('en', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
// Which role is chosen is an implementation detail.
// Each role is found on two variants, so we should have two.
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[1],
+ manifest.variants[0],
+ manifest.variants[1],
]);
});
it('chooses only one role, even if all are primary', () => {
// Regression test for https://github.com/google/shaka-player/issues/949
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(10, (stream) => {
- stream.roles = ['commentary'];
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(10, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(20, (stream) => {
- stream.roles = ['commentary'];
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(20, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(3, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(30, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(30, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(4, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(40, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(40, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(5, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(50, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(5, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(50, (stream) => {
+ stream.roles = ['main'];
});
- period.addVariant(6, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(60, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(6, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(60, (stream) => {
+ stream.roles = ['main'];
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('zh', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
// Which role is chosen is an implementation detail.
// Each role is found on two variants, so we should have two.
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[1],
+ manifest.variants[0],
+ manifest.variants[1],
]);
});
it('chooses only one language, even if all are primary', () => {
// Regression test for https://github.com/google/shaka-player/issues/918
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(10);
- });
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(20);
- });
- period.addVariant(3, (variant) => {
- variant.language = 'es';
- variant.primary = true;
- variant.addAudio(30);
- });
- period.addVariant(4, (variant) => {
- variant.language = 'es';
- variant.primary = true;
- variant.addAudio(40);
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(10);
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(20);
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'es';
+ variant.primary = true;
+ variant.addAudio(30);
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'es';
+ variant.primary = true;
+ variant.addAudio(40);
});
});
const builder = new shaka.media.PreferenceBasedCriteria('zh', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
// Which language is chosen is an implementation detail.
// Each role is found on two variants, so we should have two.
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[1],
+ manifest.variants[0],
+ manifest.variants[1],
]);
});
it('chooses a role from among primary variants without language match',
() => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(10, (stream) => {
- stream.roles = ['commentary'];
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(10, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(20, (stream) => {
- stream.roles = ['commentary'];
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(20, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(3, (variant) => {
- variant.language = 'en';
- variant.addAudio(30, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(30, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(4, (variant) => {
- variant.language = 'en';
- variant.addAudio(40, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(40, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(5, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(50, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(5, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(50, (stream) => {
+ stream.roles = ['main'];
});
- period.addVariant(6, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(60, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(6, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(60, (stream) => {
+ stream.roles = ['main'];
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('zh', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
// Which role is chosen is an implementation detail. Each role is
// found on two variants, so we should have two. Since nothing matches
// our language preference, we chose primary variants.
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[1],
+ manifest.variants[0],
+ manifest.variants[1],
]);
});
it('chooses a role from best language match, in spite of primary',
() => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(10, (stream) => {
- stream.roles = ['commentary'];
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(10, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(20, (stream) => {
- stream.roles = ['commentary'];
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(20, (stream) => {
+ stream.roles = ['commentary'];
});
- period.addVariant(3, (variant) => {
- variant.language = 'zh';
- variant.addAudio(30, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'zh';
+ variant.addAudio(30, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(4, (variant) => {
- variant.language = 'zh';
- variant.addAudio(40, (stream) => {
- stream.roles = ['secondary'];
- });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'zh';
+ variant.addAudio(40, (stream) => {
+ stream.roles = ['secondary'];
});
- period.addVariant(5, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(50, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(5, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(50, (stream) => {
+ stream.roles = ['main'];
});
- period.addVariant(6, (variant) => {
- variant.language = 'en';
- variant.primary = true;
- variant.addAudio(60, (stream) => {
- stream.roles = ['main'];
- });
+ });
+ manifest.addVariant(6, (variant) => {
+ variant.language = 'en';
+ variant.primary = true;
+ variant.addAudio(60, (stream) => {
+ stream.roles = ['main'];
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('zh', '', 0);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[2],
- manifest.periods[0].variants[3],
+ manifest.variants[2],
+ manifest.variants[3],
]);
});
it('chooses variants with preferred audio channels count', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.addAudio(10, (stream) => {
- stream.channelsCount = 2;
- });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(10, (stream) => {
+ stream.channelsCount = 2;
});
- period.addVariant(2, (variant) => {
- variant.addAudio(20, (stream) => {
- stream.channelsCount = 6;
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(20, (stream) => {
+ stream.channelsCount = 6;
});
- period.addVariant(3, (variant) => {
- variant.addAudio(30, (stream) => {
- stream.channelsCount = 2;
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.addAudio(30, (stream) => {
+ stream.channelsCount = 2;
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('', '', 2);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[2],
+ manifest.variants[0],
+ manifest.variants[2],
]);
});
it('chooses variants with largest audio channel count less than config' +
' when no exact audio channel count match is possible', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.addAudio(10, (stream) => {
- stream.channelsCount = 2;
- });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(10, (stream) => {
+ stream.channelsCount = 2;
});
- period.addVariant(2, (variant) => {
- variant.addAudio(20, (stream) => {
- stream.channelsCount = 8;
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(20, (stream) => {
+ stream.channelsCount = 8;
});
- period.addVariant(3, (variant) => {
- variant.addAudio(30, (stream) => {
- stream.channelsCount = 2;
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.addAudio(30, (stream) => {
+ stream.channelsCount = 2;
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('', '', 6);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[2],
+ manifest.variants[0],
+ manifest.variants[2],
]);
});
it('chooses variants with fewest audio channels when none fit in the ' +
'config', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.addAudio(10, (stream) => {
- stream.channelsCount = 6;
- });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(10, (stream) => {
+ stream.channelsCount = 6;
});
- period.addVariant(2, (variant) => {
- variant.addAudio(20, (stream) => {
- stream.channelsCount = 8;
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(20, (stream) => {
+ stream.channelsCount = 8;
});
- period.addVariant(3, (variant) => {
- variant.addAudio(30, (stream) => {
- stream.channelsCount = 6;
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.addAudio(30, (stream) => {
+ stream.channelsCount = 6;
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria('', '', 2);
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[2],
+ manifest.variants[0],
+ manifest.variants[2],
]);
});
it('chooses variants with preferred label', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.addAudio(10, (stream) => {
- stream.label = 'preferredLabel';
- });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(10, (stream) => {
+ stream.label = 'preferredLabel';
});
- period.addVariant(2, (variant) => {
- variant.addAudio(20, (stream) => {
- stream.label = 'otherLabel';
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(20, (stream) => {
+ stream.label = 'otherLabel';
});
- period.addVariant(3, (variant) => {
- variant.addAudio(30, (stream) => {
- stream.label = 'preferredLabel';
- });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.addAudio(30, (stream) => {
+ stream.label = 'preferredLabel';
});
});
});
const builder =
new shaka.media.PreferenceBasedCriteria('', '', 0, 'preferredLabel');
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[2],
+ manifest.variants[0],
+ manifest.variants[2],
]);
});
it('chooses variants with preferred label and language', () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- // Preferred language and label
- period.addVariant(1, (variant) => {
- variant.language = 'zh';
- variant.addAudio(10, (stream) => {
- stream.label = 'preferredLabel';
- });
+ // Preferred language and label
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'zh';
+ variant.addAudio(10, (stream) => {
+ stream.label = 'preferredLabel';
});
- // Same language, a different label
- period.addVariant(2, (variant) => {
- variant.language = 'zh';
- variant.addAudio(20, (stream) => {
- stream.label = 'otherLabel';
- });
+ });
+ // Same language, a different label
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'zh';
+ variant.addAudio(20, (stream) => {
+ stream.label = 'otherLabel';
});
- // Same language and label
- period.addVariant(3, (variant) => {
- variant.language = 'zh';
- variant.addAudio(30, (stream) => {
- stream.label = 'preferredLabel';
- });
+ });
+ // Same language and label
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'zh';
+ variant.addAudio(30, (stream) => {
+ stream.label = 'preferredLabel';
});
- // Same label different language
- period.addVariant(4, (variant) => {
- variant.language = 'pt';
- variant.addAudio(40, (stream) => {
- stream.label = 'preferredLabel';
- });
+ });
+ // Same label different language
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'pt';
+ variant.addAudio(40, (stream) => {
+ stream.label = 'preferredLabel';
});
});
});
const builder = new shaka.media.PreferenceBasedCriteria(
'zh', '', 0, 'preferredLabel');
- const set = builder.create(variants(manifest));
+ const set = builder.create(manifest.variants);
checkSet(set, [
- manifest.periods[0].variants[0],
- manifest.periods[0].variants[2],
+ manifest.variants[0],
+ manifest.variants[2],
]);
});
});
diff --git a/test/media/adaptation_set_unit.js b/test/media/adaptation_set_unit.js
index c0aa0e1fc4..de196c58df 100644
--- a/test/media/adaptation_set_unit.js
+++ b/test/media/adaptation_set_unit.js
@@ -168,7 +168,7 @@ describe('AdaptationSet', () => {
encrypted: false,
segmentIndex: null,
id: id,
- keyId: null,
+ keyIds: [],
label: null,
language: '',
mimeType: mimeType,
diff --git a/test/media/drm_engine_integration.js b/test/media/drm_engine_integration.js
index 6415590648..222d0ceeb7 100644
--- a/test/media/drm_engine_integration.js
+++ b/test/media/drm_engine_integration.js
@@ -104,22 +104,20 @@ describe('DrmEngine', () => {
drmEngine.configure(config);
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('com.widevine.alpha');
- variant.addDrmInfo('com.microsoft.playready');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- });
- variant.addAudio(2, (stream) => {
- stream.encrypted = true;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('com.widevine.alpha');
+ variant.addDrmInfo('com.microsoft.playready');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
+ });
+ variant.addAudio(2, (stream) => {
+ stream.encrypted = true;
});
});
});
- const videoStream = manifest.periods[0].variants[0].video;
- const audioStream = manifest.periods[0].variants[0].audio;
+ const videoStream = manifest.variants[0].video;
+ const audioStream = manifest.variants[0].audio;
eventManager = new shaka.util.EventManager();
@@ -191,8 +189,7 @@ describe('DrmEngine', () => {
keyStatusEventSeen.resolve();
});
- const periods = manifest.periods;
- const variants = shaka.util.Periods.getAllVariantsFrom(periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
await drmEngine.attach(video);
diff --git a/test/media/drm_engine_unit.js b/test/media/drm_engine_unit.js
index 603a6ef05c..079e11b162 100644
--- a/test/media/drm_engine_unit.js
+++ b/test/media/drm_engine_unit.js
@@ -4,7 +4,6 @@
*/
describe('DrmEngine', () => {
- const Periods = shaka.util.Periods;
const Util = shaka.test.Util;
const originalRequestMediaKeySystemAccess =
@@ -66,18 +65,16 @@ describe('DrmEngine', () => {
onEventSpy = jasmine.createSpy('onEvent');
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('drm.abc');
- variant.addDrmInfo('drm.def');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- stream.mime('video/foo', 'vbar');
- });
- variant.addAudio(2, (stream) => {
- stream.encrypted = true;
- stream.mime('audio/foo', 'abar');
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('drm.abc');
+ variant.addDrmInfo('drm.def');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
+ stream.mime('video/foo', 'vbar');
+ });
+ variant.addAudio(2, (stream) => {
+ stream.encrypted = true;
+ stream.mime('audio/foo', 'abar');
});
});
});
@@ -137,19 +134,17 @@ describe('DrmEngine', () => {
describe('supportsVariants', () => {
it('supports all clear variants', async () => {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('drm.abc');
- variant.addDrmInfo('drm.def');
- variant.addVideo(1, (stream) => {
- stream.encrypted = false;
- stream.mime('video/foo', 'vbar');
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('drm.abc');
+ variant.addDrmInfo('drm.def');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = false;
+ stream.mime('video/foo', 'vbar');
});
});
});
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.supportsVariant(variants[0])).toBeTruthy();
@@ -161,7 +156,7 @@ describe('DrmEngine', () => {
// Accept both drm.abc and drm.def. Only one can be chosen.
setRequestMediaKeySystemAccessSpy(['drm.abc', 'drm.def']);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.initialized()).toBe(true);
expect(shaka.media.DrmEngine.keySystem(drmEngine.getDrmInfo()))
@@ -177,7 +172,7 @@ describe('DrmEngine', () => {
// Fail both key systems.
setRequestMediaKeySystemAccessSpy([]);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejected();
@@ -200,7 +195,7 @@ describe('DrmEngine', () => {
// Ignore error logs, which we expect to occur due to the missing server.
logErrorSpy.and.stub();
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejected();
@@ -218,7 +213,7 @@ describe('DrmEngine', () => {
setRequestMediaKeySystemAccessSpy(['drm.abc', 'drm.def']);
// Add manifest-supplied license servers for both.
- for (const drmInfo of manifest.periods[0].variants[0].drmInfos) {
+ for (const drmInfo of manifest.variants[0].drmInfos) {
if (drmInfo.keySystem == 'drm.abc') {
drmInfo.licenseServerUri = 'http://foo.bar/abc';
} else if (drmInfo.keySystem == 'drm.def') {
@@ -239,7 +234,7 @@ describe('DrmEngine', () => {
// Ignore error logs, which we expect to occur due to the missing server.
logErrorSpy.and.stub();
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
// Although drm.def appears second in the manifest, it is queried first
@@ -255,7 +250,7 @@ describe('DrmEngine', () => {
it('detects content type capabilities of key system', async () => {
setRequestMediaKeySystemAccessSpy(['drm.abc']);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.initialized()).toBe(true);
expect(drmEngine.willSupport('audio/webm')).toBeTruthy();
@@ -273,7 +268,7 @@ describe('DrmEngine', () => {
// Accept drm.def, but not drm.abc.
setRequestMediaKeySystemAccessSpy(['drm.def']);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.initialized()).toBe(true);
expect(shaka.media.DrmEngine.keySystem(drmEngine.getDrmInfo()))
@@ -295,7 +290,7 @@ describe('DrmEngine', () => {
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.REQUESTED_KEY_SYSTEM_CONFIG_UNAVAILABLE));
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejectedWith(expected);
@@ -312,14 +307,12 @@ describe('DrmEngine', () => {
it('silences errors for unencrypted assets', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.mime('video/foo', 'vbar');
- });
- variant.addAudio(2, (stream) => {
- stream.mime('audio/foo', 'abar');
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.mime('video/foo', 'vbar');
+ });
+ variant.addAudio(2, (stream) => {
+ stream.mime('audio/foo', 'abar');
});
});
});
@@ -327,7 +320,7 @@ describe('DrmEngine', () => {
// Accept no key systems.
setRequestMediaKeySystemAccessSpy([]);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
// Both key systems were tried, since the first one failed.
@@ -341,14 +334,14 @@ describe('DrmEngine', () => {
it('fails to initialize if no key systems are recognized', async () => {
// Simulate the DASH parser inserting a blank placeholder when only
// unrecognized custom schemes are found.
- manifest.periods[0].variants[0].drmInfos[0].keySystem = '';
- manifest.periods[0].variants[0].drmInfos[1].keySystem = '';
+ manifest.variants[0].drmInfos[0].keySystem = '';
+ manifest.variants[0].drmInfos[1].keySystem = '';
const expected = Util.jasmineError(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.NO_RECOGNIZED_KEY_SYSTEMS));
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejectedWith(expected);
@@ -368,7 +361,7 @@ describe('DrmEngine', () => {
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.FAILED_TO_CREATE_CDM,
'whoops!'));
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejectedWith(expected);
@@ -383,7 +376,7 @@ describe('DrmEngine', () => {
it('queries audio/video capabilities', async () => {
setRequestMediaKeySystemAccessSpy([]);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejected();
@@ -415,7 +408,7 @@ describe('DrmEngine', () => {
it('asks for persistent state and license for offline', async () => {
setRequestMediaKeySystemAccessSpy([]);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForStorage(variants, /* usePersistentLicense= */ true))
.toBeRejected();
@@ -440,12 +433,10 @@ describe('DrmEngine', () => {
it('honors distinctive identifier and persistent state', async () => {
setRequestMediaKeySystemAccessSpy([]);
- manifest.periods[0].variants[0].drmInfos[0]
- .distinctiveIdentifierRequired = true;
- manifest.periods[0].variants[0].drmInfos[1]
- .persistentStateRequired = true;
+ manifest.variants[0].drmInfos[0].distinctiveIdentifierRequired = true;
+ manifest.variants[0].drmInfos[1].persistentStateRequired = true;
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejected();
@@ -468,12 +459,12 @@ describe('DrmEngine', () => {
it('makes no queries for clear content if no key config', async () => {
setRequestMediaKeySystemAccessSpy([]);
- manifest.periods[0].variants[0].drmInfos = [];
+ manifest.variants[0].drmInfos = [];
config.servers = {};
config.advanced = {};
drmEngine.configure(config);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.initialized()).toBe(true);
expect(shaka.media.DrmEngine.keySystem(drmEngine.getDrmInfo())).toBe('');
@@ -482,13 +473,13 @@ describe('DrmEngine', () => {
it('makes queries for clear content if key is configured', async () => {
setRequestMediaKeySystemAccessSpy(['drm.abc']);
- manifest.periods[0].variants[0].drmInfos = [];
+ manifest.variants[0].drmInfos = [];
config.servers = {
'drm.abc': 'http://abc.drm/license',
};
drmEngine.configure(config);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.initialized()).toBe(true);
expect(shaka.media.DrmEngine.keySystem(drmEngine.getDrmInfo()))
@@ -499,14 +490,12 @@ describe('DrmEngine', () => {
it('uses advanced config to fill in DrmInfo', async () => {
// Leave only one drmInfo
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('drm.abc');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- });
- variant.addAudio(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('drm.abc');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
});
+ variant.addAudio(2);
});
});
@@ -522,7 +511,7 @@ describe('DrmEngine', () => {
};
drmEngine.configure(config);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejected();
@@ -545,27 +534,22 @@ describe('DrmEngine', () => {
it('prefers advanced config from manifest if present', async () => {
// Leave only one drmInfo
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('drm.abc');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- });
- variant.addAudio(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('drm.abc');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
});
+ variant.addAudio(2);
});
});
setRequestMediaKeySystemAccessSpy([]);
// DrmInfo directly sets advanced settings.
- manifest.periods[0].variants[0].drmInfos[0]
- .distinctiveIdentifierRequired = true;
- manifest.periods[0].variants[0].drmInfos[0]
- .persistentStateRequired = true;
- manifest.periods[0].variants[0].drmInfos[0]
- .audioRobustness = 'good';
- manifest.periods[0].variants[0].drmInfos[0]
+ manifest.variants[0].drmInfos[0].distinctiveIdentifierRequired = true;
+ manifest.variants[0].drmInfos[0].persistentStateRequired = true;
+ manifest.variants[0].drmInfos[0].audioRobustness = 'good';
+ manifest.variants[0].drmInfos[0]
.videoRobustness = 'really_really_ridiculously_good';
config.advanced['drm.abc'] = {
@@ -578,7 +562,7 @@ describe('DrmEngine', () => {
};
drmEngine.configure(config);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejected();
@@ -609,7 +593,7 @@ describe('DrmEngine', () => {
shaka.util.Error.Category.DRM,
shaka.util.Error.Code.NO_LICENSE_SERVER_GIVEN,
'drm.abc'));
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await expectAsync(
drmEngine.initForPlayback(variants, manifest.offlineSessionIds))
.toBeRejectedWith(expected);
@@ -620,15 +604,13 @@ describe('DrmEngine', () => {
beforeEach(() => {
// Both audio and video with the same key system:
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('drm.abc');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- });
- variant.addAudio(2, (stream) => {
- stream.encrypted = true;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('drm.abc');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
+ });
+ variant.addAudio(2, (stream) => {
+ stream.encrypted = true;
});
});
});
@@ -636,7 +618,7 @@ describe('DrmEngine', () => {
it('does nothing for unencrypted content', async () => {
setRequestMediaKeySystemAccessSpy([]);
- manifest.periods[0].variants[0].drmInfos = [];
+ manifest.variants[0].drmInfos = [];
config.servers = {};
config.advanced = {};
@@ -661,7 +643,7 @@ describe('DrmEngine', () => {
it('prefers server certificate from DrmInfo', async () => {
const cert1 = new Uint8Array(5);
const cert2 = new Uint8Array(1);
- manifest.periods[0].variants[0].drmInfos[0].serverCertificate = cert1;
+ manifest.variants[0].drmInfos[0].serverCertificate = cert1;
config.advanced['drm.abc'] = createAdvancedConfig(cert2);
drmEngine.configure(config);
@@ -683,7 +665,7 @@ describe('DrmEngine', () => {
const initData2 = new Uint8Array(0);
/** @type {!Uint8Array} */
const initData3 = new Uint8Array(10);
- manifest.periods[0].variants[0].drmInfos[0].initData = [
+ manifest.variants[0].drmInfos[0].initData = [
{initData: initData1, initDataType: 'cenc', keyId: null},
{initData: initData2, initDataType: 'webm', keyId: null},
{initData: initData3, initDataType: 'cenc', keyId: null},
@@ -710,7 +692,7 @@ describe('DrmEngine', () => {
const initData1 = new Uint8Array(1);
const initData2 = new Uint8Array(1);
const initData3 = new Uint8Array(10);
- manifest.periods[0].variants[0].drmInfos[0].initData = [
+ manifest.variants[0].drmInfos[0].initData = [
{initData: initData1, initDataType: 'cenc', keyId: 'abc'},
{initData: initData2, initDataType: 'cenc', keyId: 'def'},
{initData: initData3, initDataType: 'cenc', keyId: 'abc'},
@@ -723,7 +705,7 @@ describe('DrmEngine', () => {
});
it('uses clearKeys config to override DrmInfo', async () => {
- manifest.periods[0].variants[0].drmInfos[0].keySystem =
+ manifest.variants[0].drmInfos[0].keySystem =
'com.fake.NOT.clearkey';
setRequestMediaKeySystemAccessSpy(['org.w3.clearkey']);
@@ -744,8 +726,8 @@ describe('DrmEngine', () => {
await initAndAttach();
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
- expect(manifest.periods[0].variants[0].drmInfos.length).toBe(1);
- expect(manifest.periods[0].variants[0].drmInfos[0].keySystem)
+ expect(manifest.variants[0].drmInfos.length).toBe(1);
+ expect(manifest.variants[0].drmInfos[0].keySystem)
.toBe('org.w3.clearkey');
expect(session.generateRequest)
@@ -764,7 +746,7 @@ describe('DrmEngine', () => {
// Regression test for #2139, in which we suppressed errors if drmInfos was
// empty and clearKeys config was given
it('fails if clearKeys config fails', async () => {
- manifest.periods[0].variants[0].drmInfos = [];
+ manifest.variants[0].drmInfos = [];
// Make it so that clear key setup fails by pretending we don't have it.
// In reality, it was failing because of missing codec info, but any
@@ -778,7 +760,7 @@ describe('DrmEngine', () => {
};
drmEngine.configure(config);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
const expected = Util.jasmineError(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
@@ -824,7 +806,7 @@ describe('DrmEngine', () => {
// Set up an init data override in the manifest to get an immediate call
// to generateRequest:
const initData1 = new Uint8Array(5);
- manifest.periods[0].variants[0].drmInfos[0].initData = [
+ manifest.variants[0].drmInfos[0].initData = [
{initData: initData1, initDataType: 'cenc', keyId: null},
];
@@ -888,7 +870,7 @@ describe('DrmEngine', () => {
it('is ignored when init data is in DrmInfo', async () => {
// Set up an init data override in the manifest:
- manifest.periods[0].variants[0].drmInfos[0].initData = [
+ manifest.variants[0].drmInfos[0].initData = [
{initData: new Uint8Array(0), initDataType: 'cenc', keyId: null},
];
@@ -918,7 +900,7 @@ describe('DrmEngine', () => {
});
it('dispatches an error if manifest says unencrypted', async () => {
- manifest.periods[0].variants[0].drmInfos = [];
+ manifest.variants[0].drmInfos = [];
config.servers = {};
config.advanced = {};
@@ -954,7 +936,7 @@ describe('DrmEngine', () => {
});
it('prefers a license server URI from configuration', async () => {
- manifest.periods[0].variants[0].drmInfos[0].licenseServerUri =
+ manifest.variants[0].drmInfos[0].licenseServerUri =
'http://foo.bar/drm';
await sendMessageTest('http://abc.drm/license');
});
@@ -1123,7 +1105,7 @@ describe('DrmEngine', () => {
// sessions.
const initData1 = new Uint8Array(10);
const initData2 = new Uint8Array(11);
- manifest.periods[0].variants[0].drmInfos[0].initData = [
+ manifest.variants[0].drmInfos[0].initData = [
{initData: initData1, initDataType: 'cenc', keyId: null},
{initData: initData2, initDataType: 'cenc', keyId: null},
];
@@ -1268,8 +1250,7 @@ describe('DrmEngine', () => {
});
it('uses clearKeys config to override DrmInfo', async () => {
- manifest.periods[0].variants[0].drmInfos[0].keySystem =
- 'com.fake.NOT.clearkey';
+ manifest.variants[0].drmInfos[0].keySystem = 'com.fake.NOT.clearkey';
setRequestMediaKeySystemAccessSpy(['org.w3.clearkey']);
// Configure clear keys (map of hex key IDs to keys)
@@ -1423,7 +1404,7 @@ describe('DrmEngine', () => {
const p = new shaka.util.PublicPromise();
requestMediaKeySystemAccessSpy.and.returnValue(p);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
const init = drmEngine.initForPlayback(
variants, manifest.offlineSessionIds);
@@ -1447,7 +1428,7 @@ describe('DrmEngine', () => {
const p = new shaka.util.PublicPromise();
requestMediaKeySystemAccessSpy.and.returnValue(p);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
const init = drmEngine.initForPlayback(
variants, manifest.offlineSessionIds);
@@ -1470,7 +1451,7 @@ describe('DrmEngine', () => {
const p = new shaka.util.PublicPromise();
mockMediaKeySystemAccess.createMediaKeys.and.returnValue(p);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
const init = drmEngine.initForPlayback(
variants, manifest.offlineSessionIds);
@@ -1737,22 +1718,20 @@ describe('DrmEngine', () => {
it('includes correct info', async () => {
// Leave only one drmInfo
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('drm.abc');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- });
- variant.addAudio(2, (stream) => {
- stream.encrypted = true;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('drm.abc');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
+ });
+ variant.addAudio(2, (stream) => {
+ stream.encrypted = true;
});
});
});
setRequestMediaKeySystemAccessSpy(['drm.abc']);
// Key IDs in manifest
- manifest.periods[0].variants[0].drmInfos[0].keyIds[0] =
+ manifest.variants[0].drmInfos[0].keyIds[0] =
'deadbeefdeadbeefdeadbeefdeadbeef';
config.advanced['drm.abc'] = {
@@ -1765,7 +1744,7 @@ describe('DrmEngine', () => {
};
drmEngine.configure(config);
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
expect(drmEngine.initialized()).toBe(true);
const drmInfo = drmEngine.getDrmInfo();
@@ -1934,7 +1913,7 @@ describe('DrmEngine', () => {
return Promise.resolve();
});
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
});
@@ -2027,7 +2006,7 @@ describe('DrmEngine', () => {
});
async function initAndAttach() {
- const variants = Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
await drmEngine.attach(mockVideo);
}
diff --git a/test/media/media_source_engine_integration.js b/test/media/media_source_engine_integration.js
index 2cc356140a..9569ab618e 100644
--- a/test/media/media_source_engine_integration.js
+++ b/test/media/media_source_engine_integration.js
@@ -66,7 +66,7 @@ describe('MediaSourceEngine', () => {
function append(type, segmentNumber) {
const segment = generators[type]
- .getSegment(segmentNumber, 0, Date.now() / 1000);
+ .getSegment(segmentNumber, Date.now() / 1000);
return mediaSourceEngine.appendBuffer(
type, segment, null, null, /* hasClosedCaptions= */ false);
}
@@ -83,7 +83,7 @@ describe('MediaSourceEngine', () => {
// captions.
function appendWithClosedCaptions(type, segmentNumber) {
const segment = generators[type]
- .getSegment(segmentNumber, 0, Date.now() / 1000);
+ .getSegment(segmentNumber, Date.now() / 1000);
return mediaSourceEngine.appendBuffer(type, segment, /* startTime= */ 0,
/* endTime= */ 2, /* hasClosedCaptions= */ true);
}
@@ -97,8 +97,8 @@ describe('MediaSourceEngine', () => {
}
function remove(type, segmentNumber) {
- const start = (segmentNumber - 1) * metadata[type].segmentDuration;
- const end = segmentNumber * metadata[type].segmentDuration;
+ const start = segmentNumber * metadata[type].segmentDuration;
+ const end = (segmentNumber + 1) * metadata[type].segmentDuration;
return mediaSourceEngine.remove(type, start, end);
}
@@ -116,11 +116,11 @@ describe('MediaSourceEngine', () => {
await mediaSourceEngine.setDuration(presentationDuration);
await appendInit(ContentType.VIDEO);
expect(buffered(ContentType.VIDEO, 0)).toBe(0);
- await append(ContentType.VIDEO, 1);
+ await append(ContentType.VIDEO, 0);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(10);
- await append(ContentType.VIDEO, 2);
+ await append(ContentType.VIDEO, 1);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20);
- await append(ContentType.VIDEO, 3);
+ await append(ContentType.VIDEO, 2);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(30);
});
@@ -131,18 +131,18 @@ describe('MediaSourceEngine', () => {
await mediaSourceEngine.setDuration(presentationDuration);
await appendInit(ContentType.VIDEO);
await Promise.all([
+ append(ContentType.VIDEO, 0),
append(ContentType.VIDEO, 1),
append(ContentType.VIDEO, 2),
- append(ContentType.VIDEO, 3),
]);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(30);
- await remove(ContentType.VIDEO, 1);
+ await remove(ContentType.VIDEO, 0);
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(10);
expect(buffered(ContentType.VIDEO, 10)).toBeCloseTo(20);
- await remove(ContentType.VIDEO, 2);
+ await remove(ContentType.VIDEO, 1);
expect(bufferStart(ContentType.VIDEO)).toBe(20);
expect(buffered(ContentType.VIDEO, 20)).toBeCloseTo(10);
- await remove(ContentType.VIDEO, 3);
+ await remove(ContentType.VIDEO, 2);
expect(bufferStart(ContentType.VIDEO)).toBe(null);
});
@@ -154,14 +154,14 @@ describe('MediaSourceEngine', () => {
await appendInit(ContentType.VIDEO);
await mediaSourceEngine.setDuration(20);
expect(mediaSource.duration).toBeCloseTo(20);
- await append(ContentType.VIDEO, 1);
+ await append(ContentType.VIDEO, 0);
expect(mediaSource.duration).toBeCloseTo(20);
await mediaSourceEngine.setDuration(35);
expect(mediaSource.duration).toBeCloseTo(35);
await Promise.all([
+ append(ContentType.VIDEO, 1),
append(ContentType.VIDEO, 2),
append(ContentType.VIDEO, 3),
- append(ContentType.VIDEO, 4),
]);
expect(mediaSource.duration).toBeCloseTo(40);
await mediaSourceEngine.setDuration(60);
@@ -174,9 +174,9 @@ describe('MediaSourceEngine', () => {
await mediaSourceEngine.init(initObject, false);
await mediaSourceEngine.setDuration(presentationDuration);
await appendInit(ContentType.VIDEO);
+ await append(ContentType.VIDEO, 0);
await append(ContentType.VIDEO, 1);
await append(ContentType.VIDEO, 2);
- await append(ContentType.VIDEO, 3);
await mediaSourceEngine.endOfStream();
expect(mediaSource.duration).toBeCloseTo(30);
});
@@ -187,7 +187,7 @@ describe('MediaSourceEngine', () => {
await mediaSourceEngine.init(initObject, false);
await mediaSourceEngine.setDuration(presentationDuration);
await appendInit(ContentType.VIDEO);
- await append(ContentType.VIDEO, 1);
+ await append(ContentType.VIDEO, 0);
// Call endOfStream twice. There should be no exception.
await mediaSourceEngine.endOfStream();
await mediaSourceEngine.endOfStream();
@@ -211,9 +211,9 @@ describe('MediaSourceEngine', () => {
await mediaSourceEngine.init(initObject, false);
checkOrder(mediaSourceEngine.setDuration(presentationDuration));
checkOrder(appendInit(ContentType.VIDEO));
+ checkOrder(append(ContentType.VIDEO, 0));
checkOrder(append(ContentType.VIDEO, 1));
checkOrder(append(ContentType.VIDEO, 2));
- checkOrder(append(ContentType.VIDEO, 3));
checkOrder(mediaSourceEngine.endOfStream());
await Promise.all(requests);
@@ -229,11 +229,11 @@ describe('MediaSourceEngine', () => {
// The test operates correctly on real hardware.
await appendInit(ContentType.AUDIO);
expect(buffered(ContentType.AUDIO, 0)).toBe(0);
- await append(ContentType.AUDIO, 1);
+ await append(ContentType.AUDIO, 0);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(10, 1);
- await append(ContentType.AUDIO, 2);
+ await append(ContentType.AUDIO, 1);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(20, 1);
- await append(ContentType.AUDIO, 3);
+ await append(ContentType.AUDIO, 2);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(30, 1);
});
@@ -247,33 +247,33 @@ describe('MediaSourceEngine', () => {
const audioStreaming = async () => {
await appendInit(ContentType.AUDIO);
- await append(ContentType.AUDIO, 1);
+ await append(ContentType.AUDIO, 0);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(10, 1);
- await append(ContentType.AUDIO, 2);
+ await append(ContentType.AUDIO, 1);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(20, 1);
- await append(ContentType.AUDIO, 3);
+ await append(ContentType.AUDIO, 2);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(30, 1);
- await append(ContentType.AUDIO, 4);
+ await append(ContentType.AUDIO, 3);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(40, 1);
- await append(ContentType.AUDIO, 5);
+ await append(ContentType.AUDIO, 4);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(50, 1);
- await append(ContentType.AUDIO, 6);
+ await append(ContentType.AUDIO, 5);
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(60, 1);
};
const videoStreaming = async () => {
await appendInit(ContentType.VIDEO);
- await append(ContentType.VIDEO, 1);
+ await append(ContentType.VIDEO, 0);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(10);
- await append(ContentType.VIDEO, 2);
+ await append(ContentType.VIDEO, 1);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20);
- await append(ContentType.VIDEO, 3);
+ await append(ContentType.VIDEO, 2);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(30);
- await append(ContentType.VIDEO, 4);
+ await append(ContentType.VIDEO, 3);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(40);
- await append(ContentType.VIDEO, 5);
+ await append(ContentType.VIDEO, 4);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(50);
- await append(ContentType.VIDEO, 6);
+ await append(ContentType.VIDEO, 5);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(60);
};
@@ -293,10 +293,10 @@ describe('MediaSourceEngine', () => {
/* appendWindowStart= */ 5,
/* appendWindowEnd= */ 18);
expect(buffered(ContentType.VIDEO, 0)).toBe(0);
- await append(ContentType.VIDEO, 1);
+ await append(ContentType.VIDEO, 0);
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(5, 1);
expect(buffered(ContentType.VIDEO, 5)).toBeCloseTo(5, 1);
- await append(ContentType.VIDEO, 2);
+ await append(ContentType.VIDEO, 1);
expect(buffered(ContentType.VIDEO, 5)).toBeCloseTo(13, 1);
});
@@ -311,8 +311,8 @@ describe('MediaSourceEngine', () => {
/* timestampOffset= */ 0,
/* appendWindowStart= */ 0,
/* appendWindowEnd= */ 20);
+ await append(ContentType.VIDEO, 0);
await append(ContentType.VIDEO, 1);
- await append(ContentType.VIDEO, 2);
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(0, 1);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20, 1);
@@ -323,8 +323,8 @@ describe('MediaSourceEngine', () => {
/* timestampOffset= */ 15,
/* appendWindowStart= */ 20,
/* appendWindowEnd= */ 35);
+ await append(ContentType.VIDEO, 0);
await append(ContentType.VIDEO, 1);
- await append(ContentType.VIDEO, 2);
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(0, 1);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(35, 1);
});
@@ -358,7 +358,7 @@ describe('MediaSourceEngine', () => {
await mediaSourceEngine.setDuration(presentationDuration);
await appendInitWithClosedCaptions(ContentType.VIDEO);
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
- await appendWithClosedCaptions(ContentType.VIDEO, 1);
+ await appendWithClosedCaptions(ContentType.VIDEO, 0);
expect(textDisplayer.appendSpy).toHaveBeenCalled();
});
diff --git a/test/media/period_observer_unit.js b/test/media/period_observer_unit.js
deleted file mode 100644
index b0f41b4ffd..0000000000
--- a/test/media/period_observer_unit.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/** @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-describe('PeriodObserver', () => {
- /** @type {shaka.extern.Manifest} */
- let manifest;
-
- /** @type {!jasmine.Spy} */
- let onPeriodChanged;
-
- /** @type {!shaka.media.PeriodObserver} */
- let observer;
-
- beforeEach(() => {
- onPeriodChanged = jasmine.createSpy('onPeriodChanged');
-
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0);
- manifest.addPeriod(10);
- manifest.addPeriod(20);
- });
-
- observer = new shaka.media.PeriodObserver(manifest);
- observer.setListeners(shaka.test.Util.spyFunc(onPeriodChanged));
- });
-
- afterEach(() => {
- observer.release();
- });
-
- // When we first update the playhead position, we should see a period changge
- // because we are entering our first period.
- it('first update calls callback', () => {
- // Our first period starts at time=0.
- poll(observer, 0);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[0]]);
- });
-
- it('does not call callback while in the same period', () => {
- // Start in period 0
- poll(observer, 0);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[0]]);
-
- // Playing in period 0 (period 1 starts at 10).
- for (const time of shaka.util.Iterables.range(10)) {
- poll(observer, time);
- expect(onPeriodChanged).not.toHaveBeenCalled();
- }
- });
-
- it('calls callback when changing to later period', () => {
- // Start in period 0
- poll(observer, 5);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[0]]);
-
- // "Play" into period 1
- poll(observer, 15);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[1]]);
-
- // "Play" into period 2
- poll(observer, 25);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[2]]);
- });
-
- it('calls callback when changing to previous period', () => {
- // Start in period 2
- poll(observer, 25);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[2]]);
-
- // "Play" into period 1
- poll(observer, 15);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[1]]);
-
- // "Play" into period 0
- poll(observer, 5);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[0]]);
- });
-
- it('calls callback once when seeking over Periods', () => {
- // Start in period 0
- poll(observer, 5);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[0]]);
-
- // Skip period 1 and "play" into period 2
- poll(observer, 25);
- expect(onPeriodChanged).toHaveBeenCalledOnceMoreWith([manifest.periods[2]]);
- });
-
- /**
- * @param {!shaka.media.IPlayheadObserver} observer
- * @param {number} inSeconds
- */
- function poll(observer, inSeconds) {
- observer.poll(
- /* position= */ inSeconds,
- /* seeking= */ false);
- }
-});
diff --git a/test/media/playhead_unit.js b/test/media/playhead_unit.js
index f64259ba03..d9f7305978 100644
--- a/test/media/playhead_unit.js
+++ b/test/media/playhead_unit.js
@@ -135,7 +135,8 @@ describe('Playhead', () => {
timeline.setDuration.and.throwError(new Error());
manifest = {
- periods: [],
+ variants: [],
+ textStreams: [],
presentationTimeline: timeline,
minBufferTime: 10,
offlineSessionIds: [],
diff --git a/test/media/segment_index_unit.js b/test/media/segment_index_unit.js
index 64d6288cff..7bf3b105ba 100644
--- a/test/media/segment_index_unit.js
+++ b/test/media/segment_index_unit.js
@@ -143,7 +143,7 @@ describe('SegmentIndex', /** @suppress {accessControls} */ () => {
});
describe('fit', () => {
- it('drops references which are outside the period bounds', () => {
+ it('clamps references to the window bounds', () => {
// These negative numbers can occur due to presentationTimeOffset in DASH.
const references = [
makeReference(uri(0), -10, -3),
@@ -160,7 +160,7 @@ describe('SegmentIndex', /** @suppress {accessControls} */ () => {
goog.asserts.assert(positionAtTimeFive != null, 'Null position!');
const referenceAtTimeFive = index.get(positionAtTimeFive);
- index.fit(/* periodStart= */ 0, /* periodEnd= */ 15);
+ index.fit(/* windowStart= */ 0, /* windowEnd= */ 15);
const newReferences = [
/* ref 0 dropped because it ends before the period starts */
makeReference(uri(1), -3, 4),
@@ -175,19 +175,20 @@ describe('SegmentIndex', /** @suppress {accessControls} */ () => {
expect(index.get(positionAtTimeFive)).toBe(referenceAtTimeFive);
});
- it('drops references which end exactly at zero', () => {
- // The end time is meant to be exclusive, so segments ending at zero
- // (after PTO adjustments) should be dropped.
+ it('drops references which end exactly at window start', () => {
+ // The end time is meant to be exclusive, so segments ending at window
+ // start should be dropped.
const references = [
makeReference(uri(0), -10, 0),
makeReference(uri(1), 0, 10),
];
+
const index = new shaka.media.SegmentIndex(references);
expect(index.references_).toEqual(references);
- index.fit(/* periodStart= */ 0, /* periodEnd= */ 10);
+ index.fit(/* windowStart= */ 0, /* windowEnd= */ 10);
const newReferences = [
- /* ref 0 dropped because it ends before the period starts (at 0) */
+ /* ref 0 dropped because it ends when the window start (at 0) */
makeReference(uri(1), 0, 10),
];
expect(index.references_).toEqual(newReferences);
diff --git a/test/media/streaming_engine_integration.js b/test/media/streaming_engine_integration.js
index b5b95c163f..ea9fa6e54d 100644
--- a/test/media/streaming_engine_integration.js
+++ b/test/media/streaming_engine_integration.js
@@ -12,6 +12,9 @@ describe('StreamingEngine', () => {
/** @type {!shaka.util.EventManager} */
let eventManager;
+ /** @type {shaka.test.Waiter} */
+ let waiter;
+
/** @type {!HTMLVideoElement} */
let video;
let timeline;
@@ -29,25 +32,15 @@ describe('StreamingEngine', () => {
/** @type {shaka.extern.Variant} */
- let variant1;
- /** @type {shaka.extern.Variant} */
- let variant2;
+ let variant;
/** @type {shaka.extern.Manifest} */
let manifest;
- /** @type {!jasmine.Spy} */
- let onChooseStreams;
- /** @type {!jasmine.Spy} */
- let onCanSwitch;
/** @type {!jasmine.Spy} */
let onError;
/** @type {!jasmine.Spy} */
let onEvent;
- /** @type {!jasmine.Spy} */
- let onInitialStreamsSetup;
- /** @type {!jasmine.Spy} */
- let onStartupComplete;
beforeAll(() => {
video = shaka.test.UiUtils.createVideoElement();
@@ -60,15 +53,13 @@ describe('StreamingEngine', () => {
beforeEach(() => {
config = shaka.util.PlayerConfiguration.createDefault().streaming;
- onChooseStreams = jasmine.createSpy('onChooseStreams');
- onCanSwitch = jasmine.createSpy('onCanSwitch');
- onInitialStreamsSetup = jasmine.createSpy('onInitialStreamsSetup');
- onStartupComplete = jasmine.createSpy('onStartupComplete');
onError = jasmine.createSpy('onError');
onError.and.callFake(fail);
onEvent = jasmine.createSpy('onEvent');
eventManager = new shaka.util.EventManager();
+ waiter = new shaka.test.Waiter(eventManager);
+
mediaSourceEngine = new shaka.media.MediaSourceEngine(
video,
new shaka.test.FakeClosedCaptionParser(),
@@ -100,11 +91,11 @@ describe('StreamingEngine', () => {
/* isLive= */ false);
setupNetworkingEngine(
- /* firstPeriodStartTime= */ 0,
- /* secondPeriodStartTime= */ 30,
/* presentationDuration= */ 60,
- {audio: metadata.audio.segmentDuration,
- video: metadata.video.segmentDuration});
+ {
+ audio: metadata.audio.segmentDuration,
+ video: metadata.video.segmentDuration,
+ });
setupManifest(
/* firstPeriodStartTime= */ 0,
@@ -138,11 +129,11 @@ describe('StreamingEngine', () => {
/* isLive= */ true);
setupNetworkingEngine(
- /* firstPeriodStartTime= */ 0,
- /* secondPeriodStartTime= */ 300,
/* presentationDuration= */ Infinity,
- {audio: metadata.audio.segmentDuration,
- video: metadata.video.segmentDuration});
+ {
+ audio: metadata.audio.segmentDuration,
+ video: metadata.video.segmentDuration,
+ });
setupManifest(
/* firstPeriodStartTime= */ 0,
@@ -181,28 +172,12 @@ describe('StreamingEngine', () => {
return generator.init();
}
- function setupNetworkingEngine(firstPeriodStartTime, secondPeriodStartTime,
- presentationDuration, segmentDurations) {
- const periodStartTimes = [firstPeriodStartTime, secondPeriodStartTime];
-
- const boundsCheckPosition = (time, number, pos) => {
- return shaka.test.StreamingEngineUtil.boundsCheckPosition(
- periodStartTimes, presentationDuration, segmentDurations, time,
- number, pos);
- };
-
- const getNumSegments = (type, number) => {
- return shaka.test.StreamingEngineUtil.getNumSegments(
- periodStartTimes, presentationDuration, segmentDurations, type,
- number);
- };
-
+ function setupNetworkingEngine(presentationDuration, segmentDurations) {
// Create the fake NetworkingEngine. Note: the StreamingEngine should never
// request a segment that does not exist.
netEngine = shaka.test.StreamingEngineUtil.createFakeNetworkingEngine(
// Init segment generator:
(type, periodNumber) => {
- expect(periodNumber).toBeLessThan(periodStartTimes.length + 1);
const wallClockTime = Date.now() / 1000;
const segment = generators[type].getInitSegment(wallClockTime);
expect(segment).not.toBeNull();
@@ -210,20 +185,8 @@ describe('StreamingEngine', () => {
},
// Media segment generator:
(type, periodNumber, position) => {
- expect(boundsCheckPosition(type, periodNumber, position))
- .not.toBeNull();
-
- // Compute the total number of segments in all Periods before the
- // |periodNumber|'th one.
- let numPriorSegments = 0;
- for (let n = 1; n < periodNumber; ++n) {
- numPriorSegments += getNumSegments(type, n);
- }
-
const wallClockTime = Date.now() / 1000;
-
- const segment = generators[type].getSegment(
- position, numPriorSegments, wallClockTime);
+ const segment = generators[type].getSegment(position, wallClockTime);
expect(segment).not.toBeNull();
return segment;
});
@@ -245,6 +208,7 @@ describe('StreamingEngine', () => {
function setupManifest(
firstPeriodStartTime, secondPeriodStartTime, presentationDuration) {
manifest = shaka.test.StreamingEngineUtil.createManifest(
+ /** @type {!shaka.media.PresentationTimeline} */(timeline),
[firstPeriodStartTime, secondPeriodStartTime], presentationDuration,
/* segmentDurations= */ {
audio: metadata.audio.segmentDuration,
@@ -255,12 +219,7 @@ describe('StreamingEngine', () => {
video: [0, null],
});
- manifest.presentationTimeline =
- /** @type {!shaka.media.PresentationTimeline} */ (timeline);
- manifest.minBufferTime = 2;
-
- variant1 = manifest.periods[0].variants[0];
- variant2 = manifest.periods[1].variants[0];
+ variant = manifest.variants[0];
}
function createStreamingEngine() {
@@ -269,14 +228,10 @@ describe('StreamingEngine', () => {
getBandwidthEstimate: () => 1e6,
mediaSourceEngine: mediaSourceEngine,
netEngine: /** @type {!shaka.net.NetworkingEngine} */(netEngine),
- onChooseStreams: Util.spyFunc(onChooseStreams),
- onCanSwitch: Util.spyFunc(onCanSwitch),
onError: Util.spyFunc(onError),
onEvent: Util.spyFunc(onEvent),
onManifestUpdate: () => {},
onSegmentAppended: () => playhead.notifyOfBufferingChange(),
- onInitialStreamsSetup: Util.spyFunc(onInitialStreamsSetup),
- onStartupComplete: Util.spyFunc(onStartupComplete),
};
streamingEngine = new shaka.media.StreamingEngine(
/** @type {shaka.extern.Manifest} */(manifest), playerInterface);
@@ -289,62 +244,47 @@ describe('StreamingEngine', () => {
});
it('plays', async () => {
- onStartupComplete.and.callFake(() => {
- video.play();
- });
-
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await reachesTheEnd();
+ video.play();
+ await waiter.timeoutAfter(90).waitForEnd(video);
});
it('plays at high playback rates', async () => {
- let startupComplete = false;
-
- onStartupComplete.and.callFake(() => {
- startupComplete = true;
- video.play();
- video.playbackRate = 10;
- });
-
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await reachesTheEnd();
- expect(startupComplete).toBe(true);
+ video.play();
+ video.playbackRate = 10;
+ await waiter.timeoutAfter(30).waitForEnd(video);
});
it('can handle buffered seeks', async () => {
- onStartupComplete.and.callFake(() => {
- video.play();
- });
-
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
+ video.play();
// After 35 seconds seek back 10 seconds into the first Period.
- await passesTime(35);
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 35);
video.currentTime = 25;
- await reachesTheEnd();
+ await waiter.timeoutAfter(60).waitForEnd(video);
});
it('can handle unbuffered seeks', async () => {
- onStartupComplete.and.callFake(() => {
- video.play();
- });
-
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(20);
+ video.play();
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 20);
video.currentTime = 40;
- await reachesTheEnd();
+ await waiter.timeoutAfter(60).waitForEnd(video);
});
});
describe('Live', () => {
+ /** @type {number} */
let slideSegmentAvailabilityWindow;
beforeEach(async () => {
@@ -360,67 +300,61 @@ describe('StreamingEngine', () => {
});
it('plays through Period transition', async () => {
- onStartupComplete.and.callFake(() => {
- // firstSegmentNumber =
- // [(segmentAvailabilityEnd - rebufferingGoal) / segmentDuration] + 1
- const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
- netEngine.expectRequest('1_video_29', segmentType);
- netEngine.expectRequest('1_audio_29', segmentType);
- video.play();
- });
-
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(305);
+
+ video.play();
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305);
+
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
+ // firstSegmentNumber =
+ // [(segmentAvailabilityEnd - rebufferingGoal) / segmentDuration] + 1
+ netEngine.expectRequest('0_video_29', segmentType);
+ netEngine.expectRequest('0_audio_29', segmentType);
});
it('can handle seeks ahead of availability window', async () => {
- const startUpCompleted = new Promise((resolve) => {
- onStartupComplete.and.callFake(() => {
- video.play();
- resolve();
- });
- });
-
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await startUpCompleted;
+ // IE is sensitive and throws InvalidStateError when you seek while
+ // readyState is 0.
+ await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
+
// Seek outside the availability window right away. The playhead
// should adjust the video's current time.
video.currentTime = timeline.segmentAvailabilityEnd + 120;
+ video.play();
// Wait until the repositioning is complete so we don't
// immediately hit this case.
await shaka.test.Util.delay(/* seconds= */ 1);
- await passesTime(305);
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305);
});
it('can handle seeks behind availability window', async () => {
- onStartupComplete.and.callFake(() => {
- video.play();
-
- // Use setTimeout to ensure the playhead has performed it's initial
- // seeking.
- setTimeout(() => {
- // Seek outside the availability window right away. The playhead
- // should adjust the video's current time.
- video.currentTime = timeline.segmentAvailabilityStart - 120;
- expect(video.currentTime).toBeGreaterThan(0);
- }, 50);
- });
-
let seekCount = 0;
eventManager.listen(video, 'seeking', () => {
seekCount++;
});
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(305);
+
+ // IE is sensitive and throws InvalidStateError when you seek while
+ // readyState is 0.
+ await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
+
+ // Seek outside the availability window right away. The playhead
+ // should adjust the video's current time.
+ video.currentTime = timeline.segmentAvailabilityStart - 120;
+ expect(video.currentTime).toBeGreaterThan(0);
+
+ video.play();
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305);
// We are playing close to the beginning of the availability window.
// We should be playing smoothly and not seeking repeatedly as we fall
@@ -443,48 +377,52 @@ describe('StreamingEngine', () => {
it('jumps small gaps at the beginning', async () => {
config.smallGapLimit = 5;
await setupGappyContent(/* gapAtStart= */ 1, /* dropSegment= */ false);
- onStartupComplete.and.callFake(() => {
- expect(video.buffered.length).toBeGreaterThan(0);
- expect(video.buffered.start(0)).toBeCloseTo(1);
-
- video.play();
- });
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(5);
+ video.play();
+
+ await waiter.timeoutAfter(5).waitUntilPlayheadReaches(video, 0.01);
+ expect(video.buffered.length).toBeGreaterThan(0);
+ expect(video.buffered.start(0)).toBeCloseTo(1);
+
+ await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 5);
});
it('jumps large gaps at the beginning', async () => {
config.smallGapLimit = 1;
config.jumpLargeGaps = true;
await setupGappyContent(/* gapAtStart= */ 5, /* dropSegment= */ false);
- onStartupComplete.and.callFake(() => {
- expect(video.buffered.length).toBeGreaterThan(0);
- expect(video.buffered.start(0)).toBeCloseTo(5);
-
- video.play();
- });
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(8);
+ video.play();
+
+ await waiter.timeoutAfter(5).waitUntilPlayheadReaches(video, 0.01);
+ expect(video.buffered.length).toBeGreaterThan(0);
+ expect(video.buffered.start(0)).toBeCloseTo(5);
+
+ await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 8);
});
it('jumps small gaps in the middle', async () => {
config.smallGapLimit = 20;
await setupGappyContent(/* gapAtStart= */ 0, /* dropSegment= */ true);
- onStartupComplete.and.callFake(() => {
- video.currentTime = 8;
- video.play();
- });
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(23);
+
+ // IE is sensitive and throws InvalidStateError when you seek while
+ // readyState is 0.
+ await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
+
+ video.currentTime = 8;
+ video.play();
+
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 23);
// Should be close enough to still have the gap buffered.
expect(video.buffered.length).toBe(2);
expect(onEvent).not.toHaveBeenCalled();
@@ -493,15 +431,19 @@ describe('StreamingEngine', () => {
it('jumps large gaps in the middle', async () => {
config.jumpLargeGaps = true;
await setupGappyContent(/* gapAtStart= */ 0, /* dropSegment= */ true);
- onStartupComplete.and.callFake(() => {
- video.currentTime = 8;
- video.play();
- });
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
- await passesTime(23);
+
+ // IE is sensitive and throws InvalidStateError when you seek while
+ // readyState is 0.
+ await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
+
+ video.currentTime = 8;
+ video.play();
+
+ await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 23);
// Should be close enough to still have the gap buffered.
expect(video.buffered.length).toBe(2);
expect(onEvent).toHaveBeenCalled();
@@ -510,26 +452,28 @@ describe('StreamingEngine', () => {
it('won\'t jump large gaps with preventDefault()', async () => {
config.jumpLargeGaps = true;
await setupGappyContent(/* gapAtStart= */ 0, /* dropSegment= */ true);
- onStartupComplete.and.callFake(() => {
- video.currentTime = 8;
- video.play();
- });
onEvent.and.callFake((event) => {
event.preventDefault();
});
// Let's go!
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ streamingEngine.switchVariant(variant);
await streamingEngine.start();
+ // IE is sensitive and throws InvalidStateError when you seek while
+ // readyState is 0.
+ await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
+
+ video.currentTime = 8;
+ video.play();
+
await shaka.test.Util.delay(5);
// IE/Edge somehow plays inside the gap. Just make sure we
// don't jump the gap.
expect(video.currentTime).toBeLessThan(20);
});
-
/**
* @param {number} gapAtStart The gap to introduce before start, in seconds.
* @param {boolean} dropSegment Whether to drop a segment in the middle.
@@ -551,8 +495,6 @@ describe('StreamingEngine', () => {
/* isLive= */ false);
setupNetworkingEngine(
- /* firstPeriodStartTime= */ 0,
- /* secondPeriodStartTime= */ 30,
/* presentationDuration= */ 30,
{
audio: metadata.audio.segmentDuration,
@@ -560,14 +502,14 @@ describe('StreamingEngine', () => {
});
manifest = setupGappyManifest(gapAtStart, dropSegment);
- variant1 = manifest.periods[0].variants[0];
+ variant = manifest.variants[0];
setupPlayhead();
createStreamingEngine();
}
/**
- * TODO: Consolidate with StreamingEngineUtils.createManifest?
+ * TODO: Consolidate with StreamingEngineUtil.createManifest?
* @param {number} gapAtStart
* @param {boolean} dropSegment
* @return {shaka.extern.Manifest}
@@ -581,13 +523,13 @@ describe('StreamingEngine', () => {
function createIndex(type, initSegmentReference) {
const d = metadata[type].segmentDuration;
const refs = [];
- let i = 1;
+ let i = 0;
let time = gapAtStart;
while (time < 30) {
let end = time + d;
- // Make segment 1 longer to make the manifest continuous, despite the
+ // Make segment 0 longer to make the manifest continuous, despite the
// dropped segment.
- if (i == 1 && dropSegment) {
+ if (i == 0 && dropSegment) {
end += d;
}
@@ -595,10 +537,10 @@ describe('StreamingEngine', () => {
const getUris = () => {
// The times in the media are based on the URL; so to drop a
// segment, we change the URL.
- if (cur >= 2 && dropSegment) {
+ if (cur >= 1 && dropSegment) {
cur++;
}
- return ['1_' + type + '_' + cur];
+ return ['0_' + type + '_' + cur];
};
refs.push(new shaka.media.SegmentReference(
/* startTime= */ time,
@@ -619,7 +561,7 @@ describe('StreamingEngine', () => {
function createInit(type) {
const getUris = () => {
- return ['1_' + type + '_init'];
+ return ['0_' + type + '_init'];
};
return new shaka.media.InitSegmentReference(getUris, 0, null);
}
@@ -628,83 +570,36 @@ describe('StreamingEngine', () => {
const videoIndex = createIndex('video', videoInit);
const audioInit = createInit('audio');
const audioIndex = createIndex('audio', audioInit);
+
return {
presentationTimeline: timeline,
offlineSessionIds: [],
minBufferTime: 2,
- periods: [{
- startTime: 0,
- textStreams: [],
- variants: [{
- id: 1,
- video: {
- id: 2,
- createSegmentIndex: () => Promise.resolve(),
- segmentIndex: videoIndex,
- mimeType: 'video/mp4',
- codecs: 'avc1.42c01e',
- bandwidth: 5000000,
- width: 600,
- height: 400,
- type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
- },
- audio: {
- id: 3,
- createSegmentIndex: () => Promise.resolve(),
- segmentIndex: audioIndex,
- mimeType: 'audio/mp4',
- codecs: 'mp4a.40.2',
- bandwidth: 192000,
- type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
- },
- }],
+ textStreams: [],
+ variants: [{
+ id: 1,
+ video: {
+ id: 2,
+ createSegmentIndex: () => Promise.resolve(),
+ segmentIndex: videoIndex,
+ mimeType: 'video/mp4',
+ codecs: 'avc1.42c01e',
+ bandwidth: 5000000,
+ width: 600,
+ height: 400,
+ type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
+ },
+ audio: {
+ id: 3,
+ createSegmentIndex: () => Promise.resolve(),
+ segmentIndex: audioIndex,
+ mimeType: 'audio/mp4',
+ codecs: 'mp4a.40.2',
+ bandwidth: 192000,
+ type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
+ },
}],
};
}
});
-
- /**
- * Choose streams for the given period.
- *
- * @param {shaka.extern.Period} period
- * @return {!Object.}
- */
- function defaultOnChooseStreams(period) {
- if (period == manifest.periods[0]) {
- return {variant: variant1, text: null};
- } else if (period == manifest.periods[1]) {
- return {variant: variant2, text: null};
- } else {
- throw new Error();
- }
- }
-
- /**
- * @param {number} seconds
- * @return {!Promise}
- */
- function passesTime(seconds) {
- return new Promise((resolve) => {
- eventManager.listen(video, 'timeupdate', () => {
- if (video.currentTime >= seconds) {
- resolve();
- }
- });
- });
- }
-
- /**
- * @return {!Promise}
- */
- function reachesTheEnd() {
- // Safari has a bug where it sometimes doesn't fire the 'ended' event,
- // so use 'timeupdate' instead.
- return new Promise((resolve) => {
- eventManager.listen(video, 'timeupdate', () => {
- if (video.ended) {
- resolve();
- }
- });
- });
- }
});
diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js
index a378c87fd6..a9a24367ae 100644
--- a/test/media/streaming_engine_unit.js
+++ b/test/media/streaming_engine_unit.js
@@ -37,23 +37,16 @@ describe('StreamingEngine', () => {
let netEngine;
let timeline;
- let audioStream1;
- let videoStream1;
- let variant1;
- let textStream1;
- let alternateVariant1;
- let alternateVideoStream1;
-
- let variant2;
- let textStream2;
+ let audioStream;
+ let videoStream;
+ let variant;
+ let textStream;
+ let alternateVariant;
+ let alternateVideoStream;
/** @type {shaka.extern.Manifest} */
let manifest;
- /** @type {!jasmine.Spy} */
- let onChooseStreams;
- /** @type {!jasmine.Spy} */
- let onCanSwitch;
/** @type {!jasmine.Spy} */
let onError;
/** @type {!jasmine.Spy} */
@@ -61,10 +54,6 @@ describe('StreamingEngine', () => {
/** @type {!jasmine.Spy} */
let onManifestUpdate;
/** @type {!jasmine.Spy} */
- let onInitialStreamsSetup;
- /** @type {!jasmine.Spy} */
- let onStartupComplete;
- /** @type {!jasmine.Spy} */
let onSegmentAppended;
/** @type {!jasmine.Spy} */
let getBandwidthEstimate;
@@ -124,8 +113,7 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.AUDIO]),
makeBuffer(segmentSizes[ContentType.AUDIO]),
],
- segmentStartTimes: [0, 10, 0, 10],
- segmentPeriodTimes: [0, 0, 20, 20],
+ segmentStartTimes: [0, 10, 20, 30],
segmentDuration: 10,
},
video: {
@@ -138,8 +126,7 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.VIDEO]),
makeBuffer(segmentSizes[ContentType.VIDEO]),
],
- segmentStartTimes: [0, 10, 0, 10],
- segmentPeriodTimes: [0, 0, 20, 20],
+ segmentStartTimes: [0, 10, 20, 30],
segmentDuration: 10,
},
text: {
@@ -150,8 +137,7 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.TEXT]),
makeBuffer(segmentSizes[ContentType.TEXT]),
],
- segmentStartTimes: [0, 10, 0, 10],
- segmentPeriodTimes: [0, 0, 20, 20],
+ segmentStartTimes: [0, 10, 20, 30],
segmentDuration: 10,
},
};
@@ -166,8 +152,7 @@ describe('StreamingEngine', () => {
makeBuffer(segmentSizes[ContentType.VIDEO]),
makeBuffer(segmentSizes[ContentType.VIDEO]),
],
- segmentStartTimes: [0, 10, 0, 10],
- segmentPeriodTimes: [0, 0, 20, 20],
+ segmentStartTimes: [0, 10, 20, 30],
segmentDuration: 10,
};
}
@@ -221,7 +206,6 @@ describe('StreamingEngine', () => {
makeBuffer(initSegmentSizeAudio)],
segments: [],
segmentStartTimes: [],
- segmentPeriodTimes: [],
segmentDuration: 10,
},
video: {
@@ -230,14 +214,12 @@ describe('StreamingEngine', () => {
makeBuffer(initSegmentSizeVideo)],
segments: [],
segmentStartTimes: [],
- segmentPeriodTimes: [],
segmentDuration: 10,
},
text: {
initSegments: [],
segments: [],
segmentStartTimes: [],
- segmentPeriodTimes: [],
segmentDuration: 10,
},
};
@@ -254,10 +236,6 @@ describe('StreamingEngine', () => {
segmentData[ContentType.AUDIO].segmentStartTimes.push(i * 10);
segmentData[ContentType.VIDEO].segmentStartTimes.push(i * 10);
segmentData[ContentType.TEXT].segmentStartTimes.push(i * 10);
-
- segmentData[ContentType.AUDIO].segmentPeriodTimes.push(0);
- segmentData[ContentType.VIDEO].segmentPeriodTimes.push(0);
- segmentData[ContentType.TEXT].segmentPeriodTimes.push(0);
}
const segmentsInSecondPeriod = 2;
@@ -269,16 +247,12 @@ describe('StreamingEngine', () => {
segmentData[ContentType.TEXT].segments.push(
makeBuffer(segmentSizes[ContentType.TEXT]));
- segmentData[ContentType.AUDIO].segmentStartTimes.push(i * 10);
- segmentData[ContentType.VIDEO].segmentStartTimes.push(i * 10);
- segmentData[ContentType.TEXT].segmentStartTimes.push(i * 10);
-
- segmentData[ContentType.AUDIO].segmentPeriodTimes.push(
- segmentsInFirstPeriod * 10);
- segmentData[ContentType.VIDEO].segmentPeriodTimes.push(
- segmentsInFirstPeriod * 10);
- segmentData[ContentType.TEXT].segmentPeriodTimes.push(
- segmentsInFirstPeriod * 10);
+ segmentData[ContentType.AUDIO].segmentStartTimes.push(
+ (segmentsInFirstPeriod + i) * 10);
+ segmentData[ContentType.VIDEO].segmentStartTimes.push(
+ (segmentsInFirstPeriod + i) * 10);
+ segmentData[ContentType.TEXT].segmentStartTimes.push(
+ (segmentsInFirstPeriod + i) * 10);
}
presentationTimeInSeconds = 110;
@@ -314,18 +288,16 @@ describe('StreamingEngine', () => {
// request a segment that does not exist.
netEngine = shaka.test.StreamingEngineUtil.createFakeNetworkingEngine(
// Init segment generator:
- (type, periodNumber) => {
- expect((periodNumber == 1) || (periodNumber == 2));
- return segmentData[type].initSegments[periodNumber - 1];
+ (type, periodIndex) => {
+ expect((periodIndex == 0) || (periodIndex == 1));
+ return segmentData[type].initSegments[periodIndex];
},
// Media segment generator:
- (type, periodNumber, position) => {
- expect(position).toBeGreaterThan(0);
- expect((periodNumber == 1 && position <= segmentsInFirstPeriod) ||
- (periodNumber == 2 && position <= segmentsInSecondPeriod));
- const i =
- (segmentsInFirstPeriod * (periodNumber - 1)) + (position - 1);
- return segmentData[type].segments[i];
+ (type, periodIndex, position) => {
+ expect(position).toBeGreaterThan(-1);
+ expect((periodIndex == 0 && position <= segmentsInFirstPeriod) ||
+ (periodIndex == 1 && position <= segmentsInSecondPeriod));
+ return segmentData[type].segments[position];
});
}
@@ -341,25 +313,22 @@ describe('StreamingEngine', () => {
segmentData['trickvideo'].segmentDuration;
}
manifest = shaka.test.StreamingEngineUtil.createManifest(
- [firstPeriodStartTime, secondPeriodStartTime], presentationDuration,
- segmentDurations, initSegmentRanges);
-
- manifest.presentationTimeline =
- /** @type {!shaka.media.PresentationTimeline} */ (timeline);
- manifest.minBufferTime = 2;
+ /** @type {!shaka.media.PresentationTimeline} */(timeline),
+ [firstPeriodStartTime, secondPeriodStartTime],
+ presentationDuration, segmentDurations, initSegmentRanges);
- audioStream1 = manifest.periods[0].variants[0].audio;
- videoStream1 = manifest.periods[0].variants[0].video;
- variant1 = manifest.periods[0].variants[0];
- textStream1 = manifest.periods[0].textStreams[0];
+ audioStream = manifest.variants[0].audio;
+ videoStream = manifest.variants[0].video;
+ variant = manifest.variants[0];
+ textStream = manifest.textStreams[0];
// This Stream is only used to verify that StreamingEngine can setup
// Streams correctly.
- alternateVideoStream1 =
+ alternateVideoStream =
shaka.test.StreamingEngineUtil.createMockVideoStream(8);
- alternateVariant1 = {
- audio: null,
- video: /** @type {shaka.extern.Stream} */ (alternateVideoStream1),
+ alternateVariant = {
+ audio: audioStream,
+ video: /** @type {shaka.extern.Stream} */ (alternateVideoStream),
id: 0,
language: 'und',
primary: false,
@@ -368,10 +337,7 @@ describe('StreamingEngine', () => {
allowedByApplication: true,
allowedByKeySystem: true,
};
- manifest.periods[0].variants.push(alternateVariant1);
-
- variant2 = manifest.periods[1].variants[0];
- textStream2 = manifest.periods[1].textStreams[0];
+ manifest.variants.push(alternateVariant);
}
/**
@@ -381,10 +347,6 @@ describe('StreamingEngine', () => {
* configuration object which overrides the default one.
*/
function createStreamingEngine(config) {
- onChooseStreams = jasmine.createSpy('onChooseStreams');
- onCanSwitch = jasmine.createSpy('onCanSwitch');
- onInitialStreamsSetup = jasmine.createSpy('onInitialStreamsSetup');
- onStartupComplete = jasmine.createSpy('onStartupComplete');
onError = jasmine.createSpy('onError');
onError.and.callFake(fail);
onEvent = jasmine.createSpy('onEvent');
@@ -408,14 +370,10 @@ describe('StreamingEngine', () => {
getBandwidthEstimate: Util.spyFunc(getBandwidthEstimate),
mediaSourceEngine: mediaSourceEngine,
netEngine: /** @type {!shaka.net.NetworkingEngine} */(netEngine),
- onChooseStreams: Util.spyFunc(onChooseStreams),
- onCanSwitch: Util.spyFunc(onCanSwitch),
onError: Util.spyFunc(onError),
onEvent: Util.spyFunc(onEvent),
onManifestUpdate: Util.spyFunc(onManifestUpdate),
onSegmentAppended: Util.spyFunc(onSegmentAppended),
- onInitialStreamsSetup: Util.spyFunc(onInitialStreamsSetup),
- onStartupComplete: Util.spyFunc(onStartupComplete),
};
streamingEngine = new shaka.media.StreamingEngine(
/** @type {shaka.extern.Manifest} */(manifest), playerInterface);
@@ -433,23 +391,14 @@ describe('StreamingEngine', () => {
// This test initializes the StreamingEngine (SE) and allows it to play
// through both Periods.
//
- // After calling start() the following should occur:
- // 1. SE should immediately call onChooseStreams() with the first Period.
- // 2. SE should setup each of the initial Streams and then call
- // onInitialStreamsSetup().
- // 3. SE should start appending the initial Streams' segments and in
- // parallel setup the remaining Streams within the Manifest.
- // - SE should call onStartupComplete() after it has buffered at least 1
- // segment of each type of content.
- // - SE should call onCanSwitch() with the first Period after it has
- // setup the remaining Streams within the first Period.
- // 4. SE should call onChooseStreams() with the second Period after it has
- // both segments within the first Period.
- // - We must return the Streams within the second Period.
- // 5. SE should call onCanSwitch() with the second Period shortly after
- // step 4.
- // 6. SE should call MediaSourceEngine.endOfStream() after it has appended
- // both segments within the second Period. At this point the playhead
+ // After construction of StreamingEngine, the following should occur:
+ // 1. The owner should immediately call switchVariant() with the initial
+ // variant.
+ // 2. The owner should call start().
+ // 3. SE should setup each of the initial Streams.
+ // 4. SE should start appending the initial Streams' segments.
+ // 5. SE should call MediaSourceEngine.endOfStream() after it has appended
+ // both segments from the second Period. At this point, the playhead
// should not be at the end of the presentation, but the test will be
// effectively over since SE will have nothing else to do.
it('initializes and plays VOD', async () => {
@@ -457,84 +406,37 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => {
- // Verify buffers.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [true, false],
- video: [true, false],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, false, false, false],
- video: [true, false, false, false],
- text: [true, false, false, false],
- });
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
- setupFakeGetTime(0);
+ // Verify buffers.
+ expect(mediaSourceEngine.initSegments).toEqual({
+ audio: [false, false],
+ video: [false, false],
+ text: [],
});
-
- expect(mediaSourceEngine.reinitText).not.toHaveBeenCalled();
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onCanSwitch.and.callFake(() => {
- expect(mediaSourceEngine.reinitText).not.toHaveBeenCalled();
- mediaSourceEngine.reinitText.calls.reset();
- onCanSwitch.and.throwError(new Error());
- });
-
- // For second Period.
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Verify buffers.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [true, false],
- video: [true, false],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, false, false],
- video: [true, true, false, false],
- text: [true, true, false, false],
- });
-
- verifyNetworkingEngineRequestCalls(1);
-
- onCanSwitch.and.callFake(() => {
- expect(mediaSourceEngine.reinitText).toHaveBeenCalled();
- mediaSourceEngine.reinitText.calls.reset();
- onCanSwitch.and.throwError(new Error());
- });
-
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
-
- // Init the first Period.
- return defaultOnChooseStreams(period);
+ expect(mediaSourceEngine.segments).toEqual({
+ audio: [false, false, false, false],
+ video: [false, false, false, false],
+ text: [false, false, false, false],
});
- onInitialStreamsSetup.and.callFake(() => {
- const expectedObject = new Map();
- expectedObject.set(ContentType.AUDIO, audioStream1);
- expectedObject.set(ContentType.VIDEO, videoStream1);
- expectedObject.set(ContentType.TEXT, textStream1);
- expect(mediaSourceEngine.init)
- .toHaveBeenCalledWith(expectedObject, false);
- expect(mediaSourceEngine.init).toHaveBeenCalledTimes(1);
- mediaSourceEngine.init.calls.reset();
+ const expectedMseInit = new Map();
+ expectedMseInit.set(ContentType.AUDIO, audioStream);
+ expectedMseInit.set(ContentType.VIDEO, videoStream);
+ expectedMseInit.set(ContentType.TEXT, textStream);
- expect(mediaSourceEngine.setDuration).toHaveBeenCalledTimes(1);
- expect(mediaSourceEngine.setDuration).toHaveBeenCalledWith(40);
- mediaSourceEngine.setDuration.calls.reset();
- });
+ expect(mediaSourceEngine.init).toHaveBeenCalledWith(expectedMseInit, false);
+ expect(mediaSourceEngine.init).toHaveBeenCalledTimes(1);
- // Here we go!
- streamingEngine.start();
+ expect(mediaSourceEngine.setDuration).toHaveBeenCalledTimes(1);
+ expect(mediaSourceEngine.setDuration).toHaveBeenCalledWith(40);
await runTest();
+
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
// Verify buffers.
@@ -549,35 +451,43 @@ describe('StreamingEngine', () => {
text: [true, true, true, true],
});
- verifyNetworkingEngineRequestCalls(2);
- });
+ netEngine.expectRangeRequest(
+ '0_audio_init',
+ initSegmentRanges[ContentType.AUDIO][0],
+ initSegmentRanges[ContentType.AUDIO][1]);
- describe('loadNewTextStream', () => {
- it('clears MediaSourceEngine', async () => {
- setupVod();
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
- });
- onChooseStreams.and.callFake(onChooseStreamsWithUnloadedText);
+ netEngine.expectRangeRequest(
+ '0_video_init',
+ initSegmentRanges[ContentType.VIDEO][0],
+ initSegmentRanges[ContentType.VIDEO][1]);
+
+ netEngine.expectRangeRequest(
+ '1_audio_init',
+ initSegmentRanges[ContentType.AUDIO][0],
+ initSegmentRanges[ContentType.AUDIO][1]);
+
+ netEngine.expectRangeRequest(
+ '1_video_init',
+ initSegmentRanges[ContentType.VIDEO][0],
+ initSegmentRanges[ContentType.VIDEO][1]);
- streamingEngine.start();
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
- await runTest(async () => {
- if (presentationTimeInSeconds == 20) {
- mediaSourceEngine.clear.calls.reset();
- mediaSourceEngine.init.calls.reset();
- await streamingEngine.loadNewTextStream(textStream2);
- expect(mediaSourceEngine.clear).toHaveBeenCalledWith('text');
+ netEngine.expectRequest('0_audio_0', segmentType);
+ netEngine.expectRequest('0_video_0', segmentType);
+ netEngine.expectRequest('0_text_0', segmentType);
- const expectedObject = new Map();
- expectedObject.set(ContentType.TEXT, jasmine.any(Object));
- expect(mediaSourceEngine.init).toHaveBeenCalledWith(
- expectedObject, false);
- }
- });
- });
+ netEngine.expectRequest('0_audio_1', segmentType);
+ netEngine.expectRequest('0_video_1', segmentType);
+ netEngine.expectRequest('0_text_1', segmentType);
+
+ netEngine.expectRequest('1_audio_2', segmentType);
+ netEngine.expectRequest('1_video_2', segmentType);
+ netEngine.expectRequest('1_text_2', segmentType);
+
+ netEngine.expectRequest('1_audio_3', segmentType);
+ netEngine.expectRequest('1_video_3', segmentType);
+ netEngine.expectRequest('1_text_3', segmentType);
});
describe('unloadTextStream', () => {
@@ -585,26 +495,26 @@ describe('StreamingEngine', () => {
setupVod();
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
- });
- onChooseStreams.and.callFake(onChooseStreamsWithUnloadedText);
- streamingEngine.start();
- const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
// Verify that after unloading text stream, no network request for text
// is sent.
await runTest(() => {
+ const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
+
if (presentationTimeInSeconds == 1) {
- netEngine.expectRequest('1_text_1', segmentType);
+ netEngine.expectRequest('0_text_0', segmentType);
netEngine.request.calls.reset();
streamingEngine.unloadTextStream();
} else if (presentationTimeInSeconds == 35) {
- netEngine.expectNoRequest('1_text_1', segmentType);
+ netEngine.expectNoRequest('0_text_0', segmentType);
+ netEngine.expectNoRequest('0_text_1', segmentType);
netEngine.expectNoRequest('1_text_2', segmentType);
- netEngine.expectNoRequest('2_text_1', segmentType);
- netEngine.expectNoRequest('2_text_2', segmentType);
+ netEngine.expectNoRequest('1_text_3', segmentType);
}
});
});
@@ -615,16 +525,12 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- presentationTimeInSeconds = 100;
-
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(100);
- });
-
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
-
// Here we go!
- streamingEngine.start();
+ presentationTimeInSeconds = 100;
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest(slideSegmentAvailabilityWindow);
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -647,101 +553,6 @@ describe('StreamingEngine', () => {
}
});
- // Start the playhead in the first Period but pass start() Streams from the
- // second Period.
- it('plays from 1st Period when passed Streams from 2nd', async () => {
- setupVod();
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
-
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
- });
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- // Start with Streams from the second Period even though the playhead is
- // in the first Period. onChooseStreams() should be called again for the
- // first Period and then eventually for the second Period.
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- return defaultOnChooseStreams(period);
- });
-
- return defaultOnChooseStreams(period);
- });
-
- return {variant: variant2, text: textStream2};
- });
-
- streamingEngine.start();
-
- await runTest();
- // Verify buffers.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [false, true],
- video: [false, true],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, true, true],
- video: [true, true, true, true],
- text: [true, true, true, true],
- });
- });
-
- // Start the playhead in the second Period but pass start() Streams from the
- // first Period.
- it('plays from 2nd Period when passed Streams from 1st', async () => {
- setupVod();
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
-
- presentationTimeInSeconds = 20;
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(20);
- });
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Start with Streams from the first Period even though the playhead is
- // in the second Period. onChooseStreams() should be called again for the
- // second Period.
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- onChooseStreams.and.throwError(new Error());
-
- return defaultOnChooseStreams(period);
- });
-
- return {variant: variant1, text: textStream1};
- });
-
- streamingEngine.start();
-
- await runTest();
- // Verify buffers.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [false, true],
- video: [false, true],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [false, false, true, true],
- video: [false, false, true, true],
- text: [false, false, true, true],
- });
- });
-
it('plays when a small gap is present at the beginning', async () => {
const drift = 0.050; // 50 ms
@@ -751,131 +562,32 @@ describe('StreamingEngine', () => {
createStreamingEngine();
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
-
- await runTest();
- expect(onStartupComplete).toHaveBeenCalled();
- });
-
- it('plays when 1st Period doesn\'t have text streams', async () => {
- setupVod();
- manifest.periods[0].textStreams = [];
-
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake((period) => {
- const chosen = defaultOnChooseStreams(period);
- if (period == manifest.periods[0]) {
- chosen.text = null;
- }
- return chosen;
- });
-
- // Here we go!
- streamingEngine.start();
- await runTest();
-
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, true, true],
- video: [true, true, true, true],
- text: [false, false, true, true],
- });
- });
-
- it('doesn\'t get stuck when 2nd Period isn\'t available yet', async () => {
- // See: https://github.com/google/shaka-player/pull/839
- setupVod();
- manifest.periods[0].textStreams = [];
-
- // For the first update, indicate the segment isn't available. This should
- // not cause us to fallback to the Playhead time to determine which segment
- // to start streaming.
- await textStream2.createSegmentIndex();
- const oldGet = textStream2.segmentIndex.get;
- textStream2.segmentIndex.get = (idx) => {
- if (idx == 1) {
- textStream2.segmentIndex.get = oldGet;
- return null;
- }
- return oldGet(idx);
- };
-
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake((period) => {
- const chosen = defaultOnChooseStreams(period);
- if (period == manifest.periods[0]) {
- chosen.text = null;
- }
- return chosen;
- });
-
- // Here we go!
- streamingEngine.start();
- await runTest();
-
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, true, true],
- video: [true, true, true, true],
- text: [false, false, true, true],
- });
- });
-
- it('only reinitializes text when switching streams', async () => {
- // See: https://github.com/google/shaka-player/issues/910
- setupVod();
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(defaultOnChooseStreams);
-
- // When we can switch in the second Period, switch to the playing stream.
- onCanSwitch.and.callFake(() => {
- onCanSwitch.and.callFake(() => {
- expect(streamingEngine.getBufferingText()).toBe(textStream2);
-
- mediaSourceEngine.reinitText.calls.reset();
- streamingEngine.switchTextStream(textStream2);
- });
- });
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
- // Here we go!
- streamingEngine.start();
await runTest();
-
- expect(mediaSourceEngine.reinitText).not.toHaveBeenCalled();
});
- it('plays when 2nd Period doesn\'t have text streams', async () => {
+ it('plays with no chosen text streams', async () => {
setupVod();
- manifest.periods[1].textStreams = [];
+ manifest.textStreams = [];
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake((period) => {
- const chosen = defaultOnChooseStreams(period);
- if (period == manifest.periods[1]) {
- chosen.text = null;
- }
- return chosen;
- });
-
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ // Don't call switchTextStream.
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.segments).toEqual({
audio: [true, true, true, true],
video: [true, true, true, true],
- text: [true, true, false, false],
+ text: [false, false, false, false],
});
});
@@ -884,9 +596,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(defaultOnChooseStreams);
-
mediaSourceEngine.endOfStream.and.callFake(() => {
expect(mediaSourceEngine.setDuration).toHaveBeenCalledWith(40);
expect(mediaSourceEngine.setDuration).toHaveBeenCalledTimes(1);
@@ -897,7 +606,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -910,9 +622,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(defaultOnChooseStreams);
-
mediaSourceEngine.endOfStream.and.callFake(() => {
expect(mediaSourceEngine.setDuration).toHaveBeenCalledWith(40);
expect(mediaSourceEngine.setDuration).toHaveBeenCalledTimes(1);
@@ -923,7 +632,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -936,15 +648,15 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(defaultOnChooseStreams);
-
// The duration can spuriously be set to 0, so we should ignore this and not
// update the duration.
mediaSourceEngine.getDuration.and.returnValue(0);
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -956,11 +668,11 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(defaultOnChooseStreams);
-
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
// The second Period starts at 20, so we should set the appendWindowStart to
@@ -972,7 +684,7 @@ describe('StreamingEngine', () => {
asymmetricMatch: (val) => val > 40 && val <= 40.1,
};
expect(mediaSourceEngine.setStreamProperties)
- .toHaveBeenCalledWith('video', 20, lt20, gt40);
+ .toHaveBeenCalledWith('video', 0, lt20, gt40);
});
it('does not buffer one media type ahead of another', async () => {
@@ -1003,6 +715,12 @@ describe('StreamingEngine', () => {
maxBuffered = Math.max(maxBuffered, buffered);
}
+ // Simulated playback doesn't start until some of each is buffered. This
+ // realism is important to the test passing.
+ if (minBuffered > 0) {
+ playing = true;
+ }
+
// Sanity check.
expect(maxBuffered).not.toBeLessThan(minBuffered);
// Proof that we didn't get too far ahead (10s == 1 segment).
@@ -1012,9 +730,12 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(defaultOnChooseStreams);
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ // Simulated playback is started in the appendBuffer fake when some of each
+ // type is buffered. This realism is important to the test passing.
+ playing = false;
await runTest();
// Make sure appendBuffer was called, so that we know that we executed the
@@ -1031,37 +752,35 @@ describe('StreamingEngine', () => {
beforeEach(() => {
// Set up a manifest with multiple variants and a text stream.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(10, (stream) => {
- stream.useSegmentTemplate('audio-10-%d.mp4', 10);
- });
- variant.addVideo(11, (stream) => {
- stream.useSegmentTemplate('video-11-%d.mp4', 10);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(10, (stream) => {
+ stream.useSegmentTemplate('audio-10-%d.mp4', 10);
});
- period.addVariant(1, (variant) => {
- variant.addExistingStream(10); // audio
- variant.addVideo(12, (stream) => {
- stream.useSegmentTemplate('video-12-%d.mp4', 10);
- });
+ variant.addVideo(11, (stream) => {
+ stream.useSegmentTemplate('video-11-%d.mp4', 10);
});
- period.addVariant(2, (variant) => {
- variant.addAudio(13, (stream) => {
- stream.useSegmentTemplate('audio-13-%d.mp4', 10);
- });
- variant.addExistingStream(12); // video
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addExistingStream(10); // audio
+ variant.addVideo(12, (stream) => {
+ stream.useSegmentTemplate('video-12-%d.mp4', 10);
});
- period.addTextStream(20, (stream) => {
- stream.useSegmentTemplate('text-20-%d.mp4', 10);
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(13, (stream) => {
+ stream.useSegmentTemplate('audio-13-%d.mp4', 10);
});
+ variant.addExistingStream(12); // video
+ });
+ manifest.addTextStream(20, (stream) => {
+ stream.useSegmentTemplate('text-20-%d.mp4', 10);
});
});
- initialVariant = manifest.periods[0].variants[0];
- sameAudioVariant = manifest.periods[0].variants[1];
- sameVideoVariant = manifest.periods[0].variants[2];
- initialTextStream = manifest.periods[0].textStreams[0];
+ initialVariant = manifest.variants[0];
+ sameAudioVariant = manifest.variants[1];
+ sameVideoVariant = manifest.variants[2];
+ initialTextStream = manifest.textStreams[0];
// For these tests, we don't care about specific data appended.
// Just return any old ArrayBuffer for any requested segment.
@@ -1102,41 +821,34 @@ describe('StreamingEngine', () => {
presentationTimeInSeconds = 0;
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(() => {
- return {variant: initialVariant, text: initialTextStream};
- });
+ streamingEngine.switchVariant(initialVariant);
+ streamingEngine.switchTextStream(initialTextStream);
});
it('will not clear buffers if streams have not changed', async () => {
- onCanSwitch.and.callFake(async () => {
- mediaSourceEngine.clear.calls.reset();
- streamingEngine.switchVariant(
- sameAudioVariant, /* clearBuffer= */ true, /* safeMargin= */ 0);
- await Util.fakeEventLoop(1);
- expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('audio');
- expect(mediaSourceEngine.clear).toHaveBeenCalledWith('video');
- expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('text');
-
- mediaSourceEngine.clear.calls.reset();
- streamingEngine.switchVariant(
- sameVideoVariant, /* clearBuffer= */ true, /* safeMargin= */ 0);
- await Util.fakeEventLoop(1);
- expect(mediaSourceEngine.clear).toHaveBeenCalledWith('audio');
- expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('video');
- expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('text');
+ streamingEngine.start().catch(fail);
+ playing = true;
- mediaSourceEngine.clear.calls.reset();
- streamingEngine.switchTextStream(initialTextStream);
- await Util.fakeEventLoop(1);
- expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
- });
+ await Util.fakeEventLoop(1);
- streamingEngine.start().catch(fail);
+ mediaSourceEngine.clear.calls.reset();
+ streamingEngine.switchVariant(sameAudioVariant, /* clearBuffer= */ true);
+ await Util.fakeEventLoop(1);
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('audio');
+ expect(mediaSourceEngine.clear).toHaveBeenCalledWith('video');
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('text');
- await Util.fakeEventLoop(10);
+ mediaSourceEngine.clear.calls.reset();
+ streamingEngine.switchVariant(sameVideoVariant, /* clearBuffer= */ true);
+ await Util.fakeEventLoop(1);
+ expect(mediaSourceEngine.clear).toHaveBeenCalledWith('audio');
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('video');
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('text');
- expect(onCanSwitch).toHaveBeenCalled();
+ mediaSourceEngine.clear.calls.reset();
+ streamingEngine.switchTextStream(initialTextStream);
+ await Util.fakeEventLoop(1);
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
});
});
@@ -1149,41 +861,27 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
-
onTick = jasmine.createSpy('onTick');
onTick.and.stub();
});
it('into buffered regions', async () => {
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Seek backwards to a buffered region in the first Period. Note that
- // since the buffering goal is 5 seconds and each segment is 10
- // seconds long, the second segment of this Period will be required at
- // 6 seconds. Then it will load the next Period, but not require the
- // new segments.
- expect(presentationTimeInSeconds).toBe(6);
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
+
+ let seekComplete = false;
+ await runTest(() => {
+ if (presentationTimeInSeconds == 6 && !seekComplete) {
+ // Seek backwards to a buffered region in the first Period.
presentationTimeInSeconds -= 5;
streamingEngine.seeked();
-
- // Although we're seeking backwards we still have to return some
- // Streams from the second Period here.
- return defaultOnChooseStreams(period);
- });
-
- // Init the first Period.
- return defaultOnChooseStreams(period);
+ seekComplete = true;
+ }
});
- // Here we go!
- streamingEngine.start();
-
- await runTest();
// Verify buffers.
expect(mediaSourceEngine.initSegments).toEqual({
audio: [false, true],
@@ -1202,49 +900,38 @@ describe('StreamingEngine', () => {
// resolution, and after the seek some states are buffered and some
// are unbuffered, StreamingEngine should only clear the unbuffered
// states.
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- mediaSourceEngine.endOfStream.and.callFake(() => {
- // Should have the first Period entirely buffered.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [false, true],
- video: [false, true],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, true, true],
- video: [true, true, true, true],
- text: [true, true, true, true],
- });
-
- // Fake the audio buffer being removed.
- mediaSourceEngine.segments[ContentType.AUDIO] =
- [true, true, false, false];
-
- // Seek back into the second Period.
- expect(presentationTimeInSeconds).toBe(26);
- presentationTimeInSeconds -= 5;
- streamingEngine.seeked();
-
-
- mediaSourceEngine.endOfStream.and.returnValue(Promise.resolve());
- return Promise.resolve();
- });
- return defaultOnChooseStreams(period);
+ mediaSourceEngine.endOfStream.and.callFake(() => {
+ // Should have the first Period entirely buffered.
+ expect(mediaSourceEngine.initSegments).toEqual({
+ audio: [false, true],
+ video: [false, true],
+ text: [],
+ });
+ expect(mediaSourceEngine.segments).toEqual({
+ audio: [true, true, true, true],
+ video: [true, true, true, true],
+ text: [true, true, true, true],
});
- return defaultOnChooseStreams(period);
- });
+ // Fake the audio buffer being removed.
+ mediaSourceEngine.segments[ContentType.AUDIO] =
+ [true, true, false, false];
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
+ // Seek back into the second Period.
+ presentationTimeInSeconds -= 5;
+ expect(presentationTimeInSeconds).toBeGreaterThan(19);
+ streamingEngine.seeked();
+
+ mediaSourceEngine.endOfStream.and.returnValue(Promise.resolve());
+ return Promise.resolve();
+ });
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
// When seeking within the same period, clear the buffer of the
@@ -1266,41 +953,24 @@ describe('StreamingEngine', () => {
});
});
-
it('into buffered regions across Periods', async () => {
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- onChooseStreams.and.throwError(new Error());
-
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
-
- mediaSourceEngine.endOfStream.and.callFake(() => {
- // Seek backwards to a buffered region in the first Period. Note
- // that since the buffering goal is 5 seconds and each segment is
- // 10 seconds long, the last segment should be required at 26 seconds.
- // Then endOfStream() should be called.
- expect(presentationTimeInSeconds).toBe(26);
- presentationTimeInSeconds -= 20;
- streamingEngine.seeked();
-
- // Verify that buffers are not cleared.
- expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
+ mediaSourceEngine.endOfStream.and.callFake(() => {
+ // Seek backwards to a buffered region in the first Period.
+ presentationTimeInSeconds -= 20;
+ expect(presentationTimeInSeconds).toBeLessThan(20);
+ streamingEngine.seeked();
- return Promise.resolve();
- });
+ // Verify that buffers are not cleared.
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
- // Init the first Period.
- return defaultOnChooseStreams(period);
+ return Promise.resolve();
});
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
// Verify buffers.
@@ -1317,24 +987,12 @@ describe('StreamingEngine', () => {
});
it('into unbuffered regions', async () => {
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.throwError(new Error());
-
- // Init the first Period.
- return defaultOnChooseStreams(period);
- });
-
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
-
- // Seek forward to an unbuffered region in the first Period.
- expect(presentationTimeInSeconds).toBe(0);
- presentationTimeInSeconds += 15;
- streamingEngine.seeked();
+ onTick.and.callFake(() => {
+ if (presentationTimeInSeconds == 6) {
+ // Note that since the buffering goal is 5 seconds and each segment is
+ // 10 seconds long, the second segment of this Period will be required
+ // at 6 seconds.
- onChooseStreams.and.callFake((period) => {
// Verify that all buffers have been cleared.
expect(mediaSourceEngine.clear)
.toHaveBeenCalledWith(ContentType.AUDIO);
@@ -1343,9 +1001,8 @@ describe('StreamingEngine', () => {
expect(mediaSourceEngine.clear)
.toHaveBeenCalledWith(ContentType.TEXT);
- expect(period).toBe(manifest.periods[1]);
-
- // Verify buffers.
+ // Verify buffers. The first segment is present because we start
+ // off-by-one after a seek.
expect(mediaSourceEngine.initSegments).toEqual({
audio: [true, false],
video: [true, false],
@@ -1356,18 +1013,22 @@ describe('StreamingEngine', () => {
video: [false, true, false, false],
text: [false, true, false, false],
});
-
- onChooseStreams.and.throwError(new Error());
-
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
+ }
});
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
+
+ // Seek forward to an unbuffered region in the first Period.
+ expect(presentationTimeInSeconds).toBe(0);
+ presentationTimeInSeconds += 15;
+ streamingEngine.seeked();
await runTest(Util.spyFunc(onTick));
+
// Verify buffers.
expect(mediaSourceEngine.initSegments).toEqual({
audio: [false, true],
@@ -1383,18 +1044,7 @@ describe('StreamingEngine', () => {
it('into unbuffered regions across Periods', async () => {
// Start from the second Period.
- presentationTimeInSeconds = 20;
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(20));
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- onChooseStreams.and.throwError(new Error());
-
- // Init the second Period.
- return defaultOnChooseStreams(period);
- });
+ presentationTimeInSeconds = 25;
mediaSourceEngine.endOfStream.and.callFake(() => {
// Verify buffers.
@@ -1409,12 +1059,9 @@ describe('StreamingEngine', () => {
text: [false, false, true, true],
});
- // Seek backwards to an unbuffered region in the first Period. Note
- // that since the buffering goal is 5 seconds and each segment is 10
- // seconds long, the last segment should be required at 26 seconds.
- // Then endOfStream() should be called.
- expect(presentationTimeInSeconds).toBe(26);
+ // Seek backwards to an unbuffered region in the first Period.
presentationTimeInSeconds -= 20;
+ expect(presentationTimeInSeconds).toBeLessThan(20);
streamingEngine.seeked();
onTick.and.callFake(() => {
@@ -1428,40 +1075,15 @@ describe('StreamingEngine', () => {
onTick.and.stub();
});
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Verify buffers.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [true, false],
- video: [true, false],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, false, false],
- video: [true, true, false, false],
- text: [true, true, false, false],
- });
-
- onChooseStreams.and.throwError(new Error());
-
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
-
- mediaSourceEngine.endOfStream.and.returnValue(Promise.resolve());
-
- // Switch to the first Period.
- return defaultOnChooseStreams(period);
- });
+ mediaSourceEngine.endOfStream.and.returnValue(Promise.resolve());
return Promise.resolve();
});
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest(Util.spyFunc(onTick));
// Verify buffers.
@@ -1470,65 +1092,39 @@ describe('StreamingEngine', () => {
video: [false, true],
text: [],
});
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, true, true],
- video: [true, true, true, true],
- text: [true, true, true, true],
- });
- });
-
- it('into unbuffered regions when nothing is buffered', async () => {
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.throwError(new Error());
-
- // Init the first Period.
- return defaultOnChooseStreams(period);
- });
-
- onInitialStreamsSetup.and.callFake(() => {
- // Seek forward to an unbuffered region in the first Period.
- expect(presentationTimeInSeconds).toBe(0);
- presentationTimeInSeconds = 15;
- streamingEngine.seeked();
-
- onTick.and.callFake(() => {
- // Nothing should have been cleared.
- expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
- onTick.and.stub();
- });
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Verify buffers.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [true, false],
- video: [true, false],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [false, true, false, false],
- video: [false, true, false, false],
- text: [false, true, false, false],
- });
+ expect(mediaSourceEngine.segments).toEqual({
+ audio: [true, true, true, true],
+ video: [true, true, true, true],
+ text: [true, true, true, true],
+ });
+ });
- onChooseStreams.and.throwError(new Error());
+ it('into unbuffered regions when nothing is buffered', async () => {
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
+ // Nothing is buffered yet.
+ expect(mediaSourceEngine.segments).toEqual({
+ audio: [false, false, false, false],
+ video: [false, false, false, false],
+ text: [false, false, false, false],
});
- // This happens after onInitialStreamsSetup(), so pass 15 so the playhead
- // resumes from 15.
- onStartupComplete.and.callFake(() => setupFakeGetTime(15));
+ // Seek forward to an unbuffered region in the first Period.
+ presentationTimeInSeconds = 15;
+ streamingEngine.seeked();
- // Here we go!
- streamingEngine.start();
+ onTick.and.callFake(() => {
+ // Nothing should have been cleared.
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
+ onTick.and.stub();
+ });
await runTest(Util.spyFunc(onTick));
+
// Verify buffers.
expect(mediaSourceEngine.initSegments).toEqual({
audio: [false, true],
@@ -1543,29 +1139,24 @@ describe('StreamingEngine', () => {
});
it('into unbuffered regions near segment start', async () => {
- onChooseStreams.and.callFake(defaultOnChooseStreams);
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
- onInitialStreamsSetup.and.callFake(() => {
- // Seek forward to an unbuffered region in the first Period.
- expect(presentationTimeInSeconds).toBe(0);
- presentationTimeInSeconds = 11;
- streamingEngine.seeked();
+ // Seek forward to an unbuffered region in the first Period.
+ presentationTimeInSeconds = 11;
+ streamingEngine.seeked();
- onTick.and.callFake(() => {
- // Nothing should have been cleared.
- expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
- onTick.and.stub();
- });
+ onTick.and.callFake(() => {
+ // Nothing should have been cleared.
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalled();
+ onTick.and.stub();
});
- // This happens after onInitialStreamsSetup(), so pass 11 so the playhead
- // resumes from 11.
- onStartupComplete.and.callFake(() => setupFakeGetTime(11));
-
- // Here we go!
- streamingEngine.start();
-
await runTest(Util.spyFunc(onTick));
+
// Verify buffers.
expect(mediaSourceEngine.initSegments).toEqual({
audio: [false, true],
@@ -1586,25 +1177,18 @@ describe('StreamingEngine', () => {
// Start from the second segment in the second Period.
presentationTimeInSeconds = 30;
- onStartupComplete.and.callFake(() => setupFakeGetTime(20));
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Init the second Period.
- return defaultOnChooseStreams(period);
- });
-
mediaSourceEngine.endOfStream.and.callFake(() => {
// Seek backwards to an unbuffered region in the second Period. Do not
// call seeked().
- expect(presentationTimeInSeconds).toBe(26);
- presentationTimeInSeconds -= 10;
+ presentationTimeInSeconds = 20;
return Promise.resolve();
});
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
// Verify buffers. Segment 3 should not be buffered since we never
@@ -1626,32 +1210,22 @@ describe('StreamingEngine', () => {
// case where the playhead moves past the end of the buffer, which may
// occur on some browsers depending on the playback rate.
it('forward into unbuffered regions without seeked()', async () => {
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- // Init the first Period.
- return defaultOnChooseStreams(period);
- });
-
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
-
- // Seek forward to an unbuffered region in the first Period. Do not
- // call seeked().
- presentationTimeInSeconds += 15;
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
+ let seekStarted = false;
+ await runTest(() => {
+ if (!seekStarted) {
+ // Seek forward to an unbuffered region in the first Period. Do not
+ // call seeked().
+ presentationTimeInSeconds += 15;
+ seekStarted = true;
+ }
});
- // Here we go!
- streamingEngine.start();
-
- await runTest();
// Verify buffers.
expect(mediaSourceEngine.initSegments).toEqual({
audio: [false, true],
@@ -1667,61 +1241,47 @@ describe('StreamingEngine', () => {
it('into partially buffered regions across periods', async () => {
// Seeking into a region where some buffers (text) are buffered and some
- // are not should work despite the media states requiring different
- // periods.
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- // Should get another call for the unbuffered Period transition.
- onChooseStreams.and.callFake(defaultOnChooseStreams);
-
- mediaSourceEngine.endOfStream.and.callFake(() => {
- // Should have the first Period entirely buffered.
- expect(mediaSourceEngine.initSegments).toEqual({
- audio: [false, true],
- video: [false, true],
- text: [],
- });
- expect(mediaSourceEngine.segments).toEqual({
- audio: [true, true, true, true],
- video: [true, true, true, true],
- text: [true, true, true, true],
- });
-
- // Fake the audio/video buffers being removed.
- mediaSourceEngine.segments[ContentType.AUDIO] =
- [false, false, true, true];
- mediaSourceEngine.segments[ContentType.VIDEO] =
- [false, false, true, true];
-
- // Seek back into the first Period.
- expect(presentationTimeInSeconds).toBe(26);
- presentationTimeInSeconds -= 20;
- streamingEngine.seeked();
-
- // When seeking across periods, if at least one stream is
- // unbuffered, we clear all the buffers.
- expect(mediaSourceEngine.clear).toHaveBeenCalledWith('audio');
- expect(mediaSourceEngine.clear).toHaveBeenCalledWith('video');
- expect(mediaSourceEngine.clear).toHaveBeenCalledWith('text');
-
- mediaSourceEngine.endOfStream.and.returnValue(Promise.resolve());
- return Promise.resolve();
- });
+ // are not should work.
- return defaultOnChooseStreams(period);
+ mediaSourceEngine.endOfStream.and.callFake(() => {
+ // Should have the first Period entirely buffered.
+ expect(mediaSourceEngine.initSegments).toEqual({
+ audio: [false, true],
+ video: [false, true],
+ text: [],
+ });
+ expect(mediaSourceEngine.segments).toEqual({
+ audio: [true, true, true, true],
+ video: [true, true, true, true],
+ text: [true, true, true, true],
});
- return defaultOnChooseStreams(period);
- });
+ // Fake the audio/video buffers being removed.
+ // Now only text is buffered from the first period.
+ mediaSourceEngine.segments[ContentType.AUDIO] =
+ [false, false, true, true];
+ mediaSourceEngine.segments[ContentType.VIDEO] =
+ [false, false, true, true];
+
+ // Seek back into the first Period.
+ presentationTimeInSeconds -= 20;
+ expect(presentationTimeInSeconds).toBeLessThan(29);
+ streamingEngine.seeked();
+
+ // Only the unbuffered streams were cleared.
+ expect(mediaSourceEngine.clear).toHaveBeenCalledWith('audio');
+ expect(mediaSourceEngine.clear).toHaveBeenCalledWith('video');
+ expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('text');
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
+ mediaSourceEngine.endOfStream.and.returnValue(Promise.resolve());
+ return Promise.resolve();
+ });
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
// Verify buffers.
@@ -1743,8 +1303,7 @@ describe('StreamingEngine', () => {
setupLive();
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData, 0);
createStreamingEngine();
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(100));
+ presentationTimeInSeconds = 100;
});
it('outside segment availability window', async () => {
@@ -1753,59 +1312,42 @@ describe('StreamingEngine', () => {
presentationTimeInSeconds = 90;
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- onChooseStreams.and.throwError(new Error());
-
- // Init the first Period.
- return defaultOnChooseStreams(period);
- });
-
- onStartupComplete.and.callFake(() => {
- // Seek forward to an unbuffered and unavailable region in the second
- // Period; set playing to false since the playhead can't move at the
- // seek target.
- expect(timeline.getSegmentAvailabilityEnd()).toBeLessThan(125);
- presentationTimeInSeconds = 125;
- playing = false;
- streamingEngine.seeked();
-
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[1]);
-
- onChooseStreams.and.throwError(new Error());
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
+
+ // Seek forward to an unbuffered and unavailable region in the second
+ // Period; set playing to false since the playhead can't move at the
+ // seek target.
+ expect(timeline.getSegmentAvailabilityEnd()).toBeLessThan(125);
+ presentationTimeInSeconds = 125;
+ playing = false;
+ streamingEngine.seeked();
- // Switch to the second Period.
- return defaultOnChooseStreams(period);
- });
+ // Eventually StreamingEngine should request the first segment (since
+ // it needs the second segment) of the second Period when it becomes
+ // available.
+ const originalAppendBuffer =
+ // eslint-disable-next-line no-restricted-syntax
+ shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
+ mediaSourceEngine.appendBuffer.and.callFake(
+ (type, data, startTime, endTime) => {
+ expect(presentationTimeInSeconds).toBe(125);
+ if (startTime >= 100) {
+ // Ignore a possible call for the first Period.
+ expect(timeline.getSegmentAvailabilityStart()).toBe(100);
+ expect(timeline.getSegmentAvailabilityEnd()).toBe(120);
+ playing = true;
+ mediaSourceEngine.appendBuffer.and.callFake(
+ originalAppendBuffer);
+ }
- // Eventually StreamingEngine should request the first segment (since
- // it needs the second segment) of the second Period when it becomes
- // available.
- const originalAppendBuffer =
// eslint-disable-next-line no-restricted-syntax
- shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
- mediaSourceEngine.appendBuffer.and.callFake(
- (type, data, startTime, endTime) => {
- expect(presentationTimeInSeconds).toBe(125);
- if (startTime >= 100) {
- // Ignore a possible call for the first Period.
- expect(timeline.getSegmentAvailabilityStart()).toBe(100);
- expect(timeline.getSegmentAvailabilityEnd()).toBe(120);
- playing = true;
- mediaSourceEngine.appendBuffer.and.callFake(
- originalAppendBuffer);
- }
-
- // eslint-disable-next-line no-restricted-syntax
- return originalAppendBuffer.call(
- mediaSourceEngine, type, data, startTime, endTime);
- });
- });
-
- // Here we go!
- streamingEngine.start();
+ return originalAppendBuffer.call(
+ mediaSourceEngine, type, data, startTime, endTime);
+ });
await runTest(slideSegmentAvailabilityWindow);
// Verify buffers.
@@ -1835,20 +1377,21 @@ describe('StreamingEngine', () => {
it('from Stream setup', async () => {
// Don't use returnValue with Promise.reject, or it may be detected as an
// unhandled Promise rejection.
- videoStream1.createSegmentIndex.and.callFake(
+ videoStream.createSegmentIndex.and.callFake(
() => Promise.reject('FAKE_ERROR'));
onError.and.callFake((error) => {
expect(error).toBe('FAKE_ERROR');
});
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
-
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
- expect(videoStream1.createSegmentIndex).toHaveBeenCalled();
+ expect(videoStream.createSegmentIndex).toHaveBeenCalled();
expect(onError).toHaveBeenCalled();
});
@@ -1860,28 +1403,25 @@ describe('StreamingEngine', () => {
// Don't use returnValue with Promise.reject, or it may be detected as an
// unhandled Promise rejection.
- alternateVideoStream1.createSegmentIndex.and.callFake(
+ alternateVideoStream.createSegmentIndex.and.callFake(
() => Promise.reject(expectedError));
onError.and.callFake((error) => {
- expect(onInitialStreamsSetup).toHaveBeenCalled();
- expect(onStartupComplete).toHaveBeenCalled();
expect(error).toBe(expectedError);
});
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- onCanSwitch.and.callFake(() => {
- streamingEngine.switchVariant(
- alternateVariant1, /* clear_buffer= */ true, /* safe_margin= */ 0);
- });
-
// Here we go!
- streamingEngine.start().catch(fail);
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
+
+ streamingEngine.switchVariant(
+ alternateVariant, /* clear_buffer= */ true, /* safe_margin= */ 0);
+
await runTest();
- expect(videoStream1.createSegmentIndex).toHaveBeenCalled();
- expect(onCanSwitch).toHaveBeenCalled();
- expect(alternateVideoStream1.createSegmentIndex).toHaveBeenCalled();
+ expect(alternateVideoStream.createSegmentIndex).toHaveBeenCalled();
expect(onError).toHaveBeenCalled();
});
@@ -1892,36 +1432,29 @@ describe('StreamingEngine', () => {
shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_FAILED);
onError.and.callFake((error) => {
- expect(onInitialStreamsSetup).toHaveBeenCalled();
- expect(onStartupComplete).not.toHaveBeenCalled();
Util.expectToEqualError(error, expectedError);
});
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- const streamsByType = defaultOnChooseStreams(period);
-
- const originalAppendBuffer =
- // eslint-disable-next-line no-restricted-syntax
- shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
- mediaSourceEngine.appendBuffer.and.callFake(
- (type, data, startTime, endTime) => {
- // Reject the first video init segment.
- if (data == segmentData[ContentType.VIDEO].initSegments[0]) {
- return Promise.reject(expectedError);
- } else {
- // eslint-disable-next-line no-restricted-syntax
- return originalAppendBuffer.call(
- mediaSourceEngine, type, data, startTime, endTime);
- }
- });
-
- return streamsByType;
- });
+ const originalAppendBuffer =
+ // eslint-disable-next-line no-restricted-syntax
+ shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
+ mediaSourceEngine.appendBuffer.and.callFake(
+ (type, data, startTime, endTime) => {
+ // Reject the first video init segment.
+ if (data == segmentData[ContentType.VIDEO].initSegments[0]) {
+ return Promise.reject(expectedError);
+ } else {
+ // eslint-disable-next-line no-restricted-syntax
+ return originalAppendBuffer.call(
+ mediaSourceEngine, type, data, startTime, endTime);
+ }
+ });
// Here we go!
- streamingEngine.start().catch(fail);
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onError).toHaveBeenCalled();
});
@@ -1933,36 +1466,29 @@ describe('StreamingEngine', () => {
shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_FAILED);
onError.and.callFake((error) => {
- expect(onInitialStreamsSetup).toHaveBeenCalled();
- expect(onStartupComplete).not.toHaveBeenCalled();
Util.expectToEqualError(error, expectedError);
});
- onChooseStreams.and.callFake((period) => {
- expect(period).toBe(manifest.periods[0]);
-
- const streamsByType = defaultOnChooseStreams(period);
-
- const originalAppendBuffer =
- // eslint-disable-next-line no-restricted-syntax
- shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
- mediaSourceEngine.appendBuffer.and.callFake(
- (type, data, startTime, endTime) => {
- // Reject the first audio segment.
- if (data == segmentData[ContentType.AUDIO].segments[0]) {
- return Promise.reject(expectedError);
- } else {
- // eslint-disable-next-line no-restricted-syntax
- return originalAppendBuffer.call(
- mediaSourceEngine, type, data, startTime, endTime);
- }
- });
-
- return streamsByType;
- });
+ const originalAppendBuffer =
+ // eslint-disable-next-line no-restricted-syntax
+ shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
+ mediaSourceEngine.appendBuffer.and.callFake(
+ (type, data, startTime, endTime) => {
+ // Reject the first audio segment.
+ if (data == segmentData[ContentType.AUDIO].segments[0]) {
+ return Promise.reject(expectedError);
+ } else {
+ // eslint-disable-next-line no-restricted-syntax
+ return originalAppendBuffer.call(
+ mediaSourceEngine, type, data, startTime, endTime);
+ }
+ });
// Here we go!
- streamingEngine.start().catch(fail);
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onError).toHaveBeenCalled();
});
@@ -1975,14 +1501,16 @@ describe('StreamingEngine', () => {
mediaSourceEngine.clear.and.returnValue(Promise.reject(expectedError));
onError.and.stub();
- onChooseStreams.and.callFake((period) => defaultOnChooseStreams(period));
- onStartupComplete.and.callFake(() => {
- streamingEngine.switchVariant(
- variant2, /* clear_buffer= */ true, /* safe_margin= */ 0);
- });
// Here we go!
- streamingEngine.start().catch(fail);
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
+
+ streamingEngine.switchVariant(
+ alternateVariant, /* clear_buffer= */ true, /* safe_margin= */ 0);
+
await runTest();
expect(onError).toHaveBeenCalledWith(Util.jasmineError(expectedError));
});
@@ -1991,7 +1519,7 @@ describe('StreamingEngine', () => {
describe('handles network errors', () => {
it('ignores text stream failures if configured to', async () => {
setupVod();
- const textUri = '1_text_1';
+ const textUri = '0_text_0';
const originalNetEngine = netEngine;
netEngine = {
request: jasmine.createSpy('request'),
@@ -2010,13 +1538,11 @@ describe('StreamingEngine', () => {
config.ignoreTextStreamFailures = true;
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
- });
-
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onError).not.toHaveBeenCalled();
@@ -2027,7 +1553,7 @@ describe('StreamingEngine', () => {
setupLive();
// Wrap the NetworkingEngine to cause errors.
- const targetUri = '1_audio_init';
+ const targetUri = '0_audio_init';
failFirstRequestForTarget(netEngine, targetUri,
shaka.util.Error.Code.BAD_HTTP_STATUS);
@@ -2038,9 +1564,6 @@ describe('StreamingEngine', () => {
createStreamingEngine(config);
presentationTimeInSeconds = 100;
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(100);
- });
onError.and.callFake((error) => {
expect(error.severity).toBe(shaka.util.Error.Severity.CRITICAL);
@@ -2049,8 +1572,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onError).toHaveBeenCalledTimes(1);
@@ -2062,7 +1587,7 @@ describe('StreamingEngine', () => {
setupLive();
// Wrap the NetworkingEngine to cause errors.
- const targetUri = '1_audio_init';
+ const targetUri = '0_audio_init';
failFirstRequestForTarget(netEngine, targetUri,
shaka.util.Error.Code.BAD_HTTP_STATUS);
@@ -2073,9 +1598,6 @@ describe('StreamingEngine', () => {
createStreamingEngine(config);
presentationTimeInSeconds = 100;
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(100);
- });
onError.and.callFake((error) => {
expect(error.severity).toBe(shaka.util.Error.Severity.CRITICAL);
@@ -2084,8 +1606,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onError).toHaveBeenCalledTimes(1);
@@ -2097,7 +1621,7 @@ describe('StreamingEngine', () => {
setupLive();
// Wrap the NetworkingEngine to cause errors.
- const targetUri = '1_audio_init';
+ const targetUri = '0_audio_init';
failFirstRequestForTarget(netEngine, targetUri,
shaka.util.Error.Code.BAD_HTTP_STATUS);
@@ -2110,17 +1634,16 @@ describe('StreamingEngine', () => {
createStreamingEngine(config);
presentationTimeInSeconds = 100;
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(100);
- });
onError.and.callFake((error) => {
error.handled = true;
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onError).toHaveBeenCalledTimes(1);
@@ -2131,7 +1654,7 @@ describe('StreamingEngine', () => {
setupLive();
// Wrap the NetworkingEngine to cause errors.
- const targetUri = '1_audio_init';
+ const targetUri = '0_audio_init';
failFirstRequestForTarget(netEngine, targetUri,
shaka.util.Error.Code.BAD_HTTP_STATUS);
@@ -2153,14 +1676,13 @@ describe('StreamingEngine', () => {
createStreamingEngine(config);
presentationTimeInSeconds = 100;
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(100);
- });
onError.and.stub();
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
const startTime = Date.now();
await runTest();
@@ -2175,7 +1697,7 @@ describe('StreamingEngine', () => {
setupVod();
// Wrap the NetworkingEngine to cause errors.
- const targetUri = '1_audio_init';
+ const targetUri = '0_audio_init';
const originalNetEngineRequest = netEngine.request;
failFirstRequestForTarget(netEngine, targetUri,
shaka.util.Error.Code.BAD_HTTP_STATUS);
@@ -2183,10 +1705,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
- });
-
onError.and.callFake((error) => {
// Restore the original fake request function.
netEngine.request = originalNetEngineRequest;
@@ -2197,8 +1715,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
// We definitely called onError().
@@ -2216,22 +1736,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
-
- // Now that setup is complete, throw QuotaExceededError on every segment
- // to quickly trigger the quota error.
- const appendBufferSpy = jasmine.createSpy('appendBuffer');
- appendBufferSpy.and.callFake((type, data, startTime, endTime) => {
- throw new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MEDIA,
- shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR,
- type);
- });
- mediaSourceEngine.appendBuffer = appendBufferSpy;
- });
-
onError.and.callFake((error) => {
expect(error.code).toBe(shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR);
@@ -2241,10 +1745,26 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
- await runTest();
+ await runTest(() => {
+ if (presentationTimeInSeconds == 2) {
+ // Now that we're streaming, throw QuotaExceededError on every segment
+ // to quickly trigger the quota error.
+ const appendBufferSpy = jasmine.createSpy('appendBuffer');
+ appendBufferSpy.and.callFake((type, data, startTime, endTime) => {
+ throw new shaka.util.Error(
+ shaka.util.Error.Severity.CRITICAL,
+ shaka.util.Error.Category.MEDIA,
+ shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR,
+ type);
+ });
+ mediaSourceEngine.appendBuffer = appendBufferSpy;
+ }
+ });
// We definitely called onError().
expect(onError).toHaveBeenCalledTimes(1);
@@ -2261,17 +1781,14 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
- onStartupComplete.and.callFake(() => {
- setupFakeGetTime(0);
- });
-
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
// Here we go!
- let count = 0;
await runTest(() => {
- if (++count == 3) {
+ if (presentationTimeInSeconds == 3) {
streamingEngine.destroy();
// Retry streaming, which should fail and return false.
@@ -2306,8 +1823,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
-
const originalRemove =
// eslint-disable-next-line no-restricted-syntax
shaka.test.FakeMediaSourceEngine.prototype.removeImpl
@@ -2331,8 +1846,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
// Since StreamingEngine is free to peform audio, video, and text updates
// in any order, there are many valid ways in which StreamingEngine can
@@ -2377,9 +1894,10 @@ describe('StreamingEngine', () => {
// Create StreamingEngine.
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest(() => {
if (presentationTimeInSeconds == 8) {
@@ -2414,8 +1932,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
-
const originalAppendBuffer =
// eslint-disable-next-line no-restricted-syntax
shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
@@ -2444,8 +1960,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -2476,8 +1994,6 @@ describe('StreamingEngine', () => {
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
-
const originalAppendBuffer =
// eslint-disable-next-line no-restricted-syntax
shaka.test.FakeMediaSourceEngine.prototype.appendBufferImpl;
@@ -2510,8 +2026,10 @@ describe('StreamingEngine', () => {
});
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
// Stop the playhead after 10 seconds since will not append any
// segments after this time.
@@ -2538,11 +2056,11 @@ describe('StreamingEngine', () => {
new shaka.test.FakeMediaSourceEngine(segmentData, drift);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(drift));
-
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -2568,11 +2086,11 @@ describe('StreamingEngine', () => {
new shaka.test.FakeMediaSourceEngine(segmentData, drift);
createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
-
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -2610,11 +2128,11 @@ describe('StreamingEngine', () => {
presentationTimeInSeconds = 100;
- onStartupComplete.and.callFake(() => setupFakeGetTime(100));
-
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest(slideSegmentAvailabilityWindow);
expect(mediaSourceEngine.endOfStream).toHaveBeenCalled();
@@ -2652,11 +2170,11 @@ describe('StreamingEngine', () => {
config.bufferingGoal = 1;
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
-
// Here we go!
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest(() => {
if (presentationTimeInSeconds == 1) {
@@ -2760,17 +2278,17 @@ describe('StreamingEngine', () => {
setupVod();
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake((p) => defaultOnChooseStreams(p));
});
it('raises an event for registered embedded emsg boxes', async () => {
segmentData[ContentType.VIDEO].segments[0] = emsgSegment;
- videoStream1.emsgSchemeIdUris = [emsgObj.schemeIdUri];
+ videoStream.emsgSchemeIdUris = [emsgObj.schemeIdUri];
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onEvent).toHaveBeenCalledTimes(1);
@@ -2784,10 +2302,13 @@ describe('StreamingEngine', () => {
shaka.util.Uint8ArrayUtils.fromHex('0000000c6672656501020304');
segmentData[ContentType.VIDEO].segments[0] =
shaka.util.Uint8ArrayUtils.concat(emsgSegment, dummyBox, emsgSegment);
- videoStream1.emsgSchemeIdUris = [emsgObj.schemeIdUri];
+ videoStream.emsgSchemeIdUris = [emsgObj.schemeIdUri];
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onEvent).toHaveBeenCalledTimes(2);
@@ -2797,17 +2318,23 @@ describe('StreamingEngine', () => {
segmentData[ContentType.VIDEO].segments[0] = emsgSegment;
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onEvent).not.toHaveBeenCalled();
});
it('won\'t raise an event when no emsg boxes present', async () => {
- videoStream1.emsgSchemeIdUris = [emsgObj.schemeIdUri];
+ videoStream.emsgSchemeIdUris = [emsgObj.schemeIdUri];
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onEvent).not.toHaveBeenCalled();
@@ -2817,7 +2344,10 @@ describe('StreamingEngine', () => {
segmentData[ContentType.VIDEO].segments[0] = emsgSegment;
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onEvent).not.toHaveBeenCalled();
@@ -2833,10 +2363,13 @@ describe('StreamingEngine', () => {
'6d7065673a646173683a6576656e743a' +
'32303132000000000031000000080000' +
'00ff0000000c74657374');
- videoStream1.emsgSchemeIdUris = ['urn:mpeg:dash:event:2012'];
+ videoStream.emsgSchemeIdUris = ['urn:mpeg:dash:event:2012'];
// Here we go!
- streamingEngine.start();
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
+ playing = true;
await runTest();
expect(onEvent).not.toHaveBeenCalled();
@@ -2845,6 +2378,8 @@ describe('StreamingEngine', () => {
});
describe('network downgrading', () => {
+ /** @type {shaka.extern.Variant} */
+ let initialVariant;
/** @type {shaka.extern.Variant} */
let newVariant;
/** @type {!Array.} */
@@ -2859,28 +2394,26 @@ describe('StreamingEngine', () => {
beforeEach(() => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline.setDuration(60);
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 500;
- variant.addVideo(10, (stream) => {
- stream.useSegmentTemplate(
- 'video-10-%d.mp4', /* segmentDuration= */ 10,
- /* segmentSize= */ 50);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 500;
+ variant.addVideo(10, (stream) => {
+ stream.useSegmentTemplate(
+ 'video-10-%d.mp4', /* segmentDuration= */ 10,
+ /* segmentSize= */ 50);
});
- period.addVariant(1, (variant) => {
- variant.bandwidth = 100;
- variant.addVideo(11, (stream) => {
- stream.useSegmentTemplate(
- 'video-11-%d.mp4', /* segmentDuration= */ 10,
- /* segmentSize= */ 10);
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 100;
+ variant.addVideo(11, (stream) => {
+ stream.useSegmentTemplate(
+ 'video-11-%d.mp4', /* segmentDuration= */ 10,
+ /* segmentSize= */ 10);
});
});
});
- const initialVariant = manifest.periods[0].variants[0];
- newVariant = manifest.periods[0].variants[1];
+ initialVariant = manifest.variants[0];
+ newVariant = manifest.variants[1];
requestUris = [];
delayedRequests = [];
lastPendingRequest = null;
@@ -2955,9 +2488,6 @@ describe('StreamingEngine', () => {
createStreamingEngine(config);
getBandwidthEstimate.and.returnValue(1); // very slow by default
-
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake(() => ({variant: initialVariant}));
});
it('aborts pending requests', async () => {
@@ -2978,7 +2508,7 @@ describe('StreamingEngine', () => {
it('still aborts if previous segment size unknown', async () => {
// This should use the "bytes remaining" from the request instead of the
// previous stream's size.
- const videoStream = manifest.periods[0].variants[0].video;
+ const videoStream = manifest.variants[0].video;
await videoStream.createSegmentIndex();
const segmentIndex = videoStream.segmentIndex;
const oldGet = segmentIndex.get;
@@ -3075,8 +2605,7 @@ describe('StreamingEngine', () => {
};
// This should abort the pending request for the second segment.
- streamingEngine.switchVariant(
- newVariant, /* clear_buffer= */ false, /* safe_margin= */ 0);
+ streamingEngine.switchVariant(newVariant);
await bufferAndCheck(/* didAbort= */ true);
@@ -3084,7 +2613,7 @@ describe('StreamingEngine', () => {
});
it('still aborts if new segment size unknown', async () => {
- const videoStream = manifest.periods[0].variants[1].video;
+ const videoStream = manifest.variants[1].video;
videoStream.bandwidth = 10;
await videoStream.createSegmentIndex();
const segmentIndex = videoStream.segmentIndex;
@@ -3124,7 +2653,9 @@ describe('StreamingEngine', () => {
* it should be waiting for the second segment request to complete.
*/
async function prepareForAbort() {
+ streamingEngine.switchVariant(initialVariant);
streamingEngine.start().catch(fail);
+ playing = true;
await Util.fakeEventLoop(1);
// Finish the first segment request.
@@ -3132,8 +2663,7 @@ describe('StreamingEngine', () => {
flushDelayedRequests();
await Util.fakeEventLoop(10);
- // We should have buffered the first segment and finished startup.
- expect(onCanSwitch).toHaveBeenCalled();
+ // We should have buffered the first segment.
expect(Util.invokeSpy(mediaSourceEngine.bufferEnd, 'video')).toBe(10);
// Confirm that the first segment is buffered.
@@ -3182,37 +2712,38 @@ describe('StreamingEngine', () => {
});
describe('embedded text tracks', () => {
+ /** @type {!jasmine.Spy} */
+ let onTick;
+
+ /** @type {shaka.extern.Stream} */
+ let externalTextStream;
+
+ /** @type {shaka.extern.Stream} */
+ let embeddedTextStream;
+
beforeEach(() => {
- // Set up a manifest with multiple Periods and text streams.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(110, (stream) => {
- stream.useSegmentTemplate('video-110-%d.mp4', 10);
- });
- });
- period.addTextStream(120, (stream) => {
- stream.useSegmentTemplate('video-120-%d.mp4', 10);
- });
- period.addTextStream(121, (stream) => {
- stream.mimeType = shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE;
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.useSegmentTemplate('video-110-%d.mp4', 10);
});
});
- manifest.addPeriod(10, (period) => {
- period.addVariant(1, (variant) => {
- variant.addVideo(210, (stream) => {
- stream.useSegmentTemplate('video-210-%d.mp4', 10);
- });
- });
- period.addTextStream(220, (stream) => {
- stream.useSegmentTemplate('text-220-%d.mp4', 10);
- });
- period.addTextStream(221, (stream) => {
- stream.mimeType = shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE;
- });
+ manifest.addTextStream(2, (stream) => {
+ stream.useSegmentTemplate('video-120-%d.mp4', 10);
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.mimeType = shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE;
});
});
+ // Capture the stream objects from the generated manifest by ID.
+ externalTextStream = manifest.textStreams.filter((s) => s.id == 2)[0];
+ goog.asserts.assert(
+ externalTextStream, 'Should have found external text!');
+ embeddedTextStream = manifest.textStreams.filter((s) => s.id == 3)[0];
+ goog.asserts.assert(
+ embeddedTextStream, 'Should have found embedded text!');
+
// For these tests, we don't care about specific data appended.
// Just return any old ArrayBuffer for any requested segment.
netEngine = {
@@ -3260,181 +2791,129 @@ describe('StreamingEngine', () => {
presentationTimeInSeconds = 0;
createStreamingEngine(config);
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- });
+ // Each test will switch text streams to simulate various activities, but
+ // all will use this variant.
+ streamingEngine.switchVariant(manifest.variants[0]);
- describe('period transition', () => {
- it('initializes new embedded captions', async () => {
- onChooseStreams.and.callFake((period) => {
- if (period == manifest.periods[0]) {
- return {variant: period.variants[0]};
- } else {
- return {variant: period.variants[0], text: period.textStreams[1]};
- }
- });
- await runEmbeddedCaptionTest();
- });
+ onTick = jasmine.createSpy('onTick');
+ });
- it('initializes embedded captions from external text', async () => {
- onChooseStreams.and.callFake((period) => {
- if (period == manifest.periods[0]) {
- return {variant: period.variants[0], text: period.textStreams[0]};
- } else {
- return {variant: period.variants[0], text: period.textStreams[1]};
- }
- });
- await runEmbeddedCaptionTest();
- });
+ it('initializes embedded captions after nothing', async () => {
+ // Start without text.
+ streamingEngine.start().catch(fail);
+ playing = true;
- it('switches to external text after embedded captions', async () => {
- onChooseStreams.and.callFake((period) => {
- if (period == manifest.periods[0]) {
- return {variant: period.variants[0], text: period.textStreams[1]};
- } else {
- return {variant: period.variants[0], text: period.textStreams[0]};
- }
- });
- await runEmbeddedCaptionTest();
- });
+ onTick.and.callFake(() => {
+ // Switch to embedded text.
+ streamingEngine.switchTextStream(embeddedTextStream);
- it('doesn\'t re-initialize', async () => {
- onChooseStreams.and.callFake((period) => {
- return {variant: period.variants[0], text: period.textStreams[1]};
- });
- await runEmbeddedCaptionTest();
+ onTick.and.stub();
});
- async function runEmbeddedCaptionTest() {
- streamingEngine.start().catch(fail);
- await Util.fakeEventLoop(10);
+ await Util.fakeEventLoop(10, (time) => Util.invokeSpy(onTick));
- // We have buffered through the Period transition.
- expect(onChooseStreams).toHaveBeenCalledTimes(2);
- expect(Util.invokeSpy(mediaSourceEngine.bufferEnd, 'video'))
- .toBeGreaterThan(12);
+ // We have buffered through the Period transition.
+ expect(Util.invokeSpy(mediaSourceEngine.bufferEnd, 'video'))
+ .toBeGreaterThan(12);
- expect(mediaSourceEngine.setSelectedClosedCaptionId)
- .toHaveBeenCalledTimes(1);
- }
+ expect(mediaSourceEngine.setSelectedClosedCaptionId)
+ .toHaveBeenCalledTimes(1);
});
- });
-
- it('calls createSegmentIndex on demand', async () => {
- setupVod();
- mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
- createStreamingEngine();
- onStartupComplete.and.callFake(() => setupFakeGetTime(0));
- onChooseStreams.and.callFake((period) => defaultOnChooseStreams(period));
+ it('initializes embedded captions after external text', async () => {
+ // Start with external text.
+ streamingEngine.switchTextStream(externalTextStream);
+ streamingEngine.start().catch(fail);
+ playing = true;
- // None of the first period streams have been set up yet because we haven't
- // started yet.
- expect(audioStream1.createSegmentIndex).not.toHaveBeenCalled();
- expect(videoStream1.createSegmentIndex).not.toHaveBeenCalled();
- expect(alternateVideoStream1.createSegmentIndex).not.toHaveBeenCalled();
+ onTick.and.callFake(() => {
+ // Switch to embedded text.
+ streamingEngine.switchTextStream(embeddedTextStream);
- // None of the second period streams have been set up yet, either.
- expect(variant2.video.createSegmentIndex).not.toHaveBeenCalled();
- expect(variant2.audio.createSegmentIndex).not.toHaveBeenCalled();
+ onTick.and.stub();
+ });
- onInitialStreamsSetup.and.callFake(() => {
- // Once we're streaming, the first period audio & video streams have been
- // set up.
- expect(audioStream1.createSegmentIndex).toHaveBeenCalled();
- expect(videoStream1.createSegmentIndex).toHaveBeenCalled();
+ await Util.fakeEventLoop(10, (time) => Util.invokeSpy(onTick));
- // But not this alternate video stream from the first period.
- expect(alternateVideoStream1.createSegmentIndex).not.toHaveBeenCalled();
+ // We have buffered through the Period transition.
+ expect(Util.invokeSpy(mediaSourceEngine.bufferEnd, 'video'))
+ .toBeGreaterThan(12);
- // And not the streams from the second period.
- expect(variant2.video.createSegmentIndex).not.toHaveBeenCalled();
- expect(variant2.audio.createSegmentIndex).not.toHaveBeenCalled();
+ expect(mediaSourceEngine.setSelectedClosedCaptionId)
+ .toHaveBeenCalledTimes(1);
});
- // Here we go!
- streamingEngine.start();
+ it('switches to external text after embedded captions', async () => {
+ // Start with embedded text.
+ streamingEngine.switchTextStream(embeddedTextStream);
+ streamingEngine.start().catch(fail);
+ playing = true;
- await runTest();
+ onTick.and.callFake(() => {
+ // Switch to external text.
+ streamingEngine.switchTextStream(externalTextStream);
- // Because we never switched to this stream, it was never set up at any time
- // during this simulated playback.
- expect(alternateVideoStream1.createSegmentIndex).not.toHaveBeenCalled();
- });
+ onTick.and.stub();
+ });
- /**
- * Verifies calls to NetworkingEngine.request(). Expects every segment
- * in the given Period to have been requested.
- *
- * @param {number} period The Period number (one-based).
- */
- function verifyNetworkingEngineRequestCalls(period) {
- netEngine.expectRangeRequest(
- period + '_audio_init',
- initSegmentRanges[ContentType.AUDIO][0],
- initSegmentRanges[ContentType.AUDIO][1]);
+ await Util.fakeEventLoop(10, (time) => Util.invokeSpy(onTick));
- netEngine.expectRangeRequest(
- period + '_video_init',
- initSegmentRanges[ContentType.VIDEO][0],
- initSegmentRanges[ContentType.VIDEO][1]);
+ // We have buffered through the Period transition.
+ expect(Util.invokeSpy(mediaSourceEngine.bufferEnd, 'video'))
+ .toBeGreaterThan(12);
- const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
- netEngine.expectRequest(period + '_audio_1', segmentType);
- netEngine.expectRequest(period + '_video_1', segmentType);
- netEngine.expectRequest(period + '_text_1', segmentType);
+ expect(mediaSourceEngine.setSelectedClosedCaptionId)
+ .toHaveBeenCalledTimes(1);
+ });
+
+ it('plays embedded text throughout', async () => {
+ // Start with embedded text.
+ streamingEngine.switchTextStream(embeddedTextStream);
+ streamingEngine.start().catch(fail);
+ playing = true;
- netEngine.expectRequest(period + '_audio_2', segmentType);
- netEngine.expectRequest(period + '_video_2', segmentType);
- netEngine.expectRequest(period + '_text_2', segmentType);
+ await Util.fakeEventLoop(10, (time) => Util.invokeSpy(onTick));
- netEngine.request.calls.reset();
- }
+ // We have buffered through the Period transition.
+ expect(Util.invokeSpy(mediaSourceEngine.bufferEnd, 'video'))
+ .toBeGreaterThan(12);
- /**
- * Choose streams for the given period.
- *
- * @param {shaka.extern.Period} period
- * @return {!Object.}
- */
- function defaultOnChooseStreams(period) {
- if (period == manifest.periods[0]) {
- return {variant: variant1, text: textStream1};
- } else if (period == manifest.periods[1]) {
- return {variant: variant2, text: textStream2};
- } else {
- throw new Error();
- }
- }
+ expect(mediaSourceEngine.setSelectedClosedCaptionId)
+ .toHaveBeenCalledTimes(1);
+ });
+ });
- /**
- * Choose streams for the given period, used for testing unload text stream.
- * The text stream of the second period is not choosen.
- *
- * @param {shaka.extern.Period} period
- * @return {!Object.}
- */
- function onChooseStreamsWithUnloadedText(period) {
- if (period == manifest.periods[0]) {
- return {variant: variant1, text: textStream1};
- } else if (period == manifest.periods[1]) {
- expect(streamingEngine.unloadTextStream).toHaveBeenCalled();
- return {variant: variant2};
- } else {
- throw new Error();
- }
- }
+ it('calls createSegmentIndex on demand', async () => {
+ setupVod();
+ mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
+ createStreamingEngine();
- /**
- * Makes the mock Playhead object behave as a fake Playhead object which
- * begins playback at the given time.
- *
- * @param {number} startTime the playhead's starting time with respect to
- * the presentation timeline.
- */
- function setupFakeGetTime(startTime) {
- presentationTimeInSeconds = startTime;
+ // None of the streams have been set up yet because we haven't started yet.
+ expect(audioStream.createSegmentIndex).not.toHaveBeenCalled();
+ expect(videoStream.createSegmentIndex).not.toHaveBeenCalled();
+ expect(alternateVideoStream.createSegmentIndex).not.toHaveBeenCalled();
+
+ // Here we go!
+ streamingEngine.switchVariant(variant);
+ streamingEngine.switchTextStream(textStream);
+ await streamingEngine.start();
playing = true;
- }
+
+ await runTest(() => {
+ if (presentationTimeInSeconds == 1) {
+ // Once we're streaming, the audio & video streams have been set up.
+ expect(audioStream.createSegmentIndex).toHaveBeenCalled();
+ expect(videoStream.createSegmentIndex).toHaveBeenCalled();
+
+ // But not this alternate video stream.
+ expect(alternateVideoStream.createSegmentIndex).not.toHaveBeenCalled();
+ }
+ });
+
+ // Because we never switched to this stream, it was never set up at any time
+ // during this simulated playback.
+ expect(alternateVideoStream.createSegmentIndex).not.toHaveBeenCalled();
+ });
/**
* Slides the segment availability window forward by 1 second.
diff --git a/test/offline/indexeddb_storage_unit.js b/test/offline/indexeddb_storage_unit.js
index 0b705b958c..bb32e2e6b8 100644
--- a/test/offline/indexeddb_storage_unit.js
+++ b/test/offline/indexeddb_storage_unit.js
@@ -178,11 +178,10 @@ filterDescribe('IndexeddbStorageCell', () => window.indexedDB, () => {
* @return {shaka.extern.StorageCell}
*/
function makeCell(connection) {
- const cell = new shaka.offline.indexeddb.V2StorageCell(
+ const cell = new shaka.offline.indexeddb.V5StorageCell(
connection,
segmentStore,
- manifestStore,
- /* allow= */ false);
+ manifestStore);
// Track the cell so that we can destroy it when the test is over.
cells.push(cell);
diff --git a/test/offline/manifest_convert_unit.js b/test/offline/manifest_convert_unit.js
index 5b5d5c1adf..b20c8d9bb0 100644
--- a/test/offline/manifest_convert_unit.js
+++ b/test/offline/manifest_convert_unit.js
@@ -24,8 +24,7 @@ describe('ManifestConverter', () => {
/** @type {!Map.} */
const variants = createConverter().createVariants(
- audios, videos, timeline, /* periodStart= */ 0,
- /* periodDuration= */ 10);
+ audios, videos, timeline);
expect(variants.size).toBe(2);
expect(variants.has(0)).toBeTruthy();
@@ -50,8 +49,7 @@ describe('ManifestConverter', () => {
/** @type {!Map.} */
const variants = createConverter().createVariants(
- audios, videos, timeline, /* periodStart= */ 0,
- /* periodDuration= */ 10);
+ audios, videos, timeline);
expect(variants.size).toBe(2);
});
@@ -68,105 +66,138 @@ describe('ManifestConverter', () => {
/** @type {!Map.} */
const variants = createConverter().createVariants(
- audios, videos, timeline, /* periodStart= */ 0,
- /* periodDuration= */ 10);
+ audios, videos, timeline);
expect(variants.size).toBe(2);
});
}); // describe('createVariants')
- describe('fromPeriodDB', () => {
- const arbitraryPeriodDuration = 180;
-
- it('will reconstruct Periods correctly', () => {
- /** @type {shaka.extern.PeriodDB} */
- const periodDb = {
- startTime: 60,
+ describe('fromManifestDB', () => {
+ it('will reconstruct Manifest correctly', () => {
+ /** @type {shaka.extern.ManifestDB} */
+ const manifestDb = {
+ originalManifestUri: 'http://example.com/foo',
+ duration: 60,
+ size: 1234,
+ expiration: Infinity,
streams: [
- createVideoStreamDB(1, 60, [0]),
- createAudioStreamDB(2, 60, [0]),
+ createVideoStreamDB(1, [0]),
+ createAudioStreamDB(2, [0]),
],
+ sessionIds: [1, 2, 3, 4],
+ drmInfo: {
+ keySystem: 'com.foo.bar',
+ licenseServerUri: 'http://example.com/drm',
+ distinctiveIdentifierRequired: true,
+ persistentStateRequired: true,
+ audioRobustness: 'very',
+ videoRobustness: 'kinda_sorta',
+ serverCertificate: new Uint8Array([1, 2, 3]),
+ initData: [{
+ initData: new Uint8Array([4, 5, 6]),
+ initDataType: 'cenc',
+ keyId: 'abc',
+ }],
+ keyIds: [
+ 'abc',
+ 'def',
+ ],
+ },
+ appMetadata: null,
};
- const timeline = createTimeline();
-
- const period = createConverter().fromPeriodDB(
- periodDb, arbitraryPeriodDuration, timeline);
- expect(period).toBeTruthy();
- expect(period.startTime).toBe(periodDb.startTime);
- expect(period.textStreams).toEqual([]);
- expect(period.variants.length).toBe(1);
+ const manifest = createConverter().fromManifestDB(manifestDb);
+ expect(manifest.presentationTimeline.getDuration())
+ .toBe(manifestDb.duration);
+ expect(manifest.textStreams).toEqual([]);
+ expect(manifest.offlineSessionIds).toEqual(manifestDb.sessionIds);
+ expect(manifest.variants.length).toBe(1);
- const variant = period.variants[0];
+ const variant = manifest.variants[0];
expect(variant.id).toEqual(jasmine.any(Number));
- expect(variant.language).toBe(periodDb.streams[1].language);
+ expect(variant.language).toBe(manifestDb.streams[1].language);
expect(variant.primary).toBe(false);
expect(variant.bandwidth).toEqual(jasmine.any(Number));
expect(variant.allowedByApplication).toBe(true);
expect(variant.allowedByKeySystem).toBe(true);
+ expect(variant.drmInfos).toEqual([manifestDb.drmInfo]);
- verifyStream(periodDb, variant.video, periodDb.streams[0]);
- verifyStream(periodDb, variant.audio, periodDb.streams[1]);
+ verifyStream(variant.video, manifestDb.streams[0]);
+ verifyStream(variant.audio, manifestDb.streams[1]);
});
it('supports video-only content', () => {
- /** @type {shaka.extern.PeriodDB} */
- const periodDb = {
- startTime: 60,
+ /** @type {shaka.extern.ManifestDB} */
+ const manifestDb = {
+ originalManifestUri: 'http://example.com/foo',
+ duration: 60,
+ size: 1234,
+ expiration: Infinity,
+ sessionIds: [],
+ drmInfo: null,
+ appMetadata: null,
streams: [
- createVideoStreamDB(1, 60, [0]),
- createVideoStreamDB(2, 60, [1]),
+ createVideoStreamDB(1, [0]),
+ createVideoStreamDB(2, [1]),
],
};
- const timeline = createTimeline();
+ const manifest = createConverter().fromManifestDB(manifestDb);
+ expect(manifest.variants.length).toBe(2);
+
+ expect(manifest.variants[0].audio).toBe(null);
+ expect(manifest.variants[0].video).toBeTruthy();
- const period = createConverter().fromPeriodDB(
- periodDb, arbitraryPeriodDuration, timeline);
- expect(period).toBeTruthy();
- expect(period.variants.length).toBe(2);
- expect(period.variants[0].audio).toBe(null);
- expect(period.variants[0].video).toBeTruthy();
+ expect(manifest.variants[1].audio).toBe(null);
+ expect(manifest.variants[1].video).toBeTruthy();
});
it('supports audio-only content', () => {
- /** @type {shaka.extern.PeriodDB} */
- const periodDb = {
- startTime: 60,
+ /** @type {shaka.extern.ManifestDB} */
+ const manifestDb = {
+ originalManifestUri: 'http://example.com/foo',
+ duration: 60,
+ size: 1234,
+ expiration: Infinity,
+ sessionIds: [],
+ drmInfo: null,
+ appMetadata: null,
streams: [
- createAudioStreamDB(1, 60, [0]),
- createAudioStreamDB(2, 60, [1]),
+ createAudioStreamDB(1, [0]),
+ createAudioStreamDB(2, [1]),
],
};
- const timeline = createTimeline();
+ const manifest = createConverter().fromManifestDB(manifestDb);
+ expect(manifest.variants.length).toBe(2);
+
+ expect(manifest.variants[0].audio).toBeTruthy();
+ expect(manifest.variants[0].video).toBe(null);
- const period = createConverter().fromPeriodDB(
- periodDb, arbitraryPeriodDuration, timeline);
- expect(period).toBeTruthy();
- expect(period.variants.length).toBe(2);
- expect(period.variants[0].audio).toBeTruthy();
- expect(period.variants[0].video).toBe(null);
+ expect(manifest.variants[1].audio).toBeTruthy();
+ expect(manifest.variants[1].video).toBe(null);
});
it('supports text streams', () => {
- /** @type {shaka.extern.PeriodDB} */
- const periodDb = {
- startTime: 60,
+ /** @type {shaka.extern.ManifestDB} */
+ const manifestDb = {
+ originalManifestUri: 'http://example.com/foo',
+ duration: 60,
+ size: 1234,
+ expiration: Infinity,
+ sessionIds: [],
+ drmInfo: null,
+ appMetadata: null,
streams: [
- createVideoStreamDB(1, 60, [0]),
- createTextStreamDB(2, 60),
+ createVideoStreamDB(1, [0]),
+ createTextStreamDB(2),
],
};
- const timeline = createTimeline();
+ const manifest = createConverter().fromManifestDB(manifestDb);
+ expect(manifest.variants.length).toBe(1);
+ expect(manifest.textStreams.length).toBe(1);
- const period = createConverter().fromPeriodDB(
- periodDb, arbitraryPeriodDuration, timeline);
- expect(period).toBeTruthy();
- expect(period.variants.length).toBe(1);
- expect(period.textStreams.length).toBe(1);
-
- verifyStream(periodDb, period.textStreams[0], periodDb.streams[1]);
+ verifyStream(manifest.textStreams[0], manifestDb.streams[1]);
});
it('combines Variants according to variantIds field', () => {
@@ -179,37 +210,37 @@ describe('ManifestConverter', () => {
const variant2 = 1;
const variant3 = 2;
- /** @type {shaka.extern.PeriodDB} */
- const periodDb = {
- startTime: 60,
+ /** @type {shaka.extern.ManifestDB} */
+ const manifestDb = {
+ originalManifestUri: 'http://example.com/foo',
+ duration: 60,
+ size: 1234,
+ expiration: Infinity,
+ sessionIds: [],
+ drmInfo: null,
+ appMetadata: null,
streams: [
// Audio
- createAudioStreamDB(audio1, 60, [variant2]),
- createAudioStreamDB(audio2, 60, [variant1, variant3]),
+ createAudioStreamDB(audio1, [variant2]),
+ createAudioStreamDB(audio2, [variant1, variant3]),
// Video
- createVideoStreamDB(video1, 60, [variant1]),
- createVideoStreamDB(video2, 60, [variant2, variant3]),
+ createVideoStreamDB(video1, [variant1]),
+ createVideoStreamDB(video2, [variant2, variant3]),
],
};
- const timeline = createTimeline();
-
- /** @type {shaka.extern.Period} */
- const period = createConverter().fromPeriodDB(
- periodDb, arbitraryPeriodDuration, timeline);
-
- expect(period).toBeTruthy();
- expect(period.variants.length).toBe(3);
+ const manifest = createConverter().fromManifestDB(manifestDb);
+ expect(manifest.variants.length).toBe(3);
// Variant 1
- expect(findVariant(period.variants, audio2, video1)).toBeTruthy();
+ expect(findVariant(manifest.variants, audio2, video1)).toBeTruthy();
// Variant 2
- expect(findVariant(period.variants, audio1, video2)).toBeTruthy();
+ expect(findVariant(manifest.variants, audio1, video2)).toBeTruthy();
// Variant 3
- expect(findVariant(period.variants, audio2, video2)).toBeTruthy();
+ expect(findVariant(manifest.variants, audio2, video2)).toBeTruthy();
});
- }); // describe('fromPeriodDB')
+ }); // describe('fromManifestDB')
/** @return {!shaka.offline.ManifestConverter} */
function createConverter() {
@@ -233,7 +264,6 @@ describe('ManifestConverter', () => {
id: id,
originalId: id.toString(),
primary: false,
- presentationTimeOffset: 0,
contentType: type,
mimeType: '',
codecs: '',
@@ -241,11 +271,14 @@ describe('ManifestConverter', () => {
label: null,
width: null,
height: null,
- initSegmentKey: null,
encrypted: false,
- keyId: null,
+ keyIds: [],
segments: [],
variantIds: variants,
+ roles: [],
+ channelsCount: null,
+ audioSamplingRate: null,
+ closedCaptions: null,
};
return streamDB;
@@ -260,9 +293,13 @@ describe('ManifestConverter', () => {
function createSegmentDB(startTime, endTime, dataKey) {
/** @type {shaka.extern.SegmentDB} */
const segment = {
- startTime: startTime,
- endTime: endTime,
- dataKey: dataKey,
+ startTime,
+ endTime,
+ dataKey,
+ initSegmentKey: null,
+ appendWindowStart: 0,
+ appendWindowEnd: Infinity,
+ timestampOffset: 0,
};
return segment;
@@ -270,17 +307,15 @@ describe('ManifestConverter', () => {
/**
* @param {number} id
- * @param {number} periodStart
* @param {!Array.} variantIds
* @return {shaka.extern.StreamDB}
*/
- function createVideoStreamDB(id, periodStart, variantIds) {
+ function createVideoStreamDB(id, variantIds) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
return {
id: id,
originalId: id.toString(),
primary: false,
- presentationTimeOffset: 25,
contentType: ContentType.VIDEO,
mimeType: 'video/mp4',
codecs: 'avc1.42c01e',
@@ -291,40 +326,41 @@ describe('ManifestConverter', () => {
label: null,
width: 250,
height: 100,
- initSegmentKey: null,
encrypted: true,
- keyId: 'key1',
+ keyIds: ['key1'],
segments: [
createSegmentDB(
- /* startTime= */ periodStart,
- /* endTime= */ periodStart + 10,
+ /* startTime= */ 0,
+ /* endTime= */ 10,
/* dataKey= */ 1),
createSegmentDB(
- /* startTime= */ periodStart + 10,
- /* endTime= */ periodStart + 20,
+ /* startTime= */ 10,
+ /* endTime= */ 20,
/* dataKey= */ 2),
createSegmentDB(
- /* startTime= */ periodStart + 20,
- /* endTime= */ periodStart + 25,
+ /* startTime= */ 20,
+ /* endTime= */ 25,
/* dataKey= */ 3),
],
variantIds: variantIds,
+ roles: [],
+ channelsCount: null,
+ audioSamplingRate: null,
+ closedCaptions: null,
};
}
/**
* @param {number} id
- * @param {number} periodStart
* @param {!Array.} variantIds
* @return {shaka.extern.StreamDB}
*/
- function createAudioStreamDB(id, periodStart, variantIds) {
+ function createAudioStreamDB(id, variantIds) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
return {
id: id,
originalId: id.toString(),
primary: false,
- presentationTimeOffset: 10,
contentType: ContentType.AUDIO,
mimeType: 'audio/mp4',
codecs: 'mp4a.40.2',
@@ -335,39 +371,40 @@ describe('ManifestConverter', () => {
label: null,
width: null,
height: null,
- initSegmentKey: 0,
encrypted: false,
- keyId: null,
+ keyIds: [],
segments: [
createSegmentDB(
- /* startTime= */ periodStart,
- /* endTime= */ periodStart + 10,
+ /* startTime= */ 0,
+ /* endTime= */ 10,
/* dataKey= */ 1),
createSegmentDB(
- /* startTime= */ periodStart + 10,
- /* endTime= */ periodStart + 20,
+ /* startTime= */ 10,
+ /* endTime= */ 20,
/* dataKey= */ 2),
createSegmentDB(
- /* startTime= */ periodStart + 20,
- /* endTime= */ periodStart + 25,
+ /* startTime= */ 20,
+ /* endTime= */ 25,
/* dataKey= */ 3),
],
variantIds: variantIds,
+ roles: [],
+ channelsCount: null,
+ audioSamplingRate: null,
+ closedCaptions: null,
};
}
/**
* @param {number} id
- * @param {number} periodStart
* @return {shaka.extern.StreamDB}
*/
- function createTextStreamDB(id, periodStart) {
+ function createTextStreamDB(id) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
return {
id: id,
originalId: id.toString(),
primary: false,
- presentationTimeOffset: 10,
contentType: ContentType.TEXT,
mimeType: 'text/vtt',
codecs: '',
@@ -378,33 +415,35 @@ describe('ManifestConverter', () => {
label: null,
width: null,
height: null,
- initSegmentKey: 0,
encrypted: false,
- keyId: null,
+ keyIds: [],
segments: [
createSegmentDB(
- /* startTime= */ periodStart,
- /* endTime= */ periodStart + 10,
+ /* startTime= */ 0,
+ /* endTime= */ 10,
/* dataKey= */ 1),
createSegmentDB(
- /* startTime= */ periodStart + 10,
- /* endTime= */ periodStart + 20,
+ /* startTime= */ 10,
+ /* endTime= */ 20,
/* dataKey= */ 2),
createSegmentDB(
- /* startTime= */ periodStart + 20,
- /* endTime= */ periodStart + 25,
+ /* startTime= */ 20,
+ /* endTime= */ 25,
/* dataKey= */ 3),
],
variantIds: [5],
+ roles: [],
+ channelsCount: null,
+ audioSamplingRate: null,
+ closedCaptions: null,
};
}
/**
- * @param {?shaka.extern.PeriodDB} periodDb
* @param {?shaka.extern.Stream} stream
* @param {?shaka.extern.StreamDB} streamDb
*/
- function verifyStream(periodDb, stream, streamDb) {
+ function verifyStream(stream, streamDb) {
if (!streamDb) {
expect(stream).toBeFalsy();
return;
@@ -418,49 +457,52 @@ describe('ManifestConverter', () => {
mimeType: streamDb.mimeType,
codecs: streamDb.codecs,
frameRate: streamDb.frameRate,
- pixelAspectRatio: streamDb.pixelAspectRatio || undefined,
+ pixelAspectRatio: streamDb.pixelAspectRatio,
width: streamDb.width || undefined,
height: streamDb.height || undefined,
kind: streamDb.kind,
encrypted: streamDb.encrypted,
- keyId: streamDb.keyId,
+ keyIds: streamDb.keyIds,
language: streamDb.language,
label: streamDb.label,
type: streamDb.contentType,
primary: streamDb.primary,
trickModeVideo: null,
emsgSchemeIdUris: null,
- roles: [],
- channelsCount: null,
- audioSamplingRate: null,
- closedCaptions: null,
+ roles: streamDb.roles,
+ channelsCount: streamDb.channelsCount,
+ audioSamplingRate: streamDb.audioSamplingRate,
+ closedCaptions: streamDb.closedCaptions,
};
expect(stream).toEqual(expectedStream);
// Assume that we don't have to call createSegmentIndex.
- const initSegmentReference = streamDb.initSegmentKey != null ?
- jasmine.any(shaka.media.InitSegmentReference) :
- null;
- const presentationTimeOffset = streamDb.presentationTimeOffset;
-
const iterator = stream.segmentIndex[Symbol.iterator]();
streamDb.segments.forEach((segmentDb, i) => {
const uri = shaka.offline.OfflineUri.segment(
'mechanism', 'cell', segmentDb.dataKey);
+ const initSegmentReference = segmentDb.initSegmentKey != null ?
+ jasmine.any(shaka.media.InitSegmentReference) :
+ null;
+
/** @type {shaka.media.SegmentReference} */
- const segment = iterator.seek(periodDb.startTime + segmentDb.startTime);
- expect(segment.startTime).toBe(periodDb.startTime + segmentDb.startTime);
- expect(segment.endTime).toBe(periodDb.startTime + segmentDb.endTime);
+ const segment = iterator.seek(segmentDb.startTime);
+
+ /** @type {shaka.media.SegmentReference} */
+ const sameSegment = iterator.seek(segmentDb.endTime - 0.1);
+
+ expect(segment).toBe(sameSegment);
+ expect(segment.startTime).toBe(segmentDb.startTime);
+ expect(segment.endTime).toBe(segmentDb.endTime);
expect(segment.startByte).toBe(0);
expect(segment.endByte).toBe(null);
expect(segment.getUris()).toEqual([uri.toString()]);
expect(segment.initSegmentReference).toEqual(initSegmentReference);
- expect(segment.timestampOffset).toBe(
- periodDb.startTime - presentationTimeOffset);
+ expect(segment.timestampOffset).toBe(segmentDb.timestampOffset);
});
}
diff --git a/test/offline/offline_integration.js b/test/offline/offline_integration.js
index e48122620f..f1d2fc5f39 100644
--- a/test/offline/offline_integration.js
+++ b/test/offline/offline_integration.js
@@ -3,7 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
-describe('Offline', () => {
+/** @return {boolean} */
+const supportsStorage = () => shaka.offline.Storage.support();
+
+// TODO: Merge with storage_integration.js. No obvious difference in purpose.
+filterDescribe('Offline', supportsStorage, () => {
/** @type {!shaka.Player} */
let player;
/** @type {!shaka.offline.Storage} */
@@ -28,11 +32,9 @@ describe('Offline', () => {
eventManager = new shaka.util.EventManager();
- if (supportsStorage()) {
- // Make sure we are starting with a blank slate.
- await shaka.offline.Storage.deleteAll();
- storage = new shaka.offline.Storage(player);
- }
+ // Make sure we are starting with a blank slate.
+ await shaka.offline.Storage.deleteAll();
+ storage = new shaka.offline.Storage(player);
});
afterEach(async () => {
@@ -43,9 +45,7 @@ describe('Offline', () => {
}
// Make sure we don't leave anything in storage after the test.
- if (supportsStorage()) {
- await shaka.offline.Storage.deleteAll();
- }
+ await shaka.offline.Storage.deleteAll();
if (player) {
await player.destroy();
@@ -53,11 +53,6 @@ describe('Offline', () => {
});
it('stores, plays, and deletes clear content', async () => {
- if (!supportsStorage()) {
- pending('Storage is not supported.');
- return;
- }
-
const content = await storage.store('test:sintel');
expect(content).toBeTruthy();
@@ -77,11 +72,6 @@ describe('Offline', () => {
drmIt(
'stores, plays, and deletes protected content with a persistent license',
async () => {
- if (!supportsStorage()) {
- pending('Storage is not supported on this platform.');
- return;
- }
-
const support = await shaka.Player.probeSupport();
const widevineSupport = support.drm['com.widevine.alpha'];
@@ -117,11 +107,6 @@ describe('Offline', () => {
drmIt(
'stores, plays, and deletes protected content with a temporary license',
async () => {
- if (!supportsStorage()) {
- pending('Storage is not supported.');
- return;
- }
-
const support = await shaka.Player.probeSupport();
const widevineSupport = support.drm['com.widevine.alpha'];
const playreadySupport = support.drm['com.microsoft.playready'];
@@ -160,9 +145,4 @@ describe('Offline', () => {
await shaka.test.Util.waitUntilPlayheadReaches(
eventManager, video, endSeconds, timeoutSeconds);
}
-
- /** @return {boolean} */
- function supportsStorage() {
- return shaka.offline.Storage.support();
- }
});
diff --git a/test/offline/offline_manifest_parser_unit.js b/test/offline/offline_manifest_parser_unit.js
index 79505fb879..e7e4914de6 100644
--- a/test/offline/offline_manifest_parser_unit.js
+++ b/test/offline/offline_manifest_parser_unit.js
@@ -202,7 +202,7 @@ filterDescribe('OfflineManifestParser', offlineManifestParserSupport, () => {
duration: 600 * seconds,
size: 100 * mb,
expiration: Infinity,
- periods: [],
+ streams: [],
sessionIds: [sessionId],
drmInfo: null,
appMetadata: {},
diff --git a/test/offline/storage_compatibility_unit.js b/test/offline/storage_compatibility_unit.js
index 3c272ec0d1..8bf01987c0 100644
--- a/test/offline/storage_compatibility_unit.js
+++ b/test/offline/storage_compatibility_unit.js
@@ -33,8 +33,7 @@ const compatibilityTestsMetadata = [
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
connection,
/* segmentStore= */ 'segment-v2',
- /* manifestStore= */ 'manifest-v2',
- /* isFixedKey= */ true), // TODO: Drop isFixedKey when v4 is out.
+ /* manifestStore= */ 'manifest-v2'),
},
{
// This is the "clean" version of the v2 database format, as created from
@@ -47,39 +46,50 @@ const compatibilityTestsMetadata = [
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
connection,
/* segmentStore= */ 'segment-v2',
- /* manifestStore= */ 'manifest-v2',
- /* isFixedKey= */ true), // TODO: Drop isFixedKey when v4 is out.
+ /* manifestStore= */ 'manifest-v2'),
},
{
// This is the v3 version of the database, which is actually identical to
// the "clean" version of the v2 database. The version number was
// incremented to overcome the "broken" v2 databases. This format was
- // introduced in v2.3.2.
+ // introduced in v2.3.2 and deprecated in v2.6.
name: 'v3',
dbImagePath: '/base/test/test/assets/db-dump-v3.json',
manifestKey: 1,
- readOnly: false,
+ readOnly: true,
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
connection,
/* segmentStore= */ 'segment-v3',
- /* manifestStore= */ 'manifest-v3',
- /* isFixedKey= */ false), // TODO: Drop isFixedKey when v4 is out.
+ /* manifestStore= */ 'manifest-v3'),
},
{
- // This is the v3 version of the database as written by v2.5.0 - v2.5.9. A
+ // This is the v4 version of the database as written by v2.5.0 - v2.5.9. A
// bug in v2.5 caused the stream metadata from all periods to be written to
// each period. This was corrected in v2.5.10.
// See https://github.com/google/shaka-player/issues/2389
- name: 'v3-broken',
- dbImagePath: '/base/test/test/assets/db-dump-v3-broken.json',
+ name: 'v4-broken',
+ dbImagePath: '/base/test/test/assets/db-dump-v4-broken.json',
manifestKey: 1,
- readOnly: false,
+ readOnly: true,
makeCell: (connection) => new shaka.offline.indexeddb.V2StorageCell(
connection,
+ // V4 of the database still used the V3 store names and structures.
/* segmentStore= */ 'segment-v3',
- /* manifestStore= */ 'manifest-v3',
- /* isFixedKey= */ false), // TODO: Drop isFixedKey when v4 is out.
+ /* manifestStore= */ 'manifest-v3'),
+ },
+ /* FIXME(#1339): Dump V5 database and enable this test case
+ {
+ // This is the v5 version of the database, introduced in v2.6.
+ name: 'v5',
+ dbImagePath: '/base/test/test/assets/db-dump-v5.json',
+ manifestKey: 1,
+ readOnly: false,
+ makeCell: (connection) => new shaka.offline.indexeddb.V5StorageCell(
+ connection,
+ /* segmentStore= * 'segment-v5',
+ /* manifestStore= * 'manifest-v5'),
},
+ */
];
filterDescribe('Storage Compatibility', () => window.indexedDB, () => {
@@ -261,7 +271,8 @@ filterDescribe('Storage Compatibility', () => window.indexedDB, () => {
await checkMissingManifests(manifestKeys);
});
- it('correctly converts to the current manifest format', async () => {
+ // FIXME(#1339): Re-enable this test!
+ xit('correctly converts to the current manifest format', async () => {
// There should be one manifest.
const manifestDb = (await cell.getManifests([metadata.manifestKey]))[0];
const converter = new shaka.offline.ManifestConverter(
@@ -272,49 +283,20 @@ filterDescribe('Storage Compatibility', () => window.indexedDB, () => {
manifest.anyTimeline();
manifest.minBufferTime = 2;
- manifest.addPeriod(0, (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.frameRate = 29.97;
- stream.mime('video/webm', 'vp9');
- stream.size(640, 480);
- });
- });
- });
-
- manifest.addPeriod(Util.closeTo(2.06874), (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.frameRate = 29.97;
- stream.mime('video/webm', 'vp9');
- stream.size(640, 480);
- });
- });
- });
-
- manifest.addPeriod(Util.closeTo(4.20413), (period) => {
- period.addPartialVariant((variant) => {
- variant.addPartialStream(ContentType.VIDEO, (stream) => {
- stream.frameRate = 29.97;
- stream.mime('video/webm', 'vp9');
- stream.size(320, 240);
- });
+ manifest.addPartialVariant((variant) => {
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
+ stream.frameRate = 29.97;
+ stream.mime('video/webm', 'vp9');
+ stream.size(640, 480);
});
});
});
expect(actual).toEqual(expected);
- const segmentIndex0 = actual.periods[0].variants[0].video.segmentIndex;
- const segmentIndex1 = actual.periods[1].variants[0].video.segmentIndex;
- const segmentIndex2 = actual.periods[2].variants[0].video.segmentIndex;
- goog.asserts.assert(segmentIndex0, 'Null segment index!');
- goog.asserts.assert(segmentIndex1, 'Null segment index!');
- goog.asserts.assert(segmentIndex2, 'Null segment index!');
-
- const segment0 = Array.from(segmentIndex0)[0];
- const segment1 = Array.from(segmentIndex1)[0];
- const segment2 = Array.from(segmentIndex2)[0];
+ const segmentIndex = actual.variants[0].video.segmentIndex;
+ goog.asserts.assert(segmentIndex != null, 'Null segmentIndex!');
+ const [segment0, segment1, segment2] = Array.from(segmentIndex);
expect(segment0).toEqual(jasmine.objectContaining({
startTime: 0,
@@ -346,21 +328,19 @@ filterDescribe('Storage Compatibility', () => window.indexedDB, () => {
* @return {!Array.}
*/
function getAllSegmentKeys(manifest) {
- const keys = [];
+ const keys = new Set();
- for (const period of manifest.periods) {
- for (const stream of period.streams) {
- if (stream.initSegmentKey != null) {
- keys.push(stream.initSegmentKey);
+ for (const stream of manifest.streams) {
+ for (const segment of stream.segments) {
+ if (segment.initSegmentKey != null) {
+ keys.add(segment.initSegmentKey);
}
- for (const segment of stream.segments) {
- keys.push(segment.dataKey);
- }
+ keys.add(segment.dataKey);
}
}
- return keys;
+ return Array.from(keys);
}
} // makeTests
});
diff --git a/test/offline/storage_integration.js b/test/offline/storage_integration.js
index e2555af847..92b33b9c81 100644
--- a/test/offline/storage_integration.js
+++ b/test/offline/storage_integration.js
@@ -23,7 +23,7 @@ filterDescribe('Storage', storageSupport, () => {
const Util = shaka.test.Util;
const englishUS = 'en-us';
- const frenchCanadian= 'fr-ca';
+ const frenchCanadian = 'fr-ca';
const fakeMimeType = 'application/test';
const manifestWithPerStreamBandwidthUri =
@@ -32,7 +32,6 @@ filterDescribe('Storage', storageSupport, () => {
'fake:manifest-without-per-stream-bandwidth';
const manifestWithNonZeroStartUri = 'fake:manifest-with-non-zero-start';
const manifestWithLiveTimelineUri = 'fake:manifest-with-live-timeline';
- const manifestWithThreePeriodsUri = 'fake:manifest-with-three-periods';
const segment1Uri = 'fake:segment-1';
const segment2Uri = 'fake:segment-2';
@@ -583,23 +582,21 @@ filterDescribe('Storage', storageSupport, () => {
function makeWithStreamBandwidth() {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline.setDuration(20);
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.language = englishUS;
- variant.bandwidth = kbps(13);
- variant.addVideo(1, (stream) => {
- stream.bandwidth = kbps(10);
- stream.size(100, 200);
- });
- variant.addAudio(2, (stream) => {
- stream.language = englishUS;
- stream.bandwidth = kbps(3);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.language = englishUS;
+ variant.bandwidth = kbps(13);
+ variant.addVideo(1, (stream) => {
+ stream.bandwidth = kbps(10);
+ stream.size(100, 200);
+ });
+ variant.addAudio(2, (stream) => {
+ stream.language = englishUS;
+ stream.bandwidth = kbps(3);
});
});
});
- const audio = manifest.periods[0].variants[0].audio;
+ const audio = manifest.variants[0].audio;
goog.asserts.assert(audio, 'Created manifest with audio, where is it?');
overrideSegmentIndex(audio, [
makeReference(audioSegment1Uri, 0, 1),
@@ -608,7 +605,7 @@ filterDescribe('Storage', storageSupport, () => {
makeReference(audioSegment4Uri, 3, 4),
]);
- const video = manifest.periods[0].variants[0].video;
+ const video = manifest.variants[0].video;
goog.asserts.assert(video, 'Created manifest with video, where is it?');
overrideSegmentIndex(video, [
makeReference(videoSegment1Uri, 0, 1),
@@ -634,13 +631,10 @@ filterDescribe('Storage', storageSupport, () => {
// the per-stream values.
const manifest = makeWithStreamBandwidth();
goog.asserts.assert(
- manifest.periods.length == 1,
- 'Expecting manifest to only have one period');
- goog.asserts.assert(
- manifest.periods[0].variants.length == 1,
+ manifest.variants.length == 1,
'Expecting manifest to only have one variant');
- const variant = manifest.periods[0].variants[0];
+ const variant = manifest.variants[0];
goog.asserts.assert(
variant.audio,
'Expecting manifest to have audio stream');
@@ -701,7 +695,6 @@ filterDescribe('Storage', storageSupport, () => {
manifestWithPerStreamBandwidthUri,
manifestWithoutPerStreamBandwidthUri,
manifestWithNonZeroStartUri,
- manifestWithThreePeriodsUri,
];
// NOTE: We're working around an apparent compiler bug here, with Closure
@@ -740,8 +733,6 @@ filterDescribe('Storage', storageSupport, () => {
},
});
- // Stored content should reflect the tracks in the first period, so we
- // should only find track there.
const stored = await storage.store(
manifestWithPerStreamBandwidthUri, noMetadata, fakeMimeType);
expect(stored.tracks.length).toBe(1);
@@ -762,13 +753,11 @@ filterDescribe('Storage', storageSupport, () => {
expect(manifests.length).toBe(1);
const manifest = manifests[0];
- expect(manifest.periods.length).toBe(1);
-
- const period = manifest.periods[0];
// There should be 2 streams, an audio and a video stream.
- expect(period.streams.length).toBe(2);
+ expect(manifest.streams.length).toBe(2);
- const audio = period.streams.filter((s) => s.contentType == 'audio')[0];
+ const audio = manifest.streams.filter(
+ (s) => s.contentType == 'audio')[0];
expect(audio.language).toBe(frenchCanadian);
} finally {
await muxer.destroy();
@@ -809,7 +798,7 @@ filterDescribe('Storage', storageSupport, () => {
overrideDrmAndManifest(
storage,
drm,
- makeManifestWithPerStreamBandwidth(1));
+ makeManifestWithPerStreamBandwidth());
const stored = await storage.store(manifestWithPerStreamBandwidthUri);
@@ -856,7 +845,7 @@ filterDescribe('Storage', storageSupport, () => {
overrideDrmAndManifest(
storage,
drm,
- makeManifestWithPerStreamBandwidth(1));
+ makeManifestWithPerStreamBandwidth());
storage.configure('offline.usePersistentLicense', false);
const stored = await storage.store(manifestWithPerStreamBandwidthUri);
@@ -923,7 +912,7 @@ filterDescribe('Storage', storageSupport, () => {
});
it('throws an error if destroyed mid-store', async () => {
- const manifest = makeManifestWithPerStreamBandwidth(1);
+ const manifest = makeManifestWithPerStreamBandwidth();
/**
* Block storage when it goes to parse the manifest. Since we don't want
@@ -1022,7 +1011,7 @@ filterDescribe('Storage', storageSupport, () => {
// Get the stream from the manifest. The segment count is based on how
// we created manifest in the "make*Manifest" functions.
- const stream = manifest.periods[0].streams[0];
+ const stream = manifest.streams[0];
expect(stream).toBeTruthy();
expect(stream.segments.length).toBe(4);
@@ -1088,55 +1077,6 @@ filterDescribe('Storage', storageSupport, () => {
expect(progressSteps).toBeTruthy();
expect(progressSteps.length).toBe(0);
});
-
- it('stores multi-period content', async () => {
- const storedContent = await storage.store(
- manifestWithThreePeriodsUri, noMetadata, fakeMimeType);
-
- let parsed = false;
-
- eventManager.listen(player, 'manifestparsed', async () => {
- const manifest = player.getManifest();
- expect(manifest.periods.length).toBe(3);
-
- const start0 = manifest.periods[0].startTime;
- const start1 = manifest.periods[1].startTime;
- const start2 = manifest.periods[2].startTime;
-
- const stream0 = manifest.periods[0].variants[0].video;
- const stream1 = manifest.periods[1].variants[0].video;
- const stream2 = manifest.periods[2].variants[0].video;
-
- await stream0.createSegmentIndex();
- await stream1.createSegmentIndex();
- await stream2.createSegmentIndex();
-
- const position0 = stream0.segmentIndex.find(start0);
- const position1 = stream1.segmentIndex.find(start1);
- const position2 = stream2.segmentIndex.find(start2);
-
- expect(position0).not.toBe(null);
- expect(position1).not.toBe(null);
- expect(position2).not.toBe(null);
-
- const segment0 = stream0.segmentIndex.get(position0);
- const segment1 = stream1.segmentIndex.get(position1);
- const segment2 = stream2.segmentIndex.get(position2);
-
- expect(segment0.startTime).toBe(start0);
- expect(segment1.startTime).toBe(start1);
- expect(segment2.startTime).toBe(start2);
-
- parsed = true;
- });
-
- await player.load(
- storedContent.offlineUri, 0, 'application/x-offline-manifest');
-
- // Make sure the listener with the expectations actually fired and
- // completed.
- expect(parsed).toBe(true);
- });
});
describe('storage without player', () => {
@@ -1232,65 +1172,48 @@ filterDescribe('Storage', storageSupport, () => {
};
}
- /**
- * @param {number} numPeriods
- * @return {shaka.extern.Manifest}
- */
- function makeManifestWithPerStreamBandwidth(numPeriods) {
- const periodDuration = 4;
- const idsPerPeriod = 6;
-
+ /** @return {shaka.extern.Manifest} */
+ function makeManifestWithPerStreamBandwidth() {
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline.setDuration(20);
- for (let i = 0; i < numPeriods; ++i) {
- const startTime = i * periodDuration;
- const baseId = i * idsPerPeriod;
-
- manifest.addPeriod(startTime, (period) => {
- period.addVariant(baseId + 0, (variant) => {
- variant.language = englishUS;
- variant.bandwidth = kbps(13);
- variant.addVideo(baseId + 1, (stream) => {
- stream.bandwidth = kbps(10);
- stream.size(100, 200);
- });
- variant.addAudio(baseId + 2, (stream) => {
- stream.language = englishUS;
- stream.bandwidth = kbps(3);
- });
- });
- period.addVariant(baseId + 3, (variant) => {
- variant.language = frenchCanadian;
- variant.bandwidth = kbps(13);
- variant.addVideo(baseId + 4, (stream) => {
- stream.bandwidth = kbps(10);
- stream.size(100, 200);
- });
- variant.addAudio(baseId + 5, (stream) => {
- stream.language = frenchCanadian;
- stream.bandwidth = kbps(3);
- });
- });
+ manifest.addVariant(0, (variant) => {
+ variant.language = englishUS;
+ variant.bandwidth = kbps(13);
+ variant.addVideo(1, (stream) => {
+ stream.bandwidth = kbps(10);
+ stream.size(100, 200);
});
- }
+ variant.addAudio(2, (stream) => {
+ stream.language = englishUS;
+ stream.bandwidth = kbps(3);
+ });
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.language = frenchCanadian;
+ variant.bandwidth = kbps(13);
+ variant.addVideo(4, (stream) => {
+ stream.bandwidth = kbps(10);
+ stream.size(100, 200);
+ });
+ variant.addAudio(5, (stream) => {
+ stream.language = frenchCanadian;
+ stream.bandwidth = kbps(3);
+ });
+ });
});
- for (let i = 0; i < numPeriods; ++i) {
- for (const stream of getAllStreams(manifest, i)) {
- const startTime = i * periodDuration;
-
- // Make a new copy each time, as the segment index can modify each
- // reference.
- const refs = [
- makeReference(segment1Uri, startTime + 0, startTime + 1),
- makeReference(segment2Uri, startTime + 1, startTime + 2),
- makeReference(segment3Uri, startTime + 2, startTime + 3),
- makeReference(segment4Uri, startTime + 3, startTime + 4),
- ];
+ for (const stream of getAllStreams(manifest)) {
+ // Make a new copy each time, as the segment index can modify each
+ // reference.
+ const refs = [
+ makeReference(segment1Uri, 0, 1),
+ makeReference(segment2Uri, 1, 2),
+ makeReference(segment3Uri, 2, 3),
+ makeReference(segment4Uri, 3, 4),
+ ];
- overrideSegmentIndex(stream, refs);
- }
+ overrideSegmentIndex(stream, refs);
}
return manifest;
@@ -1300,10 +1223,10 @@ filterDescribe('Storage', storageSupport, () => {
* @return {shaka.extern.Manifest}
*/
function makeManifestWithoutPerStreamBandwidth() {
- const manifest = makeManifestWithPerStreamBandwidth(1);
+ const manifest = makeManifestWithPerStreamBandwidth();
// Remove the per stream bandwidth.
- for (const stream of getAllStreams(manifest, 0)) {
+ for (const stream of getAllStreams(manifest)) {
stream.bandwidth = undefined;
}
@@ -1314,9 +1237,9 @@ filterDescribe('Storage', storageSupport, () => {
* @return {shaka.extern.Manifest}
*/
function makeManifestWithNonZeroStart() {
- const manifest = makeManifestWithPerStreamBandwidth(1);
+ const manifest = makeManifestWithPerStreamBandwidth();
- for (const stream of getAllStreams(manifest, 0)) {
+ for (const stream of getAllStreams(manifest)) {
const refs = [
makeReference(segment1Uri, 10, 11),
makeReference(segment2Uri, 11, 12),
@@ -1334,7 +1257,7 @@ filterDescribe('Storage', storageSupport, () => {
* @return {shaka.extern.Manifest}
*/
function makeManifestWithLiveTimeline() {
- const manifest = makeManifestWithPerStreamBandwidth(1);
+ const manifest = makeManifestWithPerStreamBandwidth();
manifest.presentationTimeline.setDuration(Infinity);
manifest.presentationTimeline.setStatic(false);
return manifest;
@@ -1342,13 +1265,12 @@ filterDescribe('Storage', storageSupport, () => {
/**
* @param {shaka.extern.Manifest} manifest
- * @param {number} periodIndex
* @return {!Array.}
*/
- function getAllStreams(manifest, periodIndex) {
+ function getAllStreams(manifest) {
const streams = [];
- for (const variant of manifest.periods[periodIndex].variants) {
+ for (const variant of manifest.variants) {
if (variant.audio) {
streams.push(variant.audio);
}
@@ -1356,7 +1278,7 @@ filterDescribe('Storage', storageSupport, () => {
streams.push(variant.video);
}
}
- for (const stream of manifest.periods[periodIndex].textStreams) {
+ for (const stream of manifest.textStreams) {
streams.push(stream);
}
@@ -1427,15 +1349,13 @@ filterDescribe('Storage', storageSupport, () => {
constructor() {
this.map_ = {};
this.map_[manifestWithPerStreamBandwidthUri] =
- makeManifestWithPerStreamBandwidth(1);
+ makeManifestWithPerStreamBandwidth();
this.map_[manifestWithoutPerStreamBandwidthUri] =
makeManifestWithoutPerStreamBandwidth();
this.map_[manifestWithNonZeroStartUri] =
makeManifestWithNonZeroStart();
this.map_[manifestWithLiveTimelineUri] =
makeManifestWithLiveTimeline();
- this.map_[manifestWithThreePeriodsUri] =
- makeManifestWithPerStreamBandwidth(3);
}
/** @override */
@@ -1517,7 +1437,7 @@ filterDescribe('Storage', storageSupport, () => {
try {
drm.configure(player.getConfiguration().drm);
- const variants = shaka.util.Periods.getAllVariantsFrom(manifest.periods);
+ const variants = manifest.variants;
await drm.initForStorage(variants, /* usePersistentLicenses= */ true);
await action(drm);
} finally {
diff --git a/test/player_integration.js b/test/player_integration.js
index f5cb80393a..739a96d98b 100644
--- a/test/player_integration.js
+++ b/test/player_integration.js
@@ -240,7 +240,7 @@ describe('Player', () => {
});
// Repro for https://github.com/google/shaka-player/issues/1879.
- it('actually appends cues when enabled initially', async () => {
+ it('appends cues when enabled initially', async () => {
let cues = [];
/** @const {!shaka.test.FakeTextDisplayer} */
const displayer = new shaka.test.FakeTextDisplayer();
@@ -254,14 +254,17 @@ describe('Player', () => {
player.configure({preferredTextLanguage: preferredTextLanguage});
await player.load('test:sintel_realistic_compiled');
- await Util.delay(1); // Allow the first segments to be appended.
+
+ // Play until a time at which the external cues would be on screen.
+ video.play();
+ await waitUntilPlayheadReaches(eventManager, video, 4, 20);
expect(player.isTextTrackVisible()).toBe(true);
expect(displayer.isTextVisible()).toBe(true);
expect(cues.length).toBeGreaterThan(0);
});
- it('actually appends cues for external text', async () => {
+ it('appends cues for external text', async () => {
let cues = [];
/** @const {!shaka.test.FakeTextDisplayer} */
const displayer = new shaka.test.FakeTextDisplayer();
@@ -275,23 +278,22 @@ describe('Player', () => {
/** @type {shaka.test.Waiter} */
const waiter = new shaka.test.Waiter(eventManager);
-
await player.load('test:sintel_no_text_compiled');
const locationUri = new goog.Uri(location.href);
const partialUri = new goog.Uri('/base/test/test/assets/text-clip.vtt');
const absoluteUri = locationUri.resolve(partialUri);
- await player.addTextTrack(absoluteUri.toString(), 'en', 'subtitles',
- 'text/vtt');
+ const newTrack = player.addTextTrack(
+ absoluteUri.toString(), 'en', 'subtitles', 'text/vtt');
- const textTracks = player.getTextTracks();
- expect(textTracks).toBeTruthy();
- expect(textTracks.length).toBe(1);
+ expect(player.getTextTracks()).toEqual([newTrack]);
+ player.selectTextTrack(newTrack);
player.setTextTrackVisibility(true);
await waiter.waitForEvent(player, 'texttrackvisibility');
- // Wait for the text cues to get appended.
- // TODO: this should be based on an event instead.
- await Util.delay(1);
+
+ // Play until a time at which the external cues would be on screen.
+ video.play();
+ await waitUntilPlayheadReaches(eventManager, video, 4, 20);
expect(player.isTextTrackVisible()).toBe(true);
expect(displayer.isTextVisible()).toBe(true);
@@ -308,15 +310,14 @@ describe('Player', () => {
const locationUri = new goog.Uri(location.href);
const partialUri = new goog.Uri('/base/test/test/assets/text-clip.vtt');
const absoluteUri = locationUri.resolve(partialUri);
- await player.addTextTrack(absoluteUri.toString(), 'en', 'subtitles',
- 'text/vtt');
+ const newTrack = player.addTextTrack(
+ absoluteUri.toString(), 'en', 'subtitles', 'text/vtt');
- const textTracks = player.getTextTracks();
- expect(textTracks).toBeTruthy();
- expect(textTracks.length).toBe(1);
+ expect(newTrack.language).toBe('en');
+ expect(player.getTextTracks()).toEqual([newTrack]);
- expect(textTracks[0].active).toBe(true);
- expect(textTracks[0].language).toBe('en');
+ player.selectTextTrack(newTrack);
+ expect(player.getTextTracks()[0].active).toBe(true);
});
it('with cea closed captions', async () => {
@@ -328,32 +329,6 @@ describe('Player', () => {
expect(textTracks[0].language).toBe('en');
});
- it('while changing languages with short Periods', async () => {
- // See: https://github.com/google/shaka-player/issues/797
- player.configure({preferredAudioLanguage: 'en'});
- await player.load('test:sintel_short_periods_compiled');
- video.play();
- await waitUntilPlayheadReaches(eventManager, video, 8, 30);
-
- // The Period changes at 10 seconds. Assert that we are in the previous
- // Period and have buffered into the next one.
- expect(video.currentTime).toBeLessThan(9);
- // The two periods might not be in a single contiguous buffer, so don't
- // check end(0). Gap-jumping will deal with any discontinuities.
- const bufferEnd = video.buffered.end(video.buffered.length - 1);
- expect(bufferEnd).toBeGreaterThan(11);
-
- // Change to a different language; this should clear the buffers and
- // cause a Period transition again.
- expect(getActiveLanguage()).toBe('en');
- player.selectAudioLanguage('es');
- await waitUntilPlayheadReaches(eventManager, video, 21, 30);
-
- // Should have gotten past the next Period transition and still be
- // playing the new language.
- expect(getActiveLanguage()).toBe('es');
- });
-
it('at higher playback rates', async () => {
await player.load('test:sintel_compiled');
video.play();
@@ -587,10 +562,7 @@ describe('Player', () => {
const waiter = (new shaka.test.Waiter(eventManager)).timeoutAfter(10);
const canPlayThrough = waiter.waitForEvent(video, 'canplaythrough');
- // Important: use a stream that starts somewhere other than zero, so that
- // the video element's time is initially different from the start time of
- // playback, and there is no content at time zero.
- await player.load('test:sintel_start_at_3_compiled', 5);
+ await player.load('test:sintel_compiled', 5);
shaka.log.debug('load resolved');
// When load is resolved(), tracks should definitely exist.
diff --git a/test/player_src_equals_integration.js b/test/player_src_equals_integration.js
index 9676085b83..4d77d9785d 100644
--- a/test/player_src_equals_integration.js
+++ b/test/player_src_equals_integration.js
@@ -335,13 +335,13 @@ describe('Player Src Equals', () => {
it('cannot add text tracks', async () => {
await loadWithSrcEquals(SMALL_MP4_CONTENT_URI, /* startTime= */ null);
- const pendingAdd = player.addTextTrack(
- 'test:need-a-uri-for-text',
- 'en-US',
- 'main',
- 'text/mp4');
-
- await expectAsync(pendingAdd).toBeRejected();
+ expect(() => {
+ player.addTextTrack(
+ 'test:need-a-uri-for-text',
+ 'en-US',
+ 'main',
+ 'text/mp4');
+ }).toThrow();
});
// Since we are not in-charge of streaming, calling |retryStreaming| should
diff --git a/test/player_unit.js b/test/player_unit.js
index 2c3e49e146..5c5f1e244b 100644
--- a/test/player_unit.js
+++ b/test/player_unit.js
@@ -23,8 +23,6 @@ describe('Player', () => {
let onError;
/** @type {shaka.extern.Manifest} */
let manifest;
- /** @type {number} */
- let periodIndex;
/** @type {!shaka.Player} */
let player;
/** @type {!shaka.test.FakeAbrManager} */
@@ -69,20 +67,15 @@ describe('Player', () => {
// Many tests assume the existence of a manifest, so create a basic one.
// Test suites can override this with more specific manifests.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(1);
- variant.addVideo(2);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(1);
+ variant.addVideo(2);
});
- manifest.addPeriod(1, (period) => {
- period.addVariant(1, (variant) => {
- variant.addAudio(3);
- variant.addVideo(4);
- });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(3);
+ variant.addVideo(4);
});
});
- periodIndex = 0;
shaka.media.ManifestParser.registerParserByMime(
fakeMimeType, () => new shaka.test.FakeManifestParser(manifest));
@@ -97,8 +90,7 @@ describe('Player', () => {
drmEngine = new shaka.test.FakeDrmEngine();
playhead = new shaka.test.FakePlayhead();
- streamingEngine = new shaka.test.FakeStreamingEngine(
- onChooseStreams, onCanSwitch);
+ streamingEngine = new shaka.test.FakeStreamingEngine();
mediaSourceEngine = {
init: jasmine.createSpy('init').and.returnValue(Promise.resolve()),
open: jasmine.createSpy('open').and.returnValue(Promise.resolve()),
@@ -161,19 +153,17 @@ describe('Player', () => {
expect(streamingEngine.destroy).toHaveBeenCalled();
const segmentIndexes = [];
- for (const period of manifest.periods) {
- for (const variant of period.variants) {
- if (variant.audio) {
- segmentIndexes.push(variant.audio.segmentIndex);
- }
- if (variant.video) {
- segmentIndexes.push(variant.video.segmentIndex);
- }
+ for (const variant of manifest.variants) {
+ if (variant.audio) {
+ segmentIndexes.push(variant.audio.segmentIndex);
}
- for (const textStream of period.textStreams) {
- segmentIndexes.push(textStream.segmentIndex);
+ if (variant.video) {
+ segmentIndexes.push(variant.video.segmentIndex);
}
}
+ for (const textStream of manifest.textStreams) {
+ segmentIndexes.push(textStream.segmentIndex);
+ }
for (const segmentIndex of segmentIndexes) {
if (segmentIndex) {
expect(segmentIndex.release).toHaveBeenCalled();
@@ -243,22 +233,20 @@ describe('Player', () => {
// We must have two different sets of codecs for some of our tests.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(1, (stream) => {
- stream.mime('audio/mp4', 'mp4a.40.2');
- });
- variant.addVideo(2, (stream) => {
- stream.mime('video/mp4', 'avc1.4d401f');
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(1, (stream) => {
+ stream.mime('audio/mp4', 'mp4a.40.2');
});
- period.addVariant(1, (variant) => {
- variant.addAudio(3, (stream) => {
- stream.mime('audio/webm', 'opus');
- });
- variant.addVideo(4, (stream) => {
- stream.mime('video/webm', 'vp9');
- });
+ variant.addVideo(2, (stream) => {
+ stream.mime('video/mp4', 'avc1.4d401f');
+ });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(3, (stream) => {
+ stream.mime('audio/webm', 'opus');
+ });
+ variant.addVideo(4, (stream) => {
+ stream.mime('video/webm', 'vp9');
});
});
});
@@ -305,33 +293,33 @@ describe('Player', () => {
describe('setTextTrackVisibility', () => {
beforeEach(() => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(1);
- variant.addVideo(2);
- });
- period.addTextStream(3, (stream) => {
- stream.bandwidth = 100;
- stream.kind = 'caption';
- stream.label = 'Spanish';
- stream.language = 'es';
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(1);
+ variant.addVideo(2);
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.bandwidth = 100;
+ stream.kind = 'caption';
+ stream.label = 'Spanish';
+ stream.language = 'es';
});
});
});
it('load text stream if caption is visible', async () => {
- await player.load(fakeManifestUri, 0, fakeMimeType);
await player.setTextTrackVisibility(true);
- expect(streamingEngine.loadNewTextStream).toHaveBeenCalled();
- expect(streamingEngine.getBufferingText()).not.toBe(null);
+ await player.load(fakeManifestUri, 0, fakeMimeType);
+ expect(streamingEngine.switchTextStream).toHaveBeenCalled();
+ expect(shaka.test.Util.invokeSpy(streamingEngine.getCurrentTextStream))
+ .not.toBe(null);
});
it('does not load text stream if caption is invisible', async () => {
- await player.load(fakeManifestUri, 0, fakeMimeType);
await player.setTextTrackVisibility(false);
- expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
- expect(streamingEngine.getBufferingText()).toBe(null);
+ await player.load(fakeManifestUri, 0, fakeMimeType);
+ expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
+ expect(shaka.test.Util.invokeSpy(streamingEngine.getCurrentTextStream))
+ .toBe(null);
});
it('loads text stream if alwaysStreamText is set', async () => {
@@ -339,14 +327,17 @@ describe('Player', () => {
player.configure({streaming: {alwaysStreamText: true}});
await player.load(fakeManifestUri, 0, fakeMimeType);
- expect(streamingEngine.getBufferingText()).not.toBe(null);
+ expect(streamingEngine.switchTextStream).toHaveBeenCalled();
+ expect(shaka.test.Util.invokeSpy(streamingEngine.getCurrentTextStream))
+ .not.toBe(null);
+ streamingEngine.switchTextStream.calls.reset();
await player.setTextTrackVisibility(true);
- expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
+ expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
expect(streamingEngine.unloadTextStream).not.toHaveBeenCalled();
await player.setTextTrackVisibility(false);
- expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
+ expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
expect(streamingEngine.unloadTextStream).not.toHaveBeenCalled();
});
});
@@ -655,10 +646,8 @@ describe('Player', () => {
timeline.setStatic(true);
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline = timeline;
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1);
});
});
goog.asserts.assert(manifest, 'manifest must be non-null');
@@ -669,9 +658,8 @@ describe('Player', () => {
});
it('does not switch for plain configuration changes', async () => {
- const switchVariantSpy = spyOn(player, 'switchVariant_');
-
await player.load(fakeManifestUri, 0, fakeMimeType);
+ streamingEngine.switchVariant.calls.reset();
player.configure({abr: {enabled: false}});
player.configure({streaming: {bufferingGoal: 9001}});
@@ -679,7 +667,7 @@ describe('Player', () => {
// Delay to ensure that the switch would have been called.
await shaka.test.Util.shortDelay();
- expect(switchVariantSpy).not.toHaveBeenCalled();
+ expect(streamingEngine.switchVariant).not.toHaveBeenCalled();
});
it('accepts parameters in a (fieldName, value) format', () => {
@@ -800,23 +788,19 @@ describe('Player', () => {
expect(abrManager.chooseVariant).toHaveBeenCalled();
});
- it('does not enable before stream startup', async () => {
+ it('enables automatically', async () => {
await player.load(fakeManifestUri, 0, fakeMimeType);
- expect(abrManager.enable).not.toHaveBeenCalled();
- streamingEngine.onCanSwitch();
expect(abrManager.enable).toHaveBeenCalled();
});
it('does not enable if adaptation is disabled', async () => {
player.configure({abr: {enabled: false}});
await player.load(fakeManifestUri, 0, fakeMimeType);
- streamingEngine.onCanSwitch();
expect(abrManager.enable).not.toHaveBeenCalled();
});
it('enables/disables though configure', async () => {
await player.load(fakeManifestUri, 0, fakeMimeType);
- streamingEngine.onCanSwitch();
abrManager.enable.calls.reset();
abrManager.disable.calls.reset();
@@ -827,16 +811,6 @@ describe('Player', () => {
expect(abrManager.enable).toHaveBeenCalled();
});
- it('waits to enable if in-between Periods', async () => {
- player.configure({abr: {enabled: false}});
- await player.load(fakeManifestUri, 0, fakeMimeType);
- player.configure({abr: {enabled: true}});
- expect(abrManager.enable).not.toHaveBeenCalled();
- // Until onCanSwitch is called, the first period hasn't been set up yet.
- streamingEngine.onCanSwitch();
- expect(abrManager.enable).toHaveBeenCalled();
- });
-
it('reuses AbrManager instance', async () => {
/** @type {!jasmine.Spy} */
const spy =
@@ -875,58 +849,29 @@ describe('Player', () => {
describe('filterTracks', () => {
it('retains only video+audio variants if they exist', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(10, (variant) => {
- variant.addAudio(1);
- });
- period.addVariant(11, (variant) => {
- variant.addAudio(2);
- variant.addVideo(3);
- });
- period.addVariant(12, (variant) => {
- variant.addVideo(4);
- });
+ manifest.addVariant(10, (variant) => {
+ variant.addAudio(1);
});
- manifest.addPeriod(1, (period) => {
- period.addVariant(20, (variant) => {
- variant.addAudio(5);
- });
- period.addVariant(21, (variant) => {
- variant.addVideo(6);
- });
- period.addVariant(22, (variant) => {
- variant.addAudio(7);
- variant.addVideo(8);
- });
+ manifest.addVariant(11, (variant) => {
+ variant.addAudio(2);
+ variant.addVideo(3);
+ });
+ manifest.addVariant(12, (variant) => {
+ variant.addVideo(4);
});
});
- const variantTracks1 = [
+ const variantTracks = [
jasmine.objectContaining({
id: 11,
active: true,
type: 'variant',
}),
];
- const variantTracks2 = [
- jasmine.objectContaining({
- id: 22,
- active: false,
- type: 'variant',
- }),
- ];
await player.load(fakeManifestUri, 0, fakeMimeType);
- // Check the first period's variant tracks.
- const actualVariantTracks1 = player.getVariantTracks();
- expect(actualVariantTracks1).toEqual(variantTracks1);
-
- // Check the second period's variant tracks.
- playhead.getTime.and.callFake(() => {
- return 100;
- });
- const actualVariantTracks2 = player.getVariantTracks();
- expect(actualVariantTracks2).toEqual(variantTracks2);
+ const actualVariantTracks = player.getVariantTracks();
+ expect(actualVariantTracks).toEqual(variantTracks);
});
});
@@ -939,146 +884,119 @@ describe('Player', () => {
beforeEach(async () => {
// A manifest we can use to test track expectations.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(100, (variant) => { // main surround, low res
- variant.bandwidth = 1300;
- variant.language = 'en';
- variant.addVideo(1, (stream) => {
- stream.originalId = 'video-1kbps';
- stream.bandwidth = 1000;
- stream.width = 100;
- stream.height = 200;
- stream.frameRate = 1000000 / 42000;
- stream.pixelAspectRatio = '59:54';
- stream.roles = ['main'];
- });
- variant.addAudio(3, (stream) => {
- stream.originalId = 'audio-en-6c';
- stream.bandwidth = 300;
- stream.channelsCount = 6;
- stream.audioSamplingRate = 48000;
- stream.roles = ['main'];
- });
- });
- period.addVariant(101, (variant) => { // main surround, high res
- variant.bandwidth = 2300;
- variant.language = 'en';
- variant.addVideo(2, (stream) => {
- stream.originalId = 'video-2kbps';
- stream.bandwidth = 2000;
- stream.frameRate = 24;
- stream.pixelAspectRatio = '59:54';
- stream.size(200, 400);
- });
- variant.addExistingStream(3); // audio
- });
- period.addVariant(102, (variant) => { // main stereo, low res
- variant.bandwidth = 1100;
- variant.language = 'en';
- variant.addExistingStream(1); // video
- variant.addAudio(4, (stream) => {
- stream.originalId = 'audio-en-2c';
- stream.bandwidth = 100;
- stream.channelsCount = 2;
- stream.audioSamplingRate = 48000;
- stream.roles = ['main'];
- });
- });
- period.addVariant(103, (variant) => { // main stereo, high res
- variant.bandwidth = 2100;
- variant.language = 'en';
- variant.addExistingStream(2); // video
- variant.addExistingStream(4); // audio
- });
- period.addVariant(104, (variant) => { // commentary stereo, low res
- variant.bandwidth = 1100;
- variant.language = 'en';
- variant.addExistingStream(1); // video
- variant.addAudio(5, (stream) => {
- stream.originalId = 'audio-commentary';
- stream.bandwidth = 100;
- stream.channelsCount = 2;
- stream.audioSamplingRate = 48000;
- stream.roles = ['commentary'];
- });
- });
- period.addVariant(105, (variant) => { // commentary stereo, low res
- variant.bandwidth = 2100;
- variant.language = 'en';
- variant.addExistingStream(2); // video
- variant.addExistingStream(5); // audio
- });
- period.addVariant(106, (variant) => { // spanish stereo, low res
- variant.language = 'es';
- variant.bandwidth = 1100;
- variant.addExistingStream(1); // video
- variant.addAudio(6, (stream) => {
- stream.originalId = 'audio-es';
- stream.bandwidth = 100;
- stream.channelsCount = 2;
- stream.audioSamplingRate = 48000;
- });
- });
- period.addVariant(107, (variant) => { // spanish stereo, high res
- variant.language = 'es';
- variant.bandwidth = 2100;
- variant.addExistingStream(2); // video
- variant.addExistingStream(6); // audio
+ manifest.addVariant(100, (variant) => { // main surround, low res
+ variant.bandwidth = 1300;
+ variant.language = 'en';
+ variant.addVideo(1, (stream) => {
+ stream.originalId = 'video-1kbps';
+ stream.bandwidth = 1000;
+ stream.width = 100;
+ stream.height = 200;
+ stream.frameRate = 1000000 / 42000;
+ stream.pixelAspectRatio = '59:54';
+ stream.roles = ['main'];
});
-
- // All text tracks should remain, even with different MIME types.
- period.addTextStream(50, (stream) => {
- stream.originalId = 'text-es';
- stream.language = 'es';
- stream.label = 'Spanish';
- stream.bandwidth = 10;
- stream.mimeType = 'text/vtt';
- stream.kind = 'caption';
+ variant.addAudio(3, (stream) => {
+ stream.originalId = 'audio-en-6c';
+ stream.bandwidth = 300;
+ stream.channelsCount = 6;
+ stream.audioSamplingRate = 48000;
+ stream.roles = ['main'];
});
- period.addTextStream(51, (stream) => {
- stream.originalId = 'text-en';
- stream.language = 'en';
- stream.label = 'English';
- stream.bandwidth = 10;
- stream.mimeType = 'application/ttml+xml';
- stream.kind = 'caption';
+ });
+ manifest.addVariant(101, (variant) => { // main surround, high res
+ variant.bandwidth = 2300;
+ variant.language = 'en';
+ variant.addVideo(2, (stream) => {
+ stream.originalId = 'video-2kbps';
+ stream.bandwidth = 2000;
+ stream.frameRate = 24;
+ stream.pixelAspectRatio = '59:54';
+ stream.size(200, 400);
+ });
+ variant.addExistingStream(3); // audio
+ });
+ manifest.addVariant(102, (variant) => { // main stereo, low res
+ variant.bandwidth = 1100;
+ variant.language = 'en';
+ variant.addExistingStream(1); // video
+ variant.addAudio(4, (stream) => {
+ stream.originalId = 'audio-en-2c';
+ stream.bandwidth = 100;
+ stream.channelsCount = 2;
+ stream.audioSamplingRate = 48000;
stream.roles = ['main'];
});
- period.addTextStream(52, (stream) => {
- stream.originalId = 'text-commentary';
- stream.language = 'en';
- stream.label = 'English';
- stream.bandwidth = 10;
- stream.mimeType = 'application/ttml+xml';
- stream.kind = 'caption';
+ });
+ manifest.addVariant(103, (variant) => { // main stereo, high res
+ variant.bandwidth = 2100;
+ variant.language = 'en';
+ variant.addExistingStream(2); // video
+ variant.addExistingStream(4); // audio
+ });
+ manifest.addVariant(104, (variant) => { // commentary stereo, low res
+ variant.bandwidth = 1100;
+ variant.language = 'en';
+ variant.addExistingStream(1); // video
+ variant.addAudio(5, (stream) => {
+ stream.originalId = 'audio-commentary';
+ stream.bandwidth = 100;
+ stream.channelsCount = 2;
+ stream.audioSamplingRate = 48000;
stream.roles = ['commentary'];
});
});
- manifest.addPeriod(1, (period) => {
- period.addVariant(200, (variant) => {
- variant.bandwidth = 1100;
- variant.language = 'en';
- variant.addVideo(10, (stream) => {
- stream.bandwidth = 1000;
- stream.size(100, 200);
- });
- variant.addAudio(11, (stream) => {
- stream.bandwidth = 100;
- stream.channelsCount = 2;
- stream.audioSamplingRate = 48000;
- });
- });
- period.addVariant(201, (variant) => {
- variant.bandwidth = 1300;
- variant.language = 'en';
- variant.addExistingStream(10); // video
- variant.addAudio(12, (stream) => {
- stream.bandwidth = 300;
- stream.channelsCount = 6;
- stream.audioSamplingRate = 48000;
- });
+ manifest.addVariant(105, (variant) => { // commentary stereo, low res
+ variant.bandwidth = 2100;
+ variant.language = 'en';
+ variant.addExistingStream(2); // video
+ variant.addExistingStream(5); // audio
+ });
+ manifest.addVariant(106, (variant) => { // spanish stereo, low res
+ variant.language = 'es';
+ variant.bandwidth = 1100;
+ variant.addExistingStream(1); // video
+ variant.addAudio(6, (stream) => {
+ stream.originalId = 'audio-es';
+ stream.bandwidth = 100;
+ stream.channelsCount = 2;
+ stream.audioSamplingRate = 48000;
});
});
+ manifest.addVariant(107, (variant) => { // spanish stereo, high res
+ variant.language = 'es';
+ variant.bandwidth = 2100;
+ variant.addExistingStream(2); // video
+ variant.addExistingStream(6); // audio
+ });
+
+ // All text tracks should remain, even with different MIME types.
+ manifest.addTextStream(50, (stream) => {
+ stream.originalId = 'text-es';
+ stream.language = 'es';
+ stream.label = 'Spanish';
+ stream.bandwidth = 10;
+ stream.mimeType = 'text/vtt';
+ stream.kind = 'caption';
+ });
+ manifest.addTextStream(51, (stream) => {
+ stream.originalId = 'text-en';
+ stream.language = 'en';
+ stream.label = 'English';
+ stream.bandwidth = 10;
+ stream.mimeType = 'application/ttml+xml';
+ stream.kind = 'caption';
+ stream.roles = ['main'];
+ });
+ manifest.addTextStream(52, (stream) => {
+ stream.originalId = 'text-commentary';
+ stream.language = 'en';
+ stream.label = 'English';
+ stream.bandwidth = 10;
+ stream.mimeType = 'application/ttml+xml';
+ stream.kind = 'caption';
+ stream.roles = ['commentary'];
+ });
});
variantTracks = [
@@ -1417,17 +1335,18 @@ describe('Player', () => {
});
await player.load(fakeManifestUri, 0, fakeMimeType);
+ streamingEngine.switchVariant.calls.reset();
+ streamingEngine.switchTextStream.calls.reset();
});
it('returns the correct tracks', () => {
- streamingEngine.onCanSwitch();
-
expect(player.getVariantTracks()).toEqual(variantTracks);
expect(player.getTextTracks()).toEqual(textTracks);
});
it('returns empty arrays before tracks can be determined', async () => {
const parser = new shaka.test.FakeManifestParser(manifest);
+
parser.start.and.callFake((manifestUri, playerInterface) => {
// The player does not yet have a manifest.
expect(player.getVariantTracks()).toEqual([]);
@@ -1436,27 +1355,14 @@ describe('Player', () => {
parser.playerInterface = playerInterface;
return Promise.resolve(manifest);
});
- drmEngine.initForPlayback.and.callFake(() => {
- // The player does not yet have a playhead.
- expect(player.getVariantTracks()).toEqual([]);
- expect(player.getTextTracks()).toEqual([]);
-
- return Promise.resolve();
- });
- shaka.media.ManifestParser.registerParserByMime(
- fakeMimeType, () => parser);
await player.load(fakeManifestUri, 0, fakeMimeType);
- // Make sure the interruptions didn't mess up the tracks.
- streamingEngine.onCanSwitch();
expect(player.getVariantTracks()).toEqual(variantTracks);
expect(player.getTextTracks()).toEqual(textTracks);
});
it('doesn\'t disable AbrManager if switching variants', () => {
- streamingEngine.onCanSwitch();
-
let config = player.getConfiguration();
expect(config.abr.enabled).toBe(true);
@@ -1468,8 +1374,6 @@ describe('Player', () => {
});
it('doesn\'t disable AbrManager if switching text', () => {
- streamingEngine.onCanSwitch();
-
let config = player.getConfiguration();
expect(config.abr.enabled).toBe(true);
@@ -1481,59 +1385,15 @@ describe('Player', () => {
});
it('switches streams', () => {
- streamingEngine.onCanSwitch();
-
- const newTrack = player.getVariantTracks().filter((t) => !t.active)[0];
- player.selectVariantTrack(newTrack);
-
- expect(streamingEngine.switchVariant).toHaveBeenCalled();
- const variant = streamingEngine.switchVariant.calls.argsFor(0)[0];
- expect(variant.id).toBe(newTrack.id);
- });
-
- it('still switches streams if called during startup', () => {
- // startup is not complete until onCanSwitch is called.
-
- // pick a track
- const newTrack = player.getVariantTracks().filter((t) => !t.active)[0];
- // ask the player to switch to it
- player.selectVariantTrack(newTrack);
- // nothing happens yet
- expect(streamingEngine.switchVariant).not.toHaveBeenCalled();
-
- // after startup is complete, the manual selection takes effect.
- streamingEngine.onCanSwitch();
- expect(streamingEngine.switchVariant).toHaveBeenCalled();
- const variant = streamingEngine.switchVariant.calls.argsFor(0)[0];
- expect(variant.id).toBe(newTrack.id);
- });
-
- it('still switches streams if called while switching Periods', () => {
- // startup is complete after onCanSwitch.
- streamingEngine.onCanSwitch();
-
- // startup doesn't call switchVariant
- expect(streamingEngine.switchVariant).not.toHaveBeenCalled();
-
- // pick a track
const newTrack = player.getVariantTracks().filter((t) => !t.active)[0];
-
- // simulate the transition to period 1
- transitionPeriod(1);
-
- // select the new track (from period 0, which is fine)
player.selectVariantTrack(newTrack);
- expect(streamingEngine.switchVariant).not.toHaveBeenCalled();
- // after transition is completed by onCanSwitch, switchVariant is called
- streamingEngine.onCanSwitch();
expect(streamingEngine.switchVariant).toHaveBeenCalled();
const variant = streamingEngine.switchVariant.calls.argsFor(0)[0];
expect(variant.id).toBe(newTrack.id);
});
it('switching audio doesn\'t change selected text track', () => {
- streamingEngine.onCanSwitch();
player.configure({
preferredTextLanguage: 'es',
});
@@ -1542,7 +1402,6 @@ describe('Player', () => {
const englishTextTrack =
player.getTextTracks().filter((t) => t.language == 'en')[0];
- streamingEngine.switchTextStream.calls.reset();
player.selectTextTrack(englishTextTrack);
expect(streamingEngine.switchTextStream).toHaveBeenCalled();
// We have selected an English text track explicitly.
@@ -1559,13 +1418,10 @@ describe('Player', () => {
it('selectAudioLanguage() takes precedence over ' +
'preferredAudioLanguage', () => {
- streamingEngine.onCanSwitch();
-
// This preference is set in beforeEach, before load().
expect(player.getConfiguration().preferredAudioLanguage).toBe('en');
expect(getActiveVariantTrack().language).toBe('en');
- streamingEngine.switchVariant.calls.reset();
player.selectAudioLanguage('es');
expect(streamingEngine.switchVariant).toHaveBeenCalled();
@@ -1576,10 +1432,8 @@ describe('Player', () => {
});
it('selectAudioLanguage() respects selected role', () => {
- streamingEngine.onCanSwitch();
expect(getActiveVariantTrack().roles).not.toContain('commentary');
- streamingEngine.switchVariant.calls.reset();
player.selectAudioLanguage('en', 'commentary');
expect(streamingEngine.switchVariant).toHaveBeenCalled();
@@ -1590,7 +1444,6 @@ describe('Player', () => {
});
it('selectAudioLanguage() applies role only to audio', () => {
- streamingEngine.onCanSwitch();
expect(getActiveVariantTrack().roles).not.toContain('commentary');
player.selectAudioLanguage('en', 'commentary');
let args = streamingEngine.switchVariant.calls.argsFor(0);
@@ -1635,13 +1488,10 @@ describe('Player', () => {
it('selectTextLanguage() takes precedence over ' +
'preferredTextLanguage', () => {
- streamingEngine.onCanSwitch();
-
// This preference is set in beforeEach, before load().
expect(player.getConfiguration().preferredTextLanguage).toBe('es');
expect(getActiveTextTrack().language).toBe('es');
- streamingEngine.switchTextStream.calls.reset();
player.selectTextLanguage('en');
expect(streamingEngine.switchTextStream).toHaveBeenCalled();
@@ -1651,10 +1501,8 @@ describe('Player', () => {
});
it('selectTextLanguage() respects selected role', () => {
- streamingEngine.onCanSwitch();
expect(getActiveTextTrack().roles).not.toContain('commentary');
- streamingEngine.switchTextStream.calls.reset();
player.selectTextLanguage('en', 'commentary');
expect(streamingEngine.switchTextStream).toHaveBeenCalled();
@@ -1664,8 +1512,6 @@ describe('Player', () => {
});
it('changing current audio language changes active stream', () => {
- streamingEngine.onCanSwitch();
-
expect(getActiveVariantTrack().language).not.toBe('es');
expect(streamingEngine.switchVariant).not.toHaveBeenCalled();
player.selectAudioLanguage('es');
@@ -1678,8 +1524,6 @@ describe('Player', () => {
});
it('changing current text language changes active stream', () => {
- streamingEngine.onCanSwitch();
-
expect(getActiveTextTrack().language).not.toBe('en');
expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
player.selectTextLanguage('en');
@@ -1692,20 +1536,18 @@ describe('Player', () => {
// https://github.com/google/shaka-player/issues/2010
it('changing text lang changes active stream when not streaming', () => {
- streamingEngine.onCanSwitch();
player.setTextTrackVisibility(false);
- expect(getActiveTextTrack().language).not.toBe('en');
+ expect(getActiveTextTrack()).toBe(null);
expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
player.selectTextLanguage('en');
+ player.setTextTrackVisibility(true);
- expect(streamingEngine.switchTextStream).not.toHaveBeenCalled();
+ expect(streamingEngine.switchTextStream).toHaveBeenCalled();
expect(getActiveTextTrack().language).toBe('en');
});
it('remembers the channel count when ABR is reenabled', () => {
- streamingEngine.onCanSwitch();
-
// We prefer 6 channels, and we are currently playing 6 channels.
expect(player.getConfiguration().preferredAudioChannelCount).toBe(6);
expect(getActiveVariantTrack().channelsCount).toBe(6);
@@ -1741,15 +1583,13 @@ describe('Player', () => {
// Simulate an encrypted stream. Mark half of the audio streams with key
// ID 'aaa', and the other half with 'bbb'. Remove all roles, so that our
// choices are limited only by channel count and key status.
- for (const variant of manifest.periods[0].variants) {
+ for (const variant of manifest.variants) {
const keyId = (variant.audio.id % 2) ? 'aaa' : 'bbb';
variant.audio.keyId = keyId;
variant.video.roles = [];
variant.audio.roles = [];
}
- streamingEngine.onCanSwitch();
-
// We prefer 6 channels, and we are currently playing 6 channels.
expect(player.getConfiguration().preferredAudioChannelCount).toBe(6);
expect(getActiveVariantTrack().channelsCount).toBe(6);
@@ -1783,6 +1623,98 @@ describe('Player', () => {
// See that we are still playing a 2-channel track.
expect(getActiveVariantTrack().channelsCount).toBe(2);
});
+
+ describe('only fires change event when something changes', () => {
+ /** @type {jasmine.Spy} */
+ let textChanged;
+
+ /** @type {jasmine.Spy} */
+ let variantChanged;
+
+ beforeEach(() => {
+ textChanged = jasmine.createSpy('textChanged');
+ player.addEventListener('textchanged', Util.spyFunc(textChanged));
+
+ variantChanged = jasmine.createSpy('variantChanged');
+ player.addEventListener('variantchanged', Util.spyFunc(variantChanged));
+ });
+
+ it('in selectTextTrack', async () => {
+ // Any text track we're not already streaming.
+ const newTrack = player.getTextTracks().filter((t) => !t.active)[0];
+
+ // Call selectTextTrack with a new track. Expect an event to fire.
+ player.selectTextTrack(newTrack);
+ await shaka.test.Util.shortDelay();
+ expect(textChanged).toHaveBeenCalled();
+ textChanged.calls.reset();
+
+ // Call again with the same track, and expect no event to fire, since
+ // nothing changed this time.
+ player.selectTextTrack(newTrack);
+ await shaka.test.Util.shortDelay();
+ expect(textChanged).not.toHaveBeenCalled();
+ });
+
+ it('in selectVariantTrack', async () => {
+ // Any variant track we're not already streaming.
+ const newTrack = player.getVariantTracks().filter((t) => !t.active)[0];
+
+ // Call selectVariantTrack with a new track. Expect an event to fire.
+ player.selectVariantTrack(newTrack);
+ await shaka.test.Util.shortDelay();
+ expect(variantChanged).toHaveBeenCalled();
+ variantChanged.calls.reset();
+
+ // Call again with the same track, and expect no event to fire, since
+ // nothing changed this time.
+ player.selectVariantTrack(newTrack);
+ await shaka.test.Util.shortDelay();
+ expect(variantChanged).not.toHaveBeenCalled();
+ });
+
+ it('in selectTextLanguage', async () => {
+ // The current text language.
+ const currentLanguage = player.getTextTracks()
+ .filter((t) => t.active)[0].language;
+ const newLanguage = player.getTextTracks()
+ .filter((t) => t.language != currentLanguage)[0].language;
+
+ // Call selectTextLanguage with a new language. Expect an event to
+ // fire.
+ player.selectTextLanguage(newLanguage);
+ await shaka.test.Util.shortDelay();
+ expect(textChanged).toHaveBeenCalled();
+ textChanged.calls.reset();
+
+ // Call again with the same language, and expect no event to fire,
+ // since nothing changed this time.
+ player.selectTextLanguage(newLanguage);
+ await shaka.test.Util.shortDelay();
+ expect(textChanged).not.toHaveBeenCalled();
+ });
+
+ it('in selectAudioLanguage', async () => {
+ // The current audio language.
+ const currentLanguage = player.getVariantTracks()
+ .filter((t) => t.active)[0].language;
+ const newLanguage = player.getVariantTracks()
+ .filter((t) => t.language != currentLanguage)[0].language;
+
+ // Call selectAudioLanguage with a new language. Expect an event to
+ // fire.
+ player.selectAudioLanguage(newLanguage);
+ await shaka.test.Util.shortDelay();
+ expect(variantChanged).toHaveBeenCalled();
+ variantChanged.calls.reset();
+
+ // Call again with the same language, and expect no event to fire,
+ // since nothing changed this time.
+ player.selectAudioLanguage(newLanguage);
+ await shaka.test.Util.shortDelay();
+ expect(variantChanged).not.toHaveBeenCalled();
+ });
+ });
}); // describe('tracks')
describe('languages', () => {
@@ -1813,21 +1745,19 @@ describe('Player', () => {
it('enables text if its language differs from audio at start', async () => {
// A manifest we can use to test text visibility.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.language = 'pt';
- variant.addAudio(0);
- });
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.addAudio(1);
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'pt';
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'fr';
- });
+ manifest.addVariant(0, (variant) => {
+ variant.language = 'pt';
+ variant.addAudio(0);
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(1);
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'pt';
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'fr';
});
});
@@ -1858,15 +1788,13 @@ describe('Player', () => {
// The Player shouldn't allow changing between languages, so it should
// choose an arbitrary language when none is given.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.language = 'pt';
- variant.addAudio(0);
- });
- period.addVariant(1, (variant) => {
- variant.language = 'en';
- variant.addAudio(1);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.language = 'pt';
+ variant.addAudio(0);
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'en';
+ variant.addAudio(1);
});
});
@@ -1893,23 +1821,21 @@ describe('Player', () => {
async function runTest(languages, preference, expectedIndex) {
// A manifest we can use to test language selection.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- const enumerate = (it) => shaka.util.Iterables.enumerate(it);
- for (const {i, item: lang} of enumerate(languages)) {
- if (lang.charAt(0) == '*') {
- period.addVariant(i, (variant) => {
- variant.primary = true;
- variant.language = lang.substr(1);
- variant.addAudio(i);
- });
- } else {
- period.addVariant(i, (variant) => {
- variant.language = lang;
- variant.addAudio(i);
- });
- }
+ const enumerate = (it) => shaka.util.Iterables.enumerate(it);
+ for (const {i, item: lang} of enumerate(languages)) {
+ if (lang.charAt(0) == '*') {
+ manifest.addVariant(i, (variant) => {
+ variant.primary = true;
+ variant.language = lang.substr(1);
+ variant.addAudio(i);
+ });
+ } else {
+ manifest.addVariant(i, (variant) => {
+ variant.language = lang;
+ variant.addAudio(i);
+ });
}
- });
+ }
});
// Set the user preferences, which must happen before load().
@@ -1920,7 +1846,7 @@ describe('Player', () => {
await player.load(fakeManifestUri, 0, fakeMimeType);
expect(getActiveVariantTrack().id).toBe(expectedIndex);
}
- });
+ }); // describe('languages')
describe('getStats', () => {
const oldDateNow = Date.now;
@@ -1934,44 +1860,39 @@ describe('Player', () => {
// A manifest we can use to test stats.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 200;
- variant.addAudio(1, (stream) => {
- stream.bandwidth = 100;
- });
- variant.addVideo(2, (stream) => {
- stream.bandwidth = 100;
- stream.size(100, 200);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 200;
+ variant.addAudio(1, (stream) => {
+ stream.bandwidth = 100;
});
- period.addVariant(1, (variant) => {
- variant.bandwidth = 300;
- variant.addExistingStream(1); // audio
- variant.addVideo(3, (stream) => {
- stream.bandwidth = 200;
- stream.size(200, 400);
- });
+ variant.addVideo(2, (stream) => {
+ stream.bandwidth = 100;
+ stream.size(100, 200);
});
- period.addVariant(2, (variant) => {
- variant.bandwidth = 300;
- variant.addAudio(4, (stream) => {
- stream.bandwidth = 200;
- });
- variant.addExistingStream(2); // video
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 300;
+ variant.addExistingStream(1); // audio
+ variant.addVideo(3, (stream) => {
+ stream.bandwidth = 200;
+ stream.size(200, 400);
});
- period.addVariant(3, (variant) => {
- variant.bandwidth = 400;
- variant.addExistingStream(4); // audio
- variant.addExistingStream(3); // video
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 300;
+ variant.addAudio(4, (stream) => {
+ stream.bandwidth = 200;
});
+ variant.addExistingStream(2); // video
+ });
+ manifest.addVariant(3, (variant) => {
+ variant.bandwidth = 400;
+ variant.addExistingStream(4); // audio
+ variant.addExistingStream(3); // video
});
});
await player.load(fakeManifestUri, 0, fakeMimeType);
-
- // Initialize the fake streams.
- streamingEngine.onCanSwitch();
});
afterEach(() => {
@@ -2069,7 +1990,7 @@ describe('Player', () => {
it('includes selectVariantTrack choices', () => {
const track = player.getVariantTracks()[3];
- const variants = manifest.periods[0].variants;
+ const variants = manifest.variants;
const variant = variants.find((variant) => variant.id == track.id);
player.selectVariantTrack(track);
@@ -2085,7 +2006,7 @@ describe('Player', () => {
});
it('includes adaptation choices', () => {
- const variant = manifest.periods[0].variants[3];
+ const variant = manifest.variants[3];
switch_(variant);
checkHistory(jasmine.arrayContaining([
@@ -2247,233 +2168,85 @@ describe('Player', () => {
}
});
- describe('unplayable periods', () => {
- beforeEach(() => {
- // overriding for good / bad codecs.
- window.MediaSource.isTypeSupported =
- (mimeType) => mimeType.includes('good');
- });
+ describe('unplayable content', () => {
+ it('throws CONTENT_UNSUPPORTED_BY_BROWSER', async () => {
+ window.MediaSource.isTypeSupported = (mimeType) => false;
- it('success when one period is playable', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'good');
- });
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(0);
});
});
- await player.load(fakeManifestUri, 0, fakeMimeType);
+ const expected = Util.jasmineError(new shaka.util.Error(
+ shaka.util.Error.Severity.CRITICAL,
+ shaka.util.Error.Category.MANIFEST,
+ shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER));
+ const load = player.load(fakeManifestUri, 0, fakeMimeType);
+ await expectAsync(load).toBeRejectedWith(expected);
});
+ });
- it('success when all periods are playable', async () => {
+ describe('restrictions', () => {
+ it('switches if active is restricted by application', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'good');
- });
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 500;
+ variant.addVideo(1);
});
- manifest.addPeriod(1, (period) => {
- period.addVariant(1, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.mime('video/mp4', 'good');
- });
- });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 100;
+ variant.addVideo(2);
});
});
await player.load(fakeManifestUri, 0, fakeMimeType);
+ let activeVariant = getActiveVariantTrack();
+ expect(activeVariant.id).toBe(0);
+
+ // Ask AbrManager to choose the 0th variant from those it is given.
+ abrManager.chooseIndex = 0;
+ abrManager.chooseVariant.calls.reset();
+
+ // This restriction should make it so that the first variant (bandwidth
+ // 500, id 0) cannot be selected.
+ player.configure({
+ restrictions: {maxBandwidth: 200},
+ });
+
+ // The restriction change should trigger a call to AbrManager.
+ expect(abrManager.chooseVariant).toHaveBeenCalled();
+
+ // The first variant is disallowed.
+ expect(manifest.variants[0].id).toBe(0);
+ expect(manifest.variants[0].allowedByApplication)
+ .toBe(false);
+
+ // AbrManager chose the second variant (id 1).
+ activeVariant = getActiveVariantTrack();
+ expect(activeVariant.id).toBe(1);
});
- it('throw UNPLAYABLE_PERIOD when some periods are unplayable', async () => {
+ it('updates AbrManager for restriction changes', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'good');
- });
- });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 500;
+ variant.addVideo(10);
});
- manifest.addPeriod(1, (period) => {
- period.addVariant(1, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.mime('video/mp4', 'bad');
- });
- });
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 100;
+ variant.addVideo(20);
});
});
- const expected = Util.jasmineError(new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.UNPLAYABLE_PERIOD));
- const load = player.load(fakeManifestUri, 0, fakeMimeType);
- await expectAsync(load).toBeRejectedWith(expected);
- });
-
- it('throw CONTENT_UNSUPPORTED_BY_BROWSER when the only period is ' +
- 'unplayable', async () => {
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'bad');
- });
- });
- });
- });
-
- const expected = Util.jasmineError(new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER));
- const load = player.load(fakeManifestUri, 0, fakeMimeType);
- await expectAsync(load).toBeRejectedWith(expected);
- });
-
- it('throw CONTENT_UNSUPPORTED_BY_BROWSER when all periods are unplayable',
- async () => {
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'bad');
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.mime('video/mp4', 'bad');
- });
- });
- });
- manifest.addPeriod(1, (period) => {
- period.addVariant(2, (variant) => {
- variant.addVideo(2, (stream) => {
- stream.mime('video/mp4', 'bad');
- });
- });
- });
- });
-
- const expected = Util.jasmineError(new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER));
- const load = player.load(fakeManifestUri, 0, fakeMimeType);
- await expectAsync(load).toBeRejectedWith(expected);
- });
-
- it('throw UNPLAYABLE_PERIOD when the new period unplayable', async () => {
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'good');
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.mime('video/mp4', 'good');
- });
- });
- });
- });
-
- /** @type {!shaka.test.FakeManifestParser} */
- const parser = new shaka.test.FakeManifestParser(manifest);
- shaka.media.ManifestParser.registerParserByMime(
- fakeMimeType, () => parser);
await player.load(fakeManifestUri, 0, fakeMimeType);
-
- const manifest2 = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(10, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0, (stream) => {
- stream.mime('video/mp4', 'bad');
- });
- });
- });
- });
- manifest.periods.push(manifest2.periods[0]);
-
- const expected = Util.jasmineError(new shaka.util.Error(
- shaka.util.Error.Severity.CRITICAL,
- shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.UNPLAYABLE_PERIOD));
- const test =
- () => parser.playerInterface.filterNewPeriod(manifest2.periods[0]);
- expect(test).toThrow(expected);
- });
- });
-
- describe('restrictions', () => {
- it('switches if active is restricted by application', async () => {
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 500;
- variant.addVideo(1);
- });
- period.addVariant(1, (variant) => {
- variant.bandwidth = 100;
- variant.addVideo(2);
- });
- });
- });
-
- await setupPlayer();
- let activeVariant = getActiveVariantTrack();
- expect(activeVariant.id).toBe(0);
-
- // Ask AbrManager to choose the 0th variant from those it is given.
- abrManager.chooseIndex = 0;
- abrManager.chooseVariant.calls.reset();
-
- // This restriction should make it so that the first variant (bandwidth
- // 500, id 0) cannot be selected.
- player.configure({
- restrictions: {maxBandwidth: 200},
- });
-
- // The restriction change should trigger a call to AbrManager.
- expect(abrManager.chooseVariant).toHaveBeenCalled();
-
- // The first variant is disallowed.
- expect(manifest.periods[0].variants[0].id).toBe(0);
- expect(manifest.periods[0].variants[0].allowedByApplication)
- .toBe(false);
-
- // AbrManager chose the second variant (id 1).
- activeVariant = getActiveVariantTrack();
- expect(activeVariant.id).toBe(1);
- });
-
- it('updates AbrManager for restriction changes', async () => {
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.bandwidth = 500;
- variant.addVideo(10);
- });
- period.addVariant(2, (variant) => {
- variant.bandwidth = 100;
- variant.addVideo(20);
- });
- });
- });
-
- await setupPlayer();
abrManager.setVariants.calls.reset();
player.configure({restrictions: {maxBandwidth: 200}});
// AbrManager should have been updated with the restricted tracks.
// The first variant is disallowed.
- expect(abrManager.setVariants).toHaveBeenCalledTimes(1);
+ expect(abrManager.setVariants).toHaveBeenCalled();
const variants = abrManager.setVariants.calls.argsFor(0)[0];
expect(variants.length).toBe(1);
expect(variants[0].id).toBe(2);
@@ -2489,19 +2262,17 @@ describe('Player', () => {
it('switches if active key status is "output-restricted"', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
let activeVariant = getActiveVariantTrack();
expect(activeVariant.id).toBe(0);
@@ -2513,9 +2284,8 @@ describe('Player', () => {
expect(abrManager.chooseVariant).toHaveBeenCalled();
// The first variant is disallowed.
- expect(manifest.periods[0].variants[0].id).toBe(0);
- expect(manifest.periods[0].variants[0].allowedByKeySystem)
- .toBe(false);
+ expect(manifest.variants[0].id).toBe(0);
+ expect(manifest.variants[0].allowedByKeySystem).toBe(false);
// The second variant was chosen.
activeVariant = getActiveVariantTrack();
@@ -2524,19 +2294,17 @@ describe('Player', () => {
it('switches if active key status is "internal-error"', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
let activeVariant = getActiveVariantTrack();
expect(activeVariant.id).toBe(0);
@@ -2546,9 +2314,8 @@ describe('Player', () => {
abrManager.chooseVariant.calls.reset();
onKeyStatus({'abc': 'internal-error'});
expect(abrManager.chooseVariant).toHaveBeenCalled();
- expect(manifest.periods[0].variants[0].id).toBe(0);
- expect(manifest.periods[0].variants[0].allowedByKeySystem)
- .toBe(false);
+ expect(manifest.variants[0].id).toBe(0);
+ expect(manifest.variants[0].allowedByKeySystem).toBe(false);
activeVariant = getActiveVariantTrack();
expect(activeVariant.id).toBe(1);
@@ -2556,19 +2323,17 @@ describe('Player', () => {
it('doesn\'t switch if the active stream isn\'t restricted', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
abrManager.chooseVariant.calls.reset();
let activeVariant = getActiveVariantTrack();
@@ -2583,19 +2348,17 @@ describe('Player', () => {
it('removes if key status is "output-restricted"', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
onKeyStatus({'abc': 'output-restricted'});
@@ -2607,19 +2370,17 @@ describe('Player', () => {
it('removes if key status is "internal-error"', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(1, (variant) => {
- variant.addVideo(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
onKeyStatus({'abc': 'internal-error'});
@@ -2631,19 +2392,17 @@ describe('Player', () => {
it('removes if we don\'t have the required key', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(2, (variant) => {
- variant.addVideo(3);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
// We have some key statuses, but not for the key IDs we know.
@@ -2654,53 +2413,46 @@ describe('Player', () => {
expect(tracks[0].id).toBe(2);
});
- // https://github.com/google/shaka-player/issues/2135
- it('updates key statuses for multi-Period content', async () => {
+ it('updates key statuses for multi-key content', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
- manifest.addPeriod(10, (period) => {
- period.addVariant(2, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.keyId = 'abc';
- });
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.keyIds = ['def'];
});
- period.addVariant(4, (variant) => {
- variant.addVideo(5, (stream) => {
- stream.keyId = 'def';
- });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.addVideo(5, (stream) => {
+ stream.keyIds = ['abc', 'def'];
});
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
onKeyStatus({'abc': 'usable'});
- expect(manifest.periods[0].variants[0].allowedByKeySystem).toBe(true);
- expect(manifest.periods[1].variants[0].allowedByKeySystem).toBe(true);
- expect(manifest.periods[1].variants[1].allowedByKeySystem).toBe(false);
+ expect(manifest.variants[0].allowedByKeySystem).toBe(true);
+ expect(manifest.variants[1].allowedByKeySystem).toBe(false);
+ expect(manifest.variants[2].allowedByKeySystem).toBe(false);
});
it('does not restrict if no key statuses are available', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(2, (variant) => {
- variant.addVideo(3);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
// This simulates, for example, the lack of key status on Chromecast
@@ -2713,19 +2465,17 @@ describe('Player', () => {
it('doesn\'t remove when using synthetic key status', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(2, (variant) => {
- variant.addVideo(3);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
// A synthetic key status contains a single key status with key '00'.
@@ -2737,24 +2487,22 @@ describe('Player', () => {
it('removes all encrypted tracks for errors with synthetic key status',
async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(2, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.keyId = 'xyz';
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
- period.addVariant(4, (variant) => {
- variant.addVideo(5);
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.keyIds = ['xyz'];
});
});
+ manifest.addVariant(4, (variant) => {
+ variant.addVideo(5);
+ });
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(3);
// A synthetic key status contains a single key status with key '00'.
@@ -2767,19 +2515,17 @@ describe('Player', () => {
it('removes if key system does not support codec', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addDrmInfo('foo.bar');
- variant.addVideo(1, (stream) => {
- stream.encrypted = true;
- stream.mimeType = 'video/unsupported';
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addDrmInfo('foo.bar');
+ variant.addVideo(1, (stream) => {
+ stream.encrypted = true;
+ stream.mimeType = 'video/unsupported';
});
- period.addVariant(1, (variant) => {
- variant.addDrmInfo('foo.bar');
- variant.addVideo(2, (stream) => {
- stream.encrypted = true;
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addDrmInfo('foo.bar');
+ variant.addVideo(2, (stream) => {
+ stream.encrypted = true;
});
});
});
@@ -2795,7 +2541,7 @@ describe('Player', () => {
return variant.video.mimeType != 'video/unsupported';
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
const tracks = player.getVariantTracks();
expect(tracks.length).toBe(1);
expect(tracks[0].id).toBe(1);
@@ -2803,23 +2549,21 @@ describe('Player', () => {
it('removes based on bandwidth', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 10;
- variant.addVideo(1);
- });
- period.addVariant(1, (variant) => {
- variant.bandwidth = 1500;
- variant.addVideo(2);
- });
- period.addVariant(2, (variant) => {
- variant.bandwidth = 500;
- variant.addVideo(3);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 10;
+ variant.addVideo(1);
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 1500;
+ variant.addVideo(2);
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 500;
+ variant.addVideo(3);
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(3);
player.configure({restrictions: {minBandwidth: 100, maxBandwidth: 1000}});
@@ -2831,26 +2575,24 @@ describe('Player', () => {
it('removes based on pixels', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.size(900, 900);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.size(900, 900);
});
- period.addVariant(1, (variant) => {
- variant.addVideo(2, (stream) => {
- stream.size(5, 5);
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2, (stream) => {
+ stream.size(5, 5);
});
- period.addVariant(2, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.size(190, 190);
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.size(190, 190);
});
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(3);
player.configure({restrictions: {minPixels: 100, maxPixels: 800 * 800}});
@@ -2862,26 +2604,24 @@ describe('Player', () => {
it('removes based on width', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.size(5, 5);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.size(5, 5);
});
- period.addVariant(1, (variant) => {
- variant.addVideo(2, (stream) => {
- stream.size(1500, 200);
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2, (stream) => {
+ stream.size(1500, 200);
});
- period.addVariant(2, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.size(190, 190);
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.size(190, 190);
});
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(3);
player.configure({restrictions: {minWidth: 100, maxWidth: 1000}});
@@ -2893,26 +2633,26 @@ describe('Player', () => {
it('removes based on height', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.size(5, 5);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.size(5, 5);
});
- period.addVariant(1, (variant) => {
- variant.addVideo(2, (stream) => {
- stream.size(200, 1500);
- });
+ });
+
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2, (stream) => {
+ stream.size(200, 1500);
});
- period.addVariant(2, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.size(190, 190);
- });
+ });
+
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.size(190, 190);
});
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(3);
player.configure({restrictions: {minHeight: 100, maxHeight: 1000}});
@@ -2924,23 +2664,22 @@ describe('Player', () => {
it('removes the whole variant if one stream is restricted', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.size(5, 5);
- });
- variant.addAudio(2);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.size(5, 5);
});
- period.addVariant(1, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.size(190, 190);
- });
- variant.addAudio(4);
+ variant.addAudio(2);
+ });
+
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.size(190, 190);
});
+ variant.addAudio(4);
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
player.configure({restrictions: {minHeight: 100, maxHeight: 1000}});
@@ -2952,26 +2691,26 @@ describe('Player', () => {
it('issues error if no streams are playable', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.size(5, 5);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.size(5, 5);
});
- period.addVariant(1, (variant) => {
- variant.addVideo(2, (stream) => {
- stream.size(200, 300);
- });
+ });
+
+ manifest.addVariant(1, (variant) => {
+ variant.addVideo(2, (stream) => {
+ stream.size(200, 300);
});
- period.addVariant(2, (variant) => {
- variant.addVideo(3, (stream) => {
- stream.size(190, 190);
- });
+ });
+
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3, (stream) => {
+ stream.size(190, 190);
});
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(3);
onError.and.callFake((e) => {
@@ -2994,50 +2733,48 @@ describe('Player', () => {
it('chooses efficient codecs and removes the rest', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- // More efficient codecs
- period.addVariant(0, (variant) => {
- variant.bandwidth = 100;
- variant.addVideo(0, (stream) => {
- stream.codecs = 'good';
- });
+ // More efficient codecs
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 100;
+ variant.addVideo(0, (stream) => {
+ stream.codecs = 'good';
});
- period.addVariant(1, (variant) => {
- variant.bandwidth = 200;
- variant.addVideo(1, (stream) => {
- stream.codecs = 'good';
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 200;
+ variant.addVideo(1, (stream) => {
+ stream.codecs = 'good';
});
- period.addVariant(2, (variant) => {
- variant.bandwidth = 300;
- variant.addVideo(2, (stream) => {
- stream.codecs = 'good';
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 300;
+ variant.addVideo(2, (stream) => {
+ stream.codecs = 'good';
});
+ });
- // Less efficient codecs
- period.addVariant(3, (variant) => {
- variant.bandwidth = 10000;
- variant.addVideo(3, (stream) => {
- stream.codecs = 'bad';
- });
+ // Less efficient codecs
+ manifest.addVariant(3, (variant) => {
+ variant.bandwidth = 10000;
+ variant.addVideo(3, (stream) => {
+ stream.codecs = 'bad';
});
- period.addVariant(4, (variant) => {
- variant.bandwidth = 20000;
- variant.addVideo(4, (stream) => {
- stream.codecs = 'bad';
- });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.bandwidth = 20000;
+ variant.addVideo(4, (stream) => {
+ stream.codecs = 'bad';
});
- period.addVariant(5, (variant) => {
- variant.bandwidth = 30000;
- variant.addVideo(5, (stream) => {
- stream.codecs = 'bad';
- });
+ });
+ manifest.addVariant(5, (variant) => {
+ variant.bandwidth = 30000;
+ variant.addVideo(5, (stream) => {
+ stream.codecs = 'bad';
});
});
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(abrManager.setVariants).toHaveBeenCalled();
const variants = abrManager.setVariants.calls.argsFor(0)[0];
// We've already chosen codecs, so only 3 tracks should remain.
@@ -3050,22 +2787,20 @@ describe('Player', () => {
it('updates AbrManager about restricted variants', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.keyId = 'abc';
- });
- });
- period.addVariant(2, (variant) => {
- variant.addVideo(3);
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.keyIds = ['abc'];
});
});
+ manifest.addVariant(2, (variant) => {
+ variant.addVideo(3);
+ });
});
/** @type {!shaka.test.FakeAbrManager} */
const abrManager = new shaka.test.FakeAbrManager();
player.configure('abrFactory', () => abrManager);
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(player.getVariantTracks().length).toBe(2);
// We have some key statuses, but not for the key IDs we know.
@@ -3080,24 +2815,23 @@ describe('Player', () => {
it('chooses codecs after considering 6-channel preference', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- // Surround sound AC-3, preferred by config
- period.addVariant(0, (variant) => {
- variant.bandwidth = 300;
- variant.addAudio(0, (stream) => {
- stream.channelsCount = 6;
- stream.audioSamplingRate = 48000;
- stream.codecs = 'ac-3';
- });
+ // Surround sound AC-3, preferred by config
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 300;
+ variant.addAudio(0, (stream) => {
+ stream.channelsCount = 6;
+ stream.audioSamplingRate = 48000;
+ stream.codecs = 'ac-3';
});
- // Stereo AAC, would win out based on bandwidth alone
- period.addVariant(1, (variant) => {
- variant.bandwidth = 100;
- variant.addAudio(1, (stream) => {
- stream.channelsCount = 2;
- stream.audioSamplingRate = 48000;
- stream.codecs = 'mp4a.40.2';
- });
+ });
+
+ // Stereo AAC, would win out based on bandwidth alone
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 100;
+ variant.addAudio(1, (stream) => {
+ stream.channelsCount = 2;
+ stream.audioSamplingRate = 48000;
+ stream.codecs = 'mp4a.40.2';
});
});
});
@@ -3106,7 +2840,7 @@ describe('Player', () => {
player.configure({
preferredAudioChannelCount: 6,
});
- await setupPlayer();
+ await player.load(fakeManifestUri, 0, fakeMimeType);
expect(abrManager.setVariants).toHaveBeenCalled();
// We've chosen codecs, so only 1 track should remain.
expect(abrManager.variants.length).toBe(1);
@@ -3114,15 +2848,6 @@ describe('Player', () => {
expect(abrManager.variants[0].audio.channelsCount).toBe(6);
expect(abrManager.variants[0].audio.codecs).toBe('ac-3');
});
-
- /**
- * @return {!Promise}
- */
- async function setupPlayer() {
- await player.load(fakeManifestUri, 0, fakeMimeType);
- // Initialize the fake streams.
- streamingEngine.onCanSwitch();
- }
});
describe('getPlayheadTimeAsDate()', () => {
@@ -3132,10 +2857,8 @@ describe('Player', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline = timeline;
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1);
});
});
@@ -3154,7 +2877,7 @@ describe('Player', () => {
const expected = Util.jasmineError(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MANIFEST,
- shaka.util.Error.Code.NO_PERIODS));
+ shaka.util.Error.Code.NO_VARIANTS));
await expectAsync(player.load(fakeManifestUri, 0, fakeMimeType))
.toBeRejectedWith(expected);
});
@@ -3166,31 +2889,30 @@ describe('Player', () => {
// type. This test covers https://github.com/google/shaka-player/issues/954
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = 100;
- variant.addVideo(0);
- variant.addAudio(9);
- });
- period.addVariant(1, (variant) => {
- variant.bandwidth = 200;
- variant.addVideo(1);
- variant.addExistingStream(9); // audio
- });
- period.addVariant(2, (variant) => {
- variant.bandwidth = 300;
- variant.addVideo(2);
- variant.addExistingStream(9); // audio
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = 100;
+ variant.addVideo(0);
+ variant.addAudio(9);
+ });
+
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 200;
+ variant.addVideo(1);
+ variant.addExistingStream(9); // audio
+ });
+
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 300;
+ variant.addVideo(2);
+ variant.addExistingStream(9); // audio
});
});
await player.load(fakeManifestUri, 0, fakeMimeType);
- streamingEngine.onCanSwitch();
// We've already loaded variants[0]. Switch to [1] and [2].
- abrManager.switchCallback(manifest.periods[0].variants[1]);
- abrManager.switchCallback(manifest.periods[0].variants[2]);
+ abrManager.switchCallback(manifest.variants[1]);
+ abrManager.switchCallback(manifest.variants[2]);
});
describe('isTextTrackVisible', () => {
@@ -3211,11 +2933,9 @@ describe('Player', () => {
expect(player.isAudioOnly()).toBe(false);
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0);
- variant.addAudio(1);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(0);
+ variant.addAudio(1);
});
});
await player.load(fakeManifestUri, 0, fakeMimeType);
@@ -3223,10 +2943,8 @@ describe('Player', () => {
expect(player.isAudioOnly()).toBe(false);
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(0);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(0);
});
});
await player.load(fakeManifestUri, 0, fakeMimeType);
@@ -3234,10 +2952,8 @@ describe('Player', () => {
expect(player.isAudioOnly()).toBe(false);
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(1);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(1);
});
});
await player.load(fakeManifestUri, 0, fakeMimeType);
@@ -3254,19 +2970,17 @@ describe('Player', () => {
it('tolerates bandwidth of NaN, undefined, or 0', async () => {
// Regression test for https://github.com/google/shaka-player/issues/938
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.bandwidth = /** @type {?} */(undefined);
- variant.addVideo(0);
- });
- period.addVariant(1, (variant) => {
- variant.bandwidth = NaN;
- variant.addVideo(1);
- });
- period.addVariant(2, (variant) => {
- variant.bandwidth = 0;
- variant.addVideo(2);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.bandwidth = /** @type {?} */(undefined);
+ variant.addVideo(0);
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = NaN;
+ variant.addVideo(1);
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 0;
+ variant.addVideo(2);
});
});
@@ -3286,10 +3000,8 @@ describe('Player', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline = timeline;
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1);
});
});
@@ -3328,69 +3040,67 @@ describe('Player', () => {
beforeEach(() => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.language = 'fr';
- variant.addVideo(0);
- variant.addAudio(1, (stream) => {
- stream.language = 'fr';
- });
+ manifest.addVariant(1, (variant) => {
+ variant.language = 'fr';
+ variant.addVideo(0);
+ variant.addAudio(1, (stream) => {
+ stream.language = 'fr';
});
+ });
- period.addVariant(2, (variant) => {
- variant.language = 'en';
- variant.addExistingStream(0); // video
- variant.addAudio(2, (stream) => {
- stream.language = 'en';
- stream.roles = ['main'];
- });
+ manifest.addVariant(2, (variant) => {
+ variant.language = 'en';
+ variant.addExistingStream(0); // video
+ variant.addAudio(2, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['main'];
});
+ });
- period.addVariant(3, (variant) => {
- variant.language = 'en';
- variant.addExistingStream(0); // video
- variant.addAudio(3, (stream) => {
- stream.language = 'en';
- stream.roles = ['commentary'];
- });
+ manifest.addVariant(3, (variant) => {
+ variant.language = 'en';
+ variant.addExistingStream(0); // video
+ variant.addAudio(3, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['commentary'];
});
+ });
- period.addVariant(4, (variant) => {
- variant.language = 'de';
- variant.addExistingStream(0); // video
- variant.addAudio(4, (stream) => {
- stream.language = 'de';
- stream.roles = ['foo', 'bar'];
- });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'de';
+ variant.addExistingStream(0); // video
+ variant.addAudio(4, (stream) => {
+ stream.language = 'de';
+ stream.roles = ['foo', 'bar'];
});
+ });
- period.addTextStream(5, (stream) => {
- stream.language = 'es';
- stream.roles = ['baz', 'qwerty'];
- });
- period.addTextStream(6, (stream) => {
- stream.language = 'en';
- stream.kind = 'caption';
- stream.roles = ['main', 'caption'];
- });
- period.addTextStream(7, (stream) => {
- stream.language = 'en';
- stream.kind = 'subtitle';
- stream.roles = ['main', 'subtitle'];
- });
+ manifest.addTextStream(5, (stream) => {
+ stream.language = 'es';
+ stream.roles = ['baz', 'qwerty'];
+ });
+
+ manifest.addTextStream(6, (stream) => {
+ stream.language = 'en';
+ stream.kind = 'caption';
+ stream.roles = ['main', 'caption'];
+ });
+
+ manifest.addTextStream(7, (stream) => {
+ stream.language = 'en';
+ stream.kind = 'subtitle';
+ stream.roles = ['main', 'subtitle'];
});
});
videoOnlyManifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(1, (variant) => {
- variant.bandwidth = 400;
- variant.addVideo(1);
- });
- period.addVariant(2, (variant) => {
- variant.bandwidth = 800;
- variant.addVideo(2);
- });
+ manifest.addVariant(1, (variant) => {
+ variant.bandwidth = 400;
+ variant.addVideo(1);
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.bandwidth = 800;
+ variant.addVideo(2);
});
});
});
@@ -3414,16 +3124,14 @@ describe('Player', () => {
describe('getAudioLanguagesAndRoles', () => {
it('ignores video roles', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.language = 'en';
- variant.addVideo(1, (stream) => {
- stream.roles = ['video-only-role'];
- });
- variant.addAudio(2, (stream) => {
- stream.roles = ['audio-only-role'];
- stream.language = 'en';
- });
+ manifest.addVariant(0, (variant) => {
+ variant.language = 'en';
+ variant.addVideo(1, (stream) => {
+ stream.roles = ['video-only-role'];
+ });
+ variant.addAudio(2, (stream) => {
+ stream.roles = ['audio-only-role'];
+ stream.language = 'en';
});
});
});
@@ -3448,11 +3156,9 @@ describe('Player', () => {
it('uses "und" for video-only tracks', async () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.roles = ['video-only-role'];
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.roles = ['video-only-role'];
});
});
});
@@ -3493,39 +3199,14 @@ describe('Player', () => {
/**
* Gets the currently active text track.
- * @return {shaka.extern.Track}
+ * @return {?shaka.extern.Track}
*/
function getActiveTextTrack() {
const activeTracks = player.getTextTracks().filter((track) => {
return track.active;
});
- expect(activeTracks.length).toBe(1);
- return activeTracks[0];
- }
-
- /**
- * Simulate the transition to a new period using the fake StreamingEngine.
- * @param {number} index
- */
- function transitionPeriod(index) {
- periodIndex = index;
- streamingEngine.onChooseStreams();
- }
-
- /**
- * Choose streams for the given period.
- *
- * @suppress {accessControls}
- * @return {shaka.media.StreamingEngine.ChosenStreams}
- */
- function onChooseStreams() {
- return player.onChooseStreams_(manifest.periods[periodIndex]);
- }
-
- /** @suppress {accessControls} */
- function onCanSwitch() {
- player.canSwitch_();
+ return activeTracks[0] || null;
}
/**
diff --git a/test/test/assets/db-dump-v3-broken.json b/test/test/assets/db-dump-v4-broken.json
similarity index 100%
rename from test/test/assets/db-dump-v3-broken.json
rename to test/test/assets/db-dump-v4-broken.json
diff --git a/test/test/boot.js b/test/test/boot.js
index 70f1c24725..5eba0872c1 100644
--- a/test/test/boot.js
+++ b/test/test/boot.js
@@ -204,6 +204,17 @@ function getClientArg(name) {
});
};
+ /**
+ * Unconditionally skip contained tests that would normally be run
+ * conditionally. Used to temporarily disable tests that use filterDescribe.
+ * See filterDescribe above.
+ *
+ * @param {string} describeName
+ * @param {function():*} cond
+ * @param {function()} describeBody
+ */
+ window.xfilterDescribe = (describeName, cond, describeBody) => {};
+
beforeAll((done) => { // eslint-disable-line no-restricted-syntax
// Configure AMD modules and their dependencies.
require.config({
diff --git a/test/test/externs/filters.js b/test/test/externs/filters.js
index eeb305718c..f4cf9361f0 100644
--- a/test/test/externs/filters.js
+++ b/test/test/externs/filters.js
@@ -29,3 +29,11 @@ var quarantinedIt = function(name, callback) {};
* @param {function()} callback
*/
var filterDescribe = function(name, cond, callback) {};
+
+
+/**
+ * @param {string} name
+ * @param {function():*} cond
+ * @param {function()} callback
+ */
+var xfilterDescribe = function(name, cond, callback) {};
diff --git a/test/test/externs/jasmine.js b/test/test/externs/jasmine.js
index 6d5a0571d3..cc867c6619 100644
--- a/test/test/externs/jasmine.js
+++ b/test/test/externs/jasmine.js
@@ -154,14 +154,10 @@ jasmine.Matchers.prototype.toHaveBeenCalledTimes = function(times) {};
jasmine.Matchers.prototype.toMatch = function(value) {};
-/** @param {*} value */
+/** @param {*=} value */
jasmine.Matchers.prototype.toThrow = function(value) {};
-/** @param {*} value */
-jasmine.Matchers.prototype.toThrowError = function(value) {};
-
-
/**
* A custom matcher for DOM Node objects.
* @param {!Element} expected
diff --git a/test/test/util/dash_parser_util.js b/test/test/util/dash_parser_util.js
index b2257dd5f9..62ba074fad 100644
--- a/test/test/util/dash_parser_util.js
+++ b/test/test/util/dash_parser_util.js
@@ -35,26 +35,15 @@ shaka.test.Dash = class {
const playerInterface = {
networkingEngine: networkingEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: () => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: fail,
onError: fail,
};
const manifest = await dashParser.start('dummy://foo', playerInterface);
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await stream.createSegmentIndex();
- // Set expected values for append window.
- const appendWindowStart = manifest.periods[0].startTime;
- const appendWindowEnd = manifest.periods[1] ?
- manifest.periods[1].startTime :
- manifest.presentationTimeline.getDuration();
- for (const ref of references) {
- ref.appendWindowStart = appendWindowStart;
- ref.appendWindowEnd = appendWindowEnd;
- }
-
shaka.test.ManifestParser.verifySegmentIndex(stream, references);
}
@@ -74,8 +63,7 @@ shaka.test.Dash = class {
const playerInterface = {
networkingEngine: networkingEngine,
- filterNewPeriod: () => {},
- filterAllPeriods: () => {},
+ filter: () => {},
onTimelineRegionAdded: fail, // Should not have any EventStream elements.
onEvent: fail,
onError: fail,
@@ -125,13 +113,7 @@ shaka.test.Dash = class {
* @return {!Promise.}
*/
static async getFirstVideoSegmentReference(manifest) {
- const period = manifest.periods[0];
- expect(period).not.toBe(null);
- if (!period) {
- return null;
- }
-
- const variant = period.variants[0];
+ const variant = manifest.variants[0];
expect(variant).not.toBe(null);
if (!variant) {
return null;
@@ -161,7 +143,7 @@ shaka.test.Dash = class {
* @return {!Promise}
*/
static async callCreateSegmentIndex(manifest) {
- const stream = manifest.periods[0].variants[0].video;
+ const stream = manifest.variants[0].video;
await expectAsync(stream.createSegmentIndex()).toBeRejected();
}
diff --git a/test/test/util/fake_media_source_engine.js b/test/test/util/fake_media_source_engine.js
index 3dd6927b1f..39011c9509 100644
--- a/test/test/util/fake_media_source_engine.js
+++ b/test/test/util/fake_media_source_engine.js
@@ -272,19 +272,16 @@ shaka.test.FakeMediaSourceEngine = class {
throw new Error('unexpected data');
}
+ // Verify that the segment is aligned.
const segmentData = this.segmentData[type];
- const expectedStartTime =
- segmentData.segmentPeriodTimes[i] + segmentData.segmentStartTimes[i];
+ const appendedTime = segmentData.segmentStartTimes[i] +
+ this.timestampOffsets_[originalType];
+ const expectedStartTime = i * segmentData.segmentDuration;
const expectedEndTime = expectedStartTime + segmentData.segmentDuration;
+ expect(appendedTime).toBe(expectedStartTime);
expect(startTime).toBe(expectedStartTime);
expect(endTime).toBe(expectedEndTime);
- // Verify that the segment is aligned.
- const start = this.segmentData[type].segmentStartTimes[i] +
- this.timestampOffsets_[originalType];
- const expectedStart = i * this.segmentData[type].segmentDuration;
- expect(start).toBe(expectedStart);
-
this.segments[type][i] = true;
return Promise.resolve();
}
@@ -410,7 +407,6 @@ shaka.test.FakeMediaSourceEngine = class {
* initSegments: !Array.,
* segments: !Array.,
* segmentStartTimes: !Array.,
- * segmentPeriodTimes: !Array.,
* segmentDuration: number
* }}
*
@@ -422,9 +418,6 @@ shaka.test.FakeMediaSourceEngine = class {
* The start time of each media segment as they would appear within a
* segment index. These values plus drift simulate the segments'
* baseMediaDecodeTime (or equivalent) values.
- * @property {!Array.} segmentPeriodTimes
- * The start time of the period of the associated segment. These are the same
- * segments as in |segmentStartTimes|.
* @property {number} segmentDuration
* The duration of each media segment.
*/
diff --git a/test/test/util/manifest_generator.js b/test/test/util/manifest_generator.js
index 928ba48e6f..d73f7bda47 100644
--- a/test/test/util/manifest_generator.js
+++ b/test/test/util/manifest_generator.js
@@ -83,8 +83,10 @@ shaka.test.ManifestGenerator.Manifest = class {
/** @private {?} */
this.shaka_ = shaka || window['shaka'];
- /** @type {shaka.test.ManifestGenerator.Period} */
- this.currentPeriod_ = null;
+ /** @type {!Array.} */
+ this.variants = [];
+ /** @type {!Array.} */
+ this.textStreams = [];
const timeline = new this.shaka_.media.PresentationTimeline(0, 0);
timeline.setSegmentAvailabilityDuration(Infinity);
@@ -92,8 +94,6 @@ shaka.test.ManifestGenerator.Manifest = class {
/** @type {!shaka.media.PresentationTimeline} */
this.presentationTimeline = timeline;
- /** @type {!Array.} */
- this.periods = [];
/** @type {!Array.} */
this.offlineSessionIds = [];
/** @type {number} */
@@ -120,23 +120,6 @@ shaka.test.ManifestGenerator.Manifest = class {
/** @type {?} */ (jasmine.any(this.shaka_.media.PresentationTimeline));
}
- /**
- * Adds a new Period to the manifest.
- *
- * @param {?number} startTime
- * @param {function(!shaka.test.ManifestGenerator.Period)=} func
- */
- addPeriod(startTime, func) {
- const period =
- new shaka.test.ManifestGenerator.Period(this, startTime);
- if (func) {
- this.currentPeriod_ = period;
- func(period);
- this.currentPeriod_ = null;
- }
- this.periods.push(period.build_());
- }
-
/**
* Gets the existing stream with the given ID.
* @param {number} id
@@ -145,25 +128,19 @@ shaka.test.ManifestGenerator.Manifest = class {
*/
findExistingStream_(id) {
const real = (obj) => shaka.test.ManifestGenerator.realObj_(obj);
- let periods = this.periods;
- if (this.currentPeriod_) {
- periods = periods.concat([this.currentPeriod_]);
- }
- for (const period of periods) {
- for (const maybeVariant of period.variants) {
- const variant = real(maybeVariant);
- if (variant.video && real(variant.video).id == id) {
- return variant.video;
- }
- if (variant.audio && real(variant.audio).id == id) {
- return variant.audio;
- }
+ for (const maybeVariant of this.variants) {
+ const variant = real(maybeVariant);
+ if (variant.video && real(variant.video).id == id) {
+ return variant.video;
+ }
+ if (variant.audio && real(variant.audio).id == id) {
+ return variant.audio;
}
- for (const maybeText of period.textStreams) {
- if (real(maybeText).id == id) {
- return maybeText;
- }
+ }
+ for (const maybeText of this.textStreams) {
+ if (real(maybeText).id == id) {
+ return maybeText;
}
}
return null;
@@ -178,37 +155,6 @@ shaka.test.ManifestGenerator.Manifest = class {
isIdUsed_(id) {
return id != null && this.findExistingStream_(id) != null;
}
-};
-
-shaka.test.ManifestGenerator.Period = class {
- /**
- * @param {!shaka.test.ManifestGenerator.Manifest} manifest
- * @param {?number} startTime
- */
- constructor(manifest, startTime) {
- /** @const {!shaka.test.ManifestGenerator.Manifest} */
- this.manifest_ = manifest;
-
- /** @type {number} */
- this.startTime =
- startTime == null ? /** @type {?} */ (jasmine.any(Number)) : startTime;
- /** @type {!Array.} */
- this.variants = [];
- /** @type {!Array.} */
- this.textStreams = [];
-
- /** @type {shaka.extern.Period} */
- const foo = this;
- goog.asserts.assert(foo, 'Checking for type compatibility');
- }
-
- /**
- * @return {shaka.extern.Period}
- * @private
- */
- build_() {
- return shaka.test.ManifestGenerator.buildCommon_(this);
- }
/**
* Adds a new variant to the manifest.
@@ -218,7 +164,7 @@ shaka.test.ManifestGenerator.Period = class {
*/
addVariant(id, func) {
const variant = new shaka.test.ManifestGenerator.Variant(
- this.manifest_, /* isPartial= */ false, id);
+ this, /* isPartial= */ false, id);
if (func) {
func(variant);
}
@@ -234,7 +180,7 @@ shaka.test.ManifestGenerator.Period = class {
*/
addPartialVariant(func) {
const variant = new shaka.test.ManifestGenerator.Variant(
- this.manifest_, /* isPartial= */ true);
+ this, /* isPartial= */ true);
if (func) {
func(variant);
}
@@ -243,7 +189,7 @@ shaka.test.ManifestGenerator.Period = class {
}
/**
- * Adds a text stream to the current period.
+ * Adds a text stream to the manifest.
*
* @param {number} id
* @param {function(!shaka.test.ManifestGenerator.Stream)=} func
@@ -251,7 +197,7 @@ shaka.test.ManifestGenerator.Period = class {
addTextStream(id, func) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const stream = new shaka.test.ManifestGenerator.Stream(
- this.manifest_, /* isPartial= */ false, id, ContentType.TEXT, 'und');
+ this, /* isPartial= */ false, id, ContentType.TEXT, 'und');
if (func) {
func(stream);
}
@@ -269,7 +215,7 @@ shaka.test.ManifestGenerator.Period = class {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const stream = new shaka.test.ManifestGenerator.Stream(
- this.manifest_, /* isPartial= */ true, null, ContentType.TEXT);
+ this, /* isPartial= */ true, null, ContentType.TEXT);
if (func) {
func(stream);
}
@@ -563,8 +509,8 @@ shaka.test.ManifestGenerator.Stream = class {
this.kind = undefined;
/** @type {boolean} */
this.encrypted = false;
- /** @type {?string} */
- this.keyId = null;
+ /** @type {!Array.} */
+ this.keyIds = [];
/** @type {string} */
this.language = lang || 'und';
/** @type {?string} */
@@ -609,16 +555,11 @@ shaka.test.ManifestGenerator.Stream = class {
useSegmentTemplate(template, segmentDuration, segmentSize = null) {
const totalDuration = this.manifest_.presentationTimeline.getDuration();
const segmentCount = totalDuration / segmentDuration;
- const currentPeriod = this.manifest_.currentPeriod_;
- const periodStart = currentPeriod.startTime;
+ const duration = this.manifest_.presentationTimeline.getDuration();
this.createSegmentIndex = () => Promise.resolve();
- this.segmentIndex.find = (time) => {
- // Note: |time| is relative to the presentation.
- const periodTime = time - periodStart;
- return Math.floor(periodTime / segmentDuration);
- };
+ this.segmentIndex.find = (time) => Math.floor(time / segmentDuration);
this.segmentIndex.get = (index) => {
goog.asserts.assert(!isNaN(index), 'Invalid index requested!');
@@ -629,21 +570,21 @@ shaka.test.ManifestGenerator.Stream = class {
const start = index * segmentDuration;
const end = Math.min(totalDuration, (index + 1) * segmentDuration);
return new this.manifest_.shaka_.media.SegmentReference(
- /* startTime= */ periodStart + start,
- /* endTime= */ periodStart + end,
+ /* startTime= */ start,
+ /* endTime= */ end,
getUris,
/* startByte= */ 0,
/* endByte= */ segmentSize,
this.initSegmentReference_,
- /* timestampOffset= */ periodStart,
- /* appendWindowStart= */ periodStart,
- /* appendWindowEnd= */ Infinity);
+ /* timestampOffset= */ 0,
+ /* appendWindowStart= */ 0,
+ /* appendWindowEnd= */ duration);
};
}
/**
* Sets the current stream to use the given text stream. It will serve a
- * single media segment at the given URI for the entire Period.
+ * single media segment at the given URI for the entire presentation.
*
* @param {string} uri
*/
diff --git a/test/test/util/offline_utils.js b/test/test/util/offline_utils.js
index 0c3ebf9982..b8b97a6ab9 100644
--- a/test/test/util/offline_utils.js
+++ b/test/test/util/offline_utils.js
@@ -18,7 +18,7 @@ shaka.test.OfflineUtils = class {
duration: 90,
expiration: Infinity,
originalManifestUri: originalUri,
- periods: [],
+ streams: [],
sessionIds: [],
size: 1024,
};
@@ -47,9 +47,13 @@ shaka.test.OfflineUtils = class {
height: null,
initSegmentKey: null,
encrypted: false,
- keyId: null,
+ keyIds: [],
segments: [],
variantIds: [],
+ roles: [],
+ channelsCount: null,
+ audioSamplingRate: null,
+ closedCaptions: null,
};
}
diff --git a/test/test/util/simple_fakes.js b/test/test/util/simple_fakes.js
index 1926f5e04f..3f48bfdb5a 100644
--- a/test/test/util/simple_fakes.js
+++ b/test/test/util/simple_fakes.js
@@ -78,24 +78,12 @@ shaka.test.FakeAbrManager = class {
/** @extends {shaka.media.StreamingEngine} */
shaka.test.FakeStreamingEngine = class {
- /**
- * @param {function():shaka.media.StreamingEngine.ChosenStreams}
- * onChooseStreams
- * @param {function()} onCanSwitch
- */
- constructor(onChooseStreams, onCanSwitch) {
+ constructor() {
const resolve = () => Promise.resolve();
- let activeAudio = null;
- let activeVideo = null;
+ let activeVariant = null;
let activeText = null;
- /** @type {function()} */
- this.onChooseStreams = onChooseStreams;
-
- /** @type {function()} */
- this.onCanSwitch = onCanSwitch;
-
/** @type {!jasmine.Spy} */
this.destroy = jasmine.createSpy('destroy').and.callFake(resolve);
@@ -106,26 +94,14 @@ shaka.test.FakeStreamingEngine = class {
this.seeked = jasmine.createSpy('seeked');
/** @type {!jasmine.Spy} */
- this.getBufferingPeriod =
- jasmine.createSpy('getBufferingPeriod').and.returnValue(null);
+ this.getCurrentVariant =
+ jasmine.createSpy('getCurrentVariant').and.callFake(
+ () => activeVariant);
/** @type {!jasmine.Spy} */
- this.getBufferingAudio =
- jasmine.createSpy('getBufferingAudio').and.callFake(() => activeAudio);
-
- /** @type {!jasmine.Spy} */
- this.getBufferingVideo =
- jasmine.createSpy('getBufferingVideo').and.callFake(() => activeVideo);
-
- this.getBufferingText =
- jasmine.createSpy('getBufferingText').and.callFake(() => activeText);
-
- /** @type {!jasmine.Spy} */
- this.loadNewTextStream =
- jasmine.createSpy('loadNewTextStream').and.callFake((stream) => {
- activeText = stream;
- return Promise.resolve();
- });
+ this.getCurrentTextStream =
+ jasmine.createSpy('getCurrentTextStream').and.callFake(
+ () => activeText);
/** @type {!jasmine.Spy} */
this.unloadTextStream =
@@ -134,25 +110,12 @@ shaka.test.FakeStreamingEngine = class {
});
/** @type {!jasmine.Spy} */
- this.start = jasmine.createSpy('start').and.callFake(async () => {
- const chosen = onChooseStreams();
- await Promise.resolve();
- if (chosen.variant && chosen.variant.audio) {
- activeAudio = chosen.variant.audio;
- }
- if (chosen.variant && chosen.variant.video) {
- activeVideo = chosen.variant.video;
- }
- if (chosen.text) {
- activeText = chosen.text;
- }
- });
+ this.start = jasmine.createSpy('start');
/** @type {!jasmine.Spy} */
this.switchVariant =
jasmine.createSpy('switchVariant').and.callFake((variant) => {
- activeAudio = variant.audio || activeAudio;
- activeVideo = variant.video || activeVideo;
+ activeVariant = variant;
});
/** @type {!jasmine.Spy} */
diff --git a/test/test/util/stream_generator.js b/test/test/util/stream_generator.js
index e01ca46e26..4cecf16a57 100644
--- a/test/test/util/stream_generator.js
+++ b/test/test/util/stream_generator.js
@@ -40,19 +40,13 @@ shaka.test.IStreamGenerator = class {
* Gets one of the stream's segments.
* The IStreamGenerator must be initialized.
*
- * @param {number} position The segment's position within a particular Period
- * p.
- * @param {number} segmentOffset The number of segments in all Periods that
- * came before Period p.
+ * @param {number} position The segment's position.
* @param {number} wallClockTime The wall-clock time in seconds.
- * @example getSegment(1, 0) gets the 1st segment in the stream,
- * and getSegment(2, 5) gets the 2nd segment in a Period that starts
- * at the 6th segment (relative to the very start of the stream).
*
* @return {ArrayBuffer} The segment if the stream has started, and the
* segment exists and is available; otherwise, return null.
*/
- getSegment(position, segmentOffset, wallClockTime) {}
+ getSegment(position, wallClockTime) {}
};
/**
@@ -85,13 +79,12 @@ shaka.test.TSVodStreamGenerator = class {
}
/** @override */
- getSegment(position, segmentOffset, wallClockTime) {
+ getSegment(position, wallClockTime) {
goog.asserts.assert(
this.segment_,
'init() must be called before getSegment().');
// TODO: complete implementation; this should change the timestamps based on
- // the given segmentOffset and wallClockTime, so as to simulate a long
- // stream.
+ // the given wallClockTime, so as to simulate a long stream.
return this.segment_;
}
};
@@ -170,7 +163,7 @@ shaka.test.Mp4VodStreamGenerator = class {
}
/** @override */
- getSegment(position, segmentOffset, wallClockTime) {
+ getSegment(position, wallClockTime) {
goog.asserts.assert(
this.segmentTemplate_,
'init() must be called before getSegment().');
@@ -178,11 +171,11 @@ shaka.test.Mp4VodStreamGenerator = class {
return null;
}
- // |position| must be an integer and >= 1.
- goog.asserts.assert((position % 1 === 0) && (position >= 1),
- 'segment number must be an integer >= 1');
+ // |position| must be an integer and >= 0.
+ goog.asserts.assert((position % 1 === 0) && (position >= 0),
+ 'segment number must be an integer >= 0');
- const segmentStartTime = (position - 1) * this.segmentDuration_;
+ const segmentStartTime = position * this.segmentDuration_;
return shaka.test.StreamGenerator.setBaseMediaDecodeTime_(
this.segmentTemplate_, this.tfdtOffset_, segmentStartTime,
@@ -292,7 +285,7 @@ shaka.test.Mp4LiveStreamGenerator = class {
}
/** @override */
- getSegment(position, segmentOffset, wallClockTime) {
+ getSegment(position, wallClockTime) {
goog.asserts.assert(
this.initSegment_,
'init() must be called before getSegment().');
@@ -300,17 +293,16 @@ shaka.test.Mp4LiveStreamGenerator = class {
return null;
}
- // |position| must be an integer and >= 1.
- goog.asserts.assert((position % 1 === 0) && (position >= 1),
- 'segment number must be an integer >= 1');
+ // |position| must be an integer and >= 0.
+ goog.asserts.assert((position % 1 === 0) && (position >= 0),
+ 'segment number must be an integer >= 0');
- const segmentStartTime = (position - 1) * this.segmentDuration_;
+ const segmentStartTime = position * this.segmentDuration_;
// Compute the segment's availability start time and end time.
// (See section 5.3.9.5.3 of the DASH spec.)
const segmentAvailabilityStartTime = this.availabilityStartTime_ +
segmentStartTime +
- (segmentOffset * this.segmentDuration_) +
this.segmentDuration_;
const segmentAvailabiltyEndTime = segmentAvailabilityStartTime +
this.segmentDuration_ +
@@ -337,8 +329,7 @@ shaka.test.Mp4LiveStreamGenerator = class {
// 0.
const artificialPresentationTimeOffset =
this.broadcastStartTime_ - this.availabilityStartTime_;
- const mediaTimestamp = segmentStartTime +
- artificialPresentationTimeOffset;
+ const mediaTimestamp = segmentStartTime + artificialPresentationTimeOffset;
return shaka.test.StreamGenerator.setBaseMediaDecodeTime_(
/** @type {!ArrayBuffer} */ (this.segmentTemplate_), this.tfdtOffset_,
diff --git a/test/test/util/streaming_engine_util.js b/test/test/util/streaming_engine_util.js
index 932739cbc0..7898aaa4b6 100644
--- a/test/test/util/streaming_engine_util.js
+++ b/test/test/util/streaming_engine_util.js
@@ -5,9 +5,6 @@
goog.provide('shaka.test.StreamingEngineUtil');
-goog.require('shaka.util.Iterables');
-
-
shaka.test.StreamingEngineUtil = class {
/**
* Creates a FakeNetworkingEngine.
@@ -17,7 +14,7 @@ shaka.test.StreamingEngineUtil = class {
*
* A request's URI must follow either the init segment URI pattern:
* PERIOD_TYPE_init, e.g., "1_audio_init" or "2_video_init"; or the media
- * segment URI pattern: PERIOD_TYPE_POSITION, e.g., "1_text_2" or "2_video_1".
+ * segment URI pattern: PERIOD_TYPE_POSITION, e.g., "1_text_2" or "2_video_5".
*
* @param {function(string, number): BufferSource} getInitSegment Init segment
* generator: takes a content type and a Period number; returns an init
@@ -44,22 +41,22 @@ shaka.test.StreamingEngineUtil = class {
const parts = request.uris[0].split('_');
expect(parts.length).toBe(3);
- const periodNumber = Number(parts[0]);
- expect(periodNumber).not.toBeNaN();
- expect(periodNumber).toBeGreaterThan(0);
- expect(Math.floor(periodNumber)).toBe(periodNumber);
+ const periodIndex = Number(parts[0]);
+ expect(periodIndex).not.toBeNaN();
+ expect(periodIndex).toBeGreaterThan(-1);
+ expect(Math.floor(periodIndex)).toBe(periodIndex);
const contentType = parts[1];
let buffer;
if (parts[2] == 'init') {
- buffer = getInitSegment(contentType, periodNumber);
+ buffer = getInitSegment(contentType, periodIndex);
} else {
const position = Number(parts[2]);
expect(position).not.toBeNaN();
- expect(position).toBeGreaterThan(0);
+ expect(position).toBeGreaterThan(-1);
expect(Math.floor(position)).toBe(position);
- buffer = getSegment(contentType, periodNumber, position);
+ buffer = getSegment(contentType, periodIndex, position);
}
const response = {uri: request.uris[0], data: buffer, headers: {}};
@@ -164,18 +161,18 @@ shaka.test.StreamingEngineUtil = class {
}
/**
- * Creates a fake Manifest.
- *
- * Each Period within the fake Manifest has one Variant and one
- * text stream.
+ * Creates a fake Manifest simulating one or more DASH periods, containing
+ * one variant and optionally one text stream. The streams we create are
+ * based on the keys in segmentDurations.
*
* Audio, Video, and Text Stream MIME types are set to
* "audio/mp4; codecs=mp4a.40.2", "video/mp4; codecs=avc1.42c01e",
* and "text/vtt" respectively.
*
* Each media segment's URI follows the media segment URI pattern:
- * PERIOD_TYPE_POSITION, e.g., "1_text_2" or "2_video_1".
+ * PERIOD_TYPE_POSITION, e.g., "1_text_2" or "2_video_5".
*
+ * @param {!shaka.media.PresentationTimeline} presentationTimeline
* @param {!Array.} periodStartTimes The start time of each Period.
* @param {number} presentationDuration
* @param {!Object.} segmentDurations The duration of each
@@ -185,40 +182,85 @@ shaka.test.StreamingEngineUtil = class {
* @return {shaka.extern.Manifest}
*/
static createManifest(
- periodStartTimes, presentationDuration, segmentDurations,
- initSegmentRanges) {
- const boundsCheckPosition = (time, period, pos) =>
- shaka.test.StreamingEngineUtil.boundsCheckPosition(
- periodStartTimes, presentationDuration, segmentDurations, time,
- period, pos);
-
+ presentationTimeline, periodStartTimes, presentationDuration,
+ segmentDurations, initSegmentRanges) {
/**
* @param {string} type
- * @param {number} periodNumber
* @param {number} time
* @return {?number} A segment position.
*/
- const find = (type, periodNumber, time) => {
- // Note: |time| is relative to the presentation, and |periodNumber| is
- // 1-based.
- const periodTime = time - periodStartTimes[periodNumber - 1];
+ const find = (type, time) => {
+ if (time >= presentationDuration || time < 0) {
+ return null;
+ }
- const position = Math.floor(periodTime / segmentDurations[type]) + 1;
- return boundsCheckPosition(type, periodNumber, position);
+ // Note that we don't just directly compute the segment position because
+ // a period start time could be in the middle of the previous period's
+ // last segment.
+ let position = 0;
+ let i;
+ for (i = 0; i < periodStartTimes.length; ++i) {
+ const startTime = periodStartTimes[i];
+ const nextStartTime = i < periodStartTimes.length - 1 ?
+ periodStartTimes[i + 1] :
+ presentationDuration;
+ if (nextStartTime > time) {
+ // This is the period in which we would find the requested time.
+ break;
+ }
+
+ // This is an earlier period. Count up the number of segments in it.
+ const periodDuration = nextStartTime - startTime;
+ const numSegments = Math.ceil(periodDuration / segmentDurations[type]);
+ position += numSegments;
+ }
+
+ goog.asserts.assert(i < periodStartTimes.length, 'Ran out of periods!');
+ const periodStartTime = periodStartTimes[i];
+ const periodTime = time - periodStartTime;
+ position += Math.floor(periodTime / segmentDurations[type]);
+
+ return position;
};
/**
* @param {string} type
- * @param {number} periodNumber
* @param {number} position
* @return {shaka.media.SegmentReference} A SegmentReference.
*/
- const get = (type, periodNumber, position) => {
- if (boundsCheckPosition(type, periodNumber, position) == null) {
+ const get = (type, position) => {
+ // Note that we don't just directly compute the segment position because
+ // a period start time could be in the middle of the previous period's
+ // last segment.
+ let periodFirstPosition = 0;
+ let i;
+ for (i = 0; i < periodStartTimes.length; ++i) {
+ const startTime = periodStartTimes[i];
+ const nextStartTime = i < periodStartTimes.length - 1 ?
+ periodStartTimes[i + 1] :
+ presentationDuration;
+
+ // Count up the number of segments in this period.
+ const periodDuration = nextStartTime - startTime;
+ const numSegments = Math.ceil(periodDuration / segmentDurations[type]);
+
+ const nextPeriodFirstPosition = periodFirstPosition + numSegments;
+
+ if (nextPeriodFirstPosition > position) {
+ // This is the period in which we would find the requested position.
+ break;
+ }
+
+ periodFirstPosition = nextPeriodFirstPosition;
+ }
+ if (i == periodStartTimes.length) {
return null;
}
- const initSegmentUri = periodNumber + '_' + type + '_init';
+ const periodIndex = i; // 0-based
+ const positionWithinPeriod = position - periodFirstPosition;
+
+ const initSegmentUri = periodIndex + '_' + type + '_init';
// The type can be 'text', 'audio', 'video', or 'trickvideo',
// but we pull video init segment metadata from the 'video' part of the
@@ -234,116 +276,109 @@ shaka.test.StreamingEngineUtil = class {
}
const d = segmentDurations[type];
- const getUris = () => [periodNumber + '_' + type + '_' + position];
- const timestampOffset = periodStartTimes[periodNumber - 1];
- const appendWindowStart = periodStartTimes[periodNumber - 1];
- const appendWindowEnd = periodNumber == periodStartTimes.length ?
- presentationDuration : periodStartTimes[periodNumber];
+ const getUris = () => [periodIndex + '_' + type + '_' + position];
+ const periodStart = periodStartTimes[periodIndex];
+ const appendWindowStart = periodStartTimes[periodIndex];
+ const appendWindowEnd = periodIndex == periodStartTimes.length - 1?
+ presentationDuration : periodStartTimes[periodIndex + 1];
return new shaka.media.SegmentReference(
- /* startTime= */ timestampOffset + (position - 1) * d,
- /* endTime= */ timestampOffset + position * d,
+ /* startTime= */ periodStart + positionWithinPeriod * d,
+ /* endTime= */ periodStart + (positionWithinPeriod + 1) * d,
getUris,
/* startByte= */ 0,
/* endByte= */ null,
initSegmentReference,
- timestampOffset,
+ /* timestampOffset= */ 0,
appendWindowStart,
appendWindowEnd);
};
+ /** @type {shaka.extern.Manifest} */
const manifest = {
- presentationTimeline: undefined, // Should be set externally.
- minBufferTime: undefined, // Should be set externally.
- periods: [],
+ presentationTimeline,
+ minBufferTime: 2,
+ offlineSessionIds: [],
+ variants: [],
+ textStreams: [],
};
+ /** @type {shaka.extern.Variant} */
+ const variant = {
+ video: null,
+ audio: null,
+ allowedByApplication: true,
+ allowedByKeySystem: true,
+ bandwidth: 0,
+ drmInfos: [],
+ id: 0,
+ language: 'und',
+ primary: false,
+ };
+
+ if ('video' in segmentDurations) {
+ variant.video = /** @type {shaka.extern.Stream} */(
+ shaka.test.StreamingEngineUtil.createMockStream('video', 0));
+ }
+
+ if ('audio' in segmentDurations) {
+ variant.audio = /** @type {shaka.extern.Stream} */(
+ shaka.test.StreamingEngineUtil.createMockStream('audio', 1));
+ }
+
+ /** @type {?shaka.extern.Stream} */
+ let textStream = null;
+
+ if ('text' in segmentDurations) {
+ textStream = /** @type {shaka.extern.Stream} */(
+ shaka.test.StreamingEngineUtil.createMockStream('text', 2));
+ }
+
+ /** @type {?shaka.extern.Stream} */
+ let trickModeVideo = null;
+
+ if ('trickvideo' in segmentDurations) {
+ trickModeVideo = /** @type {shaka.extern.Stream} */(
+ shaka.test.StreamingEngineUtil.createMockStream('video', 3));
+ }
+
// Populate the Manifest.
- let id = 0;
- const enumerate = (it) => shaka.util.Iterables.enumerate(it);
- for (const {i, item: startTime} of enumerate(periodStartTimes)) {
- const period = {
- startTime,
- variants: [],
- textStreams: [],
- };
-
- const variant = {};
- let trickModeVideo;
-
- for (const type in segmentDurations) {
- const stream =
- shaka.test.StreamingEngineUtil.createMockStream(type, id++);
-
- const segmentIndex = new shaka.test.FakeSegmentIndex();
- segmentIndex.find.and.callFake(
- (time) => find(type, i + 1, time));
- segmentIndex.get.and.callFake((pos) => get(type, i + 1, pos));
-
- stream.createSegmentIndex.and.callFake(() => {
- stream.segmentIndex = segmentIndex;
- return Promise.resolve();
- });
-
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
- if (type == ContentType.TEXT) {
- period.textStreams.push(stream);
- } else if (type == ContentType.AUDIO) {
- variant.audio = stream;
- } else if (type == 'trickvideo') {
- trickModeVideo = stream;
- } else {
- variant.video = stream;
- }
+ for (const type in segmentDurations) {
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
+ let stream;
+ if (type == ContentType.TEXT) {
+ stream = textStream;
+ } else if (type == ContentType.AUDIO) {
+ stream = variant.audio;
+ } else if (type == 'trickvideo') {
+ stream = trickModeVideo;
+ } else {
+ stream = variant.video;
}
- variant.video.trickModeVideo = trickModeVideo;
- period.variants.push(variant);
- manifest.periods.push(period);
- }
+ const segmentIndex = new shaka.test.FakeSegmentIndex();
+ segmentIndex.find.and.callFake((time) => find(type, time));
+ segmentIndex.get.and.callFake((pos) => get(type, pos));
- return /** @type {shaka.extern.Manifest} */ (manifest);
- }
+ const createSegmentIndexSpy = jasmine.createSpy('createSegmentIndex');
+ createSegmentIndexSpy.and.callFake(() => {
+ stream.segmentIndex = segmentIndex;
+ return Promise.resolve();
+ });
- /**
- * Returns |position| if |type|, |periodNumber|, and |position| correspond
- * to a valid segment, as dictated by the provided metadata:
- * |periodStartTimes|, |presentationDuration|, and |segmentDurations|.
- *
- * @param {!Array.} periodStartTimes
- * @param {number} presentationDuration
- * @param {!Object.} segmentDurations
- * @param {string} type
- * @param {number} periodNumber
- * @param {number} position
- * @return {?number}
- */
- static boundsCheckPosition(
- periodStartTimes, presentationDuration, segmentDurations,
- type, periodNumber, position) {
- const numSegments = shaka.test.StreamingEngineUtil.getNumSegments(
- periodStartTimes, presentationDuration, segmentDurations,
- type, periodNumber);
- return position >= 1 && position <= numSegments ? position : null;
- }
+ stream.createSegmentIndex =
+ shaka.test.Util.spyFunc(createSegmentIndexSpy);
+ }
- /**
- * @param {!Array.} periodStartTimes
- * @param {number} presentationDuration
- * @param {!Object.} segmentDurations
- * @param {string} type
- * @param {number} periodNumber
- * @return {number}
- */
- static getNumSegments(
- periodStartTimes, presentationDuration, segmentDurations,
- type, periodNumber) {
- const periodIndex = periodNumber - 1;
- const nextStartTime = periodIndex < periodStartTimes.length - 1 ?
- periodStartTimes[periodIndex + 1] :
- presentationDuration;
- const periodDuration = nextStartTime - periodStartTimes[periodIndex];
- return Math.ceil(periodDuration / segmentDurations[type]);
+ if (trickModeVideo) {
+ variant.video.trickModeVideo = trickModeVideo;
+ }
+ if (textStream) {
+ manifest.textStreams = [textStream];
+ }
+ manifest.variants = [variant];
+
+ return manifest;
}
/**
diff --git a/test/test/util/test_scheme.js b/test/test/util/test_scheme.js
index 8138ed078e..010dc0d48e 100644
--- a/test/test/util/test_scheme.js
+++ b/test/test/util/test_scheme.js
@@ -66,7 +66,7 @@ shaka.test.TestScheme = class {
responseData = generator.getInitSegment(0);
} else {
const index = Number(segmentParts[3]);
- responseData = generator.getSegment(index + 1, 0, 0);
+ responseData = generator.getSegment(index, 0);
}
if (!responseData) {
return shaka.util.AbortableOperation.failed(malformed);
@@ -203,138 +203,97 @@ shaka.test.TestScheme = class {
const manifest =
windowShaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline.setDuration(data.duration);
- manifest.addPeriod(/* startTime= */ 0, (period) => {
- period.addVariant(0, (variant) => {
- if (data[ContentType.VIDEO]) {
- variant.addVideo(1, (stream) => {
- addStreamInfo(
- stream, variant, data, ContentType.VIDEO, name);
- });
- }
- if (data[ContentType.AUDIO]) {
- variant.addAudio(2, (stream) => {
- addStreamInfo(
- stream, variant, data, ContentType.AUDIO, name);
- });
- }
- });
- if (data.text) {
- period.addTextStream(3, (stream) => {
- stream.mime = data.text.mimeType;
- stream.codecs = data.text.codecs;
- stream.textStream(getAbsoluteUri(data));
-
- if (data.text.language) {
- stream.language = data.text.language;
- }
+ manifest.addVariant(0, (variant) => {
+ if (data[ContentType.VIDEO]) {
+ variant.addVideo(1, (stream) => {
+ addStreamInfo(
+ stream, variant, data, ContentType.VIDEO, name);
+ });
+ }
+ if (data[ContentType.AUDIO]) {
+ variant.addAudio(2, (stream) => {
+ addStreamInfo(
+ stream, variant, data, ContentType.AUDIO, name);
});
}
});
+
+ if (data.text) {
+ manifest.addTextStream(3, (stream) => {
+ stream.mime = data.text.mimeType;
+ stream.codecs = data.text.codecs;
+ stream.textStream(getAbsoluteUri(data));
+
+ if (data.text.language) {
+ stream.language = data.text.language;
+ }
+ });
+ }
}, shaka);
MANIFESTS[name + suffix] = manifest;
}
- // Custom generators:
+ // Custom generator for multiple streams with different languages and
+ // resolutions.
+ // TODO: Can this be generalized from the DATA array instead?
const data = DATA['sintel'];
const periodDuration = 10;
-
- // Multi-period
- const numPeriods = 10;
- let manifest = windowShaka.test.ManifestGenerator.generate((manifest) => {
- manifest.presentationTimeline.setDuration(periodDuration * numPeriods);
-
- let idCount = 1;
- for (const i of windowShaka.util.Iterables.range(numPeriods)) {
- manifest.addPeriod(
- /* startTime= */ periodDuration * i,
- (period) => {
- period.addVariant(idCount++, (variant) => {
- variant.language = 'en';
- variant.addVideo(idCount++, (stream) => {
- addStreamInfo(
- stream, variant, data, ContentType.VIDEO, 'sintel');
- });
- variant.addAudio(idCount++, (stream) => {
- addStreamInfo(
- stream, variant, data, ContentType.AUDIO, 'sintel');
- });
- });
-
- period.addVariant(idCount++, (variant) => {
- variant.language = 'es';
- variant.addVideo(idCount++, (stream) => {
- addStreamInfo(
- stream, variant, data, ContentType.VIDEO, 'sintel');
- });
- variant.addAudio(idCount++, (stream) => {
- addStreamInfo(
- stream, variant, data, ContentType.AUDIO, 'sintel');
- });
- });
- });
- }
- }, shaka);
- MANIFESTS['sintel_short_periods' + suffix] = manifest;
-
-
- // Multi-stream. Different languages and resolutions.
let idCount = 1;
- manifest = windowShaka.test.ManifestGenerator.generate((manifest) => {
+ const manifest = windowShaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline.setDuration(periodDuration);
- manifest.addPeriod(/* startTime= */ 0, (period) => {
- // Variant in English, res 426x182
- period.addVariant(idCount++, (variant) => {
- variant.language = 'en';
- variant.addVideo(idCount++, (stream) => {
- stream.size(426, 182);
- addStreamInfo(stream, variant, data, ContentType.VIDEO, 'sintel');
- });
- variant.addAudio(idCount++, (stream) => {
- stream.language = 'en';
- addStreamInfo(stream, variant, data, ContentType.AUDIO, 'sintel');
- });
- });
- // Same language, different resolution
- period.addVariant(idCount++, (variant) => {
- variant.language = 'en';
- variant.addVideo(idCount++, (stream) => {
- stream.size(640, 272);
- addStreamInfo(stream, variant, data, ContentType.VIDEO, 'sintel');
- });
- variant.addAudio(idCount++, (stream) => {
- stream.language = 'en';
- addStreamInfo(stream, variant, data, ContentType.AUDIO, 'sintel');
- });
+ // Variant in English, res 426x182
+ manifest.addVariant(idCount++, (variant) => {
+ variant.language = 'en';
+ variant.addVideo(idCount++, (stream) => {
+ stream.size(426, 182);
+ addStreamInfo(stream, variant, data, ContentType.VIDEO, 'sintel');
});
-
- // Same resolution, different language
- period.addVariant(idCount++, (variant) => {
- variant.language = 'es';
- variant.addVideo(idCount++, (stream) => {
- stream.size(640, 272);
- addStreamInfo(stream, variant, data, ContentType.VIDEO, 'sintel');
- });
- variant.addAudio(idCount++, (stream) => {
- stream.language = 'es';
- addStreamInfo(stream, variant, data, ContentType.AUDIO, 'sintel');
- });
+ variant.addAudio(idCount++, (stream) => {
+ stream.language = 'en';
+ addStreamInfo(stream, variant, data, ContentType.AUDIO, 'sintel');
});
+ });
- period.addTextStream(idCount++, (stream) => {
- stream.language = 'zh';
- stream.mime = data.text.mimeType;
- stream.codecs = data.text.codecs;
- stream.textStream(getAbsoluteUri(data));
+ // Same language, different resolution
+ manifest.addVariant(idCount++, (variant) => {
+ variant.language = 'en';
+ variant.addVideo(idCount++, (stream) => {
+ stream.size(640, 272);
+ addStreamInfo(stream, variant, data, ContentType.VIDEO, 'sintel');
});
+ variant.addAudio(idCount++, (stream) => {
+ stream.language = 'en';
+ addStreamInfo(stream, variant, data, ContentType.AUDIO, 'sintel');
+ });
+ });
- period.addTextStream(idCount++, (stream) => {
- stream.language = 'fr';
- stream.mime = data.text.mimeType;
- stream.codecs = data.text.codecs;
- stream.textStream(getAbsoluteUri(data));
+ // Same resolution, different language
+ manifest.addVariant(idCount++, (variant) => {
+ variant.language = 'es';
+ variant.addVideo(idCount++, (stream) => {
+ stream.size(640, 272);
+ addStreamInfo(stream, variant, data, ContentType.VIDEO, 'sintel');
});
+ variant.addAudio(idCount++, (stream) => {
+ stream.language = 'es';
+ addStreamInfo(stream, variant, data, ContentType.AUDIO, 'sintel');
+ });
+ });
+
+ manifest.addTextStream(idCount++, (stream) => {
+ stream.language = 'zh';
+ stream.mime = data.text.mimeType;
+ stream.codecs = data.text.codecs;
+ stream.textStream(getAbsoluteUri(data));
+ });
+
+ manifest.addTextStream(idCount++, (stream) => {
+ stream.language = 'fr';
+ stream.mime = data.text.mimeType;
+ stream.codecs = data.text.codecs;
+ stream.textStream(getAbsoluteUri(data));
});
}, shaka);
MANIFESTS['sintel_multi_lingual_multi_res' + suffix] = manifest;
@@ -409,35 +368,6 @@ shaka.test.TestScheme.DATA = {
duration: 300,
},
- // Like 'sintel' above, but with a non-zero period start time.
- // This helps expose edge cases around startup and live streams.
- 'sintel_start_at_3': {
- periodStart: 3,
- video: {
- initSegmentUri: '/base/test/test/assets/sintel-video-init.mp4',
- mdhdOffset: 0x1ba,
- segmentUri: '/base/test/test/assets/sintel-video-segment.mp4',
- tfdtOffset: 0x38,
- segmentDuration: 10,
- mimeType: 'video/mp4',
- codecs: 'avc1.42c01e',
- },
- audio: {
- initSegmentUri: '/base/test/test/assets/sintel-audio-init.mp4',
- mdhdOffset: 0x1b6,
- segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
- tfdtOffset: 0x3c,
- segmentDuration: 10.005,
- mimeType: 'audio/mp4',
- codecs: 'mp4a.40.2',
- },
- text: {
- uri: '/base/test/test/assets/text-clip.vtt',
- mimeType: 'text/vtt',
- },
- duration: 30,
- },
-
// Like 'sintel' above, but with languages and delayed setup.
// These extra features help expose some edge cases.
'sintel_realistic': {
@@ -692,10 +622,7 @@ shaka.test.TestScheme.ManifestParser = class {
// This makes sure the filtering functions are covered implicitly by
// tests. This covers regression
// https://github.com/google/shaka-player/issues/988
- playerInterface.filterAllPeriods(manifest.periods);
- for (const period of manifest.periods) {
- playerInterface.filterNewPeriod(period);
- }
+ playerInterface.filter(manifest);
return Promise.resolve(manifest);
}
diff --git a/test/test/util/waiter.js b/test/test/util/waiter.js
index 806f1cce12..279f66917f 100644
--- a/test/test/util/waiter.js
+++ b/test/test/util/waiter.js
@@ -203,6 +203,7 @@ shaka.test.Waiter = class {
shaka.media.TimeRangesUtils.getBufferedInfo(mediaElement.buffered);
shaka.log.error(message,
'current time', mediaElement.currentTime,
+ 'duration', mediaElement.duration,
'ready state', mediaElement.readyState,
'playback rate', mediaElement.playbackRate,
'paused', mediaElement.paused,
diff --git a/test/ui/ui_integration.js b/test/ui/ui_integration.js
index 5c78f63dbb..96387f5bcd 100644
--- a/test/ui/ui_integration.js
+++ b/test/ui/ui_integration.js
@@ -81,6 +81,9 @@ describe('UI', () => {
eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
eventManager.listen(controls, 'error', Util.spyFunc(onErrorSpy));
+ // These tests expect text to be streaming upfront, so always stream text.
+ player.configure('streaming.alwaysStreamText', true);
+
await player.load('test:sintel_multi_lingual_multi_res_compiled');
// For this event, we ignore a timeout, since we sometimes miss this event
// on Tizen. But expect that the video is ready anyway.
@@ -355,6 +358,13 @@ describe('UI', () => {
updateResolutionButtonsAndMap();
oldResolutionTrack = findTrackWithHeight(tracks, oldResolution);
+
+ const selectedResolution =
+ getSelectedTrack(player.getVariantTracks()).height;
+ if (selectedResolution != oldResolution) {
+ player.selectVariantTrack(oldResolutionTrack);
+ await waiter.waitForEvent(player, 'variantchanged');
+ }
});
@@ -367,10 +377,6 @@ describe('UI', () => {
it('changing resolution via UI has effect on the player', async () => {
- player.selectVariantTrack(oldResolutionTrack);
-
- // Wait for the change to take effect
- await waiter.waitForEvent(player, 'variantchanged');
// Update the tracks
tracks = player.getVariantTracks();
expect(getSelectedTrack(tracks).height).toBe(oldResolution);
@@ -387,11 +393,6 @@ describe('UI', () => {
it('changing resolution via API has effect on the UI', async () => {
- // Start with the old resolution
- player.selectVariantTrack(oldResolutionTrack);
-
- // Wait for the change to take effect
- await waiter.waitForEvent(player, 'variantchanged');
updateResolutionButtonsAndMap();
expect(getSelectedTrack(tracks).height).toBe(oldResolution);
diff --git a/test/ui/ui_unit.js b/test/ui/ui_unit.js
index 6e10419880..c8ce300e6a 100644
--- a/test/ui/ui_unit.js
+++ b/test/ui/ui_unit.js
@@ -283,10 +283,8 @@ describe('UI', () => {
// Load fake content that contains only audio.
const manifest =
shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(/* startTime= */ 0, (period) => {
- period.addVariant(/* id= */ 0, (variant) => {
- variant.addAudio(/* id= */ 1);
- });
+ manifest.addVariant(/* id= */ 0, (variant) => {
+ variant.addAudio(/* id= */ 1);
});
});
shaka.media.ManifestParser.registerParserByMime(
@@ -433,14 +431,12 @@ describe('UI', () => {
it('clears the buffer when changing resolutions', async () => {
// Load fake content that has more than one quality level.
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addVideo(1, (stream) => {
- stream.size(320, 240);
- });
- variant.addVideo(2, (stream) => {
- stream.size(640, 480);
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addVideo(1, (stream) => {
+ stream.size(320, 240);
+ });
+ variant.addVideo(2, (stream) => {
+ stream.size(640, 480);
});
});
});
@@ -471,61 +467,59 @@ describe('UI', () => {
// languages/channel-counts to test the current resolution list is
// filtered.
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.primary = true;
- variant.language = 'en';
- variant.addVideo(1, (stream) => {
- stream.size(320, 240);
- });
- variant.addAudio(3, (stream) => {
- stream.channelsCount = 2;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.primary = true;
+ variant.language = 'en';
+ variant.addVideo(1, (stream) => {
+ stream.size(320, 240);
+ });
+ variant.addAudio(3, (stream) => {
+ stream.channelsCount = 2;
+ });
+ });
+ manifest.addVariant(4, (variant) => {
+ variant.language = 'en';
+ variant.addVideo(5, (stream) => {
+ stream.size(640, 480);
+ });
+ variant.addAudio(6, (stream) => {
+ stream.channelsCount = 2;
+ });
+ });
+ manifest.addVariant(7, (variant) => { // Duplicate with 4
+ variant.language = 'en';
+ variant.addVideo(8, (stream) => {
+ stream.size(640, 480);
+ });
+ variant.addAudio(9, (stream) => {
+ stream.channelsCount = 2;
+ });
+ });
+ manifest.addVariant(10, (variant) => {
+ variant.language = 'en';
+ variant.addVideo(11, (stream) => {
+ stream.size(1280, 720);
});
- period.addVariant(4, (variant) => {
- variant.language = 'en';
- variant.addVideo(5, (stream) => {
- stream.size(640, 480);
- });
- variant.addAudio(6, (stream) => {
- stream.channelsCount = 2;
- });
+ variant.addAudio(12, (stream) => {
+ stream.channelsCount = 1;
});
- period.addVariant(7, (variant) => { // Duplicate with 4
- variant.language = 'en';
- variant.addVideo(8, (stream) => {
- stream.size(640, 480);
- });
- variant.addAudio(9, (stream) => {
- stream.channelsCount = 2;
- });
+ });
+ manifest.addVariant(13, (variant) => {
+ variant.language = 'es';
+ variant.addVideo(14, (stream) => {
+ stream.size(960, 540);
});
- period.addVariant(10, (variant) => {
- variant.language = 'en';
- variant.addVideo(11, (stream) => {
- stream.size(1280, 720);
- });
- variant.addAudio(12, (stream) => {
- stream.channelsCount = 1;
- });
+ variant.addAudio(15, (stream) => {
+ stream.channelsCount = 2;
});
- period.addVariant(13, (variant) => {
- variant.language = 'es';
- variant.addVideo(14, (stream) => {
- stream.size(960, 540);
- });
- variant.addAudio(15, (stream) => {
- stream.channelsCount = 2;
- });
+ });
+ manifest.addVariant(16, (variant) => {
+ variant.language = 'fr';
+ variant.addVideo(17, (stream) => {
+ stream.size(256, 144);
});
- period.addVariant(16, (variant) => {
- variant.language = 'fr';
- variant.addVideo(17, (stream) => {
- stream.size(256, 144);
- });
- variant.addAudio(18, (stream) => {
- stream.channelsCount = 2;
- });
+ variant.addAudio(18, (stream) => {
+ stream.channelsCount = 2;
});
});
});
diff --git a/test/util/stream_utils_unit.js b/test/util/stream_utils_unit.js
index 63e853aba6..1d534011a4 100644
--- a/test/util/stream_utils_unit.js
+++ b/test/util/stream_utils_unit.js
@@ -14,94 +14,86 @@ describe('StreamUtils', () => {
describe('filterStreamsByLanguageAndRole', () => {
it('chooses text streams in user\'s preferred language', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'es';
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'en';
- });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'es';
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'en';
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'en',
'');
expect(chosen.length).toBe(2);
- expect(chosen[0]).toBe(manifest.periods[0].textStreams[0]);
- expect(chosen[1]).toBe(manifest.periods[0].textStreams[2]);
+ expect(chosen[0]).toBe(manifest.textStreams[0]);
+ expect(chosen[1]).toBe(manifest.textStreams[2]);
});
it('chooses primary text streams', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(1);
- period.addTextStream(2, (stream) => {
- stream.primary = true;
- });
- period.addTextStream(3, (stream) => {
- stream.primary = true;
- });
+ manifest.addTextStream(1);
+ manifest.addTextStream(2, (stream) => {
+ stream.primary = true;
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.primary = true;
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'en',
'');
expect(chosen.length).toBe(2);
- expect(chosen[0]).toBe(manifest.periods[0].textStreams[1]);
- expect(chosen[1]).toBe(manifest.periods[0].textStreams[2]);
+ expect(chosen[0]).toBe(manifest.textStreams[1]);
+ expect(chosen[1]).toBe(manifest.textStreams[2]);
});
it('chooses text streams in preferred language and role', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- stream.roles = ['main', 'commentary'];
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'es';
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'en';
- stream.roles = ['caption'];
- });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['main', 'commentary'];
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'es';
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['caption'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'en',
'main');
expect(chosen.length).toBe(1);
- expect(chosen[0]).toBe(manifest.periods[0].textStreams[0]);
+ expect(chosen[0]).toBe(manifest.textStreams[0]);
});
it('prefers no-role streams if there is no preferred role', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.roles = ['commentary'];
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'en';
- stream.roles = ['secondary'];
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['secondary'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'en',
'');
expect(chosen.length).toBe(1);
@@ -110,23 +102,21 @@ describe('StreamUtils', () => {
it('ignores no-role streams if there is a preferred role', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.roles = ['commentary'];
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'en';
- stream.roles = ['secondary'];
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['secondary'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'en',
'main'); // A role that is not present.
expect(chosen.length).toBe(1);
@@ -136,36 +126,34 @@ describe('StreamUtils', () => {
it('chooses only one role, even if none is preferred', () => {
// Regression test for https://github.com/google/shaka-player/issues/949
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.roles = ['commentary'];
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- stream.roles = ['commentary'];
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'en';
- stream.roles = ['secondary'];
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'en';
- stream.roles = ['secondary'];
- });
- period.addTextStream(4, (stream) => {
- stream.language = 'en';
- stream.roles = ['main'];
- });
- period.addTextStream(5, (stream) => {
- stream.language = 'en';
- stream.roles = ['main'];
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(4, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['main'];
+ });
+ manifest.addTextStream(5, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['main'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'en',
'');
// Which role is chosen is an implementation detail.
@@ -177,42 +165,40 @@ describe('StreamUtils', () => {
it('chooses only one role, even if all are primary', () => {
// Regression test for https://github.com/google/shaka-player/issues/949
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['commentary'];
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['commentary'];
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['secondary'];
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['secondary'];
- });
- period.addTextStream(4, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['main'];
- });
- period.addTextStream(5, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['main'];
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(4, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['main'];
+ });
+ manifest.addTextStream(5, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['main'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'zh',
'');
// Which role is chosen is an implementation detail.
@@ -224,28 +210,26 @@ describe('StreamUtils', () => {
it('chooses only one language, even if all are primary', () => {
// Regression test for https://github.com/google/shaka-player/issues/918
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'es';
- stream.primary = true;
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'es';
- stream.primary = true;
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'es';
+ stream.primary = true;
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'es';
+ stream.primary = true;
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'zh',
'');
// Which language is chosen is an implementation detail.
@@ -257,40 +241,38 @@ describe('StreamUtils', () => {
it('chooses a role from among primary streams without language match',
() => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['commentary'];
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['commentary'];
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'en';
- stream.roles = ['secondary'];
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'en';
- stream.roles = ['secondary'];
- });
- period.addTextStream(4, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['main'];
- });
- period.addTextStream(5, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['main'];
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'en';
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(4, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['main'];
+ });
+ manifest.addTextStream(5, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['main'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'zh',
'');
// Which role is chosen is an implementation detail.
@@ -307,40 +289,38 @@ describe('StreamUtils', () => {
it('chooses a role from best language match, in spite of primary',
() => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(0, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['commentary'];
- });
- period.addTextStream(1, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['commentary'];
- });
- period.addTextStream(2, (stream) => {
- stream.language = 'zh';
- stream.roles = ['secondary'];
- });
- period.addTextStream(3, (stream) => {
- stream.language = 'zh';
- stream.roles = ['secondary'];
- });
- period.addTextStream(4, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['main'];
- });
- period.addTextStream(5, (stream) => {
- stream.language = 'en';
- stream.primary = true;
- stream.roles = ['main'];
- });
+ manifest.addTextStream(0, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(1, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['commentary'];
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.language = 'zh';
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.language = 'zh';
+ stream.roles = ['secondary'];
+ });
+ manifest.addTextStream(4, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['main'];
+ });
+ manifest.addTextStream(5, (stream) => {
+ stream.language = 'en';
+ stream.primary = true;
+ stream.roles = ['main'];
});
});
const chosen = filterStreamsByLanguageAndRole(
- manifest.periods[0].textStreams,
+ manifest.textStreams,
'zh',
'');
expect(chosen.length).toBe(2);
@@ -354,92 +334,84 @@ describe('StreamUtils', () => {
describe('filterVariantsByAudioChannelCount', () => {
it('chooses variants with preferred audio channels count', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(0, (stream) => {
- stream.channelsCount = 2;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(0, (stream) => {
+ stream.channelsCount = 2;
});
- period.addVariant(1, (variant) => {
- variant.addAudio(1, (stream) => {
- stream.channelsCount = 6;
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(1, (stream) => {
+ stream.channelsCount = 6;
});
- period.addVariant(2, (variant) => {
- variant.addAudio(2, (stream) => {
- stream.channelsCount = 2;
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(2, (stream) => {
+ stream.channelsCount = 2;
});
});
});
- const chosen = filterVariantsByAudioChannelCount(
- manifest.periods[0].variants, 2);
+ const chosen = filterVariantsByAudioChannelCount(manifest.variants, 2);
expect(chosen.length).toBe(2);
- expect(chosen[0]).toBe(manifest.periods[0].variants[0]);
- expect(chosen[1]).toBe(manifest.periods[0].variants[2]);
+ expect(chosen[0]).toBe(manifest.variants[0]);
+ expect(chosen[1]).toBe(manifest.variants[2]);
});
it('chooses variants with largest audio channel count less than config' +
' when no exact audio channel count match is possible', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(0, (stream) => {
- stream.channelsCount = 2;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(0, (stream) => {
+ stream.channelsCount = 2;
});
- period.addVariant(1, (variant) => {
- variant.addAudio(1, (stream) => {
- stream.channelsCount = 8;
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(1, (stream) => {
+ stream.channelsCount = 8;
});
- period.addVariant(2, (variant) => {
- variant.addAudio(2, (stream) => {
- stream.channelsCount = 2;
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(2, (stream) => {
+ stream.channelsCount = 2;
});
});
});
const chosen = filterVariantsByAudioChannelCount(
- manifest.periods[0].variants, 6);
+ manifest.variants, 6);
expect(chosen.length).toBe(2);
- expect(chosen[0]).toBe(manifest.periods[0].variants[0]);
- expect(chosen[1]).toBe(manifest.periods[0].variants[2]);
+ expect(chosen[0]).toBe(manifest.variants[0]);
+ expect(chosen[1]).toBe(manifest.variants[2]);
});
it('chooses variants with fewest audio channels when none fit in the ' +
'config', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addVariant(0, (variant) => {
- variant.addAudio(0, (stream) => {
- stream.channelsCount = 6;
- });
+ manifest.addVariant(0, (variant) => {
+ variant.addAudio(0, (stream) => {
+ stream.channelsCount = 6;
});
- period.addVariant(1, (variant) => {
- variant.addAudio(1, (stream) => {
- stream.channelsCount = 8;
- });
+ });
+ manifest.addVariant(1, (variant) => {
+ variant.addAudio(1, (stream) => {
+ stream.channelsCount = 8;
});
- period.addVariant(2, (variant) => {
- variant.addAudio(2, (stream) => {
- stream.channelsCount = 6;
- });
+ });
+ manifest.addVariant(2, (variant) => {
+ variant.addAudio(2, (stream) => {
+ stream.channelsCount = 6;
});
});
});
- const chosen = filterVariantsByAudioChannelCount(
- manifest.periods[0].variants, 2);
+ const chosen = filterVariantsByAudioChannelCount(manifest.variants, 2);
expect(chosen.length).toBe(2);
- expect(chosen[0]).toBe(manifest.periods[0].variants[0]);
- expect(chosen[1]).toBe(manifest.periods[0].variants[2]);
+ expect(chosen[0]).toBe(manifest.variants[0]);
+ expect(chosen[1]).toBe(manifest.variants[2]);
});
});
- describe('filterNewPeriod', () => {
+ describe('filterManifest', () => {
let fakeDrmEngine;
beforeAll(() => {
@@ -448,34 +420,29 @@ describe('StreamUtils', () => {
it('filters text streams with the full MIME type', () => {
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
- manifest.addPeriod(0, (period) => {
- period.addTextStream(1, (stream) => {
- stream.mimeType = 'text/vtt';
- });
- period.addTextStream(2, (stream) => {
- stream.mime('application/mp4', 'wvtt');
- });
- period.addTextStream(3, (stream) => {
- stream.mimeType = 'text/bogus';
- });
- period.addTextStream(4, (stream) => {
- stream.mime('application/mp4', 'bogus');
- });
+ manifest.addTextStream(1, (stream) => {
+ stream.mimeType = 'text/vtt';
+ });
+ manifest.addTextStream(2, (stream) => {
+ stream.mime('application/mp4', 'wvtt');
+ });
+ manifest.addTextStream(3, (stream) => {
+ stream.mimeType = 'text/bogus';
+ });
+ manifest.addTextStream(4, (stream) => {
+ stream.mime('application/mp4', 'bogus');
});
});
- const noAudio = null;
- const noVideo = null;
- shaka.util.StreamUtils.filterNewPeriod(
- fakeDrmEngine, noAudio, noVideo, manifest.periods[0]);
+ const noVariant = null;
+ shaka.util.StreamUtils.filterManifest(fakeDrmEngine, noVariant, manifest);
// Covers a regression in which we would remove streams with codecs.
// The last two streams should be removed because their full MIME types
// are bogus.
- expect(manifest.periods[0].textStreams.length).toBe(2);
- const textStreams = manifest.periods[0].textStreams;
- expect(textStreams[0].id).toBe(1);
- expect(textStreams[1].id).toBe(2);
+ expect(manifest.textStreams.length).toBe(2);
+ expect(manifest.textStreams[0].id).toBe(1);
+ expect(manifest.textStreams[1].id).toBe(2);
});
});
});
diff --git a/ui/text_selection.js b/ui/text_selection.js
index 3111bda61e..d6ee41bb58 100644
--- a/ui/text_selection.js
+++ b/ui/text_selection.js
@@ -132,8 +132,7 @@ shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
const offButton = shaka.util.Dom.createHTMLElement('button');
offButton.classList.add('shaka-turn-captions-off-button');
this.eventManager.listen(offButton, 'click', () => {
- const p = this.player.setTextTrackVisibility(false);
- p.catch(() => {}); // TODO(#1993): Handle possible errors.
+ this.player.setTextTrackVisibility(false);
this.updateTextLanguages_();
});