matrix-react-sdk
Version:
SDK for matrix.org using React
137 lines (132 loc) • 18.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PlaybackClock = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _matrixWidgetApi = require("matrix-widget-api");
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
/**
* Tracks accurate human-perceptible time for an audio clip, as informed
* by managed playback. This clock is tightly coupled with the operation
* of the Playback class, making assumptions about how the provided
* AudioContext will be used (suspended/resumed to preserve time, etc).
*
* But why do we need a clock? The AudioContext exposes time information,
* and so does the audio buffer, but not in a way that is useful for humans
* to perceive. The audio buffer time is often lagged behind the context
* time due to internal processing delays of the audio API. Additionally,
* the context's time is tracked from when it was first initialized/started,
* not related to positioning within the clip. However, the context time
* is the most accurate time we can use to determine position within the
* clip if we're fast enough to track the pauses and stops.
*
* As a result, we track every play, pause, stop, and seek event from the
* Playback class (kinda: it calls us, which is close enough to the same
* thing). These events are then tracked on the AudioContext time scale,
* with assumptions that code execution will result in negligible desync
* of the clock, or at least no perceptible difference in time. It's
* extremely important that the calling code, and the clock's own code,
* is extremely fast between the event happening and the clock time being
* tracked - anything more than a dozen milliseconds is likely to stack up
* poorly, leading to clock desync.
*
* Clock desync can be dangerous for the stability of the playback controls:
* if the clock thinks the user is somewhere else in the clip, it could
* inform the playback of the wrong place in time, leading to dead air in
* the output or, if severe enough, a clock that won't stop running while
* the audio is paused/stopped. Other examples include the clip stopping at
* 90% time due to playback ending, the clip playing from the wrong spot
* relative to the time, and negative clock time.
*
* Note that the clip duration is fed to the clock: this is to ensure that
* we have the most accurate time possible to present.
*/
class PlaybackClock {
constructor(context) {
(0, _defineProperty2.default)(this, "clipStart", 0);
(0, _defineProperty2.default)(this, "stopped", true);
(0, _defineProperty2.default)(this, "lastCheck", 0);
(0, _defineProperty2.default)(this, "observable", new _matrixWidgetApi.SimpleObservable());
(0, _defineProperty2.default)(this, "timerId", void 0);
(0, _defineProperty2.default)(this, "clipDuration", 0);
(0, _defineProperty2.default)(this, "placeholderDuration", 0);
(0, _defineProperty2.default)(this, "checkTime", (force = false) => {
const now = this.timeSeconds; // calculated dynamically
if (this.lastCheck !== now || force) {
this.observable.update([now, this.durationSeconds]);
this.lastCheck = now;
}
});
this.context = context;
}
get durationSeconds() {
return this.clipDuration || this.placeholderDuration;
}
set durationSeconds(val) {
this.clipDuration = val;
this.observable.update([this.timeSeconds, this.clipDuration]);
}
get timeSeconds() {
// The modulo is to ensure that we're only looking at the most recent clip
// time, as the context is long-running and multiple plays might not be
// informed to us (if the control is looping, for example). By taking the
// remainder of the division operation, we're assuming that playback is
// incomplete or stopped, thus giving an accurate position within the active
// clip segment.
return (this.context.currentTime - this.clipStart) % this.clipDuration;
}
get liveData() {
return this.observable;
}
/**
* Populates default information about the audio clip from the event body.
* The placeholders will be overridden once known.
* @param {MatrixEvent} event The event to use for placeholders.
*/
populatePlaceholdersFrom(event) {
const durationMs = Number(event.getContent()["info"]?.["duration"]);
if (Number.isFinite(durationMs)) this.placeholderDuration = durationMs / 1000;
}
/**
* Mark the time in the audio context where the clip starts/has been loaded.
* This is to ensure the clock isn't skewed into thinking it is ~0.5s into
* a clip when the duration is set.
*/
flagLoadTime() {
this.clipStart = this.context.currentTime;
}
flagStart() {
if (this.stopped) {
this.clipStart = this.context.currentTime;
this.stopped = false;
}
if (!this.timerId) {
// 100ms interval to make sure the time is as accurate as possible without being overly insane
this.timerId = window.setInterval(this.checkTime, 100);
}
}
flagStop() {
this.stopped = true;
// Reset the clock time now so that the update going out will trigger components
// to check their seek/position information (alongside the clock).
this.clipStart = this.context.currentTime;
}
syncTo(contextTime, clipTime) {
this.clipStart = contextTime - clipTime;
this.stopped = false; // count as a mid-stream pause (if we were stopped)
this.checkTime(true);
}
destroy() {
this.observable.close();
if (this.timerId) clearInterval(this.timerId);
}
}
exports.PlaybackClock = PlaybackClock;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4V2lkZ2V0QXBpIiwicmVxdWlyZSIsIlBsYXliYWNrQ2xvY2siLCJjb25zdHJ1Y3RvciIsImNvbnRleHQiLCJfZGVmaW5lUHJvcGVydHkyIiwiZGVmYXVsdCIsIlNpbXBsZU9ic2VydmFibGUiLCJmb3JjZSIsIm5vdyIsInRpbWVTZWNvbmRzIiwibGFzdENoZWNrIiwib2JzZXJ2YWJsZSIsInVwZGF0ZSIsImR1cmF0aW9uU2Vjb25kcyIsImNsaXBEdXJhdGlvbiIsInBsYWNlaG9sZGVyRHVyYXRpb24iLCJ2YWwiLCJjdXJyZW50VGltZSIsImNsaXBTdGFydCIsImxpdmVEYXRhIiwicG9wdWxhdGVQbGFjZWhvbGRlcnNGcm9tIiwiZXZlbnQiLCJkdXJhdGlvbk1zIiwiTnVtYmVyIiwiZ2V0Q29udGVudCIsImlzRmluaXRlIiwiZmxhZ0xvYWRUaW1lIiwiZmxhZ1N0YXJ0Iiwic3RvcHBlZCIsInRpbWVySWQiLCJ3aW5kb3ciLCJzZXRJbnRlcnZhbCIsImNoZWNrVGltZSIsImZsYWdTdG9wIiwic3luY1RvIiwiY29udGV4dFRpbWUiLCJjbGlwVGltZSIsImRlc3Ryb3kiLCJjbG9zZSIsImNsZWFySW50ZXJ2YWwiLCJleHBvcnRzIl0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL2F1ZGlvL1BsYXliYWNrQ2xvY2sudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMjEgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IHsgU2ltcGxlT2JzZXJ2YWJsZSB9IGZyb20gXCJtYXRyaXgtd2lkZ2V0LWFwaVwiO1xuaW1wb3J0IHsgTWF0cml4RXZlbnQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbWF0cml4XCI7XG5cbmltcG9ydCB7IElEZXN0cm95YWJsZSB9IGZyb20gXCIuLi91dGlscy9JRGVzdHJveWFibGVcIjtcblxuLyoqXG4gKiBUcmFja3MgYWNjdXJhdGUgaHVtYW4tcGVyY2VwdGlibGUgdGltZSBmb3IgYW4gYXVkaW8gY2xpcCwgYXMgaW5mb3JtZWRcbiAqIGJ5IG1hbmFnZWQgcGxheWJhY2suIFRoaXMgY2xvY2sgaXMgdGlnaHRseSBjb3VwbGVkIHdpdGggdGhlIG9wZXJhdGlvblxuICogb2YgdGhlIFBsYXliYWNrIGNsYXNzLCBtYWtpbmcgYXNzdW1wdGlvbnMgYWJvdXQgaG93IHRoZSBwcm92aWRlZFxuICogQXVkaW9Db250ZXh0IHdpbGwgYmUgdXNlZCAoc3VzcGVuZGVkL3Jlc3VtZWQgdG8gcHJlc2VydmUgdGltZSwgZXRjKS5cbiAqXG4gKiBCdXQgd2h5IGRvIHdlIG5lZWQgYSBjbG9jaz8gVGhlIEF1ZGlvQ29udGV4dCBleHBvc2VzIHRpbWUgaW5mb3JtYXRpb24sXG4gKiBhbmQgc28gZG9lcyB0aGUgYXVkaW8gYnVmZmVyLCBidXQgbm90IGluIGEgd2F5IHRoYXQgaXMgdXNlZnVsIGZvciBodW1hbnNcbiAqIHRvIHBlcmNlaXZlLiBUaGUgYXVkaW8gYnVmZmVyIHRpbWUgaXMgb2Z0ZW4gbGFnZ2VkIGJlaGluZCB0aGUgY29udGV4dFxuICogdGltZSBkdWUgdG8gaW50ZXJuYWwgcHJvY2Vzc2luZyBkZWxheXMgb2YgdGhlIGF1ZGlvIEFQSS4gQWRkaXRpb25hbGx5LFxuICogdGhlIGNvbnRleHQncyB0aW1lIGlzIHRyYWNrZWQgZnJvbSB3aGVuIGl0IHdhcyBmaXJzdCBpbml0aWFsaXplZC9zdGFydGVkLFxuICogbm90IHJlbGF0ZWQgdG8gcG9zaXRpb25pbmcgd2l0aGluIHRoZSBjbGlwLiBIb3dldmVyLCB0aGUgY29udGV4dCB0aW1lXG4gKiBpcyB0aGUgbW9zdCBhY2N1cmF0ZSB0aW1lIHdlIGNhbiB1c2UgdG8gZGV0ZXJtaW5lIHBvc2l0aW9uIHdpdGhpbiB0aGVcbiAqIGNsaXAgaWYgd2UncmUgZmFzdCBlbm91Z2ggdG8gdHJhY2sgdGhlIHBhdXNlcyBhbmQgc3RvcHMuXG4gKlxuICogQXMgYSByZXN1bHQsIHdlIHRyYWNrIGV2ZXJ5IHBsYXksIHBhdXNlLCBzdG9wLCBhbmQgc2VlayBldmVudCBmcm9tIHRoZVxuICogUGxheWJhY2sgY2xhc3MgKGtpbmRhOiBpdCBjYWxscyB1cywgd2hpY2ggaXMgY2xvc2UgZW5vdWdoIHRvIHRoZSBzYW1lXG4gKiB0aGluZykuIFRoZXNlIGV2ZW50cyBhcmUgdGhlbiB0cmFja2VkIG9uIHRoZSBBdWRpb0NvbnRleHQgdGltZSBzY2FsZSxcbiAqIHdpdGggYXNzdW1wdGlvbnMgdGhhdCBjb2RlIGV4ZWN1dGlvbiB3aWxsIHJlc3VsdCBpbiBuZWdsaWdpYmxlIGRlc3luY1xuICogb2YgdGhlIGNsb2NrLCBvciBhdCBsZWFzdCBubyBwZXJjZXB0aWJsZSBkaWZmZXJlbmNlIGluIHRpbWUuIEl0J3NcbiAqIGV4dHJlbWVseSBpbXBvcnRhbnQgdGhhdCB0aGUgY2FsbGluZyBjb2RlLCBhbmQgdGhlIGNsb2NrJ3Mgb3duIGNvZGUsXG4gKiBpcyBleHRyZW1lbHkgZmFzdCBiZXR3ZWVuIHRoZSBldmVudCBoYXBwZW5pbmcgYW5kIHRoZSBjbG9jayB0aW1lIGJlaW5nXG4gKiB0cmFja2VkIC0gYW55dGhpbmcgbW9yZSB0aGFuIGEgZG96ZW4gbWlsbGlzZWNvbmRzIGlzIGxpa2VseSB0byBzdGFjayB1cFxuICogcG9vcmx5LCBsZWFkaW5nIHRvIGNsb2NrIGRlc3luYy5cbiAqXG4gKiBDbG9jayBkZXN5bmMgY2FuIGJlIGRhbmdlcm91cyBmb3IgdGhlIHN0YWJpbGl0eSBvZiB0aGUgcGxheWJhY2sgY29udHJvbHM6XG4gKiBpZiB0aGUgY2xvY2sgdGhpbmtzIHRoZSB1c2VyIGlzIHNvbWV3aGVyZSBlbHNlIGluIHRoZSBjbGlwLCBpdCBjb3VsZFxuICogaW5mb3JtIHRoZSBwbGF5YmFjayBvZiB0aGUgd3JvbmcgcGxhY2UgaW4gdGltZSwgbGVhZGluZyB0byBkZWFkIGFpciBpblxuICogdGhlIG91dHB1dCBvciwgaWYgc2V2ZXJlIGVub3VnaCwgYSBjbG9jayB0aGF0IHdvbid0IHN0b3AgcnVubmluZyB3aGlsZVxuICogdGhlIGF1ZGlvIGlzIHBhdXNlZC9zdG9wcGVkLiBPdGhlciBleGFtcGxlcyBpbmNsdWRlIHRoZSBjbGlwIHN0b3BwaW5nIGF0XG4gKiA5MCUgdGltZSBkdWUgdG8gcGxheWJhY2sgZW5kaW5nLCB0aGUgY2xpcCBwbGF5aW5nIGZyb20gdGhlIHdyb25nIHNwb3RcbiAqIHJlbGF0aXZlIHRvIHRoZSB0aW1lLCBhbmQgbmVnYXRpdmUgY2xvY2sgdGltZS5cbiAqXG4gKiBOb3RlIHRoYXQgdGhlIGNsaXAgZHVyYXRpb24gaXMgZmVkIHRvIHRoZSBjbG9jazogdGhpcyBpcyB0byBlbnN1cmUgdGhhdFxuICogd2UgaGF2ZSB0aGUgbW9zdCBhY2N1cmF0ZSB0aW1lIHBvc3NpYmxlIHRvIHByZXNlbnQuXG4gKi9cbmV4cG9ydCBjbGFzcyBQbGF5YmFja0Nsb2NrIGltcGxlbWVudHMgSURlc3Ryb3lhYmxlIHtcbiAgICBwcml2YXRlIGNsaXBTdGFydCA9IDA7XG4gICAgcHJpdmF0ZSBzdG9wcGVkID0gdHJ1ZTtcbiAgICBwcml2YXRlIGxhc3RDaGVjayA9IDA7XG4gICAgcHJpdmF0ZSBvYnNlcnZhYmxlID0gbmV3IFNpbXBsZU9ic2VydmFibGU8bnVtYmVyW10+KCk7XG4gICAgcHJpdmF0ZSB0aW1lcklkPzogbnVtYmVyO1xuICAgIHByaXZhdGUgY2xpcER1cmF0aW9uID0gMDtcbiAgICBwcml2YXRlIHBsYWNlaG9sZGVyRHVyYXRpb24gPSAwO1xuXG4gICAgcHVibGljIGNvbnN0cnVjdG9yKHByaXZhdGUgY29udGV4dDogQXVkaW9Db250ZXh0KSB7fVxuXG4gICAgcHVibGljIGdldCBkdXJhdGlvblNlY29uZHMoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuY2xpcER1cmF0aW9uIHx8IHRoaXMucGxhY2Vob2xkZXJEdXJhdGlvbjtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0IGR1cmF0aW9uU2Vjb25kcyh2YWw6IG51bWJlcikge1xuICAgICAgICB0aGlzLmNsaXBEdXJhdGlvbiA9IHZhbDtcbiAgICAgICAgdGhpcy5vYnNlcnZhYmxlLnVwZGF0ZShbdGhpcy50aW1lU2Vjb25kcywgdGhpcy5jbGlwRHVyYXRpb25dKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IHRpbWVTZWNvbmRzKCk6IG51bWJlciB7XG4gICAgICAgIC8vIFRoZSBtb2R1bG8gaXMgdG8gZW5zdXJlIHRoYXQgd2UncmUgb25seSBsb29raW5nIGF0IHRoZSBtb3N0IHJlY2VudCBjbGlwXG4gICAgICAgIC8vIHRpbWUsIGFzIHRoZSBjb250ZXh0IGlzIGxvbmctcnVubmluZyBhbmQgbXVsdGlwbGUgcGxheXMgbWlnaHQgbm90IGJlXG4gICAgICAgIC8vIGluZm9ybWVkIHRvIHVzIChpZiB0aGUgY29udHJvbCBpcyBsb29waW5nLCBmb3IgZXhhbXBsZSkuIEJ5IHRha2luZyB0aGVcbiAgICAgICAgLy8gcmVtYWluZGVyIG9mIHRoZSBkaXZpc2lvbiBvcGVyYXRpb24sIHdlJ3JlIGFzc3VtaW5nIHRoYXQgcGxheWJhY2sgaXNcbiAgICAgICAgLy8gaW5jb21wbGV0ZSBvciBzdG9wcGVkLCB0aHVzIGdpdmluZyBhbiBhY2N1cmF0ZSBwb3NpdGlvbiB3aXRoaW4gdGhlIGFjdGl2ZVxuICAgICAgICAvLyBjbGlwIHNlZ21lbnQuXG4gICAgICAgIHJldHVybiAodGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lIC0gdGhpcy5jbGlwU3RhcnQpICUgdGhpcy5jbGlwRHVyYXRpb247XG4gICAgfVxuXG4gICAgcHVibGljIGdldCBsaXZlRGF0YSgpOiBTaW1wbGVPYnNlcnZhYmxlPG51bWJlcltdPiB7XG4gICAgICAgIHJldHVybiB0aGlzLm9ic2VydmFibGU7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBjaGVja1RpbWUgPSAoZm9yY2UgPSBmYWxzZSk6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCBub3cgPSB0aGlzLnRpbWVTZWNvbmRzOyAvLyBjYWxjdWxhdGVkIGR5bmFtaWNhbGx5XG4gICAgICAgIGlmICh0aGlzLmxhc3RDaGVjayAhPT0gbm93IHx8IGZvcmNlKSB7XG4gICAgICAgICAgICB0aGlzLm9ic2VydmFibGUudXBkYXRlKFtub3csIHRoaXMuZHVyYXRpb25TZWNvbmRzXSk7XG4gICAgICAgICAgICB0aGlzLmxhc3RDaGVjayA9IG5vdztcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBQb3B1bGF0ZXMgZGVmYXVsdCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgYXVkaW8gY2xpcCBmcm9tIHRoZSBldmVudCBib2R5LlxuICAgICAqIFRoZSBwbGFjZWhvbGRlcnMgd2lsbCBiZSBvdmVycmlkZGVuIG9uY2Uga25vd24uXG4gICAgICogQHBhcmFtIHtNYXRyaXhFdmVudH0gZXZlbnQgVGhlIGV2ZW50IHRvIHVzZSBmb3IgcGxhY2Vob2xkZXJzLlxuICAgICAqL1xuICAgIHB1YmxpYyBwb3B1bGF0ZVBsYWNlaG9sZGVyc0Zyb20oZXZlbnQ6IE1hdHJpeEV2ZW50KTogdm9pZCB7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uTXMgPSBOdW1iZXIoZXZlbnQuZ2V0Q29udGVudCgpW1wiaW5mb1wiXT8uW1wiZHVyYXRpb25cIl0pO1xuICAgICAgICBpZiAoTnVtYmVyLmlzRmluaXRlKGR1cmF0aW9uTXMpKSB0aGlzLnBsYWNlaG9sZGVyRHVyYXRpb24gPSBkdXJhdGlvbk1zIC8gMTAwMDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNYXJrIHRoZSB0aW1lIGluIHRoZSBhdWRpbyBjb250ZXh0IHdoZXJlIHRoZSBjbGlwIHN0YXJ0cy9oYXMgYmVlbiBsb2FkZWQuXG4gICAgICogVGhpcyBpcyB0byBlbnN1cmUgdGhlIGNsb2NrIGlzbid0IHNrZXdlZCBpbnRvIHRoaW5raW5nIGl0IGlzIH4wLjVzIGludG9cbiAgICAgKiBhIGNsaXAgd2hlbiB0aGUgZHVyYXRpb24gaXMgc2V0LlxuICAgICAqL1xuICAgIHB1YmxpYyBmbGFnTG9hZFRpbWUoKTogdm9pZCB7XG4gICAgICAgIHRoaXMuY2xpcFN0YXJ0ID0gdGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lO1xuICAgIH1cblxuICAgIHB1YmxpYyBmbGFnU3RhcnQoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLnN0b3BwZWQpIHtcbiAgICAgICAgICAgIHRoaXMuY2xpcFN0YXJ0ID0gdGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lO1xuICAgICAgICAgICAgdGhpcy5zdG9wcGVkID0gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXRoaXMudGltZXJJZCkge1xuICAgICAgICAgICAgLy8gMTAwbXMgaW50ZXJ2YWwgdG8gbWFrZSBzdXJlIHRoZSB0aW1lIGlzIGFzIGFjY3VyYXRlIGFzIHBvc3NpYmxlIHdpdGhvdXQgYmVpbmcgb3Zlcmx5IGluc2FuZVxuICAgICAgICAgICAgdGhpcy50aW1lcklkID0gd2luZG93LnNldEludGVydmFsKHRoaXMuY2hlY2tUaW1lLCAxMDApO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGZsYWdTdG9wKCk6IHZvaWQge1xuICAgICAgICB0aGlzLnN0b3BwZWQgPSB0cnVlO1xuXG4gICAgICAgIC8vIFJlc2V0IHRoZSBjbG9jayB0aW1lIG5vdyBzbyB0aGF0IHRoZSB1cGRhdGUgZ29pbmcgb3V0IHdpbGwgdHJpZ2dlciBjb21wb25lbnRzXG4gICAgICAgIC8vIHRvIGNoZWNrIHRoZWlyIHNlZWsvcG9zaXRpb24gaW5mb3JtYXRpb24gKGFsb25nc2lkZSB0aGUgY2xvY2spLlxuICAgICAgICB0aGlzLmNsaXBTdGFydCA9IHRoaXMuY29udGV4dC5jdXJyZW50VGltZTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc3luY1RvKGNvbnRleHRUaW1lOiBudW1iZXIsIGNsaXBUaW1lOiBudW1iZXIpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5jbGlwU3RhcnQgPSBjb250ZXh0VGltZSAtIGNsaXBUaW1lO1xuICAgICAgICB0aGlzLnN0b3BwZWQgPSBmYWxzZTsgLy8gY291bnQgYXMgYSBtaWQtc3RyZWFtIHBhdXNlIChpZiB3ZSB3ZXJlIHN0b3BwZWQpXG4gICAgICAgIHRoaXMuY2hlY2tUaW1lKHRydWUpO1xuICAgIH1cblxuICAgIHB1YmxpYyBkZXN0cm95KCk6IHZvaWQge1xuICAgICAgICB0aGlzLm9ic2VydmFibGUuY2xvc2UoKTtcbiAgICAgICAgaWYgKHRoaXMudGltZXJJZCkgY2xlYXJJbnRlcnZhbCh0aGlzLnRpbWVySWQpO1xuICAgIH1cbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFRQSxJQUFBQSxnQkFBQSxHQUFBQyxPQUFBO0FBUkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBT0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sTUFBTUMsYUFBYSxDQUF5QjtFQVN4Q0MsV0FBV0EsQ0FBU0MsT0FBcUIsRUFBRTtJQUFBLElBQUFDLGdCQUFBLENBQUFDLE9BQUEscUJBUjlCLENBQUM7SUFBQSxJQUFBRCxnQkFBQSxDQUFBQyxPQUFBLG1CQUNILElBQUk7SUFBQSxJQUFBRCxnQkFBQSxDQUFBQyxPQUFBLHFCQUNGLENBQUM7SUFBQSxJQUFBRCxnQkFBQSxDQUFBQyxPQUFBLHNCQUNBLElBQUlDLGlDQUFnQixDQUFXLENBQUM7SUFBQSxJQUFBRixnQkFBQSxDQUFBQyxPQUFBO0lBQUEsSUFBQUQsZ0JBQUEsQ0FBQUMsT0FBQSx3QkFFOUIsQ0FBQztJQUFBLElBQUFELGdCQUFBLENBQUFDLE9BQUEs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