createStream_(streamIndex, qualityLevel, timeline, drmInfos, context) {
    const TXml = shaka.util.TXml;
    const ContentType = shaka.util.ManifestParserUtils.ContentType;
    const MssParser = shaka.mss.MssParser;

    const type = streamIndex.attributes['Type'];
    const isValidType = type === 'audio' || type === 'video' ||
        type === 'text';
    if (!isValidType) {
      shaka.log.alwaysWarn('Ignoring unrecognized type:', type);
      return null;
    }

    const lang = streamIndex.attributes['Language'];
    const id = this.globalId_++;

    const bandwidth = TXml.parseAttr(
        qualityLevel, 'Bitrate', TXml.parsePositiveInt);
    const width = TXml.parseAttr(
        qualityLevel, 'MaxWidth', TXml.parsePositiveInt);
    const height = TXml.parseAttr(
        qualityLevel, 'MaxHeight', TXml.parsePositiveInt);
    const channelsCount = TXml.parseAttr(
        qualityLevel, 'Channels', TXml.parsePositiveInt);
    const audioSamplingRate = TXml.parseAttr(
        qualityLevel, 'SamplingRate', TXml.parsePositiveInt);

    let duration = context.duration;
    if (timeline.length) {
      const start = timeline[0].start;
      const end = timeline[timeline.length - 1].end;
      duration = end - start;
    }

    const presentationDuration = this.presentationTimeline_.getDuration();
    this.presentationTimeline_.setDuration(
        Math.min(duration, presentationDuration));

    /** @type {!shaka.extern.Stream} */
    const stream = {
      id: id,
      originalId: streamIndex.attributes['Name'] || String(id),
      groupId: null,
      createSegmentIndex: () => Promise.resolve(),
      closeSegmentIndex: () => Promise.resolve(),
      segmentIndex: null,
      mimeType: '',
      codecs: '',
      frameRate: undefined,
      pixelAspectRatio: undefined,
      bandwidth: bandwidth || 0,
      width: width || undefined,
      height: height || undefined,
      kind: '',
      encrypted: drmInfos.length > 0,
      drmInfos: drmInfos,
      keyIds: new Set(),
      language: shaka.util.LanguageUtils.normalize(lang || 'und'),
      originalLanguage: lang,
      label: '',
      type: '',
      primary: false,
      trickModeVideo: null,
      dependencyStream: null,
      emsgSchemeIdUris: [],
      roles: [],
      forced: false,
      channelsCount: channelsCount,
      audioSamplingRate: audioSamplingRate,
      spatialAudio: false,
      closedCaptions: null,
      hdr: undefined,
      colorGamut: undefined,
      videoLayout: undefined,
      tilesLayout: undefined,
      matchedStreams: [],
      mssPrivateData: {
        duration: duration,
        timescale: context.timescale,
        codecPrivateData: null,
      },
      accessibilityPurpose: null,
      external: false,
      fastSwitching: false,
      fullMimeTypes: new Set(),
      isAudioMuxedInVideo: false,
      baseOriginalId: null,
    };

    // This is specifically for text tracks.
    const subType = streamIndex.attributes['Subtype'];
    if (subType) {
      const role = MssParser.ROLE_MAPPING_.get(subType);
      if (role) {
        stream.roles.push(role);
      }
      if (role === 'main') {
        stream.primary = true;
      }
    }

    let fourCCValue = qualityLevel.attributes['FourCC'];

    // If FourCC not defined at QualityLevel level,
    // then get it from StreamIndex level
    if (fourCCValue === null || fourCCValue === '') {
      fourCCValue = streamIndex.attributes['FourCC'];
    }

    // If still not defined (optional for audio stream,
    // see https://msdn.microsoft.com/en-us/library/ff728116%28v=vs.95%29.aspx),
    // then we consider the stream is an audio AAC stream
    if (!fourCCValue) {
      if (type === 'audio') {
        fourCCValue = 'AAC';
      } else if (type === 'video') {
        shaka.log.alwaysWarn('FourCC is not defined whereas it is required ' +
            'for a QualityLevel element for a StreamIndex of type "video"');
        return null;
      }
    }

    // Check if codec is supported
    if (!MssParser.SUPPORTED_CODECS_.includes(fourCCValue.toUpperCase())) {
      shaka.log.alwaysWarn('Codec not supported:', fourCCValue);
      return null;
    }

    const codecPrivateData = this.getCodecPrivateData_(
        qualityLevel, type, fourCCValue, stream);
    stream.mssPrivateData.codecPrivateData = codecPrivateData;

    switch (type) {
      case 'audio':
        if (!codecPrivateData) {
          shaka.log.alwaysWarn('Quality unsupported without CodecPrivateData',
              type);
          return null;
        }
        stream.type = ContentType.AUDIO;
        // This mimetype is fake to allow the transmuxing.
        stream.mimeType = 'mss/audio/mp4';
        stream.codecs = this.getAACCodec_(
            qualityLevel, fourCCValue, codecPrivateData);
        break;
      case 'video':
        if (!codecPrivateData) {
          shaka.log.alwaysWarn('Quality unsupported without CodecPrivateData',
              type);
          return null;
        }
        stream.type = ContentType.VIDEO;
        // This mimetype is fake to allow the transmuxing.
        stream.mimeType = 'mss/video/mp4';
        stream.codecs = this.getH264Codec_(
            qualityLevel, codecPrivateData);
        break;
      case 'text':
        stream.type = ContentType.TEXT;
        stream.mimeType = 'application/mp4';
        if (fourCCValue === 'TTML' || fourCCValue === 'DFXP') {
          stream.codecs = 'stpp';
        }
        break;
    }
    stream.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
        stream.mimeType, stream.codecs));

    // Lazy-Load the segment index to avoid create all init segment at the
    // same time
    stream.createSegmentIndex = () => {
      if (stream.segmentIndex) {
        return Promise.resolve();
      }
      let initSegmentData;
      if (this.initSegmentDataByStreamId_.has(stream.id)) {
        initSegmentData = this.initSegmentDataByStreamId_.get(stream.id);
      } else {
        let videoNalus = [];
        if (stream.type == ContentType.VIDEO) {
          const codecPrivateData = stream.mssPrivateData.codecPrivateData;
          videoNalus = codecPrivateData.split('00000001').slice(1);
        }
        /** @type {shaka.util.Mp4Generator.StreamInfo} */
        const streamInfo = {
          id: stream.id,
          type: stream.type,
          codecs: stream.codecs,
          encrypted: stream.encrypted,
          timescale: stream.mssPrivateData.timescale,
          duration: stream.mssPrivateData.duration,
          videoNalus: videoNalus,
          audioConfig: new Uint8Array([]),
          videoConfig: new Uint8Array([]),
          hSpacing: 0,
          vSpacing: 0,
          data: null, // Data is not necessary for init segment.
          stream: stream,
        };
        const mp4Generator = new shaka.util.Mp4Generator([streamInfo]);
        initSegmentData = mp4Generator.initSegment();
        this.initSegmentDataByStreamId_.set(stream.id, initSegmentData);
      }
      const qualityInfo =
          shaka.media.QualityObserver.createQualityInfo(stream);
      const initSegmentRef = new shaka.media.InitSegmentReference(
          () => [],
          /* startByte= */ 0,
          /* endByte= */ null,
          qualityInfo,
          stream.mssPrivateData.timescale,
          initSegmentData,
          /* aesKey= */ null,
          stream.encrypted);

      const segments = this.createSegments_(initSegmentRef,
          stream, streamIndex, timeline);

      stream.segmentIndex = new shaka.media.SegmentIndex(segments);
      return Promise.resolve();
    };
    stream.closeSegmentIndex = () => {
      // If we have a segment index, release it.
      if (stream.segmentIndex) {
        stream.segmentIndex.release();
        stream.segmentIndex = null;
      }
    };

    return stream;
  }
@param {!shaka.extern.xml.Node} streamIndex
@param {!shaka.extern.xml.Node} qualityLevel
@param {!Array<shaka.mss.MssParser.TimeRange>} timeline
@param {!Array<shaka.extern.DrmInfo>} drmInfos
@param {!shaka.mss.MssParser.Context} context
@return {?shaka.extern.Stream}
@private