matrix-react-sdk
Version:
SDK for matrix.org using React
293 lines (275 loc) • 43.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PlaybackState = exports.Playback = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _events = _interopRequireDefault(require("events"));
var _matrixWidgetApi = require("matrix-widget-api");
var _logger = require("matrix-js-sdk/src/logger");
var _utils = require("matrix-js-sdk/src/utils");
var _AsyncStore = require("../stores/AsyncStore");
var _arrays = require("../utils/arrays");
var _PlaybackClock = require("./PlaybackClock");
var _compat = require("./compat");
var _numbers = require("../utils/numbers");
var _consts = require("./consts");
var _PlaybackEncoder = require("../PlaybackEncoder");
/*
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.
*/
let PlaybackState = exports.PlaybackState = /*#__PURE__*/function (PlaybackState) {
PlaybackState["Decoding"] = "decoding";
PlaybackState["Stopped"] = "stopped";
PlaybackState["Paused"] = "paused";
PlaybackState["Playing"] = "playing";
return PlaybackState;
}({}); // active progress through timeline
const THUMBNAIL_WAVEFORM_SAMPLES = 100; // arbitrary: [30,120]
class Playback extends _events.default {
/**
* Creates a new playback instance from a buffer.
* @param {ArrayBuffer} buf The buffer containing the sound sample.
* @param {number[]} seedWaveform Optional seed waveform to present until the proper waveform
* can be calculated. Contains values between zero and one, inclusive.
*/
constructor(buf, seedWaveform = _consts.DEFAULT_WAVEFORM) {
super();
// Capture the file size early as reading the buffer will result in a 0-length buffer left behind
/**
* Stable waveform for representing a thumbnail of the media. Values are
* guaranteed to be between zero and one, inclusive.
*/
(0, _defineProperty2.default)(this, "thumbnailWaveform", void 0);
(0, _defineProperty2.default)(this, "context", void 0);
(0, _defineProperty2.default)(this, "source", void 0);
(0, _defineProperty2.default)(this, "state", PlaybackState.Decoding);
(0, _defineProperty2.default)(this, "audioBuf", void 0);
(0, _defineProperty2.default)(this, "element", void 0);
(0, _defineProperty2.default)(this, "resampledWaveform", void 0);
(0, _defineProperty2.default)(this, "waveformObservable", new _matrixWidgetApi.SimpleObservable());
(0, _defineProperty2.default)(this, "clock", void 0);
(0, _defineProperty2.default)(this, "fileSize", void 0);
(0, _defineProperty2.default)(this, "onPlaybackEnd", async () => {
await this.context.suspend();
this.emit(PlaybackState.Stopped);
});
this.buf = buf;
this.fileSize = this.buf.byteLength;
this.context = (0, _compat.createAudioContext)();
this.resampledWaveform = (0, _arrays.arrayFastResample)(seedWaveform ?? _consts.DEFAULT_WAVEFORM, _consts.PLAYBACK_WAVEFORM_SAMPLES);
this.thumbnailWaveform = (0, _arrays.arrayFastResample)(seedWaveform ?? _consts.DEFAULT_WAVEFORM, THUMBNAIL_WAVEFORM_SAMPLES);
this.waveformObservable.update(this.resampledWaveform);
this.clock = new _PlaybackClock.PlaybackClock(this.context);
}
/**
* Size of the audio clip in bytes. May be zero if unknown. This is updated
* when the playback goes through phase changes.
*/
get sizeBytes() {
return this.fileSize;
}
/**
* Stable waveform for the playback. Values are guaranteed to be between
* zero and one, inclusive.
*/
get waveform() {
return this.resampledWaveform;
}
get waveformData() {
return this.waveformObservable;
}
get clockInfo() {
return this.clock;
}
get liveData() {
return this.clock.liveData;
}
get timeSeconds() {
return this.clock.timeSeconds;
}
get durationSeconds() {
return this.clock.durationSeconds;
}
get currentState() {
return this.state;
}
get isPlaying() {
return this.currentState === PlaybackState.Playing;
}
emit(event, ...args) {
this.state = event;
super.emit(event, ...args);
super.emit(_AsyncStore.UPDATE_EVENT, event, ...args);
return true; // we don't ever care if the event had listeners, so just return "yes"
}
destroy() {
// Dev note: It's critical that we call stop() during cleanup to ensure that downstream callers
// are aware of the final clock position before the user triggered an unload.
// noinspection JSIgnoredPromiseFromCall - not concerned about being called async here
this.stop();
this.removeAllListeners();
this.clock.destroy();
this.waveformObservable.close();
if (this.element) {
URL.revokeObjectURL(this.element.src);
this.element.remove();
}
}
async prepare() {
// don't attempt to decode the media again
// AudioContext.decodeAudioData detaches the array buffer `this.buf`
// meaning it cannot be re-read
if (this.state !== PlaybackState.Decoding) {
return;
}
// The point where we use an audio element is fairly arbitrary, though we don't want
// it to be too low. As of writing, voice messages want to show a waveform but audio
// messages do not. Using an audio element means we can't show a waveform preview, so
// we try to target the difference between a voice message file and large audio file.
// Overall, the point of this is to avoid memory-related issues due to storing a massive
// audio buffer in memory, as that can balloon to far greater than the input buffer's
// byte length.
if (this.buf.byteLength > 5 * 1024 * 1024) {
// 5mb
_logger.logger.log("Audio file too large: processing through <audio /> element");
this.element = document.createElement("AUDIO");
const deferred = (0, _utils.defer)();
this.element.onloadeddata = deferred.resolve;
this.element.onerror = deferred.reject;
this.element.src = URL.createObjectURL(new Blob([this.buf]));
await deferred.promise; // make sure the audio element is ready for us
} else {
// Safari compat: promise API not supported on this function
this.audioBuf = await new Promise((resolve, reject) => {
this.context.decodeAudioData(this.buf, b => resolve(b), async e => {
try {
// This error handler is largely for Safari as well, which doesn't support Opus/Ogg
// very well.
_logger.logger.error("Error decoding recording: ", e);
_logger.logger.warn("Trying to re-encode to WAV instead...");
const wav = await (0, _compat.decodeOgg)(this.buf);
// noinspection ES6MissingAwait - not needed when using callbacks
this.context.decodeAudioData(wav, b => resolve(b), e => {
_logger.logger.error("Still failed to decode recording: ", e);
reject(e);
});
} catch (e) {
_logger.logger.error("Caught decoding error:", e);
reject(e);
}
});
});
// Update the waveform to the real waveform once we have channel data to use. We don't
// exactly trust the user-provided waveform to be accurate...
this.resampledWaveform = await _PlaybackEncoder.PlaybackEncoder.instance.getPlaybackWaveform(this.audioBuf.getChannelData(0));
}
this.waveformObservable.update(this.resampledWaveform);
this.clock.flagLoadTime(); // must happen first because setting the duration fires a clock update
this.clock.durationSeconds = this.element?.duration ?? this.audioBuf.duration;
// Signal that we're not decoding anymore. This is done last to ensure the clock is updated for
// when the downstream callers try to use it.
this.emit(PlaybackState.Stopped); // signal that we're not decoding anymore
}
async play() {
// We can't restart a buffer source, so we need to create a new one if we hit the end
if (this.state === PlaybackState.Stopped) {
this.disconnectSource();
this.makeNewSourceBuffer();
if (this.element) {
await this.element.play();
} else {
this.source.start();
}
}
// We use the context suspend/resume functions because it allows us to pause a source
// node, but that still doesn't help us when the source node runs out (see above).
await this.context.resume();
this.clock.flagStart();
this.emit(PlaybackState.Playing);
}
disconnectSource() {
if (this.element) return; // leave connected, we can (and must) re-use it
this.source?.disconnect();
this.source?.removeEventListener("ended", this.onPlaybackEnd);
}
makeNewSourceBuffer() {
if (this.element && this.source) return; // leave connected, we can (and must) re-use it
if (this.element) {
this.source = this.context.createMediaElementSource(this.element);
} else {
this.source = this.context.createBufferSource();
this.source.buffer = this.audioBuf ?? null;
}
this.source.addEventListener("ended", this.onPlaybackEnd);
this.source.connect(this.context.destination);
}
async pause() {
await this.context.suspend();
this.emit(PlaybackState.Paused);
}
async stop() {
await this.onPlaybackEnd();
this.clock.flagStop();
}
async toggle() {
if (this.isPlaying) await this.pause();else await this.play();
}
async skipTo(timeSeconds) {
// Dev note: this function talks a lot about clock desyncs. There is a clock running
// independently to the audio context and buffer so that accurate human-perceptible
// time can be exposed. The PlaybackClock class has more information, but the short
// version is that we need to line up the useful time (clip position) with the context
// time, and avoid as many deviations as possible as otherwise the user could see the
// wrong time, and we stop playback at the wrong time, etc.
timeSeconds = (0, _numbers.clamp)(timeSeconds, 0, this.clock.durationSeconds);
// Track playing state so we don't cause seeking to start playing the track.
const isPlaying = this.isPlaying;
if (isPlaying) {
// Pause first so we can get an accurate measurement of time
await this.context.suspend();
}
// We can't simply tell the context/buffer to jump to a time, so we have to
// start a whole new buffer and start it from the new time offset.
const now = this.context.currentTime;
this.disconnectSource();
this.makeNewSourceBuffer();
// We have to resync the clock because it can get confused about where we're
// at in the audio clip.
this.clock.syncTo(now, timeSeconds);
// Always start the source to queue it up. We have to do this now (and pause
// quickly if we're not supposed to be playing) as otherwise the clock can desync
// when it comes time to the user hitting play. After a couple jumps, the user
// will have desynced the clock enough to be about 10-15 seconds off, while this
// keeps it as close to perfect as humans can perceive.
if (this.element) {
this.element.currentTime = timeSeconds;
} else {
this.source.start(now, timeSeconds);
}
// Dev note: it's critical that the code gap between `this.source.start()` and
// `this.pause()` is as small as possible: we do not want to delay *anything*
// as that could cause a clock desync, or a buggy feeling as a single note plays
// during seeking.
if (isPlaying) {
// If we were playing before, continue the context so the clock doesn't desync.
await this.context.resume();
} else {
// As mentioned above, we'll have to pause the clip if we weren't supposed to
// be playing it just yet. If we didn't have this, the audio clip plays but all
// the states will be wrong: clock won't advance, pause state doesn't match the
// blaring noise leaving the user's speakers, etc.
//
// Also as mentioned, if the code gap is small enough then this should be
// executed immediately after the start time, leaving no feasible time for the
// user's speakers to play any sound.
await this.pause();
}
}
}
exports.Playback = Playback;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfZXZlbnRzIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsInJlcXVpcmUiLCJfbWF0cml4V2lkZ2V0QXBpIiwiX2xvZ2dlciIsIl91dGlscyIsIl9Bc3luY1N0b3JlIiwiX2FycmF5cyIsIl9QbGF5YmFja0Nsb2NrIiwiX2NvbXBhdCIsIl9udW1iZXJzIiwiX2NvbnN0cyIsIl9QbGF5YmFja0VuY29kZXIiLCJQbGF5YmFja1N0YXRlIiwiZXhwb3J0cyIsIlRIVU1CTkFJTF9XQVZFRk9STV9TQU1QTEVTIiwiUGxheWJhY2siLCJFdmVudEVtaXR0ZXIiLCJjb25zdHJ1Y3RvciIsImJ1ZiIsInNlZWRXYXZlZm9ybSIsIkRFRkFVTFRfV0FWRUZPUk0iLCJfZGVmaW5lUHJvcGVydHkyIiwiZGVmYXVsdCIsIkRlY29kaW5nIiwiU2ltcGxlT2JzZXJ2YWJsZSIsImNvbnRleHQiLCJzdXNwZW5kIiwiZW1pdCIsIlN0b3BwZWQiLCJmaWxlU2l6ZSIsImJ5dGVMZW5ndGgiLCJjcmVhdGVBdWRpb0NvbnRleHQiLCJyZXNhbXBsZWRXYXZlZm9ybSIsImFycmF5RmFzdFJlc2FtcGxlIiwiUExBWUJBQ0tfV0FWRUZPUk1fU0FNUExFUyIsInRodW1ibmFpbFdhdmVmb3JtIiwid2F2ZWZvcm1PYnNlcnZhYmxlIiwidXBkYXRlIiwiY2xvY2siLCJQbGF5YmFja0Nsb2NrIiwic2l6ZUJ5dGVzIiwid2F2ZWZvcm0iLCJ3YXZlZm9ybURhdGEiLCJjbG9ja0luZm8iLCJsaXZlRGF0YSIsInRpbWVTZWNvbmRzIiwiZHVyYXRpb25TZWNvbmRzIiwiY3VycmVudFN0YXRlIiwic3RhdGUiLCJpc1BsYXlpbmciLCJQbGF5aW5nIiwiZXZlbnQiLCJhcmdzIiwiVVBEQVRFX0VWRU5UIiwiZGVzdHJveSIsInN0b3AiLCJyZW1vdmVBbGxMaXN0ZW5lcnMiLCJjbG9zZSIsImVsZW1lbnQiLCJVUkwiLCJyZXZva2VPYmplY3RVUkwiLCJzcmMiLCJyZW1vdmUiLCJwcmVwYXJlIiwibG9nZ2VyIiwibG9nIiwiZG9jdW1lbnQiLCJjcmVhdGVFbGVtZW50IiwiZGVmZXJyZWQiLCJkZWZlciIsIm9ubG9hZGVkZGF0YSIsInJlc29sdmUiLCJvbmVycm9yIiwicmVqZWN0IiwiY3JlYXRlT2JqZWN0VVJMIiwiQmxvYiIsInByb21pc2UiLCJhdWRpb0J1ZiIsIlByb21pc2UiLCJkZWNvZGVBdWRpb0RhdGEiLCJiIiwiZSIsImVycm9yIiwid2FybiIsIndhdiIsImRlY29kZU9nZyIsIlBsYXliYWNrRW5jb2RlciIsImluc3RhbmNlIiwiZ2V0UGxheWJhY2tXYXZlZm9ybSIsImdldENoYW5uZWxEYXRhIiwiZmxhZ0xvYWRUaW1lIiwiZHVyYXRpb24iLCJwbGF5IiwiZGlzY29ubmVjdFNvdXJjZSIsIm1ha2VOZXdTb3VyY2VCdWZmZXIiLCJzb3VyY2UiLCJzdGFydCIsInJlc3VtZSIsImZsYWdTdGFydCIsImRpc2Nvbm5lY3QiLCJyZW1vdmVFdmVudExpc3RlbmVyIiwib25QbGF5YmFja0VuZCIsImNyZWF0ZU1lZGlhRWxlbWVudFNvdXJjZSIsImNyZWF0ZUJ1ZmZlclNvdXJjZSIsImJ1ZmZlciIsImFkZEV2ZW50TGlzdGVuZXIiLCJjb25uZWN0IiwiZGVzdGluYXRpb24iLCJwYXVzZSIsIlBhdXNlZCIsImZsYWdTdG9wIiwidG9nZ2xlIiwic2tpcFRvIiwiY2xhbXAiLCJub3ciLCJjdXJyZW50VGltZSIsInN5bmNUbyJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hdWRpby9QbGF5YmFjay50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAyMSBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgRXZlbnRFbWl0dGVyIGZyb20gXCJldmVudHNcIjtcbmltcG9ydCB7IFNpbXBsZU9ic2VydmFibGUgfSBmcm9tIFwibWF0cml4LXdpZGdldC1hcGlcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcbmltcG9ydCB7IGRlZmVyIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3V0aWxzXCI7XG5cbmltcG9ydCB7IFVQREFURV9FVkVOVCB9IGZyb20gXCIuLi9zdG9yZXMvQXN5bmNTdG9yZVwiO1xuaW1wb3J0IHsgYXJyYXlGYXN0UmVzYW1wbGUgfSBmcm9tIFwiLi4vdXRpbHMvYXJyYXlzXCI7XG5pbXBvcnQgeyBJRGVzdHJveWFibGUgfSBmcm9tIFwiLi4vdXRpbHMvSURlc3Ryb3lhYmxlXCI7XG5pbXBvcnQgeyBQbGF5YmFja0Nsb2NrIH0gZnJvbSBcIi4vUGxheWJhY2tDbG9ja1wiO1xuaW1wb3J0IHsgY3JlYXRlQXVkaW9Db250ZXh0LCBkZWNvZGVPZ2cgfSBmcm9tIFwiLi9jb21wYXRcIjtcbmltcG9ydCB7IGNsYW1wIH0gZnJvbSBcIi4uL3V0aWxzL251bWJlcnNcIjtcbmltcG9ydCB7IERFRkFVTFRfV0FWRUZPUk0sIFBMQVlCQUNLX1dBVkVGT1JNX1NBTVBMRVMgfSBmcm9tIFwiLi9jb25zdHNcIjtcbmltcG9ydCB7IFBsYXliYWNrRW5jb2RlciB9IGZyb20gXCIuLi9QbGF5YmFja0VuY29kZXJcIjtcblxuZXhwb3J0IGVudW0gUGxheWJhY2tTdGF0ZSB7XG4gICAgRGVjb2RpbmcgPSBcImRlY29kaW5nXCIsXG4gICAgU3RvcHBlZCA9IFwic3RvcHBlZFwiLCAvLyBubyBwcm9ncmVzcyBvbiB0aW1lbGluZVxuICAgIFBhdXNlZCA9IFwicGF1c2VkXCIsIC8vIHNvbWUgcHJvZ3Jlc3Mgb24gdGltZWxpbmVcbiAgICBQbGF5aW5nID0gXCJwbGF5aW5nXCIsIC8vIGFjdGl2ZSBwcm9ncmVzcyB0aHJvdWdoIHRpbWVsaW5lXG59XG5cbmNvbnN0IFRIVU1CTkFJTF9XQVZFRk9STV9TQU1QTEVTID0gMTAwOyAvLyBhcmJpdHJhcnk6IFszMCwxMjBdXG5cbmV4cG9ydCBpbnRlcmZhY2UgUGxheWJhY2tJbnRlcmZhY2Uge1xuICAgIHJlYWRvbmx5IGN1cnJlbnRTdGF0ZTogUGxheWJhY2tTdGF0ZTtcbiAgICByZWFkb25seSBsaXZlRGF0YTogU2ltcGxlT2JzZXJ2YWJsZTxudW1iZXJbXT47XG4gICAgcmVhZG9ubHkgdGltZVNlY29uZHM6IG51bWJlcjtcbiAgICByZWFkb25seSBkdXJhdGlvblNlY29uZHM6IG51bWJlcjtcbiAgICBza2lwVG8odGltZVNlY29uZHM6IG51bWJlcik6IFByb21pc2U8dm9pZD47XG59XG5cbmV4cG9ydCBjbGFzcyBQbGF5YmFjayBleHRlbmRzIEV2ZW50RW1pdHRlciBpbXBsZW1lbnRzIElEZXN0cm95YWJsZSwgUGxheWJhY2tJbnRlcmZhY2Uge1xuICAgIC8qKlxuICAgICAqIFN0YWJsZSB3YXZlZm9ybSBmb3IgcmVwcmVzZW50aW5nIGEgdGh1bWJuYWlsIG9mIHRoZSBtZWRpYS4gVmFsdWVzIGFyZVxuICAgICAqIGd1YXJhbnRlZWQgdG8gYmUgYmV0d2VlbiB6ZXJvIGFuZCBvbmUsIGluY2x1c2l2ZS5cbiAgICAgKi9cbiAgICBwdWJsaWMgcmVhZG9ubHkgdGh1bWJuYWlsV2F2ZWZvcm06IG51bWJlcltdO1xuXG4gICAgcHJpdmF0ZSByZWFkb25seSBjb250ZXh0OiBBdWRpb0NvbnRleHQ7XG4gICAgcHJpdmF0ZSBzb3VyY2U/OiBBdWRpb0J1ZmZlclNvdXJjZU5vZGUgfCBNZWRpYUVsZW1lbnRBdWRpb1NvdXJjZU5vZGU7XG4gICAgcHJpdmF0ZSBzdGF0ZSA9IFBsYXliYWNrU3RhdGUuRGVjb2Rpbmc7XG4gICAgcHJpdmF0ZSBhdWRpb0J1Zj86IEF1ZGlvQnVmZmVyO1xuICAgIHByaXZhdGUgZWxlbWVudD86IEhUTUxBdWRpb0VsZW1lbnQ7XG4gICAgcHJpdmF0ZSByZXNhbXBsZWRXYXZlZm9ybTogbnVtYmVyW107XG4gICAgcHJpdmF0ZSB3YXZlZm9ybU9ic2VydmFibGUgPSBuZXcgU2ltcGxlT2JzZXJ2YWJsZTxudW1iZXJbXT4oKTtcbiAgICBwcml2YXRlIHJlYWRvbmx5IGNsb2NrOiBQbGF5YmFja0Nsb2NrO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgZmlsZVNpemU6IG51bWJlcjtcblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSBuZXcgcGxheWJhY2sgaW5zdGFuY2UgZnJvbSBhIGJ1ZmZlci5cbiAgICAgKiBAcGFyYW0ge0FycmF5QnVmZmVyfSBidWYgVGhlIGJ1ZmZlciBjb250YWluaW5nIHRoZSBzb3VuZCBzYW1wbGUuXG4gICAgICogQHBhcmFtIHtudW1iZXJbXX0gc2VlZFdhdmVmb3JtIE9wdGlvbmFsIHNlZWQgd2F2ZWZvcm0gdG8gcHJlc2VudCB1bnRpbCB0aGUgcHJvcGVyIHdhdmVmb3JtXG4gICAgICogY2FuIGJlIGNhbGN1bGF0ZWQuIENvbnRhaW5zIHZhbHVlcyBiZXR3ZWVuIHplcm8gYW5kIG9uZSwgaW5jbHVzaXZlLlxuICAgICAqL1xuICAgIHB1YmxpYyBjb25zdHJ1Y3RvcihcbiAgICAgICAgcHJpdmF0ZSBidWY6IEFycmF5QnVmZmVyLFxuICAgICAgICBzZWVkV2F2ZWZvcm0gPSBERUZBVUxUX1dBVkVGT1JNLFxuICAgICkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICAvLyBDYXB0dXJlIHRoZSBmaWxlIHNpemUgZWFybHkgYXMgcmVhZGluZyB0aGUgYnVmZmVyIHdpbGwgcmVzdWx0IGluIGEgMC1sZW5ndGggYnVmZmVyIGxlZnQgYmVoaW5kXG4gICAgICAgIHRoaXMuZmlsZVNpemUgPSB0aGlzLmJ1Zi5ieXRlTGVuZ3RoO1xuICAgICAgICB0aGlzLmNvbnRleHQgPSBjcmVhdGVBdWRpb0NvbnRleHQoKTtcbiAgICAgICAgdGhpcy5yZXNhbXBsZWRXYXZlZm9ybSA9IGFycmF5RmFzdFJlc2FtcGxlKHNlZWRXYXZlZm9ybSA/PyBERUZBVUxUX1dBVkVGT1JNLCBQTEFZQkFDS19XQVZFRk9STV9TQU1QTEVTKTtcbiAgICAgICAgdGhpcy50aHVtYm5haWxXYXZlZm9ybSA9IGFycmF5RmFzdFJlc2FtcGxlKHNlZWRXYXZlZm9ybSA/PyBERUZBVUxUX1dBVkVGT1JNLCBUSFVNQk5BSUxfV0FWRUZPUk1fU0FNUExFUyk7XG4gICAgICAgIHRoaXMud2F2ZWZvcm1PYnNlcnZhYmxlLnVwZGF0ZSh0aGlzLnJlc2FtcGxlZFdhdmVmb3JtKTtcbiAgICAgICAgdGhpcy5jbG9jayA9IG5ldyBQbGF5YmFja0Nsb2NrKHRoaXMuY29udGV4dCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2l6ZSBvZiB0aGUgYXVkaW8gY2xpcCBpbiBieXRlcy4gTWF5IGJlIHplcm8gaWYgdW5rbm93bi4gVGhpcyBpcyB1cGRhdGVkXG4gICAgICogd2hlbiB0aGUgcGxheWJhY2sgZ29lcyB0aHJvdWdoIHBoYXNlIGNoYW5nZXMuXG4gICAgICovXG4gICAgcHVibGljIGdldCBzaXplQnl0ZXMoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZmlsZVNpemU7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU3RhYmxlIHdhdmVmb3JtIGZvciB0aGUgcGxheWJhY2suIFZhbHVlcyBhcmUgZ3VhcmFudGVlZCB0byBiZSBiZXR3ZWVuXG4gICAgICogemVybyBhbmQgb25lLCBpbmNsdXNpdmUuXG4gICAgICovXG4gICAgcHVibGljIGdldCB3YXZlZm9ybSgpOiBudW1iZXJbXSB7XG4gICAgICAgIHJldHVybiB0aGlzLnJlc2FtcGxlZFdhdmVmb3JtO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgd2F2ZWZvcm1EYXRhKCk6IFNpbXBsZU9ic2VydmFibGU8bnVtYmVyW10+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMud2F2ZWZvcm1PYnNlcnZhYmxlO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgY2xvY2tJbmZvKCk6IFBsYXliYWNrQ2xvY2sge1xuICAgICAgICByZXR1cm4gdGhpcy5jbG9jaztcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IGxpdmVEYXRhKCk6IFNpbXBsZU9ic2VydmFibGU8bnVtYmVyW10+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuY2xvY2subGl2ZURhdGE7XG4gICAgfVxuXG4gICAgcHVibGljIGdldCB0aW1lU2Vjb25kcygpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5jbG9jay50aW1lU2Vjb25kcztcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IGR1cmF0aW9uU2Vjb25kcygpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5jbG9jay5kdXJhdGlvblNlY29uZHM7XG4gICAgfVxuXG4gICAgcHVibGljIGdldCBjdXJyZW50U3RhdGUoKTogUGxheWJhY2tTdGF0ZSB7XG4gICAgICAgIHJldHVybiB0aGlzLnN0YXRlO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgaXNQbGF5aW5nKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5jdXJyZW50U3RhdGUgPT09IFBsYXliYWNrU3RhdGUuUGxheWluZztcbiAgICB9XG5cbiAgICBwdWJsaWMgZW1pdChldmVudDogUGxheWJhY2tTdGF0ZSwgLi4uYXJnczogYW55W10pOiBib29sZWFuIHtcbiAgICAgICAgdGhpcy5zdGF0ZSA9IGV2ZW50O1xuICAgICAgICBzdXBlci5lbWl0KGV2ZW50LCAuLi5hcmdzKTtcbiAgICAgICAgc3VwZXIuZW1pdChVUERBVEVfRVZFTlQsIGV2ZW50LCAuLi5hcmdzKTtcbiAgICAgICAgcmV0dXJuIHRydWU7IC8vIHdlIGRvbid0IGV2ZXIgY2FyZSBpZiB0aGUgZXZlbnQgaGFkIGxpc3RlbmVycywgc28ganVzdCByZXR1cm4gXCJ5ZXNcIlxuICAgIH1cblxuICAgIHB1YmxpYyBkZXN0cm95KCk6IHZvaWQge1xuICAgICAgICAvLyBEZXYgbm90ZTogSXQncyBjcml0aWNhbCB0aGF0IHdlIGNhbGwgc3RvcCgpIGR1cmluZyBjbGVhbnVwIHRvIGVuc3VyZSB0aGF0IGRvd25zdHJlYW0gY2FsbGVyc1xuICAgICAgICAvLyBhcmUgYXdhcmUgb2YgdGhlIGZpbmFsIGNsb2NrIHBvc2l0aW9uIGJlZm9yZSB0aGUgdXNlciB0cmlnZ2VyZWQgYW4gdW5sb2FkLlxuICAgICAgICAvLyBub2luc3BlY3Rpb24gSlNJZ25vcmVkUHJvbWlzZUZyb21DYWxsIC0gbm90IGNvbmNlcm5lZCBhYm91dCBiZWluZyBjYWxsZWQgYXN5bmMgaGVyZVxuICAgICAgICB0aGlzLnN0b3AoKTtcbiAgICAgICAgdGhpcy5yZW1vdmVBbGxMaXN0ZW5lcnMoKTtcbiAgICAgICAgdGhpcy5jbG9jay5kZXN0cm95KCk7XG4gICAgICAgIHRoaXMud2F2ZWZvcm1PYnNlcnZhYmxlLmNsb3NlKCk7XG4gICAgICAgIGlmICh0aGlzLmVsZW1lbnQpIHtcbiAgICAgICAgICAgIFVSTC5yZXZva2VPYmplY3RVUkwodGhpcy5lbGVtZW50LnNyYyk7XG4gICAgICAgICAgICB0aGlzLmVsZW1lbnQucmVtb3ZlKCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgcHJlcGFyZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgLy8gZG9uJ3QgYXR0ZW1wdCB0byBkZWNvZGUgdGhlIG1lZGlhIGFnYWluXG4gICAgICAgIC8vIEF1ZGlvQ29udGV4dC5kZWNvZGVBdWRpb0RhdGEgZGV0YWNoZXMgdGhlIGFycmF5IGJ1ZmZlciBgdGhpcy5idWZgXG4gICAgICAgIC8vIG1lYW5pbmcgaXQgY2Fubm90IGJlIHJlLXJlYWRcbiAgICAgICAgaWYgKHRoaXMuc3RhdGUgIT09IFBsYXliYWNrU3RhdGUuRGVjb2RpbmcpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFRoZSBwb2ludCB3aGVyZSB3ZSB1c2UgYW4gYXVkaW8gZWxlbWVudCBpcyBmYWlybHkgYXJiaXRyYXJ5LCB0aG91Z2ggd2UgZG9uJ3Qgd2FudFxuICAgICAgICAvLyBpdCB0byBiZSB0b28gbG93LiBBcyBvZiB3cml0aW5nLCB2b2ljZSBtZXNzYWdlcyB3YW50IHRvIHNob3cgYSB3YXZlZm9ybSBidXQgYXVkaW9cbiAgICAgICAgLy8gbWVzc2FnZXMgZG8gbm90LiBVc2luZyBhbiBhdWRpbyBlbGVtZW50IG1lYW5zIHdlIGNhbid0IHNob3cgYSB3YXZlZm9ybSBwcmV2aWV3LCBzb1xuICAgICAgICAvLyB3ZSB0cnkgdG8gdGFyZ2V0IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYSB2b2ljZSBtZXNzYWdlIGZpbGUgYW5kIGxhcmdlIGF1ZGlvIGZpbGUuXG4gICAgICAgIC8vIE92ZXJhbGwsIHRoZSBwb2ludCBvZiB0aGlzIGlzIHRvIGF2b2lkIG1lbW9yeS1yZWxhdGVkIGlzc3VlcyBkdWUgdG8gc3RvcmluZyBhIG1hc3NpdmVcbiAgICAgICAgLy8gYXVkaW8gYnVmZmVyIGluIG1lbW9yeSwgYXMgdGhhdCBjYW4gYmFsbG9vbiB0byBmYXIgZ3JlYXRlciB0aGFuIHRoZSBpbnB1dCBidWZmZXInc1xuICAgICAgICAvLyBieXRlIGxlbmd0aC5cbiAgICAgICAgaWYgKHRoaXMuYnVmLmJ5dGVMZW5ndGggPiA1ICogMTAyNCAqIDEwMjQpIHtcbiAgICAgICAgICAgIC8vIDVtYlxuICAgICAgICAgICAgbG9nZ2VyLmxvZyhcIkF1ZGlvIGZpbGUgdG9vIGxhcmdlOiBwcm9jZXNzaW5nIHRocm91Z2ggPGF1ZGlvIC8+IGVsZW1lbnRcIik7XG4gICAgICAgICAgICB0aGlzLmVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiQVVESU9cIikgYXMgSFRNTEF1ZGlvRWxlbWVudDtcbiAgICAgICAgICAgIGNvbnN0IGRlZmVycmVkID0gZGVmZXI8dW5rbm93bj4oKTtcbiAgICAgICAgICAgIHRoaXMuZWxlbWVudC5vbmxvYWRlZGRhdGEgPSBkZWZlcnJlZC5yZXNvbHZlO1xuICAgICAgICAgICAgdGhpcy5lbGVtZW50Lm9uZXJyb3IgPSBkZWZlcnJlZC5yZWplY3Q7XG4gICAgICAgICAgICB0aGlzLmVsZW1lbnQuc3JjID0gVVJMLmNyZWF0ZU9iamVjdFVSTChuZXcgQmxvYihbdGhpcy5idWZdKSk7XG4gICAgICAgICAgICBhd2FpdCBkZWZlcnJlZC5wcm9taXNlOyAvLyBtYWtlIHN1cmUgdGhlIGF1ZGlvIGVsZW1lbnQgaXMgcmVhZHkgZm9yIHVzXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBTYWZhcmkgY29tcGF0OiBwcm9taXNlIEFQSSBub3Qgc3VwcG9ydGVkIG9uIHRoaXMgZnVuY3Rpb25cbiAgICAgICAgICAgIHRoaXMuYXVkaW9CdWYgPSBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5jb250ZXh0LmRlY29kZUF1ZGlvRGF0YShcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5idWYsXG4gICAgICAgICAgICAgICAgICAgIChiKSA9PiByZXNvbHZlKGIpLFxuICAgICAgICAgICAgICAgICAgICBhc3luYyAoZSk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBUaGlzIGVycm9yIGhhbmRsZXIgaXMgbGFyZ2VseSBmb3IgU2FmYXJpIGFzIHdlbGwsIHdoaWNoIGRvZXNuJ3Qgc3VwcG9ydCBPcHVzL09nZ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHZlcnkgd2VsbC5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dnZXIuZXJyb3IoXCJFcnJvciBkZWNvZGluZyByZWNvcmRpbmc6IFwiLCBlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dnZXIud2FybihcIlRyeWluZyB0byByZS1lbmNvZGUgdG8gV0FWIGluc3RlYWQuLi5cIik7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25zdCB3YXYgPSBhd2FpdCBkZWNvZGVPZ2codGhpcy5idWYpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gbm9pbnNwZWN0aW9uIEVTNk1pc3NpbmdBd2FpdCAtIG5vdCBuZWVkZWQgd2hlbiB1c2luZyBjYWxsYmFja3NcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmNvbnRleHQuZGVjb2RlQXVkaW9EYXRhKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3YXYsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChiKSA9PiByZXNvbHZlKGIpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZSkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nZ2VyLmVycm9yKFwiU3RpbGwgZmFpbGVkIHRvIGRlY29kZSByZWNvcmRpbmc6IFwiLCBlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlamVjdChlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ2dlci5lcnJvcihcIkNhdWdodCBkZWNvZGluZyBlcnJvcjpcIiwgZSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVqZWN0KGUpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgLy8gVXBkYXRlIHRoZSB3YXZlZm9ybSB0byB0aGUgcmVhbCB3YXZlZm9ybSBvbmNlIHdlIGhhdmUgY2hhbm5lbCBkYXRhIHRvIHVzZS4gV2UgZG9uJ3RcbiAgICAgICAgICAgIC8vIGV4YWN0bHkgdHJ1c3QgdGhlIHVzZXItcHJvdmlkZWQgd2F2ZWZvcm0gdG8gYmUgYWNjdXJhdGUuLi5cbiAgICAgICAgICAgIHRoaXMucmVzYW1wbGVkV2F2ZWZvcm0gPSBhd2FpdCBQbGF5YmFja0VuY29kZXIuaW5zdGFuY2UuZ2V0UGxheWJhY2tXYXZlZm9ybShcbiAgICAgICAgICAgICAgICB0aGlzLmF1ZGlvQnVmLmdldENoYW5uZWxEYXRhKDApLFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMud2F2ZWZvcm1PYnNlcnZhYmxlLnVwZGF0ZSh0aGlzLnJlc2FtcGxlZFdhdmVmb3JtKTtcblxuICAgICAgICB0aGlzLmNsb2NrLmZsYWdMb2FkVGltZSgpOyAvLyBtdXN0IGhhcHBlbiBmaXJzdCBiZWNhdXNlIHNldHRpbmcgdGhlIGR1cmF0aW9uIGZpcmVzIGEgY2xvY2sgdXBkYXRlXG4gICAgICAgIHRoaXMuY2xvY2suZHVyYXRpb25TZWNvbmRzID0gdGhpcy5lbGVtZW50Py5kdXJhdGlvbiA/PyB0aGlzLmF1ZGlvQnVmIS5kdXJhdGlvbjtcblxuICAgICAgICAvLyBTaWduYWwgdGhhdCB3ZSdyZSBub3QgZGVjb2RpbmcgYW55bW9yZS4gVGhpcyBpcyBkb25lIGxhc3QgdG8gZW5zdXJlIHRoZSBjbG9jayBpcyB1cGRhdGVkIGZvclxuICAgICAgICAvLyB3aGVuIHRoZSBkb3duc3RyZWFtIGNhbGxlcnMgdHJ5IHRvIHVzZSBpdC5cbiAgICAgICAgdGhpcy5lbWl0KFBsYXliYWNrU3RhdGUuU3RvcHBlZCk7IC8vIHNpZ25hbCB0aGF0IHdlJ3JlIG5vdCBkZWNvZGluZyBhbnltb3JlXG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvblBsYXliYWNrRW5kID0gYXN5bmMgKCk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICBhd2FpdCB0aGlzLmNvbnRleHQuc3VzcGVuZCgpO1xuICAgICAgICB0aGlzLmVtaXQoUGxheWJhY2tTdGF0ZS5TdG9wcGVkKTtcbiAgICB9O1xuXG4gICAgcHVibGljIGFzeW5jIHBsYXkoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIC8vIFdlIGNhbid0IHJlc3RhcnQgYSBidWZmZXIgc291cmNlLCBzbyB3ZSBuZWVkIHRvIGNyZWF0ZSBhIG5ldyBvbmUgaWYgd2UgaGl0IHRoZSBlbmRcbiAgICAgICAgaWYgKHRoaXMuc3RhdGUgPT09IFBsYXliYWNrU3RhdGUuU3RvcHBlZCkge1xuICAgICAgICAgICAgdGhpcy5kaXNjb25uZWN0U291cmNlKCk7XG4gICAgICAgICAgICB0aGlzLm1ha2VOZXdTb3VyY2VCdWZmZXIoKTtcbiAgICAgICAgICAgIGlmICh0aGlzLmVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICBhd2FpdCB0aGlzLmVsZW1lbnQucGxheSgpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAodGhpcy5zb3VyY2UgYXMgQXVkaW9CdWZmZXJTb3VyY2VOb2RlKS5zdGFydCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gV2UgdXNlIHRoZSBjb250ZXh0IHN1c3BlbmQvcmVzdW1lIGZ1bmN0aW9ucyBiZWNhdXNlIGl0IGFsbG93cyB1cyB0byBwYXVzZSBhIHNvdXJjZVxuICAgICAgICAvLyBub2RlLCBidXQgdGhhdCBzdGlsbCBkb2Vzbid0IGhlbHAgdXMgd2hlbiB0aGUgc291cmNlIG5vZGUgcnVucyBvdXQgKHNlZSBhYm92ZSkuXG4gICAgICAgIGF3YWl0IHRoaXMuY29udGV4dC5yZXN1bWUoKTtcbiAgICAgICAgdGhpcy5jbG9jay5mbGFnU3RhcnQoKTtcbiAgICAgICAgdGhpcy5lbWl0KFBsYXliYWNrU3RhdGUuUGxheWluZyk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBkaXNjb25uZWN0U291cmNlKCk6IHZvaWQge1xuICAgICAgICBpZiAodGhpcy5lbGVtZW50KSByZXR1cm47IC8vIGxlYXZlIGNvbm5lY3RlZCwgd2UgY2FuIChhbmQgbXVzdCkgcmUtdXNlIGl0XG4gICAgICAgIHRoaXMuc291cmNlPy5kaXNjb25uZWN0KCk7XG4gICAgICAgIHRoaXMuc291cmNlPy5yZW1vdmVFdmVudExpc3RlbmVyKFwiZW5kZWRcIiwgdGhpcy5vblBsYXliYWNrRW5kKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIG1ha2VOZXdTb3VyY2VCdWZmZXIoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLmVsZW1lbnQgJiYgdGhpcy5zb3VyY2UpIHJldHVybjsgLy8gbGVhdmUgY29ubmVjdGVkLCB3ZSBjYW4gKGFuZCBtdXN0KSByZS11c2UgaXRcblxuICAgICAgICBpZiAodGhpcy5lbGVtZW50KSB7XG4gICAgICAgICAgICB0aGlzLnNvdXJjZSA9IHRoaXMuY29udGV4dC5jcmVhdGVNZWRpYUVsZW1lbnRTb3VyY2UodGhpcy5lbGVtZW50KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc291cmNlID0gdGhpcy5jb250ZXh0LmNyZWF0ZUJ1ZmZlclNvdXJjZSgpO1xuICAgICAgICAgICAgdGhpcy5zb3VyY2UuYnVmZmVyID0gdGhpcy5hdWRpb0J1ZiA/PyBudWxsO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5zb3VyY2UuYWRkRXZlbnRMaXN0ZW5lcihcImVuZGVkXCIsIHRoaXMub25QbGF5YmFja0VuZCk7XG4gICAgICAgIHRoaXMuc291cmNlLmNvbm5lY3QodGhpcy5jb250ZXh0LmRlc3RpbmF0aW9uKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgcGF1c2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGF3YWl0IHRoaXMuY29udGV4dC5zdXNwZW5kKCk7XG4gICAgICAgIHRoaXMuZW1pdChQbGF5YmFja1N0YXRlLlBhdXNlZCk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHN0b3AoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGF3YWl0IHRoaXMub25QbGF5YmFja0VuZCgpO1xuICAgICAgICB0aGlzLmNsb2NrLmZsYWdTdG9wKCk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHRvZ2dsZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKHRoaXMuaXNQbGF5aW5nKSBhd2FpdCB0aGlzLnBhdXNlKCk7XG4gICAgICAgIGVsc2UgYXdhaXQgdGhpcy5wbGF5KCk7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHNraXBUbyh0aW1lU2Vjb25kczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIC8vIERldiBub3RlOiB0aGlzIGZ1bmN0aW9uIHRhbGtzIGEgbG90IGFib3V0IGNsb2NrIGRlc3luY3MuIFRoZXJlIGlzIGEgY2xvY2sgcnVubmluZ1xuICAgICAgICAvLyBpbmRlcGVuZGVudGx5IHRvIHRoZSBhdWRpbyBjb250ZXh0IGFuZCBidWZmZXIgc28gdGhhdCBhY2N1cmF0ZSBodW1hbi1wZXJjZXB0aWJsZVxuICAgICAgICAvLyB0aW1lIGNhbiBiZSBleHBvc2VkLiBUaGUgUGxheWJhY2tDbG9jayBjbGFzcyBoYXMgbW9yZSBpbmZvcm1hdGlvbiwgYnV0IHRoZSBzaG9ydFxuICAgICAgICAvLyB2ZXJzaW9uIGlzIHRoYXQgd2UgbmVlZCB0byBsaW5lIHVwIHRoZSB1c2VmdWwgdGltZSAoY2xpcCBwb3NpdGlvbikgd2l0aCB0aGUgY29udGV4dFxuICAgICAgICAvLyB0aW1lLCBhbmQgYXZvaWQgYXMgbWFueSBkZXZpYXRpb25zIGFzIHBvc3NpYmxlIGFzIG90aGVyd2lzZSB0aGUgdXNlciBjb3VsZCBzZWUgdGhlXG4gICAgICAgIC8vIHdyb25nIHRpbWUsIGFuZCB3ZSBzdG9wIHBsYXliYWNrIGF0IHRoZSB3cm9uZyB0aW1lLCBldGMuXG5cbiAgICAgICAgdGltZVNlY29uZHMgPSBjbGFtcCh0aW1lU2Vjb25kcywgMCwgdGhpcy5jbG9jay5kdXJhdGlvblNlY29uZHMpO1xuXG4gICAgICAgIC8vIFRyYWNrIHBsYXlpbmcgc3RhdGUgc28gd2UgZG9uJ3QgY2F1c2Ugc2Vla2luZyB0byBzdGFydCBwbGF5aW5nIHRoZSB0cmFjay5cbiAgICAgICAgY29uc3QgaXNQbGF5aW5nID0gdGhpcy5pc1BsYXlpbmc7XG5cbiAgICAgICAgaWYgKGlzUGxheWluZykge1xuICAgICAgICAgICAgLy8gUGF1c2UgZmlyc3Qgc28gd2UgY2FuIGdldCBhbiBhY2N1cmF0ZSBtZWFzdXJlbWVudCBvZiB0aW1lXG4gICAgICAgICAgICBhd2FpdCB0aGlzLmNvbnRleHQuc3VzcGVuZCgpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gV2UgY2FuJ3Qgc2ltcGx5IHRlbGwgdGhlIGNvbnRleHQvYnVmZmVyIHRvIGp1bXAgdG8gYSB0aW1lLCBzbyB3ZSBoYXZlIHRvXG4gICAgICAgIC8vIHN0YXJ0IGEgd2hvbGUgbmV3IGJ1ZmZlciBhbmQgc3RhcnQgaXQgZnJvbSB0aGUgbmV3IHRpbWUgb2Zmc2V0LlxuICAgICAgICBjb25zdCBub3cgPSB0aGlzLmNvbnRleHQuY3VycmVudFRpbWU7XG4gICAgICAgIHRoaXMuZGlzY29ubmVjdFNvdXJjZSgpO1xuICAgICAgICB0aGlzLm1ha2VOZXdTb3VyY2VCdWZmZXIoKTtcblxuICAgICAgICAvLyBXZSBoYXZlIHRvIHJlc3luYyB0aGUgY2xvY2sgYmVjYXVzZSBpdCBjYW4gZ2V0IGNvbmZ1c2VkIGFib3V0IHdoZXJlIHdlJ3JlXG4gICAgICAgIC8vIGF0IGluIHRoZSBhdWRpbyBjbGlwLlxuICAgICAgICB0aGlzLmNsb2NrLnN5bmNUbyhub3csIHRpbWVTZWNvbmRzKTtcblxuICAgICAgICAvLyBBbHdheXMgc3RhcnQgdGhlIHNvdXJjZSB0byBxdWV1ZSBpdCB1cC4gV2UgaGF2ZSB0byBkbyB0aGlzIG5vdyAoYW5kIHBhdXNlXG4gICAgICAgIC8vIHF1aWNrbHkgaWYgd2UncmUgbm90IHN1cHBvc2VkIHRvIGJlIHBsYXlpbmcpIGFzIG90aGVyd2lzZSB0aGUgY2xvY2sgY2FuIGRlc3luY1xuICAgICAgICAvLyB3aGVuIGl0IGNvbWVzIHRpbWUgdG8gdGhlIHVzZXIgaGl0dGluZyBwbGF5LiBBZnRlciBhIGNvdXBsZSBqdW1wcywgdGhlIHVzZXJcbiAgICAgICAgLy8gd2lsbCBoYXZlIGRlc3luY2VkIHRoZSBjbG9jayBlbm91Z2ggdG8gYmUgYWJvdXQgMTAtMTUgc2Vjb25kcyBvZmYsIHdoaWxlIHRoaXNcbiAgICAgICAgLy8ga2VlcHMgaXQgYXMgY2xvc2UgdG8gcGVyZmVjdCBhcyBodW1hbnMgY2FuIHBlcmNlaXZlLlxuICAgICAgICBpZiAodGhpcy5lbGVtZW50KSB7XG4gICAgICAgICAgICB0aGlzLmVsZW1lbnQuY3VycmVudFRpbWUgPSB0aW1lU2Vjb25kcztcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICh0aGlzLnNvdXJjZSBhcyBBdWRpb0J1ZmZlclNvdXJjZU5vZGUpLnN0YXJ0KG5vdywgdGltZVNlY29uZHMpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gRGV2IG5vdGU6IGl0J3MgY3JpdGljYWwgdGhhdCB0aGUgY29kZSBnYXAgYmV0d2VlbiBgdGhpcy5zb3VyY2Uuc3RhcnQoKWAgYW5kXG4gICAgICAgIC8vIGB0aGlzLnBhdXNlKClgIGlzIGFzIHNtYWxsIGFzIHBvc3NpYmxlOiB3ZSBkbyBub3Qgd2FudCB0byBkZWxheSAqYW55dGhpbmcqXG4gICAgICAgIC8vIGFzIHRoYXQgY291bGQgY2F1c2UgYSBjbG9jayBkZXN5bmMsIG9yIGEgYnVnZ3kgZmVlbGluZyBhcyBhIHNpbmdsZSBub3RlIHBsYXlzXG4gICAgICAgIC8vIGR1cmluZyBzZWVraW5nLlxuXG4gICAgICAgIGlmIChpc1BsYXlpbmcpIHtcbiAgICAgICAgICAgIC8vIElmIHdlIHdlcmUgcGxheWluZyBiZWZvcmUsIGNvbnRpbnVlIHRoZSBjb250ZXh0IHNvIHRoZSBjbG9jayBkb2Vzbid0IGRlc3luYy5cbiAgICAgICAgICAgIGF3YWl0IHRoaXMuY29udGV4dC5yZXN1bWUoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIEFzIG1lbnRpb25lZCBhYm92ZSwgd2UnbGwgaGF2ZSB0byBwYXVzZSB0aGUgY2xpcCBpZiB3ZSB3ZXJlbid0IHN1cHBvc2VkIHRvXG4gICAgICAgICAgICAvLyBiZSBwbGF5aW5nIGl0IGp1c3QgeWV0LiBJZiB3ZSBkaWRuJ3QgaGF2ZSB0aGlzLCB0aGUgYXVkaW8gY2xpcCBwbGF5cyBidXQgYWxsXG4gICAgICAgICAgICAvLyB0aGUgc3RhdGVzIHdpbGwgYmUgd3Jvbmc6IGNsb2NrIHdvbid0IGFkdmFuY2UsIHBhdXNlIHN0YXRlIGRvZXNuJ3QgbWF0Y2ggdGhlXG4gICAgICAgICAgICAvLyBibGFyaW5nIG5vaXNlIGxlYXZpbmcgdGhlIHVzZXIncyBzcGVha2VycywgZXRjLlxuICAgICAgICAgICAgLy9cbiAgICAgICAgICAgIC8vIEFsc28gYXMgbWVudGlvbmVkLCBpZiB0aGUgY29kZSBnYXAgaXMgc21hbGwgZW5vdWdoIHRoZW4gdGhpcyBzaG91bGQgYmVcbiAgICAgICAgICAgIC8vIGV4ZWN1dGVkIGltbWVkaWF0ZWx5IGFmdGVyIHRoZSBzdGFydCB0aW1lLCBsZWF2aW5nIG5vIGZlYXNpYmxlIHRpbWUgZm9yIHRoZVxuICAgICAgICAgICAgLy8gdXNlcidzIHNwZWFrZXJzIHRvIHBsYXkgYW55IHNvdW5kLlxuICAgICAgICAgICAgYXdhaXQgdGhpcy5wYXVzZSgpO1xuICAgICAgICB9XG4gICAgfVxufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7OztBQVFBLElBQUFBLE9BQUEsR0FBQUMsc0JBQUEsQ0FBQUMsT0FBQTtBQUNBLElBQUFDLGdCQUFBLEdBQUFELE9BQUE7QUFDQSxJQUFBRSxPQUFBLEdBQUFGLE9BQUE7QUFDQSxJQUFBRyxNQUFBLEdBQUFILE9BQUE7QUFFQSxJQUFBSSxXQUFBLEdBQUFKLE9BQUE7QUFDQSxJQUFBSyxPQUFBLEdBQUFMLE9BQUE7QUFFQSxJQUFBTSxjQUFBLEdBQUFOLE9BQUE7QUFDQSxJQUFBTyxPQUFBLEdBQUFQLE9BQUE7QUFDQSxJQUFBUSxRQUFBLEdBQUFSLE9BQUE7QUFDQSxJQUFBUyxPQUFBLEdBQUFULE9BQUE7QUFDQSxJQUFBVSxnQkFBQSxHQUFBVixPQUFBO0FBcEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBTkEsSUFzQllXLGFB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