makeStreamObject_(id, codecs, type, languageValue, primary, name,
      channelsCount, closedCaptions, characteristics, forced, sampleRate,
      spatialAudio) {
    // Fill out a "best-guess" mimeType, for now. It will be replaced once the
    // stream is lazy-loaded.
    const mimeType = this.guessMimeTypeBeforeLoading_(type, codecs) ||
        this.guessMimeTypeFallback_(type);
    const roles = [];
    if (characteristics) {
      for (const characteristic of characteristics.split(',')) {
        roles.push(characteristic);
      }
    }

    let kind = undefined;
    let accessibilityPurpose = null;
    if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
      if (roles.includes('public.accessibility.transcribes-spoken-dialog') &&
          roles.includes('public.accessibility.describes-music-and-sound')) {
        kind = shaka.util.ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
      } else {
        kind = shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE;
      }
    } else {
      if (roles.includes('public.accessibility.describes-video')) {
        accessibilityPurpose =
              shaka.media.ManifestParser.AccessibilityPurpose.VISUALLY_IMPAIRED;
      }
    }

    // If there are no roles, and we have defaulted to the subtitle "kind" for
    // this track, add the implied subtitle role.
    if (!roles.length &&
        kind === shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE) {
      roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
    }

    const stream = {
      id: this.globalId_++,
      originalId: name,
      groupId: null,
      createSegmentIndex: () => Promise.resolve(),
      segmentIndex: null,
      mimeType,
      codecs,
      kind: (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
          shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined,
      encrypted: false,
      drmInfos: [],
      keyIds: new Set(),
      language: this.getLanguage_(languageValue),
      originalLanguage: languageValue,
      label: name,  // For historical reasons, since before "originalId".
      type,
      primary,
      // TODO: trick mode
      trickModeVideo: null,
      dependencyStream: null,
      emsgSchemeIdUris: null,
      frameRate: undefined,
      pixelAspectRatio: undefined,
      width: undefined,
      height: undefined,
      bandwidth: undefined,
      roles,
      forced,
      channelsCount,
      audioSamplingRate: sampleRate,
      spatialAudio,
      closedCaptions,
      hdr: undefined,
      colorGamut: undefined,
      videoLayout: undefined,
      tilesLayout: undefined,
      accessibilityPurpose: accessibilityPurpose,
      external: false,
      fastSwitching: false,
      fullMimeTypes: new Set(),
      isAudioMuxedInVideo: false,
      baseOriginalId: null,
    };
    this.setFullTypeForStream_(stream);
    return stream;
  }
Creates a stream object with the given parameters.
The parameters that are passed into here are only the things that can be
known without downloading the media playlist; other values must be set
manually on the object after creation.
@param {number} id
@param {string} codecs
@param {string} type
@param {?string} languageValue
@param {boolean} primary
@param {?string} name
@param {?number} channelsCount
@param {Map<string, string>} closedCaptions
@param {?string} characteristics
@param {boolean} forced
@param {?number} sampleRate
@param {boolean} spatialAudio
@return {!shaka.extern.Stream}
@private