UNPKG

dvbcss-clocks

Version:

Javascript library implementing clock objects for modelling timelines. Useful in DVB CSS implementations.

265 lines (233 loc) 7.76 kB
/**************************************************************************** * Copyright 2017 British Broadcasting Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *****************************************************************************/ var inherits = require('inherits'); var ClockBase = require('./ClockBase'); var WeakMap = require('weak-map'); var PRIVATE = new WeakMap(); /** * @exports OffsetClock * @class OffsetClock * @extends ClockBase * * @classdesc * A clock that applies an offset such that reading it is the same as * reading its parent, but as if the current time is slightly offset by an * amount ahead (+ve offset) or behind (-ve offset). * It is a subclass of {@link ClockBase}. * * <p><tt>OffsetClock</tt> inherits the tick rate of its parent. Its speed is * always 1. It takes the effective speed into account when applying the offset, * so it should always represent the same amount of time according to the root * clock. In practice this means it will be a constant offset amount of real-world * time. * * <p>This can be used to compensate for rendering delays. If it takes N seconds * to render some content and display it, then a positive offset of N seconds * will mean that the rendering code thinks time is N seconds ahead of where * it is. It will then render the correct content that is needed to be displayed * in N seconds time. * * <p>For example: A correlated clock (the "media clock") represents the time * position a video player needs to currently be at. * * <p>The video player has a 40 milisecond (0.040 second) delay between when it renders a frame and the light being emitted by the display. We therefore need the * video player to render 40 milliseconds in advance of when the frame is * to be displayed. An :class:`OffsetClock` is used to offset time in this * way and is passed to the video player: * * <pre class="prettyprint"><code> * mediaClock = new CorrelatedClock(...); * * PLAYER_DELAY_SECS = 40; * oClock = new OffsetClock(mediaClock, {offset:PLAYER_DELAY_SECS}); * * videoPlayer.syncToClock(oClock); * </code></pre> * * <p>If needed, the offset can be altered at runtime, by setting the :data:`offset` * property. For example, perhaps it needs to be changed to a 50 millisecond offset: * * <pre class="prettyprint"><code> * oClock.offset = 50; * </code></pre> * * <p>Both positive and negative offsets can be used. */ var OffsetClock = function(parent, options) { ClockBase.call(this); PRIVATE.set(this, {}); var priv = PRIVATE.get(this); if (options && (typeof options.offset !== "undefined")) { if (typeof options.offset === "number") { priv.offset = options.offset; } else { throw "'offset' option must be a number (in milliseconds)"; } } else { priv.offset = 0; } priv.parent = parent; priv.parentHandlers = { "change" : function(causeClock) { this.emit("change", this); }.bind(this), "available" : this.notifyAvailabilityChange.bind(this), "unavailable" : this.notifyAvailabilityChange.bind(this), }; priv.parent = null; this.setParent(parent); }; inherits(OffsetClock, ClockBase); /** * @inheritdoc */ OffsetClock.prototype.now = function() { var priv = PRIVATE.get(this); return priv.parent.now() + priv.offset * this.getEffectiveSpeed() * priv.parent.tickRate / 1000; }; /** * @returns {String} A human readable summary of this clock object, including its current properties * @example * > c=new Offset(parent, {offset:20}); * > c.toString() * 'OffsetClock(clock_0, {offset:20}) [clock_1]' */ OffsetClock.prototype.toString = function() { var priv = PRIVATE.get(this); var p; if (priv.parent) { p = priv.parent.id; } else { p = "<<no-parent>>"; } return "OffsetClock("+p+", {offset:"+priv.offset+"}) ["+this.id+"]"; }; /** * @inheritdoc */ OffsetClock.prototype.getSpeed = function() { return 1; }; /** * @inheritdoc */ OffsetClock.prototype.setSpeed = function(newSpeed) { throw "Cannot change the speed of this clock."; }; /** * @inheritdoc */ OffsetClock.prototype.getTickRate = function() { return PRIVATE.get(this).parent.tickRate; }; /** * @inheritdoc */ OffsetClock.prototype.setTickRate = function(newTickRate) { throw "Cannot change the tick rate of this clock."; }; /** * @var {Number} offset The amount by which this clock should be in advance, in milliseconds in terms of elapsed root clock time. * * <p>The underlying implementation of this property uses the * [getOffset]{@link OffsetClock#getOffset} and * [setOffset]{@link OffsetClock#setOffset} methods. * @default 1.0 * @memberof OffsetClock * @instance * @fires change */ Object.defineProperty(OffsetClock.prototype, "offset", { get: function() { return this.getOffset(); }, set: function(millis) { return this.setOffset(millis); }, }); /** * Read the number of milliseconds by which this clock is ahead (the offset). * * The offset is in terms of elapsed root clock time, not elapsed time of * the parent. * * @return {Number} The number of milliseconds by which this clock is ahead. */ OffsetClock.prototype.getOffset = function() { return PRIVATE.get(this).offset; }; /** * Change the number of milliseconds by which this clock is ahead (the offset) * * The offset is in terms of elapsed root clock time, not elapsed time of * the parent. * * @param {Number} millis The number of milliseconds by which this clock is ahead. */ OffsetClock.prototype.setOffset = function(millis) { var priv = PRIVATE.get(this); var changed = millis != priv.offset; priv.offset = millis; if (changed) { this.emit("change", this); } }; /** * @inheritdoc */ OffsetClock.prototype.calcWhen = function(t) { var priv = PRIVATE.get(this); var tt = t + priv.offset * this.getEffectiveSpeed() * priv.parent.tickRate / 1000; return priv.parent.calcWhen(this.toParentTime(tt)); }; /** * @inheritdoc */ OffsetClock.prototype.getParent = function() { return PRIVATE.get(this).parent; }; /** * @inheritdoc */ OffsetClock.prototype.setParent = function(newParent) { var priv = PRIVATE.get(this); var event; if (priv.parent != newParent) { if (priv.parent) { for(event in priv.parentHandlers) { priv.parent.removeListener(event, priv.parentHandlers[event]); } } priv.parent = newParent; if (priv.parent) { for(event in priv.parentHandlers) { priv.parent.on(event, priv.parentHandlers[event]); } } this.emit("change", this); } }; /** * @inheritdoc */ OffsetClock.prototype.toParentTime = function(t) { var priv = PRIVATE.get(this); return t - priv.offset * this.getEffectiveSpeed() * this.tickRate / 1000; }; /** * @inheritdoc */ OffsetClock.prototype.fromParentTime = function(t) { var priv = PRIVATE.get(this); return t + priv.offset * this.getEffectiveSpeed() * this.tickRate / 1000; }; module.exports = OffsetClock;