diff --git a/README.md b/README.md index ed7a2784..c09be79c 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ All variables listed are under a `live:` section. | `webrtc.entity` | | The RTSP entity to pass WebRTC. Specify this OR `webrtc.url` (above). | | `webrtc.*`| | Any other options in a `webrtc:` YAML dictionary are silently passed through to WebRTC. See [WebRTC Configuration](https://github.com/AlexxIT/WebRTC#configuration) for full details this external card provides.| | `actions` | | Actions to use for the `live` view. See [actions](#actions) below.| +| `jsmpeg.options.{audio, video, pauseWhenHidden, disableGl, disableWebAssembly, preserveDrawingBuffer, progressive, throttled, chunkSize, maxAudioLag, videoBufferSize, audioBufferSize}` | | **Advanced users only**: Control the underlying [JSMPEG library options](https://github.com/phoboslab/jsmpeg#usage). This is not necessary for the vast majority of users: only set these flags if you know what you're doing, as you may entirely break video rendering in the card.| #### Available Live Providers diff --git a/src/components/live.ts b/src/components/live.ts index f69a2e89..264a3418 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -1,5 +1,11 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; -import type { ExtendedHomeAssistant, FrigateCardConfig, MediaShowInfo, WebRTCConfig } from '../types.js'; +import type { + ExtendedHomeAssistant, + FrigateCardConfig, + JSMPEGConfig, + MediaShowInfo, + WebRTCConfig, +} from '../types.js'; import { HomeAssistant } from 'custom-card-helpers'; import { customElement, property } from 'lit/decorators.js'; import { until } from 'lit/directives/until.js'; @@ -93,6 +99,7 @@ export class FrigateCardLive extends LitElement { .hass=${this.hass} .cameraName=${this.frigateCameraName} .clientId=${this.config.frigate.client_id} + .jsmpegConfig=${this.config.live.jsmpeg} @frigate-card:media-show=${this._mediaShowHandler} > `}`; @@ -242,6 +249,9 @@ export class FrigateCardLiveJSMPEG extends LitElement { @property({ attribute: false }) protected clientId?: string; + @property({ attribute: false }) + protected jsmpegConfig?: JSMPEGConfig; + protected hass?: HomeAssistant & ExtendedHomeAssistant; protected _jsmpegCanvasElement?: HTMLCanvasElement; protected _jsmpegVideoPlayer?: JSMpeg.VideoElement; @@ -262,7 +272,8 @@ export class FrigateCardLiveJSMPEG extends LitElement { response = await homeAssistantSignPath( this.hass, `/api/frigate/${this.clientId}` + `/jsmpeg/${this.cameraName}`, - URL_SIGN_EXPIRY_SECONDS); + URL_SIGN_EXPIRY_SECONDS, + ); } catch (err) { console.warn(err); return null; @@ -279,11 +290,30 @@ export class FrigateCardLiveJSMPEG extends LitElement { */ protected _createJSMPEGPlayer(): JSMpeg.VideoElement { let videoDecoded = false; + + const jsmpegOptions = { + pauseWhenHidden: false, + protocols: [], + audio: false, + videoBufferSize: 1024 * 1024 * 4, + onVideoDecode: () => { + // This is the only callback that is called after the dimensions + // are available. It's called on every frame decode, so just + // ignore any subsequent calls. + if (!videoDecoded && this._jsmpegCanvasElement) { + videoDecoded = true; + dispatchMediaShowEvent(this, this._jsmpegCanvasElement); + } + }, + }; + + // Override with user-specified options. + Object.assign(jsmpegOptions, this.jsmpegConfig?.options); + return new JSMpeg.VideoElement( this, this._jsmpegURL, { - preserveDrawingBuffer: true, canvas: this._jsmpegCanvasElement, hooks: { play: () => { @@ -294,21 +324,7 @@ export class FrigateCardLiveJSMPEG extends LitElement { }, }, }, - { - pauseWhenHidden: false, - protocols: [], - audio: false, - videoBufferSize: 1024 * 1024 * 4, - onVideoDecode: () => { - // This is the only callback that is called after the dimensions - // are available. It's called on every frame decode, so just - // ignore any subsequent calls. - if (!videoDecoded && this._jsmpegCanvasElement) { - videoDecoded = true; - dispatchMediaShowEvent(this, this._jsmpegCanvasElement); - } - }, - }, + jsmpegOptions, ); } diff --git a/src/types.ts b/src/types.ts index 3c9c78c3..68d2e67d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -127,11 +127,13 @@ const actionSchema = z.union([ ]); export type ActionType = z.infer; -const actionBaseSchema = z.object({ - tap_action: actionSchema.optional(), - hold_action: actionSchema.optional(), - double_tap_action: actionSchema.optional(), -}).passthrough(); +const actionBaseSchema = z + .object({ + tap_action: actionSchema.optional(), + hold_action: actionSchema.optional(), + double_tap_action: actionSchema.optional(), + }) + .passthrough(); export type Actions = z.infer; const actionsSchema = z.object({ @@ -369,11 +371,35 @@ const webrtcConfigSchema = z .optional(); export type WebRTCConfig = z.infer; +const jsmpegConfigSchema = z + .object({ + options: z + .object({ + // https://github.com/phoboslab/jsmpeg#usage + audio: z.boolean().optional(), + video: z.boolean().optional(), + pauseWhenHidden: z.boolean().optional(), + disableGl: z.boolean().optional(), + disableWebAssembly: z.boolean().optional(), + preserveDrawingBuffer: z.boolean().optional(), + progressive: z.boolean().optional(), + throttled: z.boolean().optional(), + chunkSize: z.number().optional(), + maxAudioLag: z.number().optional(), + videoBufferSize: z.number().optional(), + audioBufferSize: z.number().optional(), + }) + .optional(), + }) + .optional(); +export type JSMPEGConfig = z.infer; + const liveConfigSchema = z .object({ provider: z.enum(LIVE_PROVIDERS).default(liveConfigDefault.provider), preload: z.boolean().default(liveConfigDefault.preload), webrtc: webrtcConfigSchema, + jsmpeg: jsmpegConfigSchema, }) .merge(actionsSchema) .default(liveConfigDefault);