123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- import defaultValue from "./defaultValue.js";
- import defined from "./defined.js";
- import destroyObject from "./destroyObject.js";
- import Iso8601 from "./Iso8601.js";
- import JulianDate from "./JulianDate.js";
- /**
- * 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 https://sandcastle.cesium.com/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._lastPlaybackRate = 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;
- }
- Object.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) {
- const 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) {
- const 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._trySetPlaybackRate = function (clock) {
- if (this._lastPlaybackRate === clock.multiplier) {
- return;
- }
- const element = this._element;
- try {
- element.playbackRate = clock.multiplier;
- } catch (error) {
- // Seek manually for unsupported playbackRates.
- element.playbackRate = 0.0;
- }
- this._lastPlaybackRate = clock.multiplier;
- };
- VideoSynchronizer.prototype._onTick = function (clock) {
- const element = this._element;
- if (!defined(element) || element.readyState < 2) {
- return;
- }
- const paused = element.paused;
- const 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;
- }
- this._trySetPlaybackRate(clock);
- const clockTime = clock.currentTime;
- const epoch = defaultValue(this.epoch, Iso8601.MINIMUM_VALUE);
- let videoTime = JulianDate.secondsDifference(clockTime, epoch);
- const duration = element.duration;
- let desiredTime;
- const 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
- const 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;
- };
- }
- export default VideoSynchronizer;
|