parsePeriod_(context, getBaseUris, periodInfo) {
    const Functional = shaka.util.Functional;
    const TXml = shaka.util.TXml;
    const ContentType = shaka.util.ManifestParserUtils.ContentType;

    goog.asserts.assert(periodInfo.node, 'periodInfo.node should exist');
    context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
    context.periodInfo = periodInfo;
    context.period.availabilityTimeOffset = context.availabilityTimeOffset;

    // If the period doesn't have an ID, give it one based on its start time.
    if (!context.period.id) {
      shaka.log.info(
          'No Period ID given for Period with start time ' + periodInfo.start +
          ',  Assigning a default');
      context.period.id = '__shaka_period_' + periodInfo.start;
    }

    const eventStreamNodes =
        TXml.findChildren(periodInfo.node, 'EventStream');
    const availabilityStart =
        context.presentationTimeline.getSegmentAvailabilityStart();

    for (const node of eventStreamNodes) {
      this.parseEventStream_(
          periodInfo.start, periodInfo.duration, node, availabilityStart);
    }


    const supplementalProperties =
        TXml.findChildren(periodInfo.node, 'SupplementalProperty');
    for (const prop of supplementalProperties) {
      const schemeId = prop.attributes['schemeIdUri'];
      if (schemeId == 'urn:mpeg:dash:urlparam:2014') {
        const urlParams = this.getURLParametersFunction_(prop);
        if (urlParams) {
          context.urlParams = urlParams;
        }
      }
    }

    const adaptationSets =
        TXml.findChildren(periodInfo.node, 'AdaptationSet')
            .map((node, position) =>
              this.parseAdaptationSet_(context, position, node))
            .filter(Functional.isNotNull);

    // For dynamic manifests, we use rep IDs internally, and they must be
    // unique.
    if (context.dynamic) {
      const ids = [];
      for (const set of adaptationSets) {
        for (const id of set.representationIds) {
          ids.push(id);
        }
      }

      const uniqueIds = new Set(ids);

      if (ids.length != uniqueIds.size) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.MANIFEST,
            shaka.util.Error.Code.DASH_DUPLICATE_REPRESENTATION_ID);
      }
    }

    /** @type {!Map<string, shaka.extern.Stream>} */
    const dependencyStreamMap = new Map();
    for (const adaptationSet of adaptationSets) {
      for (const [dependencyId, stream] of adaptationSet.dependencyStreamMap) {
        dependencyStreamMap.set(dependencyId, stream);
      }
    }

    if (dependencyStreamMap.size) {
      let duplicateAdaptationSets = null;
      for (const adaptationSet of adaptationSets) {
        const streamsWithDependencyStream = [];
        for (const stream of adaptationSet.streams) {
          if (dependencyStreamMap.has(stream.originalId)) {
            if (!duplicateAdaptationSets) {
              duplicateAdaptationSets =
                  TXml.findChildren(periodInfo.node, 'AdaptationSet')
                      .map((node, position) =>
                        this.parseAdaptationSet_(context, position, node))
                      .filter(Functional.isNotNull);
            }
            for (const duplicateAdaptationSet of duplicateAdaptationSets) {
              const newStream = duplicateAdaptationSet.streams.find(
                  (s) => s.originalId == stream.originalId);
              if (newStream) {
                newStream.dependencyStream =
                    dependencyStreamMap.get(newStream.originalId);
                newStream.originalId += newStream.dependencyStream.originalId;
                streamsWithDependencyStream.push(newStream);
              }
            }
          }
        }
        if (streamsWithDependencyStream.length) {
          adaptationSet.streams.push(...streamsWithDependencyStream);
        }
      }
    }

    const normalAdaptationSets = adaptationSets
        .filter((as) => { return !as.trickModeFor; });

    const trickModeAdaptationSets = adaptationSets
        .filter((as) => { return as.trickModeFor; });

    // Attach trick mode tracks to normal tracks.
    if (!this.config_.disableIFrames) {
      for (const trickModeSet of trickModeAdaptationSets) {
        const targetIds = trickModeSet.trickModeFor.split(' ');
        for (const normalSet of normalAdaptationSets) {
          if (targetIds.includes(normalSet.id)) {
            for (const stream of normalSet.streams) {
              shaka.util.StreamUtils.setBetterIFrameStream(
                  stream, trickModeSet.streams);
            }
          }
        }
      }
    }

    const audioStreams = this.getStreamsFromSets_(
        this.config_.disableAudio,
        normalAdaptationSets,
        ContentType.AUDIO);
    const videoStreams = this.getStreamsFromSets_(
        this.config_.disableVideo,
        normalAdaptationSets,
        ContentType.VIDEO);
    const textStreams = this.getStreamsFromSets_(
        this.config_.disableText,
        normalAdaptationSets,
        ContentType.TEXT);
    const imageStreams = this.getStreamsFromSets_(
        this.config_.disableThumbnails,
        normalAdaptationSets,
        ContentType.IMAGE);

    if (videoStreams.length === 0 && audioStreams.length === 0) {
      throw new shaka.util.Error(
          shaka.util.Error.Severity.CRITICAL,
          shaka.util.Error.Category.MANIFEST,
          shaka.util.Error.Code.DASH_EMPTY_PERIOD,
      );
    }

    return {
      id: context.period.id,
      audioStreams,
      videoStreams,
      textStreams,
      imageStreams,
    };
  }
Parses a Period XML element.  Unlike the other parse methods, this is not
given the Node; it is given a PeriodInfo structure.  Also, partial parsing
was done before this was called so start and duration are valid.

@param {shaka.dash.DashParser.Context} context
@param {function(): !Array<string>} getBaseUris
@param {shaka.dash.DashParser.PeriodInfo} periodInfo
@return {shaka.extern.Period}
@private