text: import TaskLoop from '../task-loop';
  import { FragmentState } from './fragment-tracker';
  import { BufferHelper } from '../utils/buffer-helper';
  import { logger } from '../utils/logger';
  
  export const State = {
    STOPPED: 'STOPPED',
    STARTING: 'STARTING',
    IDLE: 'IDLE',
    PAUSED: 'PAUSED',
    KEY_LOADING: 'KEY_LOADING',
    FRAG_LOADING: 'FRAG_LOADING',
    FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
    WAITING_TRACK: 'WAITING_TRACK',
    PARSING: 'PARSING',
    PARSED: 'PARSED',
    BUFFER_FLUSHING: 'BUFFER_FLUSHING',
    ENDED: 'ENDED',
    ERROR: 'ERROR',
    WAITING_INIT_PTS: 'WAITING_INIT_PTS',
    WAITING_LEVEL: 'WAITING_LEVEL'
  };
  
  export default class BaseStreamController extends TaskLoop {
    doTick () {}
  
    startLoad () {}
  
    stopLoad () {
      let frag = this.fragCurrent;
      if (frag) {
        if (frag.loader) {
          frag.loader.abort();
        }
        this.fragmentTracker.removeFragment(frag);
      }
      if (this.demuxer) {
        this.demuxer.destroy();
        this.demuxer = null;
      }
      this.fragCurrent = null;
      this.fragPrevious = null;
      this.clearInterval();
      this.clearNextTick();
      this.state = State.STOPPED;
    }
  
    _streamEnded (bufferInfo, levelDetails) {
      const { fragCurrent, fragmentTracker } = this;
      // we just got done loading the final fragment and there is no other buffered range after ...
      // rationale is that in case there are any buffered ranges after, it means that there are unbuffered portion in between
      // so we should not switch to ENDED in that case, to be able to buffer them
      // dont switch to ENDED if we need to backtrack last fragment
      if (!levelDetails.live && fragCurrent && !fragCurrent.backtracked && fragCurrent.sn === levelDetails.endSN && !bufferInfo.nextStart) {
        const fragState = fragmentTracker.getState(fragCurrent);
        return fragState === FragmentState.PARTIAL || fragState === FragmentState.OK;
      }
      return false;
    }
  
    onMediaSeeking () {
      const { config, media, mediaBuffer, state } = this;
      const currentTime = media ? media.currentTime : null;
      const bufferInfo = BufferHelper.bufferInfo(mediaBuffer || media, currentTime, this.config.maxBufferHole);
  
      logger.log(`media seeking to ${Number.isFinite(currentTime) ? currentTime.toFixed(3) : currentTime}`);
  
      if (state === State.FRAG_LOADING) {
        let fragCurrent = this.fragCurrent;
        // check if we are seeking to a unbuffered area AND if frag loading is in progress
        if (bufferInfo.len === 0 && fragCurrent) {
          const tolerance = config.maxFragLookUpTolerance;
          const fragStartOffset = fragCurrent.start - tolerance;
          const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
          // check if we seek position will be out of currently loaded frag range : if out cancel frag load, if in, don't do anything
          if (currentTime < fragStartOffset || currentTime > fragEndOffset) {
            if (fragCurrent.loader) {
              logger.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
              fragCurrent.loader.abort();
            }
            this.fragCurrent = null;
            this.fragPrevious = null;
            // switch to IDLE state to load new fragment
            this.state = State.IDLE;
          } else {
            logger.log('seeking outside of buffer but within currently loaded fragment range');
          }
        }
      } else if (state === State.ENDED) {
        // if seeking to unbuffered area, clean up fragPrevious
        if (bufferInfo.len === 0) {
          this.fragPrevious = null;
          this.fragCurrent = null;
        }
  
        // switch to IDLE state to check for potential new fragment
        this.state = State.IDLE;
      }
      if (media) {
        this.lastCurrentTime = currentTime;
      }
  
      // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
      if (!this.loadedmetadata) {
        this.nextLoadPosition = this.startPosition = currentTime;
      }
  
      // tick to speed up processing
      this.tick();
    }
  
    onMediaEnded () {
      // reset startPosition and lastCurrentTime to restart playback @ stream beginning
      this.startPosition = this.lastCurrentTime = 0;
    }
  
    onHandlerDestroying () {
      this.stopLoad();
      super.onHandlerDestroying();
    }
  
    onHandlerDestroyed () {
      this.state = State.STOPPED;
      this.fragmentTracker = null;
    }
  
    computeLivePosition (sliding, levelDetails) {
      let targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * levelDetails.targetduration;
      return sliding + Math.max(0, levelDetails.totalduration - targetLatency);
    }
  }
  