fix: Increase reliability of media auto actions (e.g. play) (#1934)

Refactors media player handling entirely to make the code more
consistent and less boilerplate. Add testing for media player actions.

- Closes #1921
- Closes #1916
This commit is contained in:
Dermot Duffy
2025-03-03 20:59:01 -08:00
committed by GitHub
parent d4650eae8a
commit 3b77b11196
58 changed files with 1972 additions and 1165 deletions
@@ -1,15 +1,15 @@
import { expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { MuteAction } from '../../../../src/card-controller/actions/actions/mute';
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
import { MediaPlayerController } from '../../../../src/types';
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
it('should handle mute action', async () => {
const api = createCardAPI();
const player = mock<AdvancedCameraCardMediaPlayer>();
const mediaPlayerController = mock<MediaPlayerController>();
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const action = new MuteAction(
@@ -22,5 +22,5 @@ it('should handle mute action', async () => {
await action.execute(api);
expect(player.mute).toBeCalled();
expect(mediaPlayerController.mute).toBeCalled();
});
@@ -1,15 +1,15 @@
import { expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { PauseAction } from '../../../../src/card-controller/actions/actions/pause';
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
import { MediaPlayerController } from '../../../../src/types';
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
it('should handle pause action', async () => {
const api = createCardAPI();
const player = mock<AdvancedCameraCardMediaPlayer>();
const mediaPlayerController = mock<MediaPlayerController>();
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const action = new PauseAction(
@@ -22,5 +22,5 @@ it('should handle pause action', async () => {
await action.execute(api);
expect(player.pause).toBeCalled();
expect(mediaPlayerController.pause).toBeCalled();
});
@@ -1,15 +1,15 @@
import { expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { PlayAction } from '../../../../src/card-controller/actions/actions/play';
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
import { MediaPlayerController } from '../../../../src/types';
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
it('should handle play action', async () => {
const api = createCardAPI();
const player = mock<AdvancedCameraCardMediaPlayer>();
const mediaPlayerController = mock<MediaPlayerController>();
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const action = new PlayAction(
@@ -22,5 +22,5 @@ it('should handle play action', async () => {
await action.execute(api);
expect(player.play).toBeCalled();
expect(mediaPlayerController.play).toBeCalled();
});
@@ -1,15 +1,15 @@
import { expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { UnmuteAction } from '../../../../src/card-controller/actions/actions/unmute';
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
import { MediaPlayerController } from '../../../../src/types';
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
it('should handle unmute action', async () => {
const api = createCardAPI();
const player = mock<AdvancedCameraCardMediaPlayer>();
const mediaPlayerController = mock<MediaPlayerController>();
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const action = new UnmuteAction(
@@ -22,5 +22,5 @@ it('should handle unmute action', async () => {
await action.execute(api);
expect(player.unmute).toBeCalled();
expect(mediaPlayerController.unmute).toBeCalled();
});
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { DownloadManager } from '../../src/card-controller/download-manager';
import { AdvancedCameraCardMediaPlayer } from '../../src/types';
import { MediaPlayerController } from '../../src/types';
import { downloadMedia, downloadURL } from '../../src/utils/download.js';
import {
createCardAPI,
@@ -62,12 +62,12 @@ describe('DownloadManager.downloadScreenshot', () => {
it('with url', async () => {
const api = createCardAPI();
const player = mock<AdvancedCameraCardMediaPlayer>();
player.getScreenshotURL.mockResolvedValue('http://screenshot');
const mediaPlayerController = mock<MediaPlayerController>();
mediaPlayerController.getScreenshotURL.mockResolvedValue('http://screenshot');
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const manager = new DownloadManager(api);
@@ -78,12 +78,12 @@ describe('DownloadManager.downloadScreenshot', () => {
it('without url', async () => {
const api = createCardAPI();
const player = mock<AdvancedCameraCardMediaPlayer>();
player.getScreenshotURL.mockResolvedValue(null);
const mediaPlayerController = mock<MediaPlayerController>();
mediaPlayerController.getScreenshotURL.mockResolvedValue(null);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const manager = new DownloadManager(api);
@@ -2,10 +2,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit';
import { ConditionStateManager } from '../../../../src/conditions/state-manager';
import {
AdvancedCameraCardMediaPlayer,
WebkitHTMLVideoElement,
} from '../../../../src/types';
import { MediaPlayerController, WebkitHTMLVideoElement } from '../../../../src/types';
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
const createWebkitVideoElement = (): HTMLVideoElement &
@@ -13,10 +10,10 @@ const createWebkitVideoElement = (): HTMLVideoElement &
return document.createElement('video');
};
const createPlayer = (element: HTMLElement): AdvancedCameraCardMediaPlayer => {
const player = mock<AdvancedCameraCardMediaPlayer>();
player.getFullscreenElement.mockReturnValue(element);
return player;
const createMediaPlayerController = (element: HTMLElement): MediaPlayerController => {
const mediaPlayerController = mock<MediaPlayerController>();
mediaPlayerController.getFullscreenElement.mockReturnValue(element);
return mediaPlayerController;
};
// @vitest-environment jsdom
@@ -57,10 +54,10 @@ describe('WebkitFullScreenProvider', () => {
const element = createWebkitVideoElement();
element.webkitDisplayingFullscreen = fullscreen;
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
@@ -76,10 +73,10 @@ describe('WebkitFullScreenProvider', () => {
const element = createWebkitVideoElement();
element.webkitSupportsFullscreen = supported;
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
@@ -96,10 +93,10 @@ describe('WebkitFullScreenProvider', () => {
element.webkitEnterFullscreen = vi.fn();
element.webkitSupportsFullscreen = true;
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
@@ -116,10 +113,10 @@ describe('WebkitFullScreenProvider', () => {
element.webkitExitFullscreen = vi.fn();
element.webkitSupportsFullscreen = true;
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
@@ -137,10 +134,10 @@ describe('WebkitFullScreenProvider', () => {
element.webkitExitFullscreen = vi.fn();
element.webkitSupportsFullscreen = false;
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
@@ -160,10 +157,10 @@ describe('WebkitFullScreenProvider', () => {
element.webkitEnterFullscreen = vi.fn();
element.webkitExitFullscreen = vi.fn();
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
@@ -190,9 +187,11 @@ describe('WebkitFullScreenProvider', () => {
provider.connect();
const element_1 = createWebkitVideoElement();
const player_1 = mock<AdvancedCameraCardMediaPlayer>();
player_1.getFullscreenElement.mockReturnValue(element_1);
const mediaLoadedInfo_1 = createMediaLoadedInfo({ player: player_1 });
const mediaPlayerController_1 = mock<MediaPlayerController>();
mediaPlayerController_1.getFullscreenElement.mockReturnValue(element_1);
const mediaLoadedInfo_1 = createMediaLoadedInfo({
mediaPlayerController: mediaPlayerController_1,
});
stateManager.setState({ mediaLoadedInfo: mediaLoadedInfo_1 });
@@ -201,9 +200,11 @@ describe('WebkitFullScreenProvider', () => {
expect(handler).toBeCalledTimes(1);
const element_2 = createWebkitVideoElement();
const player_2 = mock<AdvancedCameraCardMediaPlayer>();
player_2.getFullscreenElement.mockReturnValue(element_2);
const mediaLoadedInfo_2 = createMediaLoadedInfo({ player: player_2 });
const mediaPlayerController_2 = mock<MediaPlayerController>();
mediaPlayerController_2.getFullscreenElement.mockReturnValue(element_2);
const mediaLoadedInfo_2 = createMediaLoadedInfo({
mediaPlayerController: mediaPlayerController_2,
});
stateManager.setState({ mediaLoadedInfo: mediaLoadedInfo_2 });
@@ -243,14 +244,14 @@ describe('WebkitFullScreenProvider', () => {
const element = createWebkitVideoElement();
element.play = vi.fn();
const player = createPlayer(element);
const mediaPlayerController = createMediaPlayerController(element);
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
createMediaLoadedInfo({
player: player,
mediaPlayerController,
}),
);
const mediaLoadedInfo = createMediaLoadedInfo({ player });
const mediaLoadedInfo = createMediaLoadedInfo({ mediaPlayerController });
stateManager.setState({ mediaLoadedInfo });