async combinePeriods(periods, isDynamic, isPatchUpdate = false) {
    const ContentType = shaka.util.ManifestParserUtils.ContentType;

    // Optimization: for single-period VOD, do nothing.  This makes sure
    // single-period DASH content will be 100% accurately represented in the
    // output.
    if (!isDynamic && periods.length == 1) {
      // We need to filter out duplicates, so call getStreamsPerPeriod()
      // so it will do that by usage of Map.
      const {
        audioStreamsPerPeriod,
        videoStreamsPerPeriod,
        textStreamsPerPeriod,
        imageStreamsPerPeriod,
      } = this.getStreamsPerPeriod_(periods, /* addDummy= */ false);
      this.audioStreams_ = Array.from(audioStreamsPerPeriod[0].values());
      this.videoStreams_ = Array.from(videoStreamsPerPeriod[0].values());
      this.textStreams_ = Array.from(textStreamsPerPeriod[0].values());
      this.imageStreams_ = Array.from(imageStreamsPerPeriod[0].values());
    } else {
      // How many periods we've seen before which are not included in this call.
      const periodsMissing = isPatchUpdate ? this.usedPeriodIds_.size : 0;
      // Find the first period we haven't seen before.  Tag all the periods we
      // see now as "used".
      let firstNewPeriodIndex = -1;
      for (let i = 0; i < periods.length; i++) {
        const period = periods[i];
        if (this.usedPeriodIds_.has(period.id)) {
          // This isn't new.
        } else {
          // This one _is_ new.
          this.usedPeriodIds_.add(period.id);

          if (firstNewPeriodIndex == -1) {
            // And it's the _first_ new one.
            firstNewPeriodIndex = i;
          }
        }
      }

      if (firstNewPeriodIndex == -1) {
        // Nothing new? Nothing to do.
        return;
      }

      const {
        audioStreamsPerPeriod,
        videoStreamsPerPeriod,
        textStreamsPerPeriod,
        imageStreamsPerPeriod,
      } = this.getStreamsPerPeriod_(periods, /* addDummy= */ true);

      await Promise.all([
        this.combine_(
            this.audioStreams_,
            audioStreamsPerPeriod,
            firstNewPeriodIndex,
            shaka.util.PeriodCombiner.cloneStream_,
            shaka.util.PeriodCombiner.concatenateStreams_,
            periodsMissing),
        this.combine_(
            this.videoStreams_,
            videoStreamsPerPeriod,
            firstNewPeriodIndex,
            shaka.util.PeriodCombiner.cloneStream_,
            shaka.util.PeriodCombiner.concatenateStreams_,
            periodsMissing),
        this.combine_(
            this.textStreams_,
            textStreamsPerPeriod,
            firstNewPeriodIndex,
            shaka.util.PeriodCombiner.cloneStream_,
            shaka.util.PeriodCombiner.concatenateStreams_,
            periodsMissing),
        this.combine_(
            this.imageStreams_,
            imageStreamsPerPeriod,
            firstNewPeriodIndex,
            shaka.util.PeriodCombiner.cloneStream_,
            shaka.util.PeriodCombiner.concatenateStreams_,
            periodsMissing),
      ]);
    }

    // Create variants for all audio/video combinations.
    let nextVariantId = 0;
    const variants = [];
    if (!this.videoStreams_.length || !this.audioStreams_.length) {
      // For audio-only or video-only content, just give each stream its own
      // variant.
      const streams = this.videoStreams_.length ? this.videoStreams_ :
        this.audioStreams_;
      for (const stream of streams) {
        const id = nextVariantId++;
        let bandwidth = stream.bandwidth || 0;
        if (stream.dependencyStream) {
          bandwidth += stream.dependencyStream.bandwidth || 0;
        }
        variants.push({
          id,
          language: stream.language,
          disabledUntilTime: 0,
          primary: stream.primary,
          audio: stream.type == ContentType.AUDIO ? stream : null,
          video: stream.type == ContentType.VIDEO ? stream : null,
          bandwidth,
          drmInfos: stream.drmInfos,
          allowedByApplication: true,
          allowedByKeySystem: true,
          decodingInfos: [],
        });
      }
    } else {
      for (const audio of this.audioStreams_) {
        for (const video of this.videoStreams_) {
          const commonDrmInfos = shaka.drm.DrmUtils.getCommonDrmInfos(
              audio.drmInfos, video.drmInfos);

          if (audio.drmInfos.length && video.drmInfos.length &&
              !commonDrmInfos.length) {
            shaka.log.warning(
                'Incompatible DRM in audio & video, skipping variant creation.',
                audio, video);
            continue;
          }

          let bandwidth = (audio.bandwidth || 0) + (video.bandwidth || 0);
          if (audio.dependencyStream) {
            bandwidth += audio.dependencyStream.bandwidth || 0;
          }
          if (video.dependencyStream) {
            bandwidth += video.dependencyStream.bandwidth || 0;
          }

          const id = nextVariantId++;
          variants.push({
            id,
            language: audio.language,
            disabledUntilTime: 0,
            primary: audio.primary,
            audio,
            video,
            bandwidth,
            drmInfos: commonDrmInfos,
            allowedByApplication: true,
            allowedByKeySystem: true,
            decodingInfos: [],
          });
        }
      }
    }

    this.variants_ = variants;
  }
@param {!Array<shaka.extern.Period>} periods
@param {boolean} isDynamic
@param {boolean=} isPatchUpdate
@return {!Promise}

@export