text: /*global define*/
  define([
          './defaultValue',
          './defined',
          './defineProperties',
          './destroyObject',
          './Iso8601',
          './JulianDate'
      ], function(
          defaultValue,
          defined,
          defineProperties,
          destroyObject,
          Iso8601,
          JulianDate) {
      'use strict';
  
      /**
       * Synchronizes a video element with a simulation clock.
       *
       * @alias VideoSynchronizer
       * @constructor
       *
       * @param {Object} [options] Object with the following properties:
       * @param {Clock} [options.clock] The clock instance used to drive the video.
       * @param {HTMLVideoElement} [options.element] The video element to be synchronized.
       * @param {JulianDate} [options.epoch=Iso8601.MINIMUM_VALUE] The simulation time that marks the start of the video.
       * @param {Number} [options.tolerance=1.0] The maximum amount of time, in seconds, that the clock and video can diverge.
       *
       * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Video.html|Video Material Demo}
       */
      function VideoSynchronizer(options) {
          options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  
          this._clock = undefined;
          this._element = undefined;
          this._clockSubscription = undefined;
          this._seekFunction = undefined;
  
          this.clock = options.clock;
          this.element = options.element;
  
          /**
           * Gets or sets the simulation time that marks the start of the video.
           * @type {JulianDate}
           * @default Iso8601.MINIMUM_VALUE
           */
          this.epoch = defaultValue(options.epoch, Iso8601.MINIMUM_VALUE);
  
          /**
           * Gets or sets the amount of time in seconds the video's currentTime
           * and the clock's currentTime can diverge before a video seek is performed.
           * Lower values make the synchronization more accurate but video
           * performance might suffer.  Higher values provide better performance
           * but at the cost of accuracy.
           * @type {Number}
           * @default 1.0
           */
          this.tolerance = defaultValue(options.tolerance, 1.0);
  
          this._seeking = false;
          this._seekFunction = undefined;
          this._firstTickAfterSeek = false;
      }
  
      defineProperties(VideoSynchronizer.prototype, {
          /**
           * Gets or sets the clock used to drive the video element.
           *
           * @memberof VideoSynchronizer.prototype
           * @type {Clock}
           */
          clock : {
              get : function() {
                  return this._clock;
              },
              set : function(value) {
                  var oldValue = this._clock;
  
                  if (oldValue === value) {
                      return;
                  }
  
                  if (defined(oldValue)) {
                      this._clockSubscription();
                      this._clockSubscription = undefined;
                  }
  
                  if (defined(value)) {
                      this._clockSubscription = value.onTick.addEventListener(VideoSynchronizer.prototype._onTick, this);
                  }
  
                  this._clock = value;
              }
          },
          /**
           * Gets or sets the video element to synchronize.
           *
           * @memberof VideoSynchronizer.prototype
           * @type {HTMLVideoElement}
           */
          element : {
              get : function() {
                  return this._element;
              },
              set : function(value) {
                  var oldValue = this._element;
  
                  if (oldValue === value) {
                      return;
                  }
  
                  if (defined(oldValue)) {
                      oldValue.removeEventListener("seeked", this._seekFunction, false);
                  }
  
                  if (defined(value)) {
                      this._seeking = false;
                      this._seekFunction = createSeekFunction(this);
                      value.addEventListener("seeked", this._seekFunction, false);
                  }
  
                  this._element = value;
                  this._seeking = false;
                  this._firstTickAfterSeek = false;
              }
          }
      });
  
      /**
       * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
       *
       * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
       */
      VideoSynchronizer.prototype.destroy = function() {
          this.element = undefined;
          this.clock = undefined;
          return destroyObject(this);
      };
  
      /**
       * Returns true if this object was destroyed; otherwise, false.
       *
       * @returns {Boolean} True if this object was destroyed; otherwise, false.
       */
      VideoSynchronizer.prototype.isDestroyed = function() {
          return false;
      };
  
      VideoSynchronizer.prototype._onTick = function(clock) {
          var element = this._element;
          if (!defined(element) || element.readyState < 2) {
              return;
          }
  
          var paused = element.paused;
          var shouldAnimate = clock.shouldAnimate;
          if (shouldAnimate === paused) {
              if (shouldAnimate) {
                  element.play();
              } else {
                  element.pause();
              }
          }
  
          //We need to avoid constant seeking or the video will
          //never contain a complete frame for us to render.
          //So don't do anything if we're seeing or on the first
          //tick after a seek (the latter of which allows the frame
          //to actually be rendered.
          if (this._seeking || this._firstTickAfterSeek) {
              this._firstTickAfterSeek = false;
              return;
          }
  
          element.playbackRate = clock.multiplier;
  
          var clockTime = clock.currentTime;
          var epoch = defaultValue(this.epoch, Iso8601.MINIMUM_VALUE);
          var videoTime = JulianDate.secondsDifference(clockTime, epoch);
  
          var duration = element.duration;
          var desiredTime;
          var currentTime = element.currentTime;
          if (element.loop) {
              videoTime = videoTime % duration;
              if (videoTime < 0.0) {
                  videoTime = duration - videoTime;
              }
              desiredTime = videoTime;
          } else if (videoTime > duration) {
              desiredTime = duration;
          } else if (videoTime < 0.0) {
              desiredTime = 0.0;
          } else {
              desiredTime = videoTime;
          }
  
          //If the playing video's time and the scene's clock time
          //ever drift too far apart, we want to set the video to match
          var tolerance = shouldAnimate ? defaultValue(this.tolerance, 1.0) : 0.001;
          if (Math.abs(desiredTime - currentTime) > tolerance) {
              this._seeking = true;
              element.currentTime = desiredTime;
          }
      };
  
      function createSeekFunction(that) {
          return function() {
              that._seeking = false;
              that._firstTickAfterSeek = true;
          };
      }
  
      return VideoSynchronizer;
  });
  