# Source: source-updater.js

    /**
     * @file source-updater.js
     */
    import videojs from 'video.js';

    /**
     * A queue of callbacks to be serialized and applied when a
     * MediaSource and its associated SourceBuffers are not in the
     * updating state. It is used by the segment loader to update the
     * underlying SourceBuffers when new data is loaded, for instance.
     *
     * @class SourceUpdater
     * @param {MediaSource} mediaSource the MediaSource to create the
     * SourceBuffer from
     * @param {String} mimeType the desired MIME type of the underlying
     * SourceBuffer
     */
    export default class SourceUpdater {
      constructor(mediaSource, mimeType) {
        let createSourceBuffer = () => {
          this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType);

          // run completion handlers and process callbacks as updateend
          // events fire
          this.onUpdateendCallback_ = () => {
            let pendingCallback = this.pendingCallback_;

            this.pendingCallback_ = null;

            if (pendingCallback) {
              pendingCallback();
            }

            this.runCallback_();
          };

          this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);

          this.runCallback_();
        };

        this.callbacks_ = [];
        this.pendingCallback_ = null;
        this.timestampOffset_ = 0;
        this.mediaSource = mediaSource;

        if (mediaSource.readyState === 'closed') {
          mediaSource.addEventListener('sourceopen', createSourceBuffer);
        } else {
          createSourceBuffer();
        }
      }

      /**
       * Aborts the current segment and resets the segment parser.
       *
       * @param {Function} done function to call when done
       * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
       */
      abort(done) {
        this.queueCallback_(() => {
          this.sourceBuffer_.abort();
        }, done);
      }

      /**
       * Queue an update to append an ArrayBuffer.
       *
       * @param {ArrayBuffer} bytes
       * @param {Function} done the function to call when done
       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
       */
      appendBuffer(bytes, done) {
        this.queueCallback_(() => {
          this.sourceBuffer_.appendBuffer(bytes);
        }, done);
      }

      /**
       * Indicates what TimeRanges are buffered in the managed SourceBuffer.
       *
       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
       */
      buffered() {
        if (!this.sourceBuffer_) {
          return videojs.createTimeRanges();
        }
        return this.sourceBuffer_.buffered;
      }

      /**
       * Queue an update to set the duration.
       *
       * @param {Double} duration what to set the duration to
       * @see http://www.w3.org/TR/media-source/#widl-MediaSource-duration
       */
      duration(duration) {
        this.queueCallback_(() => {
          this.sourceBuffer_.duration = duration;
        });
      }

      /**
       * Queue an update to remove a time range from the buffer.
       *
       * @param {Number} start where to start the removal
       * @param {Number} end where to end the removal
       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
       */
      remove(start, end) {
        this.queueCallback_(() => {
          this.sourceBuffer_.remove(start, end);
        });
      }

      /**
       * wether the underlying sourceBuffer is updating or not
       *
       * @return {Boolean} the updating status of the SourceBuffer
       */
      updating() {
        return !this.sourceBuffer_ || this.sourceBuffer_.updating;
      }

      /**
       * Set/get the timestampoffset on the SourceBuffer
       *
       * @return {Number} the timestamp offset
       */
      timestampOffset(offset) {
        if (typeof offset !== 'undefined') {
          this.queueCallback_(() => {
            this.sourceBuffer_.timestampOffset = offset;
          });
          this.timestampOffset_ = offset;
        }
        return this.timestampOffset_;
      }

      /**
       * que a callback to run
       */
      queueCallback_(callback, done) {
        this.callbacks_.push([callback.bind(this), done]);
        this.runCallback_();
      }

      /**
       * run a queued callback
       */
      runCallback_() {
        let callbacks;

        if (this.sourceBuffer_ &&
            !this.sourceBuffer_.updating &&
            this.callbacks_.length) {
          callbacks = this.callbacks_.shift();
          this.pendingCallback_ = callbacks[1];
          callbacks[0]();
        }
      }

      /**
       * dispose of the source updater and the underlying sourceBuffer
       */
      dispose() {
        this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
        if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
          this.sourceBuffer_.abort();
        }
      }
    }