Refactor timers into a simple tested object.
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
DirectiveParameters,
|
DirectiveParameters,
|
||||||
} from 'lit/directive.js';
|
} from 'lit/directive.js';
|
||||||
import { stopEventFromActivatingCardWideActions } from './utils/action.js';
|
import { stopEventFromActivatingCardWideActions } from './utils/action.js';
|
||||||
|
import { Timer } from './utils/timer.js';
|
||||||
|
|
||||||
interface ActionHandler extends HTMLElement {
|
interface ActionHandler extends HTMLElement {
|
||||||
holdTime: number;
|
holdTime: number;
|
||||||
@@ -25,14 +26,13 @@ interface FrigateCardActionHandlerOptions extends ActionHandlerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ActionHandler extends HTMLElement implements ActionHandler {
|
class ActionHandler extends HTMLElement implements ActionHandler {
|
||||||
public holdTime = 400;
|
public holdTime = 0.4;
|
||||||
|
|
||||||
protected timer?: number;
|
protected holdTimer = new Timer();
|
||||||
|
protected doubleClickTimer = new Timer();
|
||||||
|
|
||||||
protected held = false;
|
protected held = false;
|
||||||
|
|
||||||
private dblClickTimeout?: number;
|
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
[
|
[
|
||||||
'touchcancel',
|
'touchcancel',
|
||||||
@@ -46,10 +46,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
|||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
ev,
|
ev,
|
||||||
() => {
|
() => {
|
||||||
if (this.timer) {
|
this.holdTimer.stop();
|
||||||
clearTimeout(this.timer);
|
|
||||||
this.timer = undefined;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ passive: true },
|
{ passive: true },
|
||||||
);
|
);
|
||||||
@@ -79,9 +76,9 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
|||||||
|
|
||||||
const start = (): void => {
|
const start = (): void => {
|
||||||
this.held = false;
|
this.held = false;
|
||||||
this.timer = window.setTimeout(() => {
|
this.holdTimer.start(this.holdTime, () => {
|
||||||
this.held = true;
|
this.held = true;
|
||||||
}, this.holdTime);
|
});
|
||||||
|
|
||||||
fireEvent(element, 'action', { action: 'start_tap' });
|
fireEvent(element, 'action', { action: 'start_tap' });
|
||||||
};
|
};
|
||||||
@@ -103,8 +100,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout(this.timer);
|
this.holdTimer.stop();
|
||||||
this.timer = undefined;
|
|
||||||
|
|
||||||
fireEvent(element, 'action', { action: 'end_tap' });
|
fireEvent(element, 'action', { action: 'end_tap' });
|
||||||
|
|
||||||
@@ -113,15 +109,13 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
|||||||
} else if (options?.hasDoubleClick) {
|
} else if (options?.hasDoubleClick) {
|
||||||
if (
|
if (
|
||||||
(ev.type === 'click' && (ev as MouseEvent).detail < 2) ||
|
(ev.type === 'click' && (ev as MouseEvent).detail < 2) ||
|
||||||
!this.dblClickTimeout
|
!this.doubleClickTimer.isRunning()
|
||||||
) {
|
) {
|
||||||
this.dblClickTimeout = window.setTimeout(() => {
|
this.doubleClickTimer.start(0.25, () =>
|
||||||
this.dblClickTimeout = undefined;
|
fireEvent(element, 'action', { action: 'tap' }),
|
||||||
fireEvent(element, 'action', { action: 'tap' });
|
);
|
||||||
}, 250);
|
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(this.dblClickTimeout);
|
this.doubleClickTimer.stop();
|
||||||
this.dblClickTimeout = undefined;
|
|
||||||
fireEvent(element, 'action', { action: 'double_tap' });
|
fireEvent(element, 'action', { action: 'double_tap' });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ReactiveController, ReactiveControllerHost } from 'lit';
|
import { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||||
|
import { Timer } from './utils/timer';
|
||||||
|
|
||||||
export class CachedValueController<T> implements ReactiveController {
|
export class CachedValueController<T> implements ReactiveController {
|
||||||
protected _value?: T;
|
protected _value?: T;
|
||||||
@@ -7,7 +8,7 @@ export class CachedValueController<T> implements ReactiveController {
|
|||||||
protected _callback: () => T;
|
protected _callback: () => T;
|
||||||
protected _timerStartCallback?: () => void;
|
protected _timerStartCallback?: () => void;
|
||||||
protected _timerStopCallback?: () => void;
|
protected _timerStopCallback?: () => void;
|
||||||
protected _timerID?: number;
|
protected _timer = new Timer();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
host: ReactiveControllerHost,
|
host: ReactiveControllerHost,
|
||||||
@@ -56,11 +57,10 @@ export class CachedValueController<T> implements ReactiveController {
|
|||||||
* Disable the timer.
|
* Disable the timer.
|
||||||
*/
|
*/
|
||||||
public stopTimer(): void {
|
public stopTimer(): void {
|
||||||
if (this._timerID !== undefined) {
|
if (this._timer.isRunning()) {
|
||||||
window.clearInterval(this._timerID);
|
this._timer.stop();
|
||||||
this._timerStopCallback?.();
|
this._timerStopCallback?.();
|
||||||
}
|
}
|
||||||
this._timerID = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,15 +71,15 @@ export class CachedValueController<T> implements ReactiveController {
|
|||||||
|
|
||||||
if (this._timerSeconds > 0) {
|
if (this._timerSeconds > 0) {
|
||||||
this._timerStartCallback?.();
|
this._timerStartCallback?.();
|
||||||
this._timerID = window.setInterval(() => {
|
this._timer.startRepeated(this._timerSeconds, () => {
|
||||||
this.updateValue();
|
this.updateValue();
|
||||||
this._host.requestUpdate();
|
this._host.requestUpdate();
|
||||||
}, this._timerSeconds * 1000);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasTimer(): boolean {
|
public hasTimer(): boolean {
|
||||||
return !!this._timerID;
|
return this._timer.isRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+16
-45
@@ -99,6 +99,7 @@ import {
|
|||||||
hasSubstream,
|
hasSubstream,
|
||||||
} from './utils/substream';
|
} from './utils/substream';
|
||||||
import { View } from './view/view.js';
|
import { View } from './view/view.js';
|
||||||
|
import { Timer } from './utils/timer';
|
||||||
|
|
||||||
/** A note on media callbacks:
|
/** A note on media callbacks:
|
||||||
*
|
*
|
||||||
@@ -196,12 +197,9 @@ class FrigateCard extends LitElement {
|
|||||||
protected _refElements: Ref<FrigateCardElements> = createRef();
|
protected _refElements: Ref<FrigateCardElements> = createRef();
|
||||||
protected _refViews: Ref<FrigateCardViews> = createRef();
|
protected _refViews: Ref<FrigateCardViews> = createRef();
|
||||||
|
|
||||||
// user interaction timer ("screensaver" functionality, return to default
|
protected _interactionTimer = new Timer();
|
||||||
// view after user interaction).
|
protected _updateTimer = new Timer();
|
||||||
protected _interactionTimerID: number | null = null;
|
protected _untriggerTimer = new Timer();
|
||||||
|
|
||||||
// Automated refreshes of the default view.
|
|
||||||
protected _updateTimerID: number | null = null;
|
|
||||||
|
|
||||||
// Information about loaded media items.
|
// Information about loaded media items.
|
||||||
protected _currentMediaLoadedInfo: MediaLoadedInfo | null = null;
|
protected _currentMediaLoadedInfo: MediaLoadedInfo | null = null;
|
||||||
@@ -227,7 +225,6 @@ class FrigateCard extends LitElement {
|
|||||||
protected _boundFullscreenHandler = this._fullscreenHandler.bind(this);
|
protected _boundFullscreenHandler = this._fullscreenHandler.bind(this);
|
||||||
|
|
||||||
protected _triggers: Map<string, Date> = new Map();
|
protected _triggers: Map<string, Date> = new Map();
|
||||||
protected _untriggerTimerID: number | null = null;
|
|
||||||
|
|
||||||
protected _mediaPlayers?: string[];
|
protected _mediaPlayers?: string[];
|
||||||
|
|
||||||
@@ -1006,7 +1003,7 @@ class FrigateCard extends LitElement {
|
|||||||
const needDarkMode =
|
const needDarkMode =
|
||||||
this._getConfig().view.dark_mode === 'on' ||
|
this._getConfig().view.dark_mode === 'on' ||
|
||||||
(this._getConfig().view.dark_mode === 'auto' &&
|
(this._getConfig().view.dark_mode === 'auto' &&
|
||||||
(!this._interactionTimerID || this._hass?.themes.darkMode));
|
(!this._interactionTimer.isRunning() || this._hass?.themes.darkMode));
|
||||||
|
|
||||||
if (needDarkMode) {
|
if (needDarkMode) {
|
||||||
this.setAttribute('dark', '');
|
this.setAttribute('dark', '');
|
||||||
@@ -1144,7 +1141,7 @@ class FrigateCard extends LitElement {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
protected _isTriggered(): boolean {
|
protected _isTriggered(): boolean {
|
||||||
return !!this._triggers.size || !!this._untriggerTimerID;
|
return !!this._triggers.size || this._untriggerTimer.isRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1153,7 +1150,7 @@ class FrigateCard extends LitElement {
|
|||||||
protected _untrigger(): void {
|
protected _untrigger(): void {
|
||||||
const wasTriggered = this._isTriggered();
|
const wasTriggered = this._isTriggered();
|
||||||
this._triggers.clear();
|
this._triggers.clear();
|
||||||
this._clearUntriggerTimer();
|
this._untriggerTimer.stop();
|
||||||
|
|
||||||
if (wasTriggered) {
|
if (wasTriggered) {
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
@@ -1164,9 +1161,7 @@ class FrigateCard extends LitElement {
|
|||||||
* Start the untrigger timer.
|
* Start the untrigger timer.
|
||||||
*/
|
*/
|
||||||
protected _startUntriggerTimer(): void {
|
protected _startUntriggerTimer(): void {
|
||||||
this._clearUntriggerTimer();
|
this._untriggerTimer.start(this._getConfig().view.scan.untrigger_seconds, () => {
|
||||||
|
|
||||||
this._untriggerTimerID = window.setTimeout(() => {
|
|
||||||
this._untrigger();
|
this._untrigger();
|
||||||
if (
|
if (
|
||||||
this._isAutomatedViewUpdateAllowed() &&
|
this._isAutomatedViewUpdateAllowed() &&
|
||||||
@@ -1174,17 +1169,7 @@ class FrigateCard extends LitElement {
|
|||||||
) {
|
) {
|
||||||
this._changeView();
|
this._changeView();
|
||||||
}
|
}
|
||||||
}, this._getConfig().view.scan.untrigger_seconds * 1000);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the user interaction ('screensaver') timer.
|
|
||||||
*/
|
|
||||||
protected _clearUntriggerTimer(): void {
|
|
||||||
if (this._untriggerTimerID) {
|
|
||||||
window.clearTimeout(this._untriggerTimerID);
|
|
||||||
this._untriggerTimerID = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _handleThrownError(error: unknown) {
|
protected _handleThrownError(error: unknown) {
|
||||||
@@ -1811,34 +1796,23 @@ class FrigateCard extends LitElement {
|
|||||||
this._startInteractionTimer();
|
this._startInteractionTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the user interaction ('screensaver') timer.
|
|
||||||
*/
|
|
||||||
protected _clearInteractionTimer(): void {
|
|
||||||
if (this._interactionTimerID) {
|
|
||||||
window.clearTimeout(this._interactionTimerID);
|
|
||||||
this._interactionTimerID = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the user interaction ('screensaver') timer to reset the view to
|
* Start the user interaction ('screensaver') timer to reset the view to
|
||||||
* default `view.timeout_seconds` after user interaction.
|
* default `view.timeout_seconds` after user interaction.
|
||||||
*/
|
*/
|
||||||
protected _startInteractionTimer(): void {
|
protected _startInteractionTimer(): void {
|
||||||
this._clearInteractionTimer();
|
this._interactionTimer.stop();
|
||||||
|
|
||||||
// Interactions reset the trigger state.
|
// Interactions reset the trigger state.
|
||||||
this._untrigger();
|
this._untrigger();
|
||||||
|
|
||||||
if (this._getConfig().view.timeout_seconds) {
|
if (this._getConfig().view.timeout_seconds) {
|
||||||
this._interactionTimerID = window.setTimeout(() => {
|
this._interactionTimer.start(this._getConfig().view.timeout_seconds, () => {
|
||||||
this._clearInteractionTimer();
|
|
||||||
if (this._isAutomatedViewUpdateAllowed()) {
|
if (this._isAutomatedViewUpdateAllowed()) {
|
||||||
this._changeView();
|
this._changeView();
|
||||||
this._setLightOrDarkMode();
|
this._setLightOrDarkMode();
|
||||||
}
|
}
|
||||||
}, this._getConfig().view.timeout_seconds * 1000);
|
});
|
||||||
}
|
}
|
||||||
this._setLightOrDarkMode();
|
this._setLightOrDarkMode();
|
||||||
}
|
}
|
||||||
@@ -1848,12 +1822,9 @@ class FrigateCard extends LitElement {
|
|||||||
* `view.update_seconds`.
|
* `view.update_seconds`.
|
||||||
*/
|
*/
|
||||||
protected _startUpdateTimer(): void {
|
protected _startUpdateTimer(): void {
|
||||||
if (this._updateTimerID) {
|
this._updateTimer.stop();
|
||||||
window.clearTimeout(this._updateTimerID);
|
|
||||||
this._updateTimerID = null;
|
|
||||||
}
|
|
||||||
if (this._getConfig().view.update_seconds) {
|
if (this._getConfig().view.update_seconds) {
|
||||||
this._updateTimerID = window.setTimeout(() => {
|
this._updateTimer.start(this._getConfig().view.update_seconds, () => {
|
||||||
if (this._isAutomatedViewUpdateAllowed()) {
|
if (this._isAutomatedViewUpdateAllowed()) {
|
||||||
this._changeView();
|
this._changeView();
|
||||||
} else {
|
} else {
|
||||||
@@ -1861,7 +1832,7 @@ class FrigateCard extends LitElement {
|
|||||||
// interval.
|
// interval.
|
||||||
this._startUpdateTimer();
|
this._startUpdateTimer();
|
||||||
}
|
}
|
||||||
}, this._getConfig().view.update_seconds * 1000);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1872,7 +1843,7 @@ class FrigateCard extends LitElement {
|
|||||||
protected _isAutomatedViewUpdateAllowed(ignoreTriggers?: boolean): boolean {
|
protected _isAutomatedViewUpdateAllowed(ignoreTriggers?: boolean): boolean {
|
||||||
return (
|
return (
|
||||||
(ignoreTriggers || !this._isTriggered()) &&
|
(ignoreTriggers || !this._isTriggered()) &&
|
||||||
(this._getConfig().view.update_force || !this._interactionTimerID)
|
(this._getConfig().view.update_force || !this._interactionTimer.isRunning())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
dispatchMediaPlayEvent,
|
dispatchMediaPlayEvent,
|
||||||
} from '../../utils/media-info.js';
|
} from '../../utils/media-info.js';
|
||||||
import { dispatchErrorMessageEvent } from '../message.js';
|
import { dispatchErrorMessageEvent } from '../message.js';
|
||||||
|
import { Timer } from '../../utils/timer.js';
|
||||||
|
|
||||||
// Number of seconds a signed URL is valid for.
|
// Number of seconds a signed URL is valid for.
|
||||||
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
||||||
@@ -41,7 +42,7 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
|||||||
|
|
||||||
protected _jsmpegCanvasElement?: HTMLCanvasElement;
|
protected _jsmpegCanvasElement?: HTMLCanvasElement;
|
||||||
protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
|
protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
|
||||||
protected _refreshPlayerTimerID?: number;
|
protected _refreshPlayerTimer = new Timer();
|
||||||
|
|
||||||
public async play(): Promise<void> {
|
public async play(): Promise<void> {
|
||||||
return this._jsmpegVideoPlayer?.play();
|
return this._jsmpegVideoPlayer?.play();
|
||||||
@@ -145,10 +146,7 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
|||||||
* Reset / destroy the player.
|
* Reset / destroy the player.
|
||||||
*/
|
*/
|
||||||
protected _resetPlayer(): void {
|
protected _resetPlayer(): void {
|
||||||
if (this._refreshPlayerTimerID) {
|
this._refreshPlayerTimer.stop();
|
||||||
window.clearTimeout(this._refreshPlayerTimerID);
|
|
||||||
this._refreshPlayerTimerID = undefined;
|
|
||||||
}
|
|
||||||
if (this._jsmpegVideoPlayer) {
|
if (this._jsmpegVideoPlayer) {
|
||||||
try {
|
try {
|
||||||
this._jsmpegVideoPlayer.destroy();
|
this._jsmpegVideoPlayer.destroy();
|
||||||
@@ -213,9 +211,10 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this._createJSMPEGPlayer(address);
|
await this._createJSMPEGPlayer(address);
|
||||||
this._refreshPlayerTimerID = window.setTimeout(() => {
|
this._refreshPlayerTimer.start(
|
||||||
this.requestUpdate();
|
JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS,
|
||||||
}, (JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
|
() => this.requestUpdate(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import './carousel.js';
|
|||||||
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
|
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
|
||||||
import { FrigateCardTitleControl } from './title-control.js';
|
import { FrigateCardTitleControl } from './title-control.js';
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
|
import { Timer } from '../utils/timer';
|
||||||
|
|
||||||
interface CarouselMediaLoadedInfo {
|
interface CarouselMediaLoadedInfo {
|
||||||
slide: number;
|
slide: number;
|
||||||
@@ -126,7 +127,7 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||||
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||||
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
||||||
protected _titleTimerID: number | null = null;
|
protected _titleTimer = new Timer();
|
||||||
|
|
||||||
protected _boundAutoPlayHandler = this.autoPlay.bind(this);
|
protected _boundAutoPlayHandler = this.autoPlay.bind(this);
|
||||||
protected _boundAutoUnmuteHandler = this.autoUnmute.bind(this);
|
protected _boundAutoUnmuteHandler = this.autoUnmute.bind(this);
|
||||||
@@ -231,13 +232,10 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
*/
|
*/
|
||||||
protected _titleHandler(): void {
|
protected _titleHandler(): void {
|
||||||
const show = () => {
|
const show = () => {
|
||||||
this._titleTimerID = null;
|
this._titleTimer.stop();
|
||||||
this._titleControlRef.value?.show();
|
this._titleControlRef.value?.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this._titleTimerID) {
|
|
||||||
window.clearTimeout(this._titleTimerID);
|
|
||||||
}
|
|
||||||
if (this._titleControlRef.value?.isVisible()) {
|
if (this._titleControlRef.value?.isVisible()) {
|
||||||
// If it's already visible, update it immediately (but also update it
|
// If it's already visible, update it immediately (but also update it
|
||||||
// after the timer expires to ensure it re-positions if necessary, see
|
// after the timer expires to ensure it re-positions if necessary, see
|
||||||
@@ -248,7 +246,7 @@ export class FrigateCardMediaCarousel extends LitElement {
|
|||||||
// Allow a brief pause after the media loads, but before the title is
|
// Allow a brief pause after the media loads, but before the title is
|
||||||
// displayed. This allows for a pleasant appearance/disappear of the title,
|
// displayed. This allows for a pleasant appearance/disappear of the title,
|
||||||
// and allows for the browser to finish rendering the carousel.
|
// and allows for the browser to finish rendering the carousel.
|
||||||
this._titleTimerID = window.setTimeout(show, 0.5 * 1000);
|
this._titleTimer.start(0.5, show);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-7
@@ -1,4 +1,5 @@
|
|||||||
import { FrigateCardMediaPlayer } from '../types';
|
import { FrigateCardMediaPlayer } from '../types';
|
||||||
|
import { Timer } from './timer';
|
||||||
|
|
||||||
// The number of seconds to hide the video controls for after loading (in order
|
// The number of seconds to hide the video controls for after loading (in order
|
||||||
// to give a cleaner UI appearance, see:
|
// to give a cleaner UI appearance, see:
|
||||||
@@ -15,19 +16,17 @@ const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1;
|
|||||||
export const hideMediaControlsTemporarily = (
|
export const hideMediaControlsTemporarily = (
|
||||||
element: HTMLElement & {
|
element: HTMLElement & {
|
||||||
controls: boolean;
|
controls: boolean;
|
||||||
_controlsHideTimeoutID?: number;
|
_controlsHideTimer?: Timer;
|
||||||
},
|
},
|
||||||
seconds = MEDIA_SEEK_CONTROLS_HIDE_SECONDS,
|
seconds = MEDIA_SEEK_CONTROLS_HIDE_SECONDS,
|
||||||
): void => {
|
): void => {
|
||||||
element.controls = false;
|
element.controls = false;
|
||||||
|
|
||||||
if (element._controlsHideTimeoutID) {
|
element._controlsHideTimer ??= new Timer();
|
||||||
window.clearTimeout(element._controlsHideTimeoutID);
|
element._controlsHideTimer.start(seconds, () => {
|
||||||
}
|
|
||||||
element._controlsHideTimeoutID = window.setTimeout(() => {
|
|
||||||
element.controls = true;
|
element.controls = true;
|
||||||
delete element._controlsHideTimeoutID;
|
delete element._controlsHideTimer;
|
||||||
}, seconds * 1000);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+5
-13
@@ -1,8 +1,9 @@
|
|||||||
import { errorToConsole } from "./basic";
|
import { errorToConsole } from './basic';
|
||||||
|
import { Timer } from './timer';
|
||||||
|
|
||||||
export class MicrophoneController {
|
export class MicrophoneController {
|
||||||
protected _stream?: MediaStream | null;
|
protected _stream?: MediaStream | null;
|
||||||
protected _timerID: number | null = null;
|
protected _timer = new Timer();
|
||||||
|
|
||||||
// We keep mute state separate from the stream state so that mute/unmute can
|
// We keep mute state separate from the stream state so that mute/unmute can
|
||||||
// be expressed before the stream is created -- and when it's create it will
|
// be expressed before the stream is created -- and when it's create it will
|
||||||
@@ -68,20 +69,11 @@ export class MicrophoneController {
|
|||||||
return !this._stream || this._stream.getTracks().every((track) => !track.enabled);
|
return !this._stream || this._stream.getTracks().every((track) => !track.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _clearTimer(): void {
|
|
||||||
if (this._timerID) {
|
|
||||||
window.clearTimeout(this._timerID);
|
|
||||||
this._timerID = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _startTimer(): void {
|
protected _startTimer(): void {
|
||||||
if (this._disconnectSeconds) {
|
if (this._disconnectSeconds) {
|
||||||
this._clearTimer();
|
this._timer.start(this._disconnectSeconds, () => {
|
||||||
this._timerID = window.setTimeout(() => {
|
|
||||||
this._clearTimer();
|
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
}, this._disconnectSeconds * 1000);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export class Timer {
|
||||||
|
protected _timer: number | null = null;
|
||||||
|
|
||||||
|
public stop(): void {
|
||||||
|
if (this._timer) {
|
||||||
|
window.clearTimeout(this._timer);
|
||||||
|
this._timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRunning(): boolean {
|
||||||
|
return this._timer !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(seconds: number, func: () => void): void {
|
||||||
|
this.stop();
|
||||||
|
this._timer = window.setTimeout(() => {
|
||||||
|
this._timer = null;
|
||||||
|
func();
|
||||||
|
}, seconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public startRepeated(seconds: number, func: () => void): void {
|
||||||
|
this.stop();
|
||||||
|
this._timer = window.setInterval(() => {
|
||||||
|
func();
|
||||||
|
}, seconds * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { Timer } from '../../src/utils/timer';
|
||||||
|
|
||||||
|
// @vitest-environment jsdom
|
||||||
|
describe('Timer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be running on construct', () => {
|
||||||
|
const timer = new Timer();
|
||||||
|
expect(timer.isRunning()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire when started', () => {
|
||||||
|
const timer = new Timer();
|
||||||
|
const handler = vi.fn();
|
||||||
|
timer.start(10, handler);
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeTruthy();
|
||||||
|
expect(handler).not.toBeCalled();
|
||||||
|
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeFalsy();
|
||||||
|
expect(handler).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fire repeatedly when started', () => {
|
||||||
|
const timer = new Timer();
|
||||||
|
const handler = vi.fn();
|
||||||
|
timer.startRepeated(10, handler);
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeTruthy();
|
||||||
|
expect(handler).not.toBeCalled();
|
||||||
|
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeTruthy();
|
||||||
|
expect(handler).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeTruthy();
|
||||||
|
expect(handler).toBeCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fire when stopped', () => {
|
||||||
|
const timer = new Timer();
|
||||||
|
const handler = vi.fn();
|
||||||
|
timer.start(10, handler);
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeTruthy();
|
||||||
|
expect(handler).not.toBeCalled();
|
||||||
|
|
||||||
|
timer.stop();
|
||||||
|
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
|
||||||
|
expect(timer.isRunning()).toBeFalsy();
|
||||||
|
expect(handler).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user