updateCuesRecursive_(cues, container, currentTime, parents) {
    // Set to true if the cues have changed in some way, which will require
    // DOM changes. E.g. if a cue was added or removed.
    let updateDOM = false;
    /**
     * The elements to remove from the DOM.
     * Some of these elements may be added back again, if their corresponding
     * cue is in toPlant.
     * These elements are only removed if updateDOM is true.
     * @type {!Array<!HTMLElement>}
     */
    const toUproot = [];
    /**
     * The cues whose corresponding elements should be in the DOM.
     * Some of these might be new, some might have been displayed beforehand.
     * These will only be added if updateDOM is true.
     * @type {!Array<!shaka.text.Cue>}
     */
    const toPlant = [];
    for (const cue of cues) {
      parents.push(cue);

      let cueRegistry = this.currentCuesMap_.get(cue);
      const shouldBeDisplayed =
          cue.startTime <= currentTime && cue.endTime > currentTime;
      let wrapper = cueRegistry ? cueRegistry.wrapper : null;

      if (cueRegistry) {
        // If the cues are replanted, all existing cues should be uprooted,
        // even ones which are going to be planted again.
        toUproot.push(cueRegistry.cueElement);

        // Also uproot all displayed region elements.
        if (cueRegistry.regionElement) {
          toUproot.push(cueRegistry.regionElement);
        }

        // If the cue should not be displayed, remove it entirely.
        if (!shouldBeDisplayed) {
          // Since something has to be removed, we will need to update the DOM.
          updateDOM = true;
          this.currentCuesMap_.delete(cue);
          cueRegistry = null;
        }
      }

      if (shouldBeDisplayed) {
        toPlant.push(cue);
        if (!cueRegistry) {
          // The cue has to be made!
          this.createCue_(cue, parents);
          cueRegistry = this.currentCuesMap_.get(cue);
          wrapper = cueRegistry.wrapper;
          updateDOM = true;
        } else if (!this.isElementUnderTextContainer_(wrapper)) {
          // We found that the wrapper needs to be in the DOM
          updateDOM = true;
        }
      }

      // Recursively check the nested cues, to see if they need to be added or
      // removed.
      // If wrapper is null, that means that the cue is not only not being
      // displayed currently, it also was not removed this tick. So it's
      // guaranteed that the children will neither need to be added nor removed.
      if (cue.nestedCues.length > 0 && wrapper) {
        this.updateCuesRecursive_(
            cue.nestedCues, wrapper, currentTime, parents);
      }

      const topCue = parents.pop();
      goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
    }

    if (updateDOM) {
      for (const element of toUproot) {
        // NOTE: Because we uproot shared region elements, too, we might hit an
        // element here that has no parent because we've already processed it.
        if (element.parentElement) {
          element.parentElement.removeChild(element);
        }
      }
      toPlant.sort((a, b) => {
        if (a.startTime != b.startTime) {
          return a.startTime - b.startTime;
        } else {
          return a.endTime - b.endTime;
        }
      });
      for (const cue of toPlant) {
        const cueRegistry = this.currentCuesMap_.get(cue);
        goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
        if (cueRegistry.regionElement) {
          if (cueRegistry.regionElement.contains(container)) {
            cueRegistry.regionElement.removeChild(container);
          }
          container.appendChild(cueRegistry.regionElement);
          cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
        } else {
          container.appendChild(cueRegistry.cueElement);
        }
      }
    }
  }
@param {!Array<!shaka.text.Cue>} cues
@param {!HTMLElement} container
@param {number} currentTime
@param {!Array<!shaka.text.Cue>} parents
@private