constructor(segmentData, drift) {
    /**
     * @type {!Object<string, shaka.test.FakeMediaSourceEngine.SegmentData>}
     */
    this.segmentData = segmentData;

    /** @type {!Object<string, !Array<boolean>>} */
    this.initSegments = {};

    /** @type {!Object<string, !Array<boolean>>} */
    this.segments = {};

    /** @private {number} */
    this.drift_ = drift || 0;

    /** @private {!Object<string, number>} */
    this.timestampOffsets_ = {};

    /** @private {number} */
    this.duration_ = Infinity;

    for (const type in segmentData) {
      const data = segmentData[type];

      this.initSegments[type] = data.initSegments.map(() => false);
      this.segments[type] = data.segments.map(() => false);

      this.timestampOffsets_[type] = data.timestampOffset || 0;
    }

    /** @type {!jasmine.Spy} */
    this.init = jasmine.createSpy('init').and.returnValue(Promise.resolve());

    /** @type {!jasmine.Spy} */
    this.open = jasmine.createSpy('open').and.returnValue(Promise.resolve());

    /** @type {!jasmine.Spy} */
    this.configure = jasmine.createSpy('configure').and.stub();

    /** @type {!jasmine.Spy} */
    this.reinitText = jasmine.createSpy('reinitText').and.stub();

    /** @type {!jasmine.Spy} */
    this.ended = jasmine.createSpy('ended').and.returnValue(false);

    /** @type {!jasmine.Spy} */
    this.endOfStream =
        jasmine.createSpy('endOfStream').and.returnValue(Promise.resolve());

    /** @type {!jasmine.Spy} */
    this.getBufferedInfo =
        jasmine.createSpy('getBufferedInfo').and.returnValue({});

    /** @type {!jasmine.Spy} */
    this.setDuration = jasmine.createSpy('setDuration')
        .and.callFake((dur) => this.setDurationImpl_(dur));

    /** @type {!jasmine.Spy} */
    this.getDuration = jasmine.createSpy('getDuration')
        .and.callFake(() => this.getDurationImpl_());

    /** @type {!jasmine.Spy} */
    this.appendBuffer = jasmine.createSpy('appendBuffer')
        .and.callFake((type, data, reference) =>
          this.appendBufferImpl(type, data, reference));

    /** @type {!jasmine.Spy} */
    this.clear = jasmine.createSpy('clear')
        .and.callFake((type) => this.clearImpl_(type));

    /** @type {!jasmine.Spy} */
    this.resetCaptionParser = jasmine.createSpy('resetCaptionParser')
        .and.stub();

    /** @type {!jasmine.Spy} */
    this.bufferStart = jasmine.createSpy('bufferStart')
        .and.callFake((type) => this.bufferStartImpl_(type));

    /** @type {!jasmine.Spy} */
    this.bufferEnd = jasmine.createSpy('bufferEnd')
        .and.callFake((type) => this.bufferEndImpl_(type));

    /** @type {!jasmine.Spy} */
    this.isBuffered = jasmine.createSpy('isBuffered')
        .and.callFake((type, time) => this.isBufferedImpl_(type, time));

    /** @type {!jasmine.Spy} */
    this.bufferedAheadOf = jasmine.createSpy('bufferedAheadOf')
        .and.callFake((type, time) => this.bufferedAheadOfImpl(type, time));

    /** @type {!jasmine.Spy} */
    this.setStreamProperties = jasmine.createSpy('setStreamProperties')
        .and.callFake((type, offset, end, sequenceMode) =>
          this.setStreamPropertiesImpl_(type, offset, end, sequenceMode));

    /** @type {!jasmine.Spy} */
    this.remove = jasmine.createSpy('remove')
        .and.callFake((type, start, end) => this.removeImpl(type, start, end));

    /** @type {!jasmine.Spy} */
    this.flush = jasmine.createSpy('flush').and.returnValue(Promise.resolve());

    /** @type {!jasmine.Spy} */
    this.clearSelectedClosedCaptionId =
        jasmine.createSpy('clearSelectedClosedCaptionId');

    /** @type {!jasmine.Spy} */
    this.getTextDisplayer =
        jasmine.createSpy('getTextDisplayer')
            .and.returnValue(new shaka.test.FakeTextDisplayer());

    /** @type {!jasmine.Spy} */
    this.setSegmentRelativeVttTiming =
        jasmine.createSpy('setSegmentRelativeVttTiming').and.stub();

    /** @type {!jasmine.Spy} */
    this.updateLcevcDec =
        jasmine.createSpy('updateLcevcDec').and.stub();

    /** @type {!jasmine.Spy} */
    this.resync =
        jasmine.createSpy('resync').and.stub();

    /** @type {!jasmine.Spy} */
    this.setLiveSeekableRange =
        jasmine.createSpy('setLiveSeekableRange').and.stub();

    /** @type {!jasmine.Spy} */
    this.clearLiveSeekableRange =
        jasmine.createSpy('clearLiveSeekableRange').and.stub();
  }
@param {!Object<string,
                 shaka.test.FakeMediaSourceEngine.SegmentData>} segmentData
@param {number=} drift Optional drift. Defaults to 0.