fix: Treat a un/selection of grid items as selected for auto media actions (#1623)

This is a slight behavioral change vs earlier v6 betas, but makes the media actions less surprising. This also allows for automated unmuting in a grid when a camera is selected, without having _every_ camera automatically unmuted.
This commit is contained in:
Dermot Duffy
2024-10-06 16:32:16 -07:00
committed by GitHub
parent d7fa2933da
commit 323d1ff7c4
7 changed files with 223 additions and 152 deletions
+5 -5
View File
@@ -8,12 +8,12 @@ live:
```
| Option | Default | Description |
| ------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions` | | [Actions](actions/README.md) to use for the `live` view. |
| `auto_mute` | `[unselected, hidden, microphone]` | A list of conditions in which live camera feeds are muted. `unselected` will automatically mute when a camera is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `microphone` will automatically mute after the microphone is muted as long as the camera stays selected (see the `live.microphone.mute_after_microphone_mute_seconds` to control how long after). Use an empty list (`[]`) to never automatically mute. Note that if `auto_play` is enabled, the stream may mute itself automatically in order to honor the `auto_play` setting, as some browsers will not auto play media that is unmuted -- that is to say, where necessary, the `auto_play` parameter will take priority over the `auto_mute` parameter. |
| `auto_pause` | `[]` | A list of conditions in which live camera feeds are automatically paused. `unselected` will automatically pause when a camera is unselected in the carousel and `hidden` will automatically pause when the browser/tab becomes hidden. Use an empty list (`[]`) to never automatically pause. **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 is used. |
| `auto_play` | `[selected, visible]` | A list of conditions in which live camera feeds are automatically played.`selected` will automatically play when a camera is selected in the carousel and `visible` will automatically play when the browser/tab becomes visible. Use an empty list (`[]`) to never automatically play. Some 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_unmute` | `[microphone]` | A list of conditions in which live camera feeds are unmuted. `selected` will automatically unmute when a camera is unselected in the carousel, `visible` will automatically unmute when the browser/tab becomes visible or `microphone` will automatically unmute after the microphone is unmuted. Use an empty list (`[]`) to never automatically unmute. |
| `auto_mute` | `[unselected, hidden, microphone]` | A list of conditions in which live camera feeds are muted. `unselected` will automatically mute when a camera is unselected in the carousel or grid. `hidden` will automatically mute when the camera becomes hidden (e.g. browser tab change) or `microphone` will automatically mute after the microphone is muted as long as the camera stays selected (see the `live.microphone.mute_after_microphone_mute_seconds` to control how long after). Use an empty list (`[]`) to never automatically mute. Note that if `auto_play` is enabled, the stream may mute itself automatically in order to honor the `auto_play` setting, as some browsers will not auto play media that is unmuted -- that is to say, where necessary, the `auto_play` parameter will take priority over the `auto_mute` parameter. |
| `auto_pause` | `[]` | A list of conditions in which live camera feeds are automatically paused. `unselected` will automatically pause when a camera is unselected in the carousel or grid. `hidden` will automatically pause when the browser/tab becomes hidden. Use an empty list (`[]`) to never automatically pause. **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 is used. |
| `auto_play` | `[selected, visible]` | A list of conditions in which live camera feeds are automatically played. `selected` will automatically play when a camera is selected in a carousel or grid. `visible` will automatically play when a camera becomes visible (e.g. browser tab change, or visible in a grid but not selected). Use an empty list (`[]`) to never automatically play. Some 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 parameter on play-after-pause. |
| `auto_unmute` | `[microphone]` | A list of conditions in which live camera feeds are unmuted. `selected` will automatically unmute when a camera is selected in a carousel or grid. `visible` will automatically unmute when a camera becomes visible (e.g. a browser/tab change, or visible in a grid but not selected). `microphone` will automatically unmute after the microphone is unmuted. Use an empty list (`[]`) to never automatically unmute. 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. |
| `controls` | | Configuration for the `live` view controls. See below. |
| `display` | | Controls whether to show a single or grid `live` view. See below. |
| `draggable` | `true` | Whether or not the live carousel can be dragged left or right, via touch/swipe and mouse dragging. |
+5 -5
View File
@@ -8,12 +8,12 @@ media_viewer:
```
| Option | Default | Description |
| --------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions` | | [Actions](actions/README.md) to use for all views that use the `media_viewer` (e.g. `clip`, `snapshot`). |
| `auto_mute` | `[unselected, hidden]` | A list of conditions in which media items are muted. `unselected` will automatically mute when a media item is unselected in the carousel and `hidden` will automatically mute when the browser/tab becomes hidden. Use an empty list (`[]`) to never automatically mute. |
| `auto_pause` | `[unselected, hidden]` | A list of conditions in which media items are automatically paused. `unselected` will automatically pause when a media item is unselected in the carousel and `hidden` will automatically pause when the browser/tab becomes hidden. Use an empty list (`[]`) to never automatically pause. |
| `auto_play` | `[selected, visible]` | A list of conditions in which media items are automatically played.`selected` will automatically play when a media item is selected in the carousel and `visible` will automatically play when the browser/tab becomes visible. Use an empty list (`[]`) to never automatically play. |
| `auto_unmute` | `[]` | A list of conditions in which media items are unmuted. `selected` will automatically unmute when a media item is unselected in the carousel and `visible` will automatically unmute when the browser/tab becomes visible. Use an empty list (`[]`) to never automatically unmute. 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_mute` | `[unselected, hidden]` | A list of conditions in which media items are muted. `unselected` will automatically mute when a media item is unselected in the carousel or grid. `hidden` will automatically mute when the browser/tab becomes hidden. Use an empty list (`[]`) to never automatically mute. |
| `auto_pause` | `[unselected, hidden]` | A list of conditions in which media items are automatically paused. `unselected` will automatically pause when a media item is unselected in the carousel or grid. `hidden` will automatically pause when the browser/tab becomes hidden. Use an empty list (`[]`) to never automatically pause. |
| `auto_play` | `[selected, visible]` | A list of conditions in which media items are automatically played. `selected` will automatically play when a media item is selected in a carousel or grid. `visible` will automatically play when a media item becomes visible (e.g. browser tab change, or visible in a grid but not selected). Use an empty list (`[]`) to never automatically play. |
| `auto_unmute` | `[]` | A list of conditions in which media items are unmuted. `selected` will automatically unmute when a media item is selected in a carousel or grid. `visible` will automatically unmute when a media item becomes visible (e.g. a browser/tab change, or visible in a grid but not selected). Use an empty list (`[]`) to never automatically unmute. 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. |
| `controls` | | Configuration for the Media viewer controls. See below. |
| `draggable` | `true` | Whether or not the Media viewer carousel can be dragged left or right, via touch/swipe and mouse dragging. |
| `lazy_load` | `true` | Whether or not to lazily load media in the Media 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. |
+48 -42
View File
@@ -34,6 +34,11 @@ type PlayerElement = HTMLElement & FrigateCardMediaPlayer;
* start the video.
*/
type MediaActionsTarget = {
selected: boolean;
index: number;
};
export class MediaActionsController {
protected _options: MediaActionsControllerOptions | null = null;
protected _viewportIntersecting: boolean | null = null;
@@ -42,7 +47,7 @@ export class MediaActionsController {
protected _eventListeners = new Map<HTMLElement, () => void>();
protected _children: PlayerElement[] = [];
protected _selected: number | null = null;
protected _target: MediaActionsTarget | null = null;
protected _mutationObserver = new MutationObserver(this._mutationHandler.bind(this));
protected _intersectionObserver = new IntersectionObserver(
this._intersectionHandler.bind(this),
@@ -67,59 +72,62 @@ export class MediaActionsController {
this._root = null;
this._removeChildHandlers();
this._children = [];
this._selected = null;
this._target = null;
this._mutationObserver.disconnect();
this._intersectionObserver.disconnect();
this._options?.microphoneManager?.removeListener(this._microphoneChangeHandler);
document.removeEventListener('visibilitychange', this._visibilityHandler);
}
public async select(index: number): Promise<void> {
if (this._selected === index) {
public async setTarget(index: number, selected: boolean): Promise<void> {
if (this._target?.index === index && this._target?.selected === selected) {
return;
}
if (this._selected !== null) {
await this.unselect();
}
this._selected = index;
await this._unmuteSelectedIfConfigured('selected');
await this._playSelectedIfConfigured('selected');
}
public async unselect(): Promise<void> {
await this._pauseSelectedIfConfigured('unselected');
await this._muteSelectedIfConfigured('unselected');
// If there's already a selected target, unselect it.
if (!!this._target?.selected) {
await this._pauseTargetIfConfigured('unselected');
await this._muteTargetIfConfigured('unselected');
this._microphoneMuteTimer.stop();
this._selected = null;
}
public async unselectAll(): Promise<void> {
this._selected = null;
await this._pauseAllIfConfigured('unselected');
await this._muteAllIfConfigured('unselected');
this._target = {
selected,
index,
};
if (selected) {
await this._unmuteTargetIfConfigured('selected');
await this._playTargetIfConfigured('selected');
} else {
await this._unmuteTargetIfConfigured('visible');
await this._playTargetIfConfigured('visible');
}
}
protected async _playSelectedIfConfigured(
condition: AutoPlayCondition,
): Promise<void> {
public unsetTarget(): void {
this._target = null;
}
protected async _playTargetIfConfigured(condition: AutoPlayCondition): Promise<void> {
if (
this._selected !== null &&
this._target !== null &&
this._options?.autoPlayConditions?.includes(condition)
) {
await this._play(this._selected);
await this._play(this._target.index);
}
}
protected async _play(index: number): Promise<void> {
await this._children[index]?.play();
}
protected async _unmuteSelectedIfConfigured(
protected async _unmuteTargetIfConfigured(
condition: AutoUnmuteCondition,
): Promise<void> {
if (
this._selected !== null &&
this._target !== null &&
this._options?.autoUnmuteConditions?.includes(condition)
) {
await this._unmute(this._selected);
await this._unmute(this._target.index);
}
}
protected async _unmute(index: number): Promise<void> {
@@ -133,14 +141,14 @@ export class MediaActionsController {
}
}
}
protected async _pauseSelectedIfConfigured(
protected async _pauseTargetIfConfigured(
condition: AutoPauseCondition,
): Promise<void> {
if (
this._selected !== null &&
this._target !== null &&
this._options?.autoPauseConditions?.includes(condition)
) {
await this._pause(this._selected);
await this._pause(this._target.index);
}
}
protected async _pause(index: number): Promise<void> {
@@ -154,14 +162,12 @@ export class MediaActionsController {
}
}
}
protected async _muteSelectedIfConfigured(
condition: AutoMuteCondition,
): Promise<void> {
protected async _muteTargetIfConfigured(condition: AutoMuteCondition): Promise<void> {
if (
this._selected !== null &&
this._target !== null &&
this._options?.autoMuteConditions?.includes(condition)
) {
await this._mute(this._selected);
await this._mute(this._target.index);
}
}
protected async _mute(index: number): Promise<void> {
@@ -178,11 +184,11 @@ export class MediaActionsController {
}
protected _mediaLoadedHandler = async (index: number): Promise<void> => {
if (this._selected !== index) {
if (this._target?.index !== index) {
return;
}
await this._unmuteSelectedIfConfigured('selected');
await this._playSelectedIfConfigured('selected');
await this._unmuteTargetIfConfigured(this._target.selected ? 'selected' : 'visible');
await this._playTargetIfConfigured(this._target.selected ? 'selected' : 'visible');
};
protected _removeChildHandlers(): void {
@@ -242,8 +248,8 @@ export class MediaActionsController {
protected _changeVisibility = async (visible: boolean): Promise<void> => {
if (visible) {
await this._unmuteSelectedIfConfigured('visible');
await this._playSelectedIfConfigured('visible');
await this._unmuteTargetIfConfigured('visible');
await this._playTargetIfConfigured('visible');
} else {
await this._pauseAllIfConfigured('hidden');
await this._muteAllIfConfigured('hidden');
@@ -253,7 +259,7 @@ export class MediaActionsController {
change: MicrophoneManagerListenerChange,
): Promise<void> => {
if (change === 'unmuted') {
await this._unmuteSelectedIfConfigured('microphone');
await this._unmuteTargetIfConfigured('microphone');
} else if (
change === 'muted' &&
this._options?.autoMuteConditions?.includes('microphone')
@@ -261,7 +267,7 @@ export class MediaActionsController {
this._microphoneMuteTimer.start(
this._options.microphoneMuteSeconds ?? 60,
async () => {
await this._muteSelectedIfConfigured('microphone');
await this._muteTargetIfConfigured('microphone');
},
);
}
+19 -9
View File
@@ -325,13 +325,17 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _getSelectedCameraIndex(): number {
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
const view = this.viewManagerEpoch?.manager.getView();
if (!cameraIDs?.size || !view || this.viewFilterCameraID) {
if (this.viewFilterCameraID) {
// If the carousel is limited to a single cameraID, the first (only)
// element is always the selected one.
return 0;
}
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
const view = this.viewManagerEpoch?.manager.getView();
if (!cameraIDs?.size || !view) {
return 0;
}
return Math.max(0, Array.from(cameraIDs).indexOf(view.camera));
}
@@ -364,13 +368,19 @@ export class FrigateCardLiveCarousel extends LitElement {
}
if (changedProps.has('viewManagerEpoch')) {
if (
this.viewFilterCameraID &&
this.viewManagerEpoch?.manager.getView()?.camera !== this.viewFilterCameraID
) {
this._mediaActionsController.unselectAll();
const view = this.viewManagerEpoch?.manager.getView();
const selectedCameraIndex = this._getSelectedCameraIndex();
if (this.viewFilterCameraID) {
this._mediaActionsController.setTarget(
selectedCameraIndex,
// Camera in this carousel is only selected if the camera from the
// view matches the filtered camera.
view?.camera === this.viewFilterCameraID,
);
} else {
this._mediaActionsController.select(this._getSelectedCameraIndex());
// Carousel is not filtered, so the targeted camera is always selected.
this._mediaActionsController.setTarget(selectedCameraIndex, true);
}
}
}
+14 -3
View File
@@ -386,10 +386,21 @@ export class FrigateCardViewerCarousel extends LitElement {
this._selected = newSelected;
}
if (!newMedia) {
this._mediaActionsController.unselectAll();
if (!newMedia?.length) {
// No media will be rendered.
this._mediaActionsController.unsetTarget();
} else {
this._mediaActionsController.select(newSelected);
if (this.viewFilterCameraID) {
this._mediaActionsController.setTarget(
newSelected,
// Camera in this carousel is only selected if the camera from the
// view matches the filtered camera.
view?.camera === this.viewFilterCameraID,
);
} else {
// Carousel is not filtered, so the targeted camera is always selected.
this._mediaActionsController.setTarget(newSelected, true);
}
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { z } from 'zod';
export const deviceSchema = z.object({
const deviceSchema = z.object({
id: z.string(),
model: z.string().nullable(),
config_entries: z.string().array(),
@@ -90,7 +90,7 @@ describe('MediaActionsController', () => {
const parent = createParent({ children: children });
controller.initialize(parent);
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).not.toBeCalled();
});
@@ -112,7 +112,7 @@ describe('MediaActionsController', () => {
await callMutationHandler();
await controller.select(1);
await controller.setTarget(1, true);
expect(newPlayer.play).toBeCalled();
});
@@ -132,13 +132,13 @@ describe('MediaActionsController', () => {
controller.destroy();
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).not.toBeCalled();
});
});
describe('should respond to select', () => {
describe('should respond to setting target', () => {
it.each([
['should play', { autoPlayConditions: ['selected' as const] }, 'play', true],
['should not play', { autoPlayConditions: [] }, 'play', false],
@@ -161,13 +161,13 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
},
);
it('should not re-select', async () => {
it('should not reselect previously selected target', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPlayConditions: ['selected' as const],
@@ -177,14 +177,14 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
});
it('should unselect first', async () => {
it('should unselect before selecting a new target', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPauseConditions: ['unselected' as const],
@@ -195,16 +195,66 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.select(1);
await controller.setTarget(0, true);
await controller.setTarget(1, true);
expect(getPlayer(children[0], 'video')?.pause).toBeCalled();
expect(getPlayer(children[0], 'video')?.mute).toBeCalled();
});
it('should select after target was previously visible', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPlayConditions: ['selected' as const],
autoUnmuteConditions: ['selected' as const],
playerSelector: 'video',
});
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.setTarget(0, false);
expect(getPlayer(children[0], 'video')?.play).not.toBeCalled();
expect(getPlayer(children[0], 'video')?.unmute).not.toBeCalled();
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).toBeCalled();
expect(getPlayer(children[0], 'video')?.unmute).toBeCalled();
});
});
it('should take no action after target unset', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPlayConditions: ['selected' as const, 'visible' as const],
autoUnmuteConditions: ['selected' as const, 'visible' as const],
playerSelector: 'video',
});
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
controller.unsetTarget();
getPlayer(children[0], 'video')?.dispatchEvent(
new Event('frigate-card:media:loaded'),
);
await flushPromises();
// Play/Mute will not have been called again.
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
});
describe('should respond to media loaded', () => {
it('should play', async () => {
it('should play after media load', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPlayConditions: ['selected' as const],
@@ -214,7 +264,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
getPlayer(children[0], 'video')?.dispatchEvent(
@@ -226,7 +276,7 @@ describe('MediaActionsController', () => {
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(2);
});
it('should unmute', async () => {
it('should unmute after media load', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoUnmuteConditions: ['selected' as const],
@@ -236,7 +286,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
getPlayer(children[0], 'video')?.dispatchEvent(
@@ -248,18 +298,18 @@ describe('MediaActionsController', () => {
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(2);
});
it('should take no action on unselected media load', async () => {
it('should take no action on unrelated media load', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPlayConditions: ['selected' as const],
autoUnmuteConditions: ['selected' as const],
autoPlayConditions: ['selected' as const, 'visible' as const],
autoUnmuteConditions: ['selected' as const, 'visible' as const],
playerSelector: 'video',
});
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
getPlayer(children[9], 'video')?.dispatchEvent(
new Event('frigate-card:media:loaded'),
@@ -270,9 +320,35 @@ describe('MediaActionsController', () => {
expect(getPlayer(children[9], 'video')?.play).not.toBeCalled();
expect(getPlayer(children[9], 'video')?.unmute).not.toBeCalled();
});
it('should play and unmute on unselected but targeted media load', async () => {
const controller = new MediaActionsController();
controller.setOptions({
autoPlayConditions: ['visible' as const],
autoUnmuteConditions: ['visible' as const],
playerSelector: 'video',
});
describe('should respond to unselect', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.setTarget(0, false);
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
getPlayer(children[0], 'video')?.dispatchEvent(
new Event('frigate-card:media:loaded'),
);
await flushPromises();
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(2);
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(2);
});
});
describe('should take action on unselect', () => {
it.each([
['should pause', { autoPauseConditions: ['unselected' as const] }, 'pause', true],
['should not pause', { autoPauseConditions: [] }, 'pause', false],
@@ -295,47 +371,15 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.unselect();
await controller.setTarget(0, true);
await controller.setTarget(0, false);
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
},
);
});
describe('should respond to unselect all', () => {
it.each([
['should pause', { autoPauseConditions: ['unselected' as const] }, 'pause', true],
['should not pause', { autoPauseConditions: [] }, 'pause', false],
['should mute', { autoMuteConditions: ['unselected' as const] }, 'mute', true],
['should not mute', { autoMuteConditions: [] }, 'mute', false],
])(
'%s',
async (
_: string,
options: Partial<MediaActionsControllerOptions>,
func: string,
called: boolean,
) => {
const controller = new MediaActionsController();
controller.setOptions({
playerSelector: 'video',
...options,
});
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.unselectAll();
children.forEach((child) => {
expect(getPlayer(child, 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
});
},
);
});
describe('should respond to page being visible', () => {
describe('should take action on page being visible', () => {
it.each([
['should play', { autoPlayConditions: ['visible' as const] }, 'play', true],
['should not play', { autoPlayConditions: [] }, 'play', false],
@@ -359,7 +403,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
// Not configured to take action on selection.
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
@@ -376,7 +420,7 @@ describe('MediaActionsController', () => {
);
});
describe('should respond to page being hiddne', () => {
describe('should take action on page being hiddne', () => {
beforeAll(() => {
vi.spyOn(global.document, 'addEventListener');
});
@@ -402,7 +446,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
// Not configured to take action on selection.
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
@@ -419,7 +463,7 @@ describe('MediaActionsController', () => {
);
});
describe('should respond to page intersecting with viewport', () => {
describe('should take action on page intersecting with viewport', () => {
it.each([
['should play', { autoPlayConditions: ['visible' as const] }, 'play', true],
['should not play', { autoPlayConditions: [] }, 'play', false],
@@ -441,7 +485,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
// Not configured to take action on selection.
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
@@ -458,7 +502,7 @@ describe('MediaActionsController', () => {
);
});
describe('should respond to page not intersecting with viewport', () => {
describe('should take action on page not intersecting with viewport', () => {
it.each([
['should play', { autoPlayConditions: ['visible' as const] }, 'play', true],
['should not play', { autoPlayConditions: [] }, 'play', false],
@@ -480,7 +524,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
// Not configured to take action on selection.
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
@@ -497,7 +541,7 @@ describe('MediaActionsController', () => {
);
});
describe('should respond to microphone changes', () => {
describe('should take action on microphone changes', () => {
beforeAll(() => {
vi.useFakeTimers();
});
@@ -519,7 +563,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
callMicrophoneListener(microphoneManager, 'unmuted');
@@ -539,7 +583,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
callMicrophoneListener(microphoneManager, 'muted');
@@ -561,7 +605,7 @@ describe('MediaActionsController', () => {
const children = createPlayerSlideNodes();
controller.initialize(createParent({ children: children }));
await controller.select(0);
await controller.setTarget(0, true);
callMicrophoneListener(microphoneManager, 'muted');