createPlayheadObserversForMSE_(startTime) {
    goog.asserts.assert(this.manifest_, 'Must have manifest');
    goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
    goog.asserts.assert(this.metadataRegionTimeline_,
        'Must have metadata region timeline');
    goog.asserts.assert(this.emsgRegionTimeline_,
        'Must have emsg region timeline');
    goog.asserts.assert(this.video_, 'Must have video element');

    const startsPastZero = this.isLive() ||
        (typeof startTime === 'number' && startTime > 0);

    // Create the region observer. This will allow us to notify the app when we
    // move in and out of timeline regions.
    /** @type {!shaka.media.RegionObserver<shaka.extern.TimelineRegionInfo>} */
    const regionObserver = new shaka.media.RegionObserver(
        this.regionTimeline_, startsPastZero);

    regionObserver.addEventListener('enter', (event) => {
      /** @type {shaka.extern.TimelineRegionInfo} */
      const region = event['region'];
      this.onRegionEvent_(
          shaka.util.FakeEvent.EventName.TimelineRegionEnter, region);
    });

    regionObserver.addEventListener('exit', (event) => {
      /** @type {shaka.extern.TimelineRegionInfo} */
      const region = event['region'];
      this.onRegionEvent_(
          shaka.util.FakeEvent.EventName.TimelineRegionExit, region);
    });

    regionObserver.addEventListener('skip', (event) => {
      /** @type {shaka.extern.TimelineRegionInfo} */
      const region = event['region'];
      /** @type {boolean} */
      const seeking = event['seeking'];
      // If we are seeking, we don't want to surface the enter/exit events since
      // they didn't play through them.
      if (!seeking) {
        this.onRegionEvent_(
            shaka.util.FakeEvent.EventName.TimelineRegionEnter, region);
        this.onRegionEvent_(
            shaka.util.FakeEvent.EventName.TimelineRegionExit, region);
      }
    });

    /**
     * @type {!shaka.media.RegionObserver<
     *         shaka.extern.MetadataTimelineRegionInfo>}
     */
    const metadataRegionObserver = new shaka.media.RegionObserver(
        this.metadataRegionTimeline_, startsPastZero);

    metadataRegionObserver.addEventListener('enter', (event) => {
      /** @type {shaka.extern.MetadataTimelineRegionInfo} */
      const region = event['region'];
      this.dispatchMetadataEvent_(region,
          shaka.util.FakeEvent.EventName.Metadata);
    });

    /**
     * @type {!shaka.media.RegionObserver<shaka.extern.EmsgTimelineRegionInfo>}
     */
    const emsgRegionObserver = new shaka.media.RegionObserver(
        this.emsgRegionTimeline_, startsPastZero);

    emsgRegionObserver.addEventListener('enter', (event) => {
      /** @type {shaka.extern.EmsgTimelineRegionInfo} */
      const region = event['region'];
      this.dispatchEmsgEvent_(region);
    });

    // Now that we have all our observers, create a manager for them.
    const manager = new shaka.media.PlayheadObserverManager(this.video_);
    manager.manage(regionObserver);
    manager.manage(metadataRegionObserver);
    manager.manage(emsgRegionObserver);
    if (this.qualityObserver_) {
      manager.manage(this.qualityObserver_);
    }
    return manager;
  }
Create the observers for MSE playback. These observers are responsible for
notifying the app and player of specific events during MSE playback.

@param {number|Date} startTime
@return {!shaka.media.PlayheadObserverManager}
@private