Skip to content

Commit

Permalink
fix: media_stop not supported on Google Cast devices (#1605)
Browse files Browse the repository at this point in the history
* fix: `media_stop` not supported on Google Cast devices

* Add tests for new stop functionality

---------

Co-authored-by: Dermot Duffy <[email protected]>
  • Loading branch information
felipecrs and dermotduffy authored Oct 3, 2024
1 parent 9098eb3 commit 1f5d4bd
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 21 deletions.
32 changes: 25 additions & 7 deletions src/card-controller/media-player-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { CameraConfig, FrigateCardConfig } from '../config/types';
import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA } from '../const';
import {
MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA,
MEDIA_PLAYER_SUPPORT_STOP,
MEDIA_PLAYER_SUPPORT_TURN_OFF,
} from '../const';
import { localize } from '../localize/localize';
import { errorToConsole } from '../utils/basic';
import { Entity } from '../utils/ha/registry/entity/types';
Expand Down Expand Up @@ -85,12 +89,26 @@ export class MediaPlayerManager {
}

public async stop(mediaPlayer: string): Promise<void> {
await this._api
.getHASSManager()
.getHASS()
?.callService('media_player', 'media_stop', {
entity_id: mediaPlayer,
});
const hass = this._api.getHASSManager().getHASS();
const stateObj = hass?.states[mediaPlayer];
if (!stateObj) {
return;
}

let service: string;
if (supportsFeature(stateObj, MEDIA_PLAYER_SUPPORT_STOP)) {
service = 'media_stop';
} else if (supportsFeature(stateObj, MEDIA_PLAYER_SUPPORT_TURN_OFF)) {
// Google Cast devices don't support media_stop, but turning off has the
// same effect.
service = 'turn_off';
} else {
return;
}

await hass.callService('media_player', service, {
entity_id: mediaPlayer,
});
}

public async playLive(mediaPlayer: string, cameraID: string): Promise<void> {
Expand Down
4 changes: 3 additions & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ export const CONF_PERFORMANCE_STYLE_BORDER_RADIUS = `${CONF_PERFORMANCE}.style.b

export const CONF_PROFILES = 'profiles' as const;

// Taken from https://github.dev/home-assistant/frontend/blob/b5861869e39290fd2e15737e89571dfc543b3ad3/src/data/media-player.ts#L93
// Taken from https://github.com/home-assistant/frontend/blob/a759767d794f02527d127802831e68d3caf0cb7a/src/data/media-player.ts#L82
export const MEDIA_PLAYER_SUPPORT_TURN_OFF = 256;
export const MEDIA_PLAYER_SUPPORT_STOP = 4096;
export const MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA = 131072;

// The number of media items to fetch at a time (for clips/snapshot views, and
Expand Down
93 changes: 80 additions & 13 deletions tests/card-controller/media-player-manager.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { MediaPlayerManager } from '../../src/card-controller/media-player-manager';
import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA } from '../../src/const';
import {
MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA,
MEDIA_PLAYER_SUPPORT_STOP,
MEDIA_PLAYER_SUPPORT_TURN_OFF,
} from '../../src/const';
import { ExtendedHomeAssistant } from '../../src/types';
import { EntityRegistryManager } from '../../src/utils/ha/registry/entity';
import {
Expand Down Expand Up @@ -179,21 +183,84 @@ describe('MediaPlayerManager', () => {
});
});

it('should stop', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
describe('should stop', () => {
it('should call media_stop when device supports it', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS({
'media_player.foo': createStateEntity({
entity_id: 'media_player.foo',
attributes: {
supported_features: MEDIA_PLAYER_SUPPORT_STOP,
},
}),
}),
);

const manager = new MediaPlayerManager(api);

await manager.stop('media_player.foo');

expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
'media_player',
'media_stop',
{
entity_id: 'media_player.foo',
},
);
});

it('should call turn_off when device does supports it', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS({
'media_player.foo': createStateEntity({
entity_id: 'media_player.foo',
attributes: {
supported_features: MEDIA_PLAYER_SUPPORT_TURN_OFF,
},
}),
}),
);

const manager = new MediaPlayerManager(api);

const manager = new MediaPlayerManager(api);
await manager.stop('media_player.foo');

await manager.stop('media_player.foo');
expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
'media_player',
'turn_off',
{
entity_id: 'media_player.foo',
},
);
});

expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
'media_player',
'media_stop',
{
entity_id: 'media_player.foo',
},
);
it('should do nothing without some supported feature', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS({
'media_player.foo': createStateEntity({
entity_id: 'media_player.foo',
}),
}),
);
const manager = new MediaPlayerManager(api);

await manager.stop('media_player.foo');

expect(api.getHASSManager().getHASS()?.callService).not.toBeCalled();
});

it('should do nothing without hass state', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
const manager = new MediaPlayerManager(api);

await manager.stop('media_player.foo');

expect(api.getHASSManager().getHASS()?.callService).not.toBeCalled();
});
});

describe('should play', () => {
Expand Down

0 comments on commit 1f5d4bd

Please sign in to comment.