static async combineDbStreams(streamDbsPerPeriod) {
    const ContentType = shaka.util.ManifestParserUtils.ContentType;
    const PeriodCombiner = shaka.util.PeriodCombiner;

    // Optimization: for single-period content, do nothing.  This makes sure
    // single-period DASH or any HLS content stored offline will be 100%
    // accurately represented in the output.
    if (streamDbsPerPeriod.length == 1) {
      return streamDbsPerPeriod[0];
    }

    const audioStreamDbsPerPeriod = streamDbsPerPeriod.map(
        (streams) => new Map(streams
            .filter((s) => s.type === ContentType.AUDIO)
            .map((s) => [PeriodCombiner.generateAudioKey_(s), s])));
    const videoStreamDbsPerPeriod = streamDbsPerPeriod.map(
        (streams) => new Map(streams
            .filter((s) => s.type === ContentType.VIDEO)
            .map((s) => [PeriodCombiner.generateVideoKey_(s), s])));
    const textStreamDbsPerPeriod = streamDbsPerPeriod.map(
        (streams) => new Map(streams
            .filter((s) => s.type === ContentType.TEXT)
            .map((s) => [PeriodCombiner.generateTextKey_(s), s])));
    const imageStreamDbsPerPeriod = streamDbsPerPeriod.map(
        (streams) => new Map(streams
            .filter((s) => s.type === ContentType.IMAGE)
            .map((s) => [PeriodCombiner.generateImageKey_(s), s])));

    // It's okay to have a period with no text or images, but our algorithm
    // fails on any period without matching streams.  So we add dummy streams to
    // each period.  Since we combine text streams by language and image streams
    // by resolution, we might need a dummy even in periods with these streams
    // already.
    for (const textStreams of textStreamDbsPerPeriod) {
      const dummy = PeriodCombiner.dummyStreamDB_(ContentType.TEXT);
      textStreams.set(PeriodCombiner.generateTextKey_(dummy), dummy);
    }
    for (const imageStreams of imageStreamDbsPerPeriod) {
      const dummy = PeriodCombiner.dummyStreamDB_(ContentType.IMAGE);
      imageStreams.set(PeriodCombiner.generateImageKey_(dummy), dummy);
    }

    const periodCombiner = new shaka.util.PeriodCombiner();

    const combinedAudioStreamDbs = await periodCombiner.combine_(
        /* outputStreams= */ [],
        audioStreamDbsPerPeriod,
        /* firstNewPeriodIndex= */ 0,
        shaka.util.PeriodCombiner.cloneStreamDB_,
        shaka.util.PeriodCombiner.concatenateStreamDBs_,
        /* periodsMissing= */ 0);

    const combinedVideoStreamDbs = await periodCombiner.combine_(
        /* outputStreams= */ [],
        videoStreamDbsPerPeriod,
        /* firstNewPeriodIndex= */ 0,
        shaka.util.PeriodCombiner.cloneStreamDB_,
        shaka.util.PeriodCombiner.concatenateStreamDBs_,
        /* periodsMissing= */ 0);

    const combinedTextStreamDbs = await periodCombiner.combine_(
        /* outputStreams= */ [],
        textStreamDbsPerPeriod,
        /* firstNewPeriodIndex= */ 0,
        shaka.util.PeriodCombiner.cloneStreamDB_,
        shaka.util.PeriodCombiner.concatenateStreamDBs_,
        /* periodsMissing= */ 0);

    const combinedImageStreamDbs = await periodCombiner.combine_(
        /* outputStreams= */ [],
        imageStreamDbsPerPeriod,
        /* firstNewPeriodIndex= */ 0,
        shaka.util.PeriodCombiner.cloneStreamDB_,
        shaka.util.PeriodCombiner.concatenateStreamDBs_,
        /* periodsMissing= */ 0);

    // Recreate variantIds from scratch in the output.
    // HLS content is always single-period, so the early return at the top of
    // this method would catch all HLS content.  DASH content stored with v3.0
    // will already be flattened before storage.  Therefore the only content
    // that reaches this point is multi-period DASH content stored before v3.0.
    // Such content always had variants generated from all combinations of audio
    // and video, so we can simply do that now without loss of correctness.
    let nextVariantId = 0;
    if (!combinedVideoStreamDbs.length || !combinedAudioStreamDbs.length) {
      // For audio-only or video-only content, just give each stream its own
      // variant ID.
      const combinedStreamDbs =
          combinedVideoStreamDbs.concat(combinedAudioStreamDbs);
      for (const stream of combinedStreamDbs) {
        stream.variantIds = [nextVariantId++];
      }
    } else {
      for (const audio of combinedAudioStreamDbs) {
        for (const video of combinedVideoStreamDbs) {
          const id = nextVariantId++;
          video.variantIds.push(id);
          audio.variantIds.push(id);
        }
      }
    }

    return combinedVideoStreamDbs
        .concat(combinedAudioStreamDbs)
        .concat(combinedTextStreamDbs)
        .concat(combinedImageStreamDbs);
  }
Stitch together DB streams across periods, taking a mix of stream types.
The offline database does not separate these by type.

Unlike the DASH case, this does not need to maintain any state for manifest
updates.

@param {!Array<!Array<shaka.extern.StreamDB>>} streamDbsPerPeriod
@return {!Promise<!Array<shaka.extern.StreamDB>>}