77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import type { ConditionStateChange } from '../condition-trigger/conditions/types';
|
|
import type { PIPElement } from '../types';
|
|
import type { CardPIPAPI } from './types';
|
|
|
|
export class PIPManager {
|
|
private _api: CardPIPAPI;
|
|
private _videoElement: PIPElement | null = null;
|
|
private _disconnect: (() => void) | null = null;
|
|
|
|
constructor(api: CardPIPAPI) {
|
|
this._api = api;
|
|
}
|
|
|
|
public static isSupported(): boolean {
|
|
return !!document.pictureInPictureEnabled;
|
|
}
|
|
|
|
public initialize(): void {
|
|
this._api.getConditionStateManager().addListener(this._stateChangeHandler);
|
|
}
|
|
|
|
public uninitialize(): void {
|
|
this._trackElement(null);
|
|
this._api.getConditionStateManager().removeListener(this._stateChangeHandler);
|
|
}
|
|
|
|
public isInPIP(): boolean {
|
|
return !!document.pictureInPictureElement;
|
|
}
|
|
|
|
public isAvailable(): boolean {
|
|
return PIPManager.isSupported() && !!this._videoElement;
|
|
}
|
|
|
|
public async togglePIP(): Promise<void> {
|
|
if (document.pictureInPictureElement) {
|
|
await document.exitPictureInPicture();
|
|
} else if (this._videoElement && PIPManager.isSupported()) {
|
|
await this._videoElement.requestPictureInPicture();
|
|
}
|
|
}
|
|
|
|
private _stateChangeHandler = (change: ConditionStateChange): void => {
|
|
const oldElement =
|
|
change.old.mediaLoadedInfo?.mediaPlayerController?.getPIPElement() ?? null;
|
|
const newElement =
|
|
change.new.mediaLoadedInfo?.mediaPlayerController?.getPIPElement() ?? null;
|
|
|
|
if (oldElement !== newElement) {
|
|
// Exit PIP when the video element is destroyed (e.g. view change) to
|
|
// avoid a black/empty PIP window.
|
|
if (!newElement && this.isInPIP()) {
|
|
// Ignore errors in existing PIP.
|
|
document.exitPictureInPicture().catch(() => {});
|
|
}
|
|
this._trackElement(newElement);
|
|
this._api.getCardElementManager().update();
|
|
}
|
|
};
|
|
|
|
private _trackElement(element: PIPElement | null): void {
|
|
this._disconnect?.();
|
|
this._disconnect = null;
|
|
this._videoElement = element;
|
|
|
|
if (element) {
|
|
const handler = (): void => this._api.getCardElementManager().update();
|
|
element.addEventListener('enterpictureinpicture', handler);
|
|
element.addEventListener('leavepictureinpicture', handler);
|
|
this._disconnect = () => {
|
|
element.removeEventListener('enterpictureinpicture', handler);
|
|
element.removeEventListener('leavepictureinpicture', handler);
|
|
};
|
|
}
|
|
}
|
|
}
|