Fix zoom in Firefox and Safari.
This commit is contained in:
@@ -2,6 +2,7 @@ import { mayHaveAudio } from '../../../utils/audio';
|
||||
import {
|
||||
hideMediaControlsTemporarily,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
setControlsOnVideo,
|
||||
} from '../../../utils/media';
|
||||
import {
|
||||
dispatchMediaLoadedEvent,
|
||||
@@ -271,7 +272,7 @@ export class VideoRTC extends HTMLElement {
|
||||
*/
|
||||
oninit() {
|
||||
this.video = document.createElement('video');
|
||||
this.video.controls = this.controls;
|
||||
setControlsOnVideo(this.video, this.controls);
|
||||
this.video.playsInline = true;
|
||||
this.video.preload = 'auto';
|
||||
|
||||
@@ -633,7 +634,7 @@ export class VideoRTC extends HTMLElement {
|
||||
let receivedFirstFrame = false;
|
||||
|
||||
this.ondata = (data) => {
|
||||
this.video.controls = false;
|
||||
setControlsOnVideo(this.video, false);
|
||||
this.video.poster = 'data:image/jpeg;base64,' + VideoRTC.btoa(data);
|
||||
|
||||
if (!receivedFirstFrame) {
|
||||
@@ -672,7 +673,7 @@ export class VideoRTC extends HTMLElement {
|
||||
|
||||
context.drawImage(video2, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
this.video.controls = false;
|
||||
setControlsOnVideo(this.video, false);
|
||||
this.video.poster = canvas.toDataURL('image/jpeg');
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
MicrophoneConfig,
|
||||
} from '../../types.js';
|
||||
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint';
|
||||
import { setControlsOnVideo } from '../../utils/media.js';
|
||||
import '../image.js';
|
||||
import { dispatchErrorMessageEvent } from '../message';
|
||||
import { VideoRTC } from './go2rtc/video-rtc';
|
||||
@@ -84,7 +85,7 @@ export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPla
|
||||
|
||||
public async setControls(controls?: boolean): Promise<void> {
|
||||
if (this._player?.video) {
|
||||
this._player.video.controls = controls ?? this.controls;
|
||||
setControlsOnVideo(this._player.video, controls ?? this.controls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { renderTask } from '../../utils/task.js';
|
||||
import {
|
||||
hideMediaControlsTemporarily,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
setControlsOnVideo,
|
||||
} from '../../utils/media.js';
|
||||
import { CameraEndpoints } from '../../camera-manager/types.js';
|
||||
import { mayHaveAudio } from '../../utils/audio.js';
|
||||
@@ -85,7 +86,7 @@ export class FrigateCardLiveWebRTCCard
|
||||
public async setControls(controls?: boolean): Promise<void> {
|
||||
const player = this._getPlayer();
|
||||
if (player) {
|
||||
player.controls = controls ?? this.controls;
|
||||
setControlsOnVideo(player, controls ?? this.controls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +192,7 @@ export class FrigateCardLiveWebRTCCard
|
||||
this.updateComplete.then(() => {
|
||||
const video = this._getPlayer();
|
||||
if (video) {
|
||||
setControlsOnVideo(video, this.controls);
|
||||
video.onloadeddata = () => {
|
||||
if (this.controls) {
|
||||
hideMediaControlsTemporarily(video, MEDIA_LOAD_CONTROLS_HIDE_SECONDS);
|
||||
@@ -206,7 +208,6 @@ export class FrigateCardLiveWebRTCCard
|
||||
video.onplay = () => dispatchMediaPlayEvent(this);
|
||||
video.onpause = () => dispatchMediaPauseEvent(this);
|
||||
video.onvolumechange = () => dispatchMediaVolumeChangeEvent(this);
|
||||
video.controls = this.controls;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
hideMediaControlsTemporarily,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
playMediaMutingIfNecessary,
|
||||
setControlsOnVideo,
|
||||
} from '../utils/media.js';
|
||||
import { ViewMediaClassifier } from '../view/media-classifier';
|
||||
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
||||
@@ -612,7 +613,10 @@ export class FrigateCardViewerProvider
|
||||
if (this._refFrigateCardMediaPlayer.value) {
|
||||
return this._refFrigateCardMediaPlayer.value.setControls(controls);
|
||||
} else if (this._refVideoProvider.value) {
|
||||
this._refVideoProvider.value.controls = controls ?? this.viewerConfig?.controls.builtin ?? true;
|
||||
setControlsOnVideo(
|
||||
this._refVideoProvider.value,
|
||||
controls ?? this.viewerConfig?.controls.builtin ?? true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import {
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
hideMediaControlsTemporarily,
|
||||
setControlsOnVideo,
|
||||
} from '../utils/media.js';
|
||||
|
||||
customElements.whenDefined('ha-hls-player').then(() => {
|
||||
@@ -75,7 +76,7 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
||||
|
||||
public async setControls(controls?: boolean): Promise<void> {
|
||||
if (this._video) {
|
||||
this._video.controls = controls ?? this.controls;
|
||||
setControlsOnVideo(this._video, controls ?? this.controls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import {
|
||||
hideMediaControlsTemporarily,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
setControlsOnVideo,
|
||||
} from '../utils/media.js';
|
||||
|
||||
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
||||
@@ -74,7 +75,7 @@ customElements.whenDefined('ha-web-rtc-player').then(() => {
|
||||
|
||||
public async setControls(controls?: boolean): Promise<void> {
|
||||
if (this._video) {
|
||||
this._video.controls = controls ?? this.controls;
|
||||
setControlsOnVideo(this._video, controls ?? this.controls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-14
@@ -7,25 +7,42 @@ import { Timer } from './timer';
|
||||
export const MEDIA_LOAD_CONTROLS_HIDE_SECONDS = 2;
|
||||
const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1;
|
||||
|
||||
export type FrigateCardHTMLVideoElement = HTMLVideoElement & {
|
||||
_controlsHideTimer?: Timer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the controls on a video and removes a timer that may have been added by
|
||||
* hideMediaControlsTemporarily.
|
||||
* @param video
|
||||
* @param value
|
||||
*/
|
||||
export const setControlsOnVideo = (
|
||||
video: FrigateCardHTMLVideoElement,
|
||||
value: boolean,
|
||||
): void => {
|
||||
if (video._controlsHideTimer) {
|
||||
video._controlsHideTimer.stop();
|
||||
delete video._controlsHideTimer;
|
||||
}
|
||||
video.controls = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporarily hide media controls.
|
||||
* @param element Any HTMLElement that has a controls property (e.g.
|
||||
* @param elemaent Any HTMLElement that has a controls property (e.g.
|
||||
* HTMLVideoElement, FrigateCardHaHlsPlayer)
|
||||
* @param seconds The number of seconds to hide the controls for.
|
||||
*/
|
||||
export const hideMediaControlsTemporarily = (
|
||||
element: HTMLElement & {
|
||||
controls: boolean;
|
||||
_controlsHideTimer?: Timer;
|
||||
},
|
||||
video: FrigateCardHTMLVideoElement,
|
||||
seconds = MEDIA_SEEK_CONTROLS_HIDE_SECONDS,
|
||||
): void => {
|
||||
element.controls = false;
|
||||
|
||||
element._controlsHideTimer ??= new Timer();
|
||||
element._controlsHideTimer.start(seconds, () => {
|
||||
element.controls = true;
|
||||
delete element._controlsHideTimer;
|
||||
const oldValue = video.controls;
|
||||
setControlsOnVideo(video, false);
|
||||
video._controlsHideTimer ??= new Timer();
|
||||
video._controlsHideTimer.start(seconds, () => {
|
||||
setControlsOnVideo(video, oldValue);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -41,8 +58,10 @@ export const playMediaMutingIfNecessary = async (
|
||||
// and then try again. This works around some browsers that prevent
|
||||
// auto-play unless the video is muted.
|
||||
if (video?.play) {
|
||||
video.play().catch(async (ev) => {
|
||||
if (ev.name === 'NotAllowedError' && !player.isMuted()) {
|
||||
try {
|
||||
await video.play();
|
||||
} catch (err: unknown) {
|
||||
if ((err as Error).name === 'NotAllowedError' && !player.isMuted()) {
|
||||
await player.mute();
|
||||
try {
|
||||
await video.play();
|
||||
@@ -50,6 +69,6 @@ export const playMediaMutingIfNecessary = async (
|
||||
// Pass.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+20
-3
@@ -1,5 +1,4 @@
|
||||
import { PanzoomObject, PanzoomEventDetail } from '@dermotduffy/panzoom';
|
||||
import Panzoom from '@dermotduffy/panzoom';
|
||||
import Panzoom, { PanzoomEventDetail, PanzoomObject } from '@dermotduffy/panzoom';
|
||||
import round from 'lodash-es/round';
|
||||
import { dispatchFrigateCardEvent, isHoverableDevice } from '../basic';
|
||||
|
||||
@@ -11,6 +10,7 @@ export class Zoom {
|
||||
protected _element: HTMLElement;
|
||||
protected _panzoom?: PanzoomObject;
|
||||
protected _zoomed = false;
|
||||
protected _allowClick = true;
|
||||
|
||||
protected _events = isHoverableDevice()
|
||||
? {
|
||||
@@ -31,9 +31,23 @@ export class Zoom {
|
||||
|
||||
// If we do not prevent default here, the media carousels scroll.
|
||||
ev.preventDefault();
|
||||
this._allowClick = false;
|
||||
} else {
|
||||
this._allowClick = true;
|
||||
}
|
||||
};
|
||||
|
||||
protected _clickHandler = (ev: Event) => {
|
||||
// When mouse clicking is used to pan, need to avoid that causing a click
|
||||
// handler elsewhere in the card being called. Example: Viewing a snapshot,
|
||||
// and panning within it should not cause a related clip to play (the click
|
||||
// handler in the viewer).
|
||||
if (!this._allowClick) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
this._allowClick = true;
|
||||
};
|
||||
|
||||
protected _moveHandler = (ev: Event) => {
|
||||
if (this._shouldZoomOrPan(ev)) {
|
||||
this._panzoom?.handleMove(ev as PointerEvent);
|
||||
@@ -64,7 +78,9 @@ export class Zoom {
|
||||
protected _shouldZoomOrPan(ev: Event): boolean {
|
||||
return (
|
||||
!this._isScaleNormal(this._panzoom?.getScale()) ||
|
||||
(ev instanceof TouchEvent && ev.touches.length > 1) ||
|
||||
// TouchEvent does not exist on Firefox on non-touch events.
|
||||
// See: https://github.com/dermotduffy/frigate-hass-card/issues/1174
|
||||
(window.TouchEvent && ev instanceof TouchEvent && ev.touches.length > 1) ||
|
||||
(ev instanceof WheelEvent && ev.ctrlKey)
|
||||
);
|
||||
}
|
||||
@@ -94,6 +110,7 @@ export class Zoom {
|
||||
registerListeners(this._events['move'], this._moveHandler, { capture: true });
|
||||
registerListeners(this._events['up'], this._upHandler, { capture: true });
|
||||
registerListeners(['wheel'], this._wheelHandler);
|
||||
registerListeners(['click'], this._clickHandler, { capture: true });
|
||||
|
||||
this._element.addEventListener('panzoomzoom', (ev: Event) => {
|
||||
// Take care here to only dispatch the zoomed/unzoomed events when the
|
||||
|
||||
Reference in New Issue
Block a user