async store_(uri, appMetadata, externalThumbnails, externalText,
      getParser, config, downloader) {
    this.requireSupport_();

    // Since we will need to use |parser|, |drmEngine|, |activeHandle|, and
    // |muxer| in the catch/finally blocks, we need to define them out here.
    // Since they may not get initialized when we enter the catch/finally block,
    // we need to assume that they may be null/undefined when we get there.

    /** @type {?shaka.extern.ManifestParser} */
    let parser = null;
    /** @type {?shaka.drm.DrmEngine} */
    let drmEngine = null;
    /** @type {shaka.offline.StorageMuxer} */
    const muxer = new shaka.offline.StorageMuxer();
    /** @type {?shaka.offline.StorageCellHandle} */
    let activeHandle = null;
    /** @type {?number} */
    let manifestId = null;

    // This will be used to store any errors from drm engine. Whenever drm
    // engine is passed to another function to do work, we should check if this
    // was set.
    let drmError = null;

    try {
      parser = await getParser();

      const manifest = await this.parseManifest(uri, parser, config);

      // Check if we were asked to destroy ourselves while we were "away"
      // downloading the manifest.
      this.ensureNotDestroyed_();

      // Check if we can even download this type of manifest before trying to
      // create the drm engine.
      const canDownload = !manifest.presentationTimeline.isLive() &&
                          !manifest.presentationTimeline.isInProgress();
      if (!canDownload) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.STORAGE,
            shaka.util.Error.Code.CANNOT_STORE_LIVE_OFFLINE,
            uri);
      }

      for (const thumbnailUri of externalThumbnails) {
        const imageStream =
            // eslint-disable-next-line no-await-in-loop
            await this.createExternalImageStream_(thumbnailUri, manifest);
        manifest.imageStreams.push(imageStream);
        this.ensureNotDestroyed_();
      }

      for (const text of externalText) {
        const textStream =
            // eslint-disable-next-line no-await-in-loop
            await this.createExternalTextStream_(manifest,
                text.uri, text.language, text.kind, text.mime, text.codecs);
        manifest.textStreams.push(textStream);
        this.ensureNotDestroyed_();
      }

      shaka.drm.DrmEngine.configureClearKey(
          config.drm.clearKeys, manifest.variants);

      const clearKeyDataLicenseServerUri = manifest.variants.some((v) => {
        if (v.audio) {
          for (const drmInfo of v.audio.drmInfos) {
            if (drmInfo.licenseServerUri.startsWith('data:')) {
              return true;
            }
          }
        }
        if (v.video) {
          for (const drmInfo of v.video.drmInfos) {
            if (drmInfo.licenseServerUri.startsWith('data:')) {
              return true;
            }
          }
        }
        return false;
      });

      let usePersistentLicense = config.offline.usePersistentLicense;
      if (clearKeyDataLicenseServerUri) {
        usePersistentLicense = false;
      }

      // Create the DRM engine, and load the keys in the manifest.
      drmEngine = await this.createDrmEngine(
          manifest,
          (e) => { drmError = drmError || e; },
          config,
          usePersistentLicense);

      // We could have been asked to destroy ourselves while we were "away"
      // creating the drm engine.
      this.ensureNotDestroyed_();
      if (drmError) {
        throw drmError;
      }

      await this.filterManifest_(
          manifest, drmEngine, config, usePersistentLicense);

      await muxer.init();
      this.ensureNotDestroyed_();

      // Get the cell that we are saving the manifest to. Once we get a cell
      // we will only reference the cell and not the muxer so that the manifest
      // and segments will all be saved to the same cell.
      activeHandle = await muxer.getActive();
      this.ensureNotDestroyed_();

      goog.asserts.assert(drmEngine, 'drmEngine should be non-null here.');
      const {manifestDB, toDownload} = this.makeManifestDB_(
          drmEngine, manifest, uri, appMetadata, config, downloader,
          usePersistentLicense);

      // Store the empty manifest, before downloading the segments.
      const ids = await activeHandle.cell.addManifests([manifestDB]);
      this.ensureNotDestroyed_();
      manifestId = ids[0];

      goog.asserts.assert(drmEngine, 'drmEngine should be non-null here.');
      this.ensureNotDestroyed_();
      if (drmError) {
        throw drmError;
      }

      await this.downloadSegments_(toDownload, manifestId, manifestDB,
          downloader, config, activeHandle.cell, manifest, drmEngine,
          usePersistentLicense);
      this.ensureNotDestroyed_();

      this.setManifestDrmFields_(
          manifest, manifestDB, drmEngine, usePersistentLicense);
      await activeHandle.cell.updateManifest(manifestId, manifestDB);
      this.ensureNotDestroyed_();

      const offlineUri = shaka.offline.OfflineUri.manifest(
          activeHandle.path.mechanism, activeHandle.path.cell, manifestId);

      return shaka.offline.StoredContentUtils.fromManifestDB(
          offlineUri, manifestDB);
    } catch (e) {
      if (manifestId != null) {
        await shaka.offline.Storage.cleanStoredManifest(manifestId);
      }

      // If we already had an error, ignore this error to avoid hiding
      // the original error.
      throw drmError || e;
    } finally {
      await muxer.destroy();

      if (parser) {
        await parser.stop();
      }

      if (drmEngine) {
        await drmEngine.destroy();
      }
    }
  }
See |shaka.offline.Storage.store| for details.

@param {string} uri
@param {!Object} appMetadata
@param {!Array<string>} externalThumbnails
@param {!Array<shaka.extern.ExtraText>} externalText
@param {function(): !Promise<shaka.extern.ManifestParser>} getParser
@param {shaka.extern.PlayerConfiguration} config
@param {!shaka.offline.DownloadManager} downloader
@return {!Promise<shaka.extern.StoredContent>}
@private