@@ -0,0 +1,24 @@
|
||||
import screenfull from 'screenfull';
|
||||
import { WebkitHTMLVideoElement } from '../../types';
|
||||
import { CardFullscreenAPI } from '../types';
|
||||
import { ScreenfullFullScreenProvider } from './screenfull';
|
||||
import { FullscreenHandler, FullscreenProvider } from './types';
|
||||
import { WebkitFullScreenProvider } from './webkit';
|
||||
|
||||
export class FullscreenProviderFactory {
|
||||
public static create(
|
||||
api: CardFullscreenAPI,
|
||||
handler: FullscreenHandler,
|
||||
): FullscreenProvider | null {
|
||||
if (screenfull.isEnabled) {
|
||||
return new ScreenfullFullScreenProvider(api, handler);
|
||||
}
|
||||
|
||||
const video = document.createElement('video') as Partial<WebkitHTMLVideoElement>;
|
||||
if (!!video.webkitEnterFullscreen) {
|
||||
return new WebkitFullScreenProvider(api, handler);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { isBeingCasted } from '../../utils/casting';
|
||||
import { CardFullscreenAPI } from '../types';
|
||||
import { FullscreenProviderFactory } from './factory';
|
||||
import { FullscreenProvider } from './types';
|
||||
|
||||
export class FullscreenManager {
|
||||
protected _api: CardFullscreenAPI;
|
||||
protected _provider: FullscreenProvider | null;
|
||||
|
||||
constructor(api: CardFullscreenAPI, provider?: FullscreenProvider) {
|
||||
this._api = api;
|
||||
this._provider =
|
||||
provider ?? FullscreenProviderFactory.create(api, this._fullscreenHandler);
|
||||
}
|
||||
|
||||
public connect(): void {
|
||||
this._provider?.connect();
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this._provider?.disconnect();
|
||||
}
|
||||
|
||||
public setFullscreen(fullscreen: boolean): void {
|
||||
this._provider?.setFullscreen(fullscreen);
|
||||
}
|
||||
|
||||
public isInFullscreen(): boolean {
|
||||
return this._provider?.isInFullscreen() ?? false;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
if (isBeingCasted()) {
|
||||
return false;
|
||||
}
|
||||
return this._provider?.isSupported() ?? false;
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this._setConditionState();
|
||||
}
|
||||
|
||||
public toggleFullscreen(): void {
|
||||
if (this._provider?.isInFullscreen()) {
|
||||
this._provider.setFullscreen(false);
|
||||
} else {
|
||||
this._provider?.setFullscreen(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected _fullscreenHandler = (): void => {
|
||||
this._api.getExpandManager().setExpanded(false);
|
||||
|
||||
this._setConditionState();
|
||||
|
||||
// Re-render after a change to fullscreen mode to take advantage of
|
||||
// the expanded screen real-estate (vs staying in aspect-ratio locked
|
||||
// modes).
|
||||
this._api.getCardElementManager().update();
|
||||
};
|
||||
|
||||
protected _setConditionState(): void {
|
||||
this._api.getConditionsManager()?.setState({
|
||||
fullscreen: this.isInFullscreen(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CardFullscreenAPI } from '../types';
|
||||
import { FullscreenHandler } from './types';
|
||||
|
||||
export class FullscreenProviderBase {
|
||||
protected _api: CardFullscreenAPI;
|
||||
protected _handler: FullscreenHandler;
|
||||
|
||||
constructor(api: CardFullscreenAPI, handler: FullscreenHandler) {
|
||||
this._api = api;
|
||||
this._handler = handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import screenfull from 'screenfull';
|
||||
import { FullscreenProviderBase } from '../provider';
|
||||
import { FullscreenProvider } from '../types';
|
||||
|
||||
export class ScreenfullFullScreenProvider
|
||||
extends FullscreenProviderBase
|
||||
implements FullscreenProvider
|
||||
{
|
||||
public connect(): void {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.on('change', this._handler);
|
||||
}
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.off('change', this._handler);
|
||||
}
|
||||
}
|
||||
|
||||
public isInFullscreen(): boolean {
|
||||
return screenfull.isEnabled && screenfull.isFullscreen;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return screenfull.isEnabled;
|
||||
}
|
||||
|
||||
public setFullscreen(fullscreen: boolean): void {
|
||||
if (!this.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullscreen) {
|
||||
screenfull.request(this._api.getCardElementManager().getElement());
|
||||
} else {
|
||||
screenfull.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export type FullscreenHandler = () => void;
|
||||
|
||||
export interface FullscreenProvider {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
|
||||
isInFullscreen(): boolean;
|
||||
isSupported(): boolean;
|
||||
|
||||
setFullscreen(fullscreen: boolean): void;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { WebkitHTMLVideoElement } from '../../../types';
|
||||
import { Timer } from '../../../utils/timer';
|
||||
import { ConditionState } from '../../conditions-manager';
|
||||
import { FullscreenProviderBase } from '../provider';
|
||||
import { FullscreenProvider } from '../types';
|
||||
|
||||
const WEBKIT_PLAY_SECONDS = 0.5;
|
||||
|
||||
/**
|
||||
* Fullscreen implementation for webkit based browsers that do not support the
|
||||
* standard Fullscreen API (expected to be exclusively iOS on an iPhone).
|
||||
* See: https://github.com/dermotduffy/frigate-hass-card/issues/1444
|
||||
*/
|
||||
export class WebkitFullScreenProvider
|
||||
extends FullscreenProviderBase
|
||||
implements FullscreenProvider
|
||||
{
|
||||
protected _playTimer = new Timer();
|
||||
|
||||
public connect(): void {
|
||||
this._api.getConditionsManager().addListener(this._conditionChangeHandler);
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
this._api.getConditionsManager().removeListener(this._conditionChangeHandler);
|
||||
}
|
||||
|
||||
protected _conditionChangeHandler = (
|
||||
newState: ConditionState,
|
||||
oldState?: ConditionState,
|
||||
): void => {
|
||||
if (
|
||||
newState.mediaLoadedInfo?.player?.getFullscreenElement() !==
|
||||
oldState?.mediaLoadedInfo?.player?.getFullscreenElement()
|
||||
) {
|
||||
const oldElement = oldState?.mediaLoadedInfo?.player?.getFullscreenElement();
|
||||
oldElement?.removeEventListener('webkitbeginfullscreen', this._handler);
|
||||
oldElement?.removeEventListener('webkitendfullscreen', this._endHandler);
|
||||
|
||||
const newElement = newState.mediaLoadedInfo?.player?.getFullscreenElement();
|
||||
newElement?.addEventListener('webkitbeginfullscreen', this._handler);
|
||||
newElement?.addEventListener('webkitendfullscreen', this._endHandler);
|
||||
}
|
||||
};
|
||||
|
||||
protected _getVideoElement():
|
||||
| (HTMLVideoElement & Partial<WebkitHTMLVideoElement>)
|
||||
| null {
|
||||
const element = this._api
|
||||
.getMediaLoadedInfoManager()
|
||||
.get()
|
||||
?.player?.getFullscreenElement();
|
||||
return element instanceof HTMLVideoElement ? element : null;
|
||||
}
|
||||
|
||||
public isInFullscreen(): boolean {
|
||||
return !!this._getVideoElement()?.webkitDisplayingFullscreen;
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return !!this._getVideoElement()?.webkitSupportsFullscreen;
|
||||
}
|
||||
|
||||
public setFullscreen(fullscreen: boolean): void {
|
||||
if (!this.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const video = this._getVideoElement();
|
||||
|
||||
if (fullscreen) {
|
||||
video?.webkitEnterFullscreen?.();
|
||||
} else {
|
||||
video?.webkitExitFullscreen?.();
|
||||
}
|
||||
}
|
||||
|
||||
protected _endHandler = (): void => {
|
||||
this._handler();
|
||||
|
||||
// Webkit on iPhone stops the video when exiting fullscreen (why!). This
|
||||
// hack workaround has the disadvantage that there's no way to tell if the
|
||||
// video was legitimately paused before they user exited fullscreen (as the
|
||||
// common path is for exit fullscreen to not involve
|
||||
// setFullscreen(...) being called at all, but rather the user simply
|
||||
// clicking the 'X' which then fires this event). That's probably the rare
|
||||
// case though.
|
||||
this._playTimer.start(WEBKIT_PLAY_SECONDS, () => {
|
||||
this._getVideoElement()?.play();
|
||||
});
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user