Add auto_play and auto_unmute for consistency.

This commit is contained in:
Dermot Duffy
2022-05-08 08:05:33 -07:00
parent d552cf1a0d
commit f6cdf2f01d
9 changed files with 176 additions and 53 deletions
+9 -4
View File
@@ -223,9 +223,12 @@ live:
| Option | Default | Overridable | Description |
| - | - | - | - |
| `preload` | `false` | :heavy_multiplication_x: | Whether or not to preload the live view. Preloading causes the live view to render in the background regardless of what view is actually shown, so it's instantly available when requested. This consumes additional network/CPU resources continually. |
| `auto_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute live cameras. Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead. |
| `auto_play` | `all` | :heavy_multiplication_x: | Whether to automatically play live camera feeds. `never` will never automatically play, `selected` will automatically play when a camera is selected in the carousel, `visible` will automatically play when the browser/tab becomes visible or `all` on any opportunity to automatically play (i.e. either case). Some live live providers (e.g. `webrtc-card`, `jsmpeg`) do not support the prevention of automatic play on initial load, but should still respect the value of this flag on play-after-pause.|
| `auto_pause` | `never` | :heavy_multiplication_x: | Whether to automatically pause live camera feeds. `never` will never automatically pause, `unselected` will automatically pause when a camera is unselected in the carousel, `hidden` will automatically pause when the browser/tab becomes hidden or `all` on any opportunity to automatically pause (i.e. either case). **Caution**: Some live providers (e.g. `jsmpeg`) may not offer human-accessible means to resume play if it is paused, unless the `auto_play` option (above) is used.|
| `auto_mute` | `all` | :heavy_multiplication_x: | Whether to automatically mute live camera feeds. `never` will never automatically mute, `unselected` will automatically mute when a camera is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `all` on any opportunity to automatically mute (i.e. either case).|
| `auto_unmute` | `never` | :heavy_multiplication_x: | Whether to automatically unmute live camera feeds. `never` will never automatically unmute, `selected` will automatically unmute when a camera is unselected in the carousel, `visible` will automatically unmute when the browser/tab becomes hidden or `all` on any opportunity to automatically unmute (i.e. either case).|
| `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load cameras in the camera carousel. Setting this will `false` will cause all cameras to load simultaneously when the `live` carousel is opened (or cause all cameras to load continually if both `lazy_load` and `preload` are `true`). This will result in a smoother carousel experience at a cost of (potentially) a substantial amount of continually streamed data. |
| `lazy_unload` | `never` | :heavy_multiplication_x: | When to lazily **un**load lazyily-loaded cameras. `never` will never lazily-unload, `unselected` will lazy-unload a camera when it is unselected in the carousel, `hidden` will lazy-unload all cameras when the browser/tab is hidden or `all` on any opportunity to lazily unload (i.e. either case). This will cause a reloading delay on revisiting that camera in the carousel but will save the streaming network resources that are otherwise consumed. This option has no effect if `lazy_load` is false. Some live providers (e.g. `webrtc-card`) implement their own lazy unloading independently which may occur regardless of the value of this setting.|
| `lazy_unload` | `never` | :heavy_multiplication_x: | When to lazily **un**load lazyily-loaded cameras. `never` will never lazily-unload, `unselected` will lazy-unload a camera when it is unselected in the carousel, `hidden` will lazy-unload all cameras when the browser/tab becomes hidden or `all` on any opportunity to lazily unload (i.e. either case). This will cause a reloading delay on revisiting that camera in the carousel but will save the streaming network resources that are otherwise consumed. This option has no effect if `lazy_load` is false. Some live providers (e.g. `webrtc-card`) implement their own lazy unloading independently which may occur regardless of the value of this setting.|
| `draggable` | `true` | :heavy_multiplication_x: | Whether or not the live carousel can be dragged left or right, via touch/swipe and mouse dragging. |
| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between live cameras. Accepted values: `slide` or `none`. |
| `actions` | | :white_check_mark: | Actions to use for the `live` view. See [actions](#actions) below.|
@@ -325,8 +328,10 @@ event_viewer:
| Option | Default | Overridable | Description |
| - | - | - | - |
| `auto_play` | `true` | :heavy_multiplication_x: | Whether or not to autoplay clips.|
| `auto_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute clips. Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead. |
| `auto_play` | `all` | :heavy_multiplication_x: | Whether to automatically play events. `never` will never automatically play, `selected` will automatically play when an event is selected in the carousel, `visible` will automatically play when the browser/tab becomes visible or `all` on any opportunity to automatically play (i.e. either case).|
| `auto_pause` | `all` | :heavy_multiplication_x: | Whether to automatically pause events. `never` will never automatically pause, `unselected` will automatically pause when an event is unselected in the carousel, `hidden` will automatically pause when the browser/tab becomes hidden or `all` on any opportunity to automatically pause (i.e. either case).|
| `auto_mute` | `all` | :heavy_multiplication_x: | Whether to automatically mute events. `never` will never automatically mute, `unselected` will automatically mute when an event is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `all` on any opportunity to automatically mute (i.e. either case).|
| `auto_unmute` | `never` | :heavy_multiplication_x: | Whether to automatically unmute events. `never` will never automatically unmute, `selected` will automatically unmute when an event is selected in the carousel, `visible` will automatically unmute when the browser/tab becomes visible or `all` on any opportunity to automatically unmute (i.e. either case). Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead.|
| `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load media in the event viewer carousel. Setting this will false will fetch all media immediately which may make the carousel experience smoother at a cost of (potentially) a substantial number of simultaneous media fetches on load. |
| `draggable` | `true` | :heavy_multiplication_x: | Whether or not the event viewer carousel can be dragged left or right, via touch/swipe and mouse dragging. |
| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between event media. Accepted values: `slide` or `none`. |
+23 -10
View File
@@ -1,18 +1,25 @@
import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel';
import { AutoMuteCondition, AutoPauseCondition, FrigateCardMediaPlayer } from '../../types.js';
import {
AutoMuteCondition,
AutoPauseCondition,
AutoPlayCondition,
AutoUnmuteCondition,
FrigateCardMediaPlayer,
} from '../../types.js';
export type AutoMediaPluginOptionsType = {
playerSelector: string;
autoPlayWhenVisible?: boolean;
autoUnmuteWhenVisible?: boolean;
// Note: Neither play nor unmute will activate on selection. The caller is
// expected to call the methods manually when the media is actually loaded
// (not just the slide shown).
autoPlayCondition?: AutoPlayCondition;
autoUnmuteCondition?: AutoUnmuteCondition;
autoPauseCondition?: AutoPauseCondition;
autoMuteCondition?: AutoMuteCondition;
};
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {
autoPlayWhenVisible: true,
autoUnmuteWhenVisible: true,
};
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {};
export type AutoMediaPluginType = EmblaPluginType<AutoMediaPluginOptionsType> & {
play: () => void;
@@ -102,10 +109,16 @@ export function AutoMediaPlugin(
muteAll();
}
} else if (document.visibilityState == 'visible') {
if (options.autoPlayWhenVisible) {
if (
options.autoPlayCondition &&
['all', 'visible'].includes(options.autoPlayCondition)
) {
play();
}
if (options.autoUnmuteWhenVisible) {
if (
options.autoUnmuteCondition &&
['all', 'visible'].includes(options.autoUnmuteCondition)
) {
unmute();
}
}
@@ -174,7 +187,7 @@ export function AutoMediaPlugin(
/**
* Mute all slides.
*/
function muteAll(): void {
function muteAll(): void {
for (const slide of slides) {
getPlayer(slide)?.mute();
}
+25 -4
View File
@@ -299,22 +299,42 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
}),
AutoMediaPlugin({
playerSelector: 'frigate-card-live-provider',
autoUnmuteWhenVisible: !!this.liveConfig?.auto_unmute,
...(this.liveConfig?.auto_play && {
autoPlayCondition: this.liveConfig.auto_play,
}),
...(this.liveConfig?.auto_pause && {
autoPauseCondition: this.liveConfig.auto_pause,
}),
...(this.liveConfig?.auto_mute && {
autoMuteCondition: this.liveConfig.auto_mute,
}),
...(this.liveConfig?.auto_unmute && {
autoUnmuteCondition: this.liveConfig.auto_unmute,
}),
}),
];
}
/**
* Unmute the media on the selected slide.
* Play the media on the loaded slide.
*/
protected _autoUnmuteHandler(): void {
if (this.liveConfig?.auto_unmute) {
protected _autoPlayHandler(): void {
if (
this.liveConfig?.auto_play &&
['all', 'selected'].includes(this.liveConfig.auto_play)
) {
super._autoPlayHandler();
}
}
/**
* Unmute the media on the loaded slide.
*/
protected _autoUnmuteHandler(): void {
if (
this.liveConfig?.auto_unmute &&
['all', 'selected'].includes(this.liveConfig.auto_unmute)
) {
super._autoUnmuteHandler();
}
}
@@ -984,6 +1004,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
// The media carousel may automatically pause when the browser tab is
// inactive, JSMPEG does not need to also do so independently.
pauseWhenHidden: false,
autoplay: false,
protocols: [],
audio: false,
videoBufferSize: 1024 * 1024 * 4,
+20 -7
View File
@@ -206,23 +206,32 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}
/**
* Play the media on the selected slide.
* Play the media on the loaded slide.
*/
protected _autoPlayHandler(): void {
if (this.viewerConfig?.auto_play) {
protected _autoPlayHandler(): void {
if (
this.viewerConfig?.auto_play &&
['all', 'selected'].includes(this.viewerConfig.auto_play)
) {
super._autoPlayHandler();
}
}
/**
* Unmute the media on the selected slide.
* Unmute the media on the loaded slide.
*/
protected _autoUnmuteHandler(): void {
if (this.viewerConfig?.auto_unmute) {
if (
this.viewerConfig?.auto_unmute &&
['all', 'selected'].includes(this.viewerConfig.auto_unmute)
) {
super._autoUnmuteHandler();
}
}
/**
* Destroy the carousel.
*/
protected _destroyCarousel(): void {
super._destroyCarousel();
@@ -280,14 +289,18 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}),
AutoMediaPlugin({
playerSelector: 'frigate-card-ha-hls-player',
autoPlayWhenVisible: !!this.viewerConfig?.auto_play,
autoUnmuteWhenVisible: !!this.viewerConfig?.auto_unmute,
...(this.viewerConfig?.auto_play && {
autoPlayCondition: this.viewerConfig.auto_play,
}),
...(this.viewerConfig?.auto_pause && {
autoPauseCondition: this.viewerConfig.auto_pause,
}),
...(this.viewerConfig?.auto_mute && {
autoMuteCondition: this.viewerConfig.auto_mute,
}),
...(this.viewerConfig?.auto_unmute && {
autoUnmuteCondition: this.viewerConfig.auto_unmute,
}),
}),
];
}
+11
View File
@@ -35,6 +35,8 @@ import {
CONF_VIEW_DEFAULT,
CONF_VIEW_TIMEOUT_SECONDS,
CONF_VIEW_UPDATE_ENTITIES,
CONF_LIVE_AUTO_UNMUTE,
CONF_EVENT_VIEWER_AUTO_UNMUTE,
} from './const';
import {
BUTTON_SIZE_MIN,
@@ -592,4 +594,13 @@ const UPGRADES = [
upgrade(CONF_LIVE_LAZY_UNLOAD, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgrade(CONF_LIVE_AUTO_UNMUTE, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgrade(CONF_EVENT_VIEWER_AUTO_PLAY, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgrade(CONF_EVENT_VIEWER_AUTO_UNMUTE, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
];
+2 -1
View File
@@ -43,8 +43,8 @@ export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE =
`${CONF_EVENT_GALLERY}.controls.thumbnails.size` as const;
export const CONF_EVENT_VIEWER = 'event_viewer' as const;
export const CONF_EVENT_VIEWER_AUTO_PAUSE = `${CONF_EVENT_VIEWER}.auto_pause` as const;
export const CONF_EVENT_VIEWER_AUTO_PLAY = `${CONF_EVENT_VIEWER}.auto_play` as const;
export const CONF_EVENT_VIEWER_AUTO_PAUSE = `${CONF_EVENT_VIEWER}.auto_pause` as const;
export const CONF_EVENT_VIEWER_AUTO_MUTE = `${CONF_EVENT_VIEWER}.auto_mute` as const;
export const CONF_EVENT_VIEWER_AUTO_UNMUTE = `${CONF_EVENT_VIEWER}.auto_unmute` as const;
export const CONF_EVENT_VIEWER_DRAGGABLE = `${CONF_EVENT_VIEWER}.draggable` as const;
@@ -69,6 +69,7 @@ export const CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
`${CONF_EVENT_VIEWER}.controls.title.duration_seconds` as const;
export const CONF_LIVE = 'live' as const;
export const CONF_LIVE_AUTO_PLAY = `${CONF_LIVE}.auto_play` as const;
export const CONF_LIVE_AUTO_PAUSE = `${CONF_LIVE}.auto_pause` as const;
export const CONF_LIVE_AUTO_MUTE = `${CONF_LIVE}.auto_mute` as const;
export const CONF_LIVE_AUTO_UNMUTE = `${CONF_LIVE}.auto_unmute` as const;
+33 -11
View File
@@ -54,6 +54,7 @@ import {
CONF_IMAGE_URL,
CONF_LIVE_AUTO_MUTE,
CONF_LIVE_AUTO_PAUSE,
CONF_LIVE_AUTO_PLAY,
CONF_LIVE_AUTO_UNMUTE,
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
@@ -353,7 +354,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'auto', label: localize('config.view.dark_modes.auto') },
];
protected _mediaActionConditions: EditorSelectOption[] = [
protected _mediaActionNegativeConditions: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'all', label: localize('config.common.media_action_conditions.all') },
{
@@ -364,6 +365,20 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'never', label: localize('config.common.media_action_conditions.never') },
];
protected _mediaActionPositiveConditions: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'all', label: localize('config.common.media_action_conditions.all') },
{
value: 'selected',
label: localize('config.common.media_action_conditions.selected'),
},
{
value: 'visible',
label: localize('config.common.media_action_conditions.visible'),
},
{ value: 'never', label: localize('config.common.media_action_conditions.never') },
];
public setConfig(config: RawFrigateCardConfig): void {
// Note: This does not use Zod to parse the configuration, so it may be
// partially or completely invalid. It's more useful to have a partially
@@ -1006,17 +1021,24 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)}
${this._renderOptionSelector(
CONF_LIVE_LAZY_UNLOAD,
this._mediaActionConditions,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_PLAY,
this._mediaActionPositiveConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_PAUSE,
this._mediaActionConditions,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_MUTE,
this._mediaActionConditions,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_UNMUTE,
this._mediaActionPositiveConditions,
)}
${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)}
${this._renderOptionSelector(
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
this._liveNextPreviousControlStyles,
@@ -1079,21 +1101,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSetHeader('event_viewer')}
${this._expandedMenus[MENU_OPTIONS] === 'event_viewer'
? html` <div class="values">
${this._renderSwitch(
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_PLAY,
defaults.event_viewer.auto_play,
this._mediaActionPositiveConditions,
)}
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_PAUSE,
this._mediaActionConditions,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_MUTE,
this._mediaActionConditions,
this._mediaActionNegativeConditions,
)}
${this._renderSwitch(
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_UNMUTE,
defaults.event_viewer.auto_unmute,
this._mediaActionPositiveConditions,
)}
${this._renderSwitch(
CONF_EVENT_VIEWER_DRAGGABLE,
+3
View File
@@ -37,7 +37,9 @@
"media_action_conditions": {
"all": "All opportunities",
"unselected": "On unselection",
"selected": "On selection",
"hidden": "On browser/tab hiding",
"visible": "On browser/tab visibility",
"never": "Never"
}
},
@@ -127,6 +129,7 @@
"draggable": "Live cameras view can be dragged/swiped",
"lazy_load": "Live cameras are lazily loaded",
"lazy_unload": "Live cameras are lazily unloaded",
"auto_play": "Automatically play live cameras",
"auto_pause": "Automatically pause live cameras",
"auto_mute": "Automatically mute live cameras",
"auto_unmute": "Automatically unmute live cameras",
+50 -16
View File
@@ -64,10 +64,24 @@ export const FRIGATE_MENU_PRIORITY_MAX = 100;
const LIVE_PROVIDERS = ['auto', 'ha', 'frigate-jsmpeg', 'webrtc-card'] as const;
export type LiveProvider = typeof LIVE_PROVIDERS[number];
const MEDIA_ACTION_CONDITIONS = ['all', 'unselected', 'hidden', 'never'] as const;
export type LazyUnloadCondition = typeof MEDIA_ACTION_CONDITIONS[number];
export type AutoMuteCondition = typeof MEDIA_ACTION_CONDITIONS[number];
export type AutoPauseCondition = typeof MEDIA_ACTION_CONDITIONS[number];
const MEDIA_ACTION_NEGATIVE_CONDITIONS = [
'all',
'unselected',
'hidden',
'never',
] as const;
export type LazyUnloadCondition = typeof MEDIA_ACTION_NEGATIVE_CONDITIONS[number];
export type AutoMuteCondition = typeof MEDIA_ACTION_NEGATIVE_CONDITIONS[number];
export type AutoPauseCondition = typeof MEDIA_ACTION_NEGATIVE_CONDITIONS[number];
const MEDIA_ACTION_POSITIVE_CONDITIONS = [
'all',
'selected',
'visible',
'never',
] as const;
export type AutoUnmuteCondition = typeof MEDIA_ACTION_POSITIVE_CONDITIONS[number];
export type AutoPlayCondition = typeof MEDIA_ACTION_POSITIVE_CONDITIONS[number];
export class FrigateCardError extends Error {}
@@ -562,9 +576,10 @@ export type TitleControlConfig = z.infer<typeof titleControlConfigSchema>;
* Live view configuration section.
*/
const liveConfigDefault = {
auto_pause: 'all' as const,
auto_play: 'all' as const,
auto_pause: 'never' as const,
auto_mute: 'all' as const,
auto_unmute: false,
auto_unmute: 'never' as const,
preload: false,
lazy_load: true,
lazy_unload: 'never' as const,
@@ -669,12 +684,23 @@ const liveOverridableConfigSchema = z
const liveConfigSchema = liveOverridableConfigSchema
.extend({
// Non-overrideable parameters.
auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_pause),
auto_mute: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_mute),
auto_unmute: z.boolean().default(liveConfigDefault.auto_unmute),
auto_play: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(liveConfigDefault.auto_play),
auto_pause: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(liveConfigDefault.auto_pause),
auto_mute: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(liveConfigDefault.auto_mute),
auto_unmute: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(liveConfigDefault.auto_unmute),
preload: z.boolean().default(liveConfigDefault.preload),
lazy_load: z.boolean().default(liveConfigDefault.lazy_load),
lazy_unload: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.lazy_unload),
lazy_unload: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(liveConfigDefault.lazy_unload),
draggable: z.boolean().default(liveConfigDefault.draggable),
transition_effect: transitionEffectConfigSchema.default(
liveConfigDefault.transition_effect,
@@ -756,10 +782,10 @@ export type MenuConfig = z.infer<typeof menuConfigSchema>;
* Event viewer configuration section (clip, snapshot).
*/
const viewerConfigDefault = {
auto_play: 'all' as const,
auto_pause: 'all' as const,
auto_play: true,
auto_mute: 'all' as const,
auto_unmute: false,
auto_unmute: 'never' as const,
lazy_load: true,
draggable: true,
transition_effect: 'slide' as const,
@@ -794,10 +820,18 @@ export type ViewerNextPreviousControlConfig = z.infer<
const viewerConfigSchema = z
.object({
auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_pause),
auto_play: z.boolean().default(viewerConfigDefault.auto_play),
auto_mute: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_mute),
auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute),
auto_play: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(viewerConfigDefault.auto_play),
auto_pause: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(viewerConfigDefault.auto_pause),
auto_mute: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(viewerConfigDefault.auto_mute),
auto_unmute: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(viewerConfigDefault.auto_unmute),
lazy_load: z.boolean().default(viewerConfigDefault.lazy_load),
draggable: z.boolean().default(viewerConfigDefault.draggable),
transition_effect: transitionEffectConfigSchema.default(