UNPKG

matrix-react-sdk

Version:
137 lines (132 loc) 18.1 kB
"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+KCk7XG4gICAgcHJpdmF0ZSB0aW1lcklkPzogbnVtYmVyO1xuICAgIHByaXZhdGUgY2xpcER1cmF0aW9uID0gMDtcbiAgICBwcml2YXRlIHBsYWNlaG9sZGVyRHVyYXRpb24gPSAwO1xuXG4gICAgcHVibGljIGNvbnN0cnVjdG9yKHByaXZhdGUgY29udGV4dDogQXVkaW9Db250ZXh0KSB7fVxuXG4gICAgcHVibGljIGdldCBkdXJhdGlvblNlY29uZHMoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuY2xpcER1cmF0aW9uIHx8IHRoaXMucGxhY2Vob2xkZXJEdXJhdGlvbjtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0IGR1cmF0aW9uU2Vjb25kcyh2YWw6IG51bWJlcikge1xuICAgICAgICB0aGlzLmNsaXBEdXJhdGlvbiA9IHZhbDtcbiAgICAgICAgdGhpcy5vYnNlcnZhYmxlLnVwZGF0ZShbdGhpcy50aW1lU2Vjb25kcywgdGhpcy5jbGlwRHVyYXRpb25dKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IHRpbWVTZWNvbmRzKCk6IG51bWJlciB7XG4gICAgICAgIC8vIFRoZSBtb2R1bG8gaXMgdG8gZW5zdXJlIHRoYXQgd2UncmUgb25seSBsb29raW5nIGF0IHRoZSBtb3N0IHJlY2VudCBjbGlwXG4gICAgICAgIC8vIHRpbWUsIGFzIHRoZSBjb250ZXh0IGlzIGxvbmctcnVubmluZyBhbmQgbXVsdGlwbGUgcGxheXMgbWlnaHQgbm90IGJlXG4gICAgICAgIC8vIGluZm9ybWVkIHRvIHVzIChpZiB0aGUgY29udHJvbCBpcyBsb29waW5nLCBmb3IgZXhhbXBsZSkuIEJ5IHRha2luZyB0aGVcbiAgICAgICAgLy8gcmVtYWluZGVyIG9mIHRoZSBkaXZpc2lvbiBvcGVyYXRpb24sIHdlJ3JlIGFzc3VtaW5nIHRoYXQgcGxheWJhY2sgaXNcbiAgICAgICAgLy8gaW5jb21wbGV0ZSBvciBzdG9wcGVkLCB0aHVzIGdpdmluZyBhbiBhY2N1cmF0ZSBwb3NpdGlvbiB3aXRoaW4gdGhlIGFjdGl2ZVxuICAgICAgICAvLyBjbGlwIHNlZ21lbnQuXG4gICAgICAgIHJldHVybiAodGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lIC0gdGhpcy5jbGlwU3RhcnQpICUgdGhpcy5jbGlwRHVyYXRpb247XG4gICAgfVxuXG4gICAgcHVibGljIGdldCBsaXZlRGF0YSgpOiBTaW1wbGVPYnNlcnZhYmxlPG51bWJlcltdPiB7XG4gICAgICAgIHJldHVybiB0aGlzLm9ic2VydmFibGU7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBjaGVja1RpbWUgPSAoZm9yY2UgPSBmYWxzZSk6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCBub3cgPSB0aGlzLnRpbWVTZWNvbmRzOyAvLyBjYWxjdWxhdGVkIGR5bmFtaWNhbGx5XG4gICAgICAgIGlmICh0aGlzLmxhc3RDaGVjayAhPT0gbm93IHx8IGZvcmNlKSB7XG4gICAgICAgICAgICB0aGlzLm9ic2VydmFibGUudXBkYXRlKFtub3csIHRoaXMuZHVyYXRpb25TZWNvbmRzXSk7XG4gICAgICAgICAgICB0aGlzLmxhc3RDaGVjayA9IG5vdztcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBQb3B1bGF0ZXMgZGVmYXVsdCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgYXVkaW8gY2xpcCBmcm9tIHRoZSBldmVudCBib2R5LlxuICAgICAqIFRoZSBwbGFjZWhvbGRlcnMgd2lsbCBiZSBvdmVycmlkZGVuIG9uY2Uga25vd24uXG4gICAgICogQHBhcmFtIHtNYXRyaXhFdmVudH0gZXZlbnQgVGhlIGV2ZW50IHRvIHVzZSBmb3IgcGxhY2Vob2xkZXJzLlxuICAgICAqL1xuICAgIHB1YmxpYyBwb3B1bGF0ZVBsYWNlaG9sZGVyc0Zyb20oZXZlbnQ6IE1hdHJpeEV2ZW50KTogdm9pZCB7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uTXMgPSBOdW1iZXIoZXZlbnQuZ2V0Q29udGVudCgpW1wiaW5mb1wiXT8uW1wiZHVyYXRpb25cIl0pO1xuICAgICAgICBpZiAoTnVtYmVyLmlzRmluaXRlKGR1cmF0aW9uTXMpKSB0aGlzLnBsYWNlaG9sZGVyRHVyYXRpb24gPSBkdXJhdGlvbk1zIC8gMTAwMDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNYXJrIHRoZSB0aW1lIGluIHRoZSBhdWRpbyBjb250ZXh0IHdoZXJlIHRoZSBjbGlwIHN0YXJ0cy9oYXMgYmVlbiBsb2FkZWQuXG4gICAgICogVGhpcyBpcyB0byBlbnN1cmUgdGhlIGNsb2NrIGlzbid0IHNrZXdlZCBpbnRvIHRoaW5raW5nIGl0IGlzIH4wLjVzIGludG9cbiAgICAgKiBhIGNsaXAgd2hlbiB0aGUgZHVyYXRpb24gaXMgc2V0LlxuICAgICAqL1xuICAgIHB1YmxpYyBmbGFnTG9hZFRpbWUoKTogdm9pZCB7XG4gICAgICAgIHRoaXMuY2xpcFN0YXJ0ID0gdGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lO1xuICAgIH1cblxuICAgIHB1YmxpYyBmbGFnU3RhcnQoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLnN0b3BwZWQpIHtcbiAgICAgICAgICAgIHRoaXMuY2xpcFN0YXJ0ID0gdGhpcy5jb250ZXh0LmN1cnJlbnRUaW1lO1xuICAgICAgICAgICAgdGhpcy5zdG9wcGVkID0gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXRoaXMudGltZXJJZCkge1xuICAgICAgICAgICAgLy8gMTAwbXMgaW50ZXJ2YWwgdG8gbWFrZSBzdXJlIHRoZSB0aW1lIGlzIGFzIGFjY3VyYXRlIGFzIHBvc3NpYmxlIHdpdGhvdXQgYmVpbmcgb3Zlcmx5IGluc2FuZVxuICAgICAgICAgICAgdGhpcy50aW1lcklkID0gd2luZG93LnNldEludGVydmFsKHRoaXMuY2hlY2tUaW1lLCAxMDApO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGZsYWdTdG9wKCk6IHZvaWQge1xuICAgICAgICB0aGlzLnN0b3BwZWQgPSB0cnVlO1xuXG4gICAgICAgIC8vIFJlc2V0IHRoZSBjbG9jayB0aW1lIG5vdyBzbyB0aGF0IHRoZSB1cGRhdGUgZ29pbmcgb3V0IHdpbGwgdHJpZ2dlciBjb21wb25lbnRzXG4gICAgICAgIC8vIHRvIGNoZWNrIHRoZWlyIHNlZWsvcG9zaXRpb24gaW5mb3JtYXRpb24gKGFsb25nc2lkZSB0aGUgY2xvY2spLlxuICAgICAgICB0aGlzLmNsaXBTdGFydCA9IHRoaXMuY29udGV4dC5jdXJyZW50VGltZTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc3luY1RvKGNvbnRleHRUaW1lOiBudW1iZXIsIGNsaXBUaW1lOiBudW1iZXIpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5jbGlwU3RhcnQgPSBjb250ZXh0VGltZSAtIGNsaXBUaW1lO1xuICAgICAgICB0aGlzLnN0b3BwZWQgPSBmYWxzZTsgLy8gY291bnQgYXMgYSBtaWQtc3RyZWFtIHBhdXNlIChpZiB3ZSB3ZXJlIHN0b3BwZWQpXG4gICAgICAgIHRoaXMuY2hlY2tUaW1lKHRydWUpO1xuICAgIH1cblxuICAgIHB1YmxpYyBkZXN0cm95KCk6IHZvaWQge1xuICAgICAgICB0aGlzLm9ic2VydmFibGUuY2xvc2UoKTtcbiAgICAgICAgaWYgKHRoaXMudGltZXJJZCkgY2xlYXJJbnRlcnZhbCh0aGlzLnRpbWVySWQpO1xuICAgIH1cbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFRQSxJQUFBQSxnQkFBQSxHQUFBQyxPQUFBO0FBUkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBT0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sTUFBTUMsYUFBYSxDQUF5QjtFQVN4Q0MsV0FBV0EsQ0FBU0MsT0FBcUIsRUFBRTtJQUFBLElBQUFDLGdCQUFBLENBQUFDLE9BQUEscUJBUjlCLENBQUM7SUFBQSxJQUFBRCxnQkFBQSxDQUFBQyxPQUFBLG1CQUNILElBQUk7SUFBQSxJQUFBRCxnQkFBQSxDQUFBQyxPQUFBLHFCQUNGLENBQUM7SUFBQSxJQUFBRCxnQkFBQSxDQUFBQyxPQUFBLHNCQUNBLElBQUlDLGlDQUFnQixDQUFXLENBQUM7SUFBQSxJQUFBRixnQkFBQSxDQUFBQyxPQUFBO0lBQUEsSUFBQUQsZ0JBQUEsQ0FBQUMsT0FBQSx3QkFFOUIsQ0FBQztJQUFBLElBQUFELGdCQUFBLENBQUFDLE9BQUEsK0JBQ00sQ0FBQztJQUFBLElBQUFELGdCQUFBLENBQUFDLE9BQUEscUJBMkJYLENBQUNFLEtBQUssR0FBRyxLQUFLLEtBQVc7TUFDekMsTUFBTUMsR0FBRyxHQUFHLElBQUksQ0FBQ0MsV0FBVyxDQUFDLENBQUM7TUFDOUIsSUFBSSxJQUFJLENBQUNDLFNBQVMsS0FBS0YsR0FBRyxJQUFJRCxLQUFLLEVBQUU7UUFDakMsSUFBSSxDQUFDSSxVQUFVLENBQUNDLE1BQU0sQ0FBQyxDQUFDSixHQUFHLEVBQUUsSUFBSSxDQUFDSyxlQUFlLENBQUMsQ0FBQztRQUNuRCxJQUFJLENBQUNILFNBQVMsR0FBR0YsR0FBRztNQUN4QjtJQUNKLENBQUM7SUFBQSxLQS9CMEJMLE9BQXFCLEdBQXJCQSxPQUFxQjtFQUFHO0VBRW5ELElBQVdVLGVBQWVBLENBQUEsRUFBVztJQUNqQyxPQUFPLElBQUksQ0FBQ0MsWUFBWSxJQUFJLElBQUksQ0FBQ0MsbUJBQW1CO0VBQ3hEO0VBRUEsSUFBV0YsZUFBZUEsQ0FBQ0csR0FBVyxFQUFFO0lBQ3BDLElBQUksQ0FBQ0YsWUFBWSxHQUFHRSxHQUFHO0lBQ3ZCLElBQUksQ0FBQ0wsVUFBVSxDQUFDQyxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUNILFdBQVcsRUFBRSxJQUFJLENBQUNLLFlBQVksQ0FBQyxDQUFDO0VBQ2pFO0VBRUEsSUFBV0wsV0FBV0EsQ0FBQSxFQUFXO0lBQzdCO0lBQ0E7SUFDQTtJQUNBO0lBQ0E7SUFDQTtJQUNBLE9BQU8sQ0FBQyxJQUFJLENBQUNOLE9BQU8sQ0FBQ2MsV0FBVyxHQUFHLElBQUksQ0FBQ0MsU0FBUyxJQUFJLElBQUksQ0FBQ0osWUFBWTtFQUMxRTtFQUVBLElBQVdLLFFBQVFBLENBQUEsRUFBK0I7SUFDOUMsT0FBTyxJQUFJLENBQUNSLFVBQVU7RUFDMUI7RUFVQTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0VBQ1dTLHdCQUF3QkEsQ0FBQ0MsS0FBa0IsRUFBUTtJQUN0RCxNQUFNQyxVQUFVLEdBQUdDLE1BQU0sQ0FBQ0YsS0FBSyxDQUFDRyxVQUFVLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLFVBQVUsQ0FBQyxDQUFDO0lBQ25FLElBQUlELE1BQU0sQ0FBQ0UsUUFBUSxDQUFDSCxVQUFVLENBQUMsRUFBRSxJQUFJLENBQUNQLG1CQUFtQixHQUFHTyxVQUFVLEdBQUcsSUFBSTtFQUNqRjs7RUFFQTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0VBQ1dJLFlBQVlBLENBQUEsRUFBUztJQUN4QixJQUFJLENBQUNSLFNBQVMsR0FBRyxJQUFJLENBQUNmLE9BQU8sQ0FBQ2MsV0FBVztFQUM3QztFQUVPVSxTQUFTQSxDQUFBLEVBQVM7SUFDckIsSUFBSSxJQUFJLENBQUNDLE9BQU8sRUFBRTtNQUNkLElBQUksQ0FBQ1YsU0FBUyxHQUFHLElBQUksQ0FBQ2YsT0FBTyxDQUFDYyxXQUFXO01BQ3pDLElBQUksQ0FBQ1csT0FBTyxHQUFHLEtBQUs7SUFDeEI7SUFFQSxJQUFJLENBQUMsSUFBSSxDQUFDQyxPQUFPLEVBQUU7TUFDZjtNQUNBLElBQUksQ0FBQ0EsT0FBTyxHQUFHQyxNQUFNLENBQUNDLFdBQVcsQ0FBQyxJQUFJLENBQUNDLFNBQVMsRUFBRSxHQUFHLENBQUM7SUFDMUQ7RUFDSjtFQUVPQyxRQUFRQSxDQUFBLEVBQVM7SUFDcEIsSUFBSSxDQUFDTCxPQUFPLEdBQUcsSUFBSTs7SUFFbkI7SUFDQTtJQUNBLElBQUksQ0FBQ1YsU0FBUyxHQUFHLElBQUksQ0FBQ2YsT0FBTyxDQUFDYyxXQUFXO0VBQzdDO0VBRU9pQixNQUFNQSxDQUFDQyxXQUFtQixFQUFFQyxRQUFnQixFQUFRO0lBQ3ZELElBQUksQ0FBQ2xCLFNBQVMsR0FBR2lCLFdBQVcsR0FBR0MsUUFBUTtJQUN2QyxJQUFJLENBQUNSLE9BQU8sR0FBRyxLQUFLLENBQUMsQ0FBQztJQUN0QixJQUFJLENBQUNJLFNBQVMsQ0FBQyxJQUFJLENBQUM7RUFDeEI7RUFFT0ssT0FBT0EsQ0FBQSxFQUFTO0lBQ25CLElBQUksQ0FBQzFCLFVBQVUsQ0FBQzJCLEtBQUssQ0FBQyxDQUFDO0lBQ3ZCLElBQUksSUFBSSxDQUFDVCxPQUFPLEVBQUVVLGFBQWEsQ0FBQyxJQUFJLENBQUNWLE9BQU8sQ0FBQztFQUNqRDtBQUNKO0FBQUNXLE9BQUEsQ0FBQXZDLGFBQUEsR0FBQUEsYUFBQSIsImlnbm9yZUxpc3QiOltdfQ==