parseAdaptationSet_(context, position, elem) {
    const TXml = shaka.util.TXml;
    const Functional = shaka.util.Functional;
    const ManifestParserUtils = shaka.util.ManifestParserUtils;
    const ContentType = ManifestParserUtils.ContentType;
    const ContentProtection = shaka.dash.ContentProtection;

    context.adaptationSet = this.createFrame_(elem, context.period, null);
    context.adaptationSet.position = position;

    let main = false;
    const roleElements = TXml.findChildren(elem, 'Role');
    const roleValues = roleElements.map((role) => {
      return role.attributes['value'];
    }).filter(Functional.isNotNull);

    // Default kind for text streams is 'subtitle' if unspecified in the
    // manifest.
    let kind = undefined;
    const isText = context.adaptationSet.contentType == ContentType.TEXT;
    if (isText) {
      kind = ManifestParserUtils.TextStreamKind.SUBTITLE;
    }

    for (const roleElement of roleElements) {
      const scheme = roleElement.attributes['schemeIdUri'];
      if (scheme == null || scheme == 'urn:mpeg:dash:role:2011') {
        // These only apply for the given scheme, but allow them to be specified
        // if there is no scheme specified.
        // See: DASH section 5.8.5.5
        const value = roleElement.attributes['value'];
        switch (value) {
          case 'main':
            main = true;
            break;
          case 'caption':
          case 'subtitle':
            kind = value;
            break;
        }
      }
    }

    // Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
    let videoRange;

    let colorGamut;

    // Ref. https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf
    // If signaled, a Supplemental or Essential Property descriptor
    // shall be used, with the schemeIdUri set to
    // urn:mpeg:mpegB:cicp:<Parameter> as defined in
    // ISO/IEC 23001-8 [49] and <Parameter> one of the
    // following: ColourPrimaries, TransferCharacteristics,
    // or MatrixCoefficients.
    const scheme = 'urn:mpeg:mpegB:cicp';
    const transferCharacteristicsScheme = `${scheme}:TransferCharacteristics`;
    const colourPrimariesScheme = `${scheme}:ColourPrimaries`;
    const matrixCoefficientsScheme = `${scheme}:MatrixCoefficients`;

    const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
      switch (cicp) {
        case 1:
        case 6:
        case 13:
        case 14:
        case 15:
          return 'SDR';
        case 16:
          return 'PQ';
        case 18:
          return 'HLG';
      }
      return undefined;
    };

    const getColorGamutFromColourPrimariesCICP = (cicp) => {
      switch (cicp) {
        case 1:
        case 5:
        case 6:
        case 7:
          return 'srgb';
        case 9:
          return 'rec2020';
        case 11:
        case 12:
          return 'p3';
      }
      return undefined;
    };

    const parseFont = (prop) => {
      const fontFamily = prop.attributes['dvb:fontFamily'];
      const fontUrl = prop.attributes['dvb:url'];
      if (fontFamily && fontUrl) {
        const uris = shaka.util.ManifestParserUtils.resolveUris(
            context.adaptationSet.getBaseUris(), [fontUrl],
            context.urlParams());
        this.playerInterface_.addFont(fontFamily, uris[0]);
      }
    };

    const essentialProperties =
        TXml.findChildren(elem, 'EssentialProperty');
    // ID of real AdaptationSet if this is a trick mode set:
    let trickModeFor = null;
    let isFastSwitching = false;
    let adaptationSetUrlParams = null;
    let unrecognizedEssentialProperty = false;
    for (const prop of essentialProperties) {
      const schemeId = prop.attributes['schemeIdUri'];
      if (schemeId == 'http://dashif.org/guidelines/trickmode') {
        trickModeFor = prop.attributes['value'];
      } else if (schemeId == transferCharacteristicsScheme) {
        videoRange = getVideoRangeFromTransferCharacteristicCICP(
            parseInt(prop.attributes['value'], 10),
        );
      } else if (schemeId == colourPrimariesScheme) {
        colorGamut = getColorGamutFromColourPrimariesCICP(
            parseInt(prop.attributes['value'], 10),
        );
      } else if (schemeId == matrixCoefficientsScheme) {
        continue;
      } else if (schemeId == 'urn:mpeg:dash:ssr:2023' &&
          this.config_.dash.enableFastSwitching) {
        isFastSwitching = true;
      } else if (schemeId == 'urn:dvb:dash:fontdownload:2014') {
        parseFont(prop);
      } else if (schemeId == 'urn:mpeg:dash:urlparam:2014') {
        adaptationSetUrlParams = this.getURLParametersFunction_(prop);
        if (!adaptationSetUrlParams) {
          unrecognizedEssentialProperty = true;
        }
      } else {
        unrecognizedEssentialProperty = true;
      }
    }

    // According to DASH spec (2014) section 5.8.4.8, "the successful processing
    // of the descriptor is essential to properly use the information in the
    // parent element".  According to DASH IOP v3.3, section 3.3.4, "if the
    // scheme or the value" for EssentialProperty is not recognized, "the DASH
    // client shall ignore the parent element."
    if (unrecognizedEssentialProperty) {
      // Stop parsing this AdaptationSet and let the caller filter out the
      // nulls.
      return null;
    }

    let lastSegmentNumber = null;

    const supplementalProperties =
        TXml.findChildren(elem, 'SupplementalProperty');
    for (const prop of supplementalProperties) {
      const schemeId = prop.attributes['schemeIdUri'];
      if (schemeId == 'http://dashif.org/guidelines/last-segment-number') {
        lastSegmentNumber = parseInt(prop.attributes['value'], 10) - 1;
      } else if (schemeId == transferCharacteristicsScheme) {
        videoRange = getVideoRangeFromTransferCharacteristicCICP(
            parseInt(prop.attributes['value'], 10),
        );
      } else if (schemeId == colourPrimariesScheme) {
        colorGamut = getColorGamutFromColourPrimariesCICP(
            parseInt(prop.attributes['value'], 10),
        );
      } else if (schemeId == 'urn:dvb:dash:fontdownload:2014') {
        parseFont(prop);
      } else if (schemeId == 'urn:mpeg:dash:urlparam:2014') {
        adaptationSetUrlParams = this.getURLParametersFunction_(prop);
      }
    }

    if (adaptationSetUrlParams) {
      context.urlParams = adaptationSetUrlParams;
    }

    const accessibilities = TXml.findChildren(elem, 'Accessibility');
    const LanguageUtils = shaka.util.LanguageUtils;
    const closedCaptions = new Map();
    /** @type {?shaka.media.ManifestParser.AccessibilityPurpose} */
    let accessibilityPurpose;
    for (const prop of accessibilities) {
      const schemeId = prop.attributes['schemeIdUri'];
      const value = prop.attributes['value'];
      if (schemeId == 'urn:scte:dash:cc:cea-608:2015' &&
          !this.config_.disableText) {
        let channelId = 1;
        if (value != null) {
          const channelAssignments = value.split(';');
          for (const captionStr of channelAssignments) {
            let channel;
            let language;
            // Some closed caption descriptions have channel number and
            // language ("CC1=eng") others may only have language ("eng,spa").
            if (!captionStr.includes('=')) {
              // When the channel assignments are not explicitly provided and
              // there are only 2 values provided, it is highly likely that the
              // assignments are CC1 and CC3 (most commonly used CC streams).
              // Otherwise, cycle through all channels arbitrarily (CC1 - CC4)
              // in order of provided langs.
              channel = `CC${channelId}`;
              if (channelAssignments.length == 2) {
                channelId += 2;
              } else {
                channelId ++;
              }
              language = captionStr;
            } else {
              const channelAndLanguage = captionStr.split('=');
              // The channel info can be '1' or 'CC1'.
              // If the channel info only has channel number(like '1'), add 'CC'
              // as prefix so that it can be a full channel id (like 'CC1').
              channel = channelAndLanguage[0].startsWith('CC') ?
                  channelAndLanguage[0] : `CC${channelAndLanguage[0]}`;

              // 3 letters (ISO 639-2).  In b/187442669, we saw a blank string
              // (CC2=;CC3=), so default to "und" (the code for "undetermined").
              language = channelAndLanguage[1] || 'und';
            }
            closedCaptions.set(channel, LanguageUtils.normalize(language));
          }
        } else {
          // If channel and language information has not been provided, assign
          // 'CC1' as channel id and 'und' as language info.
          closedCaptions.set('CC1', 'und');
        }
      } else if (schemeId == 'urn:scte:dash:cc:cea-708:2015' &&
          !this.config_.disableText) {
        let serviceNumber = 1;
        if (value != null) {
          for (const captionStr of value.split(';')) {
            let service;
            let language;
            // Similar to CEA-608, it is possible that service # assignments
            // are not explicitly provided e.g. "eng;deu;swe" In this case,
            // we just cycle through the services for each language one by one.
            if (!captionStr.includes('=')) {
              service = `svc${serviceNumber}`;
              serviceNumber ++;
              language = captionStr;
            } else {
            // Otherwise, CEA-708 caption values take the form "
            // 1=lang:eng;2=lang:deu" i.e. serviceNumber=lang:threeLetterCode.
              const serviceAndLanguage = captionStr.split('=');
              service = `svc${serviceAndLanguage[0]}`;

              // The language info can be different formats, lang:eng',
              // or 'lang:eng,war:1,er:1'. Extract the language info.
              language = serviceAndLanguage[1].split(',')[0].split(':').pop();
            }
            closedCaptions.set(service, LanguageUtils.normalize(language));
          }
        } else {
          // If service and language information has not been provided, assign
          // 'svc1' as service number and 'und' as language info.
          closedCaptions.set('svc1', 'und');
        }
      } else if (schemeId == 'urn:mpeg:dash:role:2011') {
        // See DASH IOP 3.9.2 Table 4.
        if (value != null) {
          roleValues.push(value);
          if (value == 'captions') {
            kind = ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
          }
        }
      } else if (schemeId == 'urn:tva:metadata:cs:AudioPurposeCS:2007') {
        // See DASH DVB Document A168 Rev.6 Table 5.
        if (value == '1') {
          accessibilityPurpose =
              shaka.media.ManifestParser.AccessibilityPurpose.VISUALLY_IMPAIRED;
        } else if (value == '2') {
          accessibilityPurpose =
              shaka.media.ManifestParser.AccessibilityPurpose.HARD_OF_HEARING;
        } else if (value == '9') {
          accessibilityPurpose =
              shaka.media.ManifestParser.AccessibilityPurpose.SPOKEN_SUBTITLES;
        }
      }
    }

    const contentProtectionElements =
        TXml.findChildren(elem, 'ContentProtection');
    const contentProtection = ContentProtection.parseFromAdaptationSet(
        contentProtectionElements,
        this.config_.ignoreDrmInfo,
        this.config_.dash.keySystemsByURI);

    // We us contentProtectionElements instead of drmInfos as the latter is
    // not populated yet, and we need the encrypted flag for the upcoming
    // parseRepresentation that will set the encrypted flag to the init seg.
    context.adaptationSet.encrypted = contentProtectionElements.length > 0;

    const language = shaka.util.LanguageUtils.normalize(
        context.adaptationSet.language || 'und');

    const label = context.adaptationSet.label;

    /** @type {!Map<string, shaka.extern.Stream>} */
    const dependencyStreamMap = new Map();

    // Parse Representations into Streams.
    const representations = TXml.findChildren(elem, 'Representation');
    if (!this.config_.ignoreSupplementalCodecs) {
      const supplementalRepresentations = [];
      for (const rep of representations) {
        const supplementalCodecs = TXml.getAttributeNS(
            rep, shaka.dash.DashParser.SCTE214_, 'supplementalCodecs');
        if (supplementalCodecs) {
          // Duplicate representations with their supplementalCodecs
          const obj = shaka.util.ObjectUtils.cloneObject(rep);
          obj.attributes['codecs'] = supplementalCodecs.split(' ').join(',');
          if (obj.attributes['id']) {
            obj.attributes['supplementalId'] =
                obj.attributes['id'] + '_supplementalCodecs';
          }
          supplementalRepresentations.push(obj);
        }
      }
      representations.push(...supplementalRepresentations);
    }
    const streams = representations.map((representation) => {
      const parsedRepresentation = this.parseRepresentation_(context,
          contentProtection, kind, language, label, main, roleValues,
          closedCaptions, representation, accessibilityPurpose,
          lastSegmentNumber);
      if (parsedRepresentation) {
        parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
        parsedRepresentation.colorGamut =
            parsedRepresentation.colorGamut || colorGamut;
        parsedRepresentation.fastSwitching = isFastSwitching;
        const dependencyId = representation.attributes['dependencyId'];
        if (dependencyId) {
          parsedRepresentation.baseOriginalId = dependencyId;
          dependencyStreamMap.set(dependencyId, parsedRepresentation);
          return null;
        }
      }
      return parsedRepresentation;
    }).filter((s) => !!s);

    if (streams.length == 0 && dependencyStreamMap.size == 0) {
      const isImage = context.adaptationSet.contentType == ContentType.IMAGE;
      // Ignore empty AdaptationSets if ignoreEmptyAdaptationSet is true
      // or they are for text/image content.
      if (this.config_.dash.ignoreEmptyAdaptationSet || isText || isImage) {
        return null;
      }
      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.MANIFEST,
          shaka.util.Error.Code.DASH_EMPTY_ADAPTATION_SET);
    }

    // If AdaptationSet's type is unknown or is ambiguously "application",
    // guess based on the information in the first stream.  If the attributes
    // mimeType and codecs are split across levels, they will both be inherited
    // down to the stream level by this point, so the stream will have all the
    // necessary information.
    if (!context.adaptationSet.contentType ||
        context.adaptationSet.contentType == ContentType.APPLICATION) {
      const mimeType = streams[0].mimeType;
      const codecs = streams[0].codecs;
      context.adaptationSet.contentType =
          shaka.dash.DashParser.guessContentType_(mimeType, codecs);

      for (const stream of streams) {
        stream.type = context.adaptationSet.contentType;
      }
    }

    const adaptationId = context.adaptationSet.id ||
        ('__fake__' + this.globalId_++);

    for (const stream of streams) {
      // Some DRM license providers require that we have a default
      // key ID from the manifest in the wrapped license request.
      // Thus, it should be put in drmInfo to be accessible to request filters.
      for (const drmInfo of contentProtection.drmInfos) {
        drmInfo.keyIds = drmInfo.keyIds && stream.keyIds ?
            new Set([...drmInfo.keyIds, ...stream.keyIds]) :
            drmInfo.keyIds || stream.keyIds;
      }
      stream.groupId = adaptationId;
    }

    const repIds = representations
        .map((node) => {
          return node.attributes['supplementalId'] || node.attributes['id'];
        }).filter(shaka.util.Functional.isNotNull);

    return {
      id: adaptationId,
      contentType: context.adaptationSet.contentType,
      language: language,
      main: main,
      streams: streams,
      drmInfos: contentProtection.drmInfos,
      trickModeFor: trickModeFor,
      representationIds: repIds,
      dependencyStreamMap,
    };
  }
Parses an AdaptationSet XML element.

@param {shaka.dash.DashParser.Context} context
@param {number} position
@param {!shaka.extern.xml.Node} elem The AdaptationSet element.
@return {?shaka.dash.DashParser.AdaptationInfo}
@private