createVariants_(
      audioInfos, videoInfos, bandwidth, width, height, frameRate, videoRange,
      videoLayout, drmInfos, keyIds, iFrameStreams) {
    const ContentType = shaka.util.ManifestParserUtils.ContentType;
    const DrmUtils = shaka.drm.DrmUtils;

    for (const info of videoInfos) {
      this.addVideoAttributes_(
          info.stream, width, height, frameRate, videoRange, videoLayout,
          /** colorGamut= */ null);
    }

    // In case of audio-only or video-only content or the audio/video is
    // disabled by the config, we create an array of one item containing
    // a null. This way, the double-loop works for all kinds of content.
    // NOTE: we currently don't have support for audio-only content.
    const disableAudio = this.config_.disableAudio;
    if (!audioInfos.length || disableAudio) {
      audioInfos = [null];
    }
    const disableVideo = this.config_.disableVideo;
    if (!videoInfos.length || disableVideo) {
      videoInfos = [null];
    }

    const variants = [];
    for (const audioInfo of audioInfos) {
      for (const videoInfo of videoInfos) {
        const audioStream = audioInfo ? audioInfo.stream : null;
        if (audioStream) {
          audioStream.drmInfos = drmInfos;
          audioStream.keyIds = keyIds;
        }
        const videoStream = videoInfo ? videoInfo.stream : null;
        if (videoStream) {
          videoStream.drmInfos = drmInfos;
          videoStream.keyIds = keyIds;
          if (!this.config_.disableIFrames) {
            shaka.util.StreamUtils.setBetterIFrameStream(
                videoStream, iFrameStreams);
          }
        }
        if (videoStream && !audioStream) {
          videoStream.bandwidth = bandwidth;
        }
        if (!videoStream && audioStream) {
          audioStream.bandwidth = bandwidth;
        }
        const audioDrmInfos = audioInfo ? audioInfo.stream.drmInfos : null;
        const videoDrmInfos = videoInfo ? videoInfo.stream.drmInfos : null;
        const videoStreamUri =
            videoInfo ? videoInfo.getUris().sort().join(',') : '';
        const audioStreamUri =
            audioInfo ? audioInfo.getUris().sort().join(',') : '';
        const codecs = [];
        if (audioStream && audioStream.codecs) {
          codecs.push(audioStream.codecs);
        }
        if (videoStream && videoStream.codecs) {
          codecs.push(videoStream.codecs);
        }
        const variantUriKey = [
          videoStreamUri,
          audioStreamUri,
          codecs.sort(),
        ].join('-');

        if (audioStream && videoStream) {
          if (!DrmUtils.areDrmCompatible(audioDrmInfos, videoDrmInfos)) {
            shaka.log.warning(
                'Incompatible DRM info in HLS variant.  Skipping.');
            continue;
          }
        }

        if (this.variantUriSet_.has(variantUriKey)) {
          // This happens when two variants only differ in their text streams.
          shaka.log.debug(
              'Skipping variant which only differs in text streams.');
          continue;
        }

        // Since both audio and video are of the same type, this assertion will
        // catch certain mistakes at runtime that the compiler would miss.
        goog.asserts.assert(!audioStream ||
            audioStream.type == ContentType.AUDIO, 'Audio parameter mismatch!');
        goog.asserts.assert(!videoStream ||
            videoStream.type == ContentType.VIDEO, 'Video parameter mismatch!');

        const variant = {
          id: this.globalId_++,
          language: audioStream ? audioStream.language : 'und',
          disabledUntilTime: 0,
          primary: (!!audioStream && audioStream.primary) ||
              (!!videoStream && videoStream.primary),
          audio: audioStream,
          video: videoStream,
          bandwidth,
          allowedByApplication: true,
          allowedByKeySystem: true,
          decodingInfos: [],
        };

        variants.push(variant);
        this.variantUriSet_.add(variantUriKey);
      }
    }
    return variants;
  }
@param {!Array<shaka.hls.HlsParser.StreamInfo>} audioInfos
@param {!Array<shaka.hls.HlsParser.StreamInfo>} videoInfos
@param {number} bandwidth
@param {?string} width
@param {?string} height
@param {?string} frameRate
@param {?string} videoRange
@param {?string} videoLayout
@param {!Array<shaka.extern.DrmInfo>} drmInfos
@param {!Set<string>} keyIds
@param {!Array<!shaka.extern.Stream>} iFrameStreams
@return {!Array<!shaka.extern.Variant>}
@private