Skip to content

Commit c56c350

Browse files
committed
fix(HLS): Fix preload initial variant (#8835)
This is necessary to obtain the necessary DRM information, which in most cases is not available until the media playlist is downloaded. Also avoid DRM setup for VOD that does not need it for HLS. Fixes #8830
1 parent 21d773d commit c56c350

File tree

2 files changed

+103
-61
lines changed

2 files changed

+103
-61
lines changed

lib/media/preload_manager.js

Lines changed: 101 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -430,13 +430,18 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
430430
await this.parseManifestInner_();
431431
this.throwIfDestroyed_();
432432

433+
await this.chooseInitialVariant_();
434+
this.throwIfDestroyed_();
435+
433436
if (!shaka.drm.DrmUtils.isMediaKeysPolyfilled('webkit')) {
434437
await this.initializeDrm();
435438
this.throwIfDestroyed_();
436439
}
437440

438-
await this.chooseInitialVariantAndPrefetchInner_();
439-
this.throwIfDestroyed_();
441+
if (this.allowPrefetch_) {
442+
await this.prefetchInner_();
443+
this.throwIfDestroyed_();
444+
}
440445

441446
// We don't need the drm keys to load completely for the initial variant
442447
// to be chosen, but we won't mark the load as a success until it has
@@ -552,19 +557,6 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
552557
if (!this.manifest_) {
553558
this.manifest_ = await this.parser_.start(
554559
this.assetUri_, this.manifestPlayerInterface_);
555-
556-
if (this.manifest_.variants.length == 1) {
557-
const createSegmentIndexPromises = [];
558-
const variant = this.manifest_.variants[0];
559-
for (const stream of [variant.video, variant.audio]) {
560-
if (stream && !stream.segmentIndex) {
561-
createSegmentIndexPromises.push(stream.createSegmentIndex());
562-
}
563-
}
564-
if (createSegmentIndexPromises.length > 0) {
565-
await Promise.all(createSegmentIndexPromises);
566-
}
567-
}
568560
}
569561

570562
this.manifestPromise_.resolve();
@@ -590,6 +582,16 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
590582
// audio-video.
591583
shaka.media.PreloadManager.filterForAVVariants_(this.manifest_);
592584

585+
const tracksChangedInitial = this.manifestFilterer_.applyRestrictions(
586+
this.manifest_);
587+
if (tracksChangedInitial) {
588+
const event = this.makeEvent_(
589+
shaka.util.FakeEvent.EventName.TracksChanged);
590+
await Promise.resolve();
591+
this.throwIfDestroyed_();
592+
this.dispatchEvent(event);
593+
}
594+
593595
const now = Date.now() / 1000;
594596
const delta = now - startTime;
595597
this.stats_.setManifestTime(delta);
@@ -614,23 +616,10 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
614616

615617
this.drmEngine_.configure(this.config_.drm, () => this.isPreload_);
616618

617-
const tracksChangedInitial = this.manifestFilterer_.applyRestrictions(
618-
this.manifest_);
619-
if (tracksChangedInitial) {
620-
const event = this.makeEvent_(
621-
shaka.util.FakeEvent.EventName.TracksChanged);
622-
await Promise.resolve();
623-
this.throwIfDestroyed_();
624-
this.dispatchEvent(event);
625-
}
626-
627619
const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
628620
this.manifest_.variants);
629621
let isLive = true;
630-
// In HLS we need to parse the media playlist to know if it is Live or not.
631-
// So at this point we don't know yet. By default we assume it is Live.
632-
if (this.manifest_ && this.manifest_.presentationTimeline &&
633-
this.manifest_.type != shaka.media.ManifestParser.HLS) {
622+
if (this.manifest_ && this.manifest_.presentationTimeline) {
634623
isLive = this.manifest_.presentationTimeline.isLive();
635624
}
636625
await this.drmEngine_.initForPlayback(
@@ -679,13 +668,13 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
679668
}
680669

681670
/**
682-
* Performs a final filtering of the manifest, and chooses the initial
683-
* variant. Also prefetches segments.
671+
* Performs a filtering of the manifest, and chooses the initial
672+
* variant.
684673
*
685674
* @return {!Promise}
686675
* @private
687676
*/
688-
async chooseInitialVariantAndPrefetchInner_() {
677+
async chooseInitialVariant_() {
689678
goog.asserts.assert(
690679
this.manifest_, 'The manifest should already be parsed.');
691680

@@ -724,40 +713,92 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
724713
this.abrManager_.configure(this.config_.abr);
725714
}
726715

727-
if (this.allowPrefetch_) {
728-
const isLive = this.manifest_.presentationTimeline.isLive();
729-
// Prefetch segments for the predicted first variant.
730-
// We start these here, but don't wait for them; it's okay to start the
731-
// full load process while the segments are being prefetched.
732-
const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
733-
this.manifest_.variants);
734-
const adaptationSet = this.currentAdaptationSetCriteria_.create(
735-
playableVariants);
736-
// Guess what the first variant will be, based on a SimpleAbrManager.
737-
this.abrManager_.configure(this.config_.abr);
738-
this.abrManager_.setVariants(Array.from(adaptationSet.values()));
739-
const variant = this.abrManager_.chooseVariant();
740-
if (variant) {
741-
const promises = [];
742-
this.prefetchedVariant_ = variant;
743-
if (variant.video) {
744-
promises.push(this.prefetchStream_(variant.video, isLive));
745-
}
746-
if (variant.audio) {
747-
promises.push(this.prefetchStream_(variant.audio, isLive));
748-
}
749-
const textStream = this.chooseTextStream_();
750-
if (textStream && shaka.util.StreamUtils.shouldInitiallyShowText(
751-
variant.audio, textStream, this.config_)) {
752-
promises.push(this.prefetchStream_(textStream, isLive));
753-
this.prefetchedTextStream_ = textStream;
716+
const variant = this.configureAbrManagerAndChooseVariant_();
717+
if (variant &&
718+
this.shouldCreateSegmentIndexBeforeDrmEngineInitialization_()) {
719+
const createSegmentIndexPromises = [];
720+
for (const stream of [variant.video, variant.audio]) {
721+
if (stream && !stream.segmentIndex) {
722+
createSegmentIndexPromises.push(stream.createSegmentIndex());
754723
}
724+
}
725+
if (createSegmentIndexPromises.length > 0) {
726+
await Promise.all(createSegmentIndexPromises);
727+
}
728+
}
729+
}
755730

756-
await Promise.all(promises);
731+
/**
732+
* @return {boolean}
733+
* @private
734+
*/
735+
shouldCreateSegmentIndexBeforeDrmEngineInitialization_() {
736+
goog.asserts.assert(
737+
this.manifest_, 'The manifest should already be parsed.');
738+
739+
// If we only have one variant, it is useful to preload it, because it will
740+
// be the only one we can use.
741+
if (this.manifest_.variants.length == 1) {
742+
return true;
743+
}
744+
745+
// In HLS, DRM information is usually included in the media playlist, so we
746+
// need to download the media playlist to get the real information.
747+
if (this.manifest_.type == shaka.media.ManifestParser.HLS) {
748+
return true;
749+
}
750+
751+
return false;
752+
}
753+
754+
/**
755+
* Prefetches segments.
756+
*
757+
* @return {!Promise}
758+
* @private
759+
*/
760+
async prefetchInner_() {
761+
const variant = this.configureAbrManagerAndChooseVariant_();
762+
if (variant) {
763+
const isLive = this.manifest_.presentationTimeline.isLive();
764+
const promises = [];
765+
this.prefetchedVariant_ = variant;
766+
if (variant.video) {
767+
promises.push(this.prefetchStream_(variant.video, isLive));
757768
}
769+
if (variant.audio) {
770+
promises.push(this.prefetchStream_(variant.audio, isLive));
771+
}
772+
const textStream = this.chooseTextStream_();
773+
if (textStream && shaka.util.StreamUtils.shouldInitiallyShowText(
774+
variant.audio, textStream, this.config_)) {
775+
promises.push(this.prefetchStream_(textStream, isLive));
776+
this.prefetchedTextStream_ = textStream;
777+
}
778+
779+
await Promise.all(promises);
758780
}
759781
}
760782

783+
/**
784+
* @return {shaka.extern.Variant}
785+
* @private
786+
*/
787+
configureAbrManagerAndChooseVariant_() {
788+
goog.asserts.assert(this.currentAdaptationSetCriteria_,
789+
'Must have an AdaptationSetCriteria');
790+
791+
const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
792+
this.manifest_.variants);
793+
const adaptationSet = this.currentAdaptationSetCriteria_.create(
794+
playableVariants);
795+
// Guess what the first variant will be, based on a SimpleAbrManager.
796+
this.abrManager_.configure(this.config_.abr);
797+
this.abrManager_.setVariants(Array.from(adaptationSet.values()));
798+
799+
return this.abrManager_.chooseVariant();
800+
}
801+
761802
/**
762803
* @return {?shaka.extern.Stream}
763804
* @private

test/player_unit.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4157,7 +4157,8 @@ describe('Player', () => {
41574157

41584158
await player.load(fakeManifestUri, 0, fakeMimeType);
41594159
expect(abrManager.setVariants).toHaveBeenCalled();
4160-
const variants = abrManager.setVariants.calls.argsFor(0)[0];
4160+
const lastCallIndex = abrManager.setVariants.calls.count() - 1;
4161+
const variants = abrManager.setVariants.calls.argsFor(lastCallIndex)[0];
41614162
// We've already chosen codecs, so only 3 tracks should remain.
41624163
expect(variants.length).toBe(3);
41634164
// They should be the low-bandwidth ones.

0 commit comments

Comments
 (0)