feat: Add PIP (Picture-in-Picture) support (#2391)

- Closes: #1657
This commit is contained in:
Dermot Duffy
2026-03-05 20:19:20 -08:00
committed by GitHub
parent ecc8ecfd7e
commit c398562d40
32 changed files with 735 additions and 14 deletions
+5 -1
View File
@@ -1,5 +1,5 @@
import { LitElement } from 'lit';
import { FullscreenElement, MediaPlayerController } from '../../types';
import { FullscreenElement, MediaPlayerController, PIPElement } from '../../types';
import { screenshotImage } from '../../utils/screenshot';
export class ImageMediaPlayerController implements MediaPlayerController {
@@ -58,4 +58,8 @@ export class ImageMediaPlayerController implements MediaPlayerController {
public getFullscreenElement(): FullscreenElement | null {
return this._getImageCallback() ?? null;
}
public getPIPElement(): PIPElement | null {
return null;
}
}
+6 -1
View File
@@ -1,6 +1,6 @@
import JSMpeg from '@cycjimmy/jsmpeg-player';
import { LitElement } from 'lit';
import { FullscreenElement, MediaPlayerController } from '../../types';
import { FullscreenElement, MediaPlayerController, PIPElement } from '../../types';
export class JSMPEGMediaPlayerController implements MediaPlayerController {
private _host: LitElement;
@@ -70,4 +70,9 @@ export class JSMPEGMediaPlayerController implements MediaPlayerController {
public getFullscreenElement(): FullscreenElement | null {
return this._getCanvasElementCallback() ?? null;
}
public getPIPElement(): PIPElement | null {
// JSMpeg renders to a canvas, not a video element.
return null;
}
}
@@ -1,5 +1,5 @@
import { LitElement } from 'lit';
import { FullscreenElement, MediaPlayerController } from '../../types';
import { FullscreenElement, MediaPlayerController, PIPElement } from '../../types';
import { CachedValueController } from '../cached-value-controller';
export class UpdatingImageMediaPlayerController implements MediaPlayerController {
@@ -61,4 +61,8 @@ export class UpdatingImageMediaPlayerController implements MediaPlayerController
public getFullscreenElement(): FullscreenElement | null {
return this._getImageCallback() ?? null;
}
public getPIPElement(): PIPElement | null {
return null;
}
}
+5 -1
View File
@@ -1,5 +1,5 @@
import { LitElement } from 'lit';
import { FullscreenElement, MediaPlayerController } from '../../types';
import { FullscreenElement, MediaPlayerController, PIPElement } from '../../types';
import { hideMediaControlsTemporarily, setControlsOnVideo } from '../../utils/controls';
import { screenshotVideo } from '../../utils/screenshot';
@@ -107,4 +107,8 @@ export class VideoMediaPlayerController implements MediaPlayerController {
public getFullscreenElement(): FullscreenElement | null {
return this._getVideoCallback() ?? null;
}
public getPIPElement(): PIPElement | null {
return this._getVideoCallback() ?? null;
}
}
@@ -4,6 +4,7 @@ import { FoldersManager } from '../card-controller/folders/manager';
import { FullscreenManager } from '../card-controller/fullscreen/fullscreen-manager';
import { MediaPlayerManager } from '../card-controller/media-player-manager';
import { MicrophoneManager } from '../card-controller/microphone-manager';
import { PIPManager } from '../card-controller/pip-manager';
import { ViewManager } from '../card-controller/view/view-manager';
import {
AdvancedCameraCardView,
@@ -45,6 +46,7 @@ export interface MenuButtonControllerOptions {
inExpandedMode?: boolean;
microphoneManager?: MicrophoneManager | null;
mediaPlayerController?: MediaPlayerManager | null;
pipManager?: PIPManager | null;
viewManager?: ViewManager | null;
view?: View | null;
}
@@ -100,6 +102,7 @@ export class MenuButtonController {
),
this._getExpandButton(config, options?.inExpandedMode),
this._getFullscreenButton(config, options?.fullscreenManager),
this._getPIPButton(config, options?.pipManager),
this._getCastButton(
hass,
config,
@@ -550,6 +553,25 @@ export class MenuButtonController {
: null;
}
private _getPIPButton(
config: AdvancedCameraCardConfig,
pipManager?: PIPManager | null,
): MenuItem | null {
const inPIP = pipManager?.isInPIP();
return pipManager?.isAvailable()
? {
icon: inPIP
? 'mdi:picture-in-picture-bottom-right-outline'
: 'mdi:picture-in-picture-bottom-right',
...config.menu.buttons.pip,
type: 'custom:advanced-camera-card-menu-icon',
title: localize('config.menu.buttons.pip'),
tap_action: createGeneralAction('pip'),
style: inPIP ? this._getEmphasizedStyle() : {},
}
: null;
}
private _getCastButton(
hass: HomeAssistant,
config: AdvancedCameraCardConfig,