async load(assetUriOrPreloader, startTime = null, mimeType) {
    // Do not allow the player to be used after |destroy| is called.
    if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
      throw this.createAbortLoadError_();
    }

    /** @type {?shaka.media.PreloadManager} */
    let preloadManager = null;
    let assetUri = '';
    if (assetUriOrPreloader instanceof shaka.media.PreloadManager) {
      if (assetUriOrPreloader.isDestroyed()) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.PLAYER,
            shaka.util.Error.Code.PRELOAD_DESTROYED);
      }
      preloadManager = assetUriOrPreloader;
      assetUri = preloadManager.getAssetUri() || '';
    } else {
      assetUri = assetUriOrPreloader || '';
    }

    // Quickly acquire the mutex, so this will wait for other top-level
    // operations.
    await this.mutex_.acquire('load');
    this.mutex_.release();

    if (!this.video_) {
      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.PLAYER,
          shaka.util.Error.Code.NO_VIDEO_ELEMENT);
    }

    if (this.assetUri_) {
      // Note: This is used to avoid the destruction of the nextUrl
      // preloadManager that can be the current one.
      this.assetUri_ = assetUri;
      await this.unload(/* initializeMediaSource= */ false);
    }

    // Add a mechanism to detect if the load process has been interrupted by a
    // call to another top-level operation (unload, load, etc).
    const operationId = ++this.operationId_;
    const detectInterruption = async () => {
      if (this.operationId_ != operationId) {
        if (preloadManager) {
          await preloadManager.destroy();
        }
        throw this.createAbortLoadError_();
      }
    };

    /**
     * Wraps a given operation with mutex.acquire and mutex.release, along with
     * calls to detectInterruption, to catch any other top-level calls happening
     * while waiting for the mutex.
     * @param {function():!Promise} operation
     * @param {string} mutexIdentifier
     * @return {!Promise}
     */
    const mutexWrapOperation = async (operation, mutexIdentifier) => {
      try {
        await this.mutex_.acquire(mutexIdentifier);
        await detectInterruption();
        await operation();
        await detectInterruption();
        if (preloadManager && this.config_) {
          preloadManager.reconfigure(this.config_);
        }
      } finally {
        this.mutex_.release();
      }
    };

    try {
      if (startTime == null && preloadManager) {
        startTime = preloadManager.getStartTime();
      }
      this.startTime_ = startTime;
      this.fullyLoaded_ = false;

      // We dispatch the loading event when someone calls |load| because we want
      // to surface the user intent.
      this.dispatchEvent(shaka.Player.makeEvent_(
          shaka.util.FakeEvent.EventName.Loading));

      if (preloadManager) {
        mimeType = preloadManager.getMimeType();
      } else if (!mimeType) {
        await mutexWrapOperation(async () => {
          mimeType = await this.guessMimeType_(assetUri);
        }, 'guessMimeType_');
      }

      const wasPreloaded = !!preloadManager;
      if (!preloadManager) {
        // For simplicity, if an asset is NOT preloaded, start an internal
        // "preload" here without prefetch.
        // That way, both a preload and normal load can follow the same code
        // paths.
        // NOTE: await preloadInner_ can be outside the mutex because it should
        // not mutate "this".
        preloadManager = await this.preloadInner_(
            assetUri, startTime, mimeType, /* standardLoad= */ true,
            this.config_);
        if (preloadManager) {
          preloadManager.markIsLoad();
          preloadManager.setEventHandoffTarget(this);
          this.stats_ = preloadManager.getStats();
          preloadManager.start();
          // Silence "uncaught error" warnings from this. Unless we are
          // interrupted, we will check the result of this process and respond
          // appropriately. If we are interrupted, we can ignore any error
          // there.
          preloadManager.waitForFinish().catch(() => {});
        } else {
          this.stats_ = new shaka.util.Stats();
        }
      } else {
        // Hook up events, so any events emitted by the preloadManager will
        // instead be emitted by the player.
        preloadManager.setEventHandoffTarget(this);
        this.stats_ = preloadManager.getStats();
      }
      // Now, if there is no preload manager, that means that this is a src=
      // asset.
      const shouldUseSrcEquals = !preloadManager;

      const startTimeOfLoad = Date.now() / 1000;

      // Stats are for a single playback/load session. Stats must be initialized
      // before we allow calls to |updateStateHistory|.
      this.stats_ =
          preloadManager ? preloadManager.getStats() : new shaka.util.Stats();

      this.assetUri_ = assetUri;
      this.mimeType_ = mimeType || null;

      // Make sure that the app knows of the new buffering state.
      this.updateBufferState_();

      const bufferRange = () => {
        const bufferedInfo = this.getBufferedInfo();
        const range = {
          start: 0,
          end: 0,
        };
        if (bufferedInfo.total.length) {
          range.start = Infinity;
          for (const buffered of bufferedInfo.total) {
            if (buffered.start < range.start) {
              range.start = buffered.start;
            }
            if (buffered.end > range.end) {
              range.end = buffered.end;
            }
          }
        }
        return range;
      };

      this.metadataRegionTimeline_ =
          new shaka.media.RegionTimeline(bufferRange);
      this.metadataRegionTimeline_.addEventListener('regionadd', (event) => {
        /** @type {shaka.extern.MetadataTimelineRegionInfo} */
        const region = event['region'];
        this.dispatchMetadataEvent_(region,
            shaka.util.FakeEvent.EventName.MetadataAdded);
      });

      if (shouldUseSrcEquals) {
        await mutexWrapOperation(async () => {
          goog.asserts.assert(mimeType, 'We should know the mimeType by now!');
          await this.initializeSrcEqualsDrmInner_(mimeType);
        }, 'initializeSrcEqualsDrmInner_');
        await mutexWrapOperation(async () => {
          goog.asserts.assert(mimeType, 'We should know the mimeType by now!');
          await this.srcEqualsInner_(startTimeOfLoad, mimeType);
        }, 'srcEqualsInner_');
      } else {
        this.emsgRegionTimeline_ =
            new shaka.media.RegionTimeline(bufferRange);
        // Wait for the manifest to be parsed.
        await mutexWrapOperation(async () => {
          await preloadManager.waitForManifest();
          // Retrieve the manifest. This is specifically put before the media
          // source engine is initialized, for the benefit of event handlers.
          this.parserFactory_ = preloadManager.getParserFactory();
          this.parser_ = preloadManager.receiveParser();
          this.manifest_ = preloadManager.getManifest();
        }, 'waitForFinish');

        if (!this.mediaSourceEngine_) {
          await mutexWrapOperation(async () => {
            await this.initializeMediaSourceEngineInner_();
          }, 'initializeMediaSourceEngineInner_');
        }

        if (this.manifest_ && this.manifest_.textStreams.length) {
          if (this.textDisplayer_.enableTextDisplayer) {
            this.textDisplayer_.enableTextDisplayer();
          } else {
            shaka.Deprecate.deprecateFeature(5,
                'Text displayer w/ enableTextDisplayer',
                'Text displayer should have a "enableTextDisplayer" method!');
          }
        }

        // Wait for the preload manager to do all of the loading it can do.
        await mutexWrapOperation(async () => {
          await preloadManager.waitForFinish();
        }, 'waitForFinish');

        // Get manifest and associated values from preloader.
        this.config_ = preloadManager.getConfiguration();
        this.manifestFilterer_ = preloadManager.getManifestFilterer();
        if (this.parser_ && this.parser_.setMediaElement && this.video_) {
          this.parser_.setMediaElement(this.video_);
        }
        this.regionTimeline_ = preloadManager.receiveRegionTimeline();
        this.qualityObserver_ = preloadManager.getQualityObserver();
        const currentAdaptationSetCriteria =
            preloadManager.getCurrentAdaptationSetCriteria();
        if (currentAdaptationSetCriteria) {
          this.currentAdaptationSetCriteria_ = currentAdaptationSetCriteria;
        }
        if (wasPreloaded && this.video_ && this.video_.nodeName === 'AUDIO') {
          // Filter the variants to be audio-only after the fact.
          // As, when preloading, we don't know if we are going to be attached
          // to a video or audio element when we load, we have to do the auto
          // audio-only filtering here, post-facto.
          this.makeManifestAudioOnly_();
          // And continue to do so in the future.
          this.configure('manifest.disableVideo', true);
        }

        // Init DRM engine if it's not created yet (happens on polyfilled EME).
        if (!preloadManager.getDrmEngine()) {
          await mutexWrapOperation(async () => {
            await preloadManager.initializeDrm(this.video_);
          }, 'drmEngine_.init');
        }

        // Get drm engine from preloader, then finalize it.
        this.drmEngine_ = preloadManager.receiveDrmEngine();
        await mutexWrapOperation(async () => {
          await this.drmEngine_.attach(this.video_);
        }, 'drmEngine_.attach');

        // Also get the ABR manager, which has special logic related to being
        // received.
        const abrManagerFactory = preloadManager.getAbrManagerFactory();
        if (abrManagerFactory) {
          if (!this.abrManagerFactory_ ||
              this.abrManagerFactory_ != abrManagerFactory) {
            this.abrManager_ = preloadManager.receiveAbrManager();
            this.abrManagerFactory_ = preloadManager.getAbrManagerFactory();
            if (typeof this.abrManager_.setMediaElement != 'function') {
              shaka.Deprecate.deprecateFeature(5,
                  'AbrManager w/o setMediaElement',
                  'Please use an AbrManager with setMediaElement function.');
              this.abrManager_.setMediaElement = () => {};
            }
            if (typeof this.abrManager_.setCmsdManager != 'function') {
              shaka.Deprecate.deprecateFeature(5,
                  'AbrManager w/o setCmsdManager',
                  'Please use an AbrManager with setCmsdManager function.');
              this.abrManager_.setCmsdManager = () => {};
            }
            if (typeof this.abrManager_.trySuggestStreams != 'function') {
              shaka.Deprecate.deprecateFeature(5,
                  'AbrManager w/o trySuggestStreams',
                  'Please use an AbrManager with trySuggestStreams function.');
              this.abrManager_.trySuggestStreams = () => {};
            }
          }
        }

        // Load the asset.
        const segmentPrefetchById =
              preloadManager.receiveSegmentPrefetchesById();
        const prefetchedVariant = preloadManager.getPrefetchedVariant();
        await mutexWrapOperation(async () => {
          await this.loadInner_(
              startTimeOfLoad, prefetchedVariant, segmentPrefetchById);
        }, 'loadInner_');
        preloadManager.stopQueuingLatePhaseQueuedOperations();

        if (this.mimeType_ &&
            shaka.device.DeviceFactory.getDevice().supportsAirPlay() &&
            shaka.util.MimeUtils.isHlsType(this.mimeType_)) {
          this.mediaSourceEngine_.addSecondarySource(
              this.assetUri_, this.mimeType_);
        }
      }
      this.dispatchEvent(shaka.Player.makeEvent_(
          shaka.util.FakeEvent.EventName.Loaded));
    } catch (error) {
      if (error && error.code != shaka.util.Error.Code.LOAD_INTERRUPTED) {
        await this.unload(/* initializeMediaSource= */ false);
      }
      throw error;
    } finally {
      if (preloadManager) {
        // This will cause any resources that were generated but not used to be
        // properly destroyed or released.
        await preloadManager.destroy();
      }
      this.preloadNextUrl_ = null;
    }
  }
Loads a new stream.
If another stream was already playing, first unloads that stream.

@param {string|shaka.media.PreloadManager} assetUriOrPreloader
@param {?number|Date=} startTime
   When <code>startTime</code> is <code>null</code> or
   <code>undefined</code>, playback will start at the default start time (0
   for VOD and liveEdge for LIVE).
@param {?string=} mimeType
@return {!Promise}
@export