diff --git a/README.md b/README.md index ae571ec..5e19496 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Custom Home Assistant card which integrates plex into Home Assistant and makes it possible to launch movies or tv shows on TV with a simple click. -Supported are **ALL** Plex clients, some even with enhanced functionality. Kodi with PlexKodiConnect is also supported. +Supported are **ALL** Plex clients, some even with enhanced functionality. Kodi with PlexKodiConnect and Google Cast is also supported. Video of the card: @@ -53,6 +53,7 @@ _Available special libraries:_ - **androidtv**: Entity id of your media_player configured via [Android TV](https://www.home-assistant.io/integrations/androidtv/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#android-tv-or-fire-tv). - **kodi**: Entity id of your media_player configured via [Kodi](https://www.home-assistant.io/integrations/kodi/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#kodi). - **plexPlayer**: Name or machine ID of your plex client. Use this if you do not have devices above. See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#all-other-plex-clients). +- **cast**: Entity id of your media_player configured via [Google Cast](https://www.home-assistant.io/integrations/cast/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#google-cast). Example of card configuration: @@ -69,6 +70,7 @@ entity: kodi: media_player.kodi_123456qwe789rty androidtv: media_player.living_room_nvidia_shield plexPlayer: 192.168.13.38 + cast: media_player.bedroom_tv ``` Example using lists: @@ -93,11 +95,14 @@ entity: plexPlayer: - TV 2020 - 192.168.13.50 + cast: + - media_player.bedroom_tv ``` In this example, it will try to first play via kodi, in bedroom. If that kodi is unavailable or off, it tries in living room kodi. If that fails, it moves on to android tvs, starting with living room, continuing with bedroom and ending with kitchen. -Finally, if a possible player still has not been found (all kodis and shields are off) it tries to play via plexPlayer, trying TV 2020 and if not found, IP 192.168.13.50. +Next, if a possible player still has not been found (all kodis and shields are off) it tries to play via plexPlayer, trying TV 2020 and if not found, IP 192.168.13.50. +Finally, it tries to cast into media_player.bedroom_tv. ## Detailed configuration instructions for end devices @@ -175,6 +180,30 @@ Play button is only visible if all the conditions inside Availability section of ✅ Episodes +### Google Cast + +**Difficulty to setup**: Very easy + +**Steps**: + +- Set up [Google Cast](https://www.home-assistant.io/integrations/cast/) in Home Assistant. +- Use entity_id of media_player provided by Google Cast integration in card, example: `cast: media_player.bedroom_tv`. +- Save card configuration and make sure the entity is not `unavailable`, if you see play buttons on movies or individual episodes configuration was successful. + +**Availability**: + +- Media player entity cannot be `unavailable` + +**Supported play types**: + +✅ Movies + +❌ Show + +❌ Season + +✅ Episodes + ### All other plex clients **Difficulty to setup**: Very Easy to Moderate diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index a81f44c..a04448d 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -17206,7 +17206,8 @@ const CSS_STYLE = { const supported = { kodi: ['movie', 'episode'], androidtv: ['movie', 'show', 'season', 'episode', 'clip'], - plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'] + plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'], + cast: ['movie', 'episode'] }; var bind = function bind(fn, thisArg) { @@ -18901,6 +18902,9 @@ class PlayController { case 'plexPlayer': await this.playViaPlexPlayer(entity.value, data.key.split('/')[3]); break; + case 'cast': + this.playViaCast(entity.value, data.Media[0].Part[0].key); + break; default: throw Error(`No service available to play ${data.title}!`); } @@ -18990,6 +18994,16 @@ class PlayController { throw Error(`Plex type ${type} is not supported in Kodi.`); } }; + this.playViaCast = (entityName, mediaLink) => { + this.hass.callService('media_player', 'play_media', { + // eslint-disable-next-line @typescript-eslint/camelcase + entity_id: entityName, + // eslint-disable-next-line @typescript-eslint/camelcase + media_content_type: 'video', + // eslint-disable-next-line @typescript-eslint/camelcase + media_content_id: this.plex.authorizeURL(`${this.plex.getBasicURL()}${mediaLink}`) + }); + }; this.playViaAndroidTV = async (entityName, mediaID, instantPlay = false) => { const serverID = await this.plex.getServerID(); let command = `am start`; @@ -19018,7 +19032,8 @@ class PlayController { if (lodash.includes(this.supported[key], data.type)) { if ((key === 'kodi' && this.isKodiSupported(entity)) || (key === 'androidtv' && this.isAndroidTVSupported(entity)) || - (key === 'plexPlayer' && this.isPlexPlayerSupported(entity))) { + (key === 'plexPlayer' && this.isPlexPlayerSupported(entity)) || + (key === 'cast' && this.isCastSupported(entity))) { service = { key, value: entity }; return false; } @@ -19028,7 +19043,8 @@ class PlayController { else if (lodash.includes(this.supported[key], data.type)) { if ((key === 'kodi' && this.isKodiSupported(entityVal)) || (key === 'androidtv' && this.isAndroidTVSupported(entityVal)) || - (key === 'plexPlayer' && this.isPlexPlayerSupported(entityVal))) { + (key === 'plexPlayer' && this.isPlexPlayerSupported(entityVal)) || + (key === 'cast' && this.isCastSupported(entityVal))) { service = { key, value: entityVal }; return false; } @@ -19071,6 +19087,11 @@ class PlayController { } return false; }; + this.isCastSupported = (entityName) => { + return (this.hass.states[entityName] && + !lodash.isNil(this.hass.states[entityName].attributes) && + this.hass.states[entityName].state !== 'unavailable'); + }; this.isAndroidTVSupported = (entityName) => { return (this.hass.states[entityName] && !lodash.isEqual(this.hass.states[entityName].state, 'off') && diff --git a/src/const.ts b/src/const.ts index 8d45489..0b389a5 100644 --- a/src/const.ts +++ b/src/const.ts @@ -11,7 +11,8 @@ const CSS_STYLE = { const supported: any = { kodi: ['movie', 'episode'], androidtv: ['movie', 'show', 'season', 'episode', 'clip'], - plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'] + plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'], + cast: ['movie', 'episode'] }; const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec semper risus vitae aliquet interdum. Nulla facilisi. Pellentesque viverra sagittis lorem eget aliquet. Cras vehicula, purus vel consectetur mattis, ipsum arcu ullamcorper mi, id viverra purus ex eu dolor. Integer vehicula lacinia sem convallis iaculis. Nulla fermentum erat interdum, efficitur felis in, mollis neque. Vivamus luctus metus eget nisl pellentesque, placerat elementum magna eleifend. diff --git a/src/modules/PlayController.ts b/src/modules/PlayController.ts index e3ecde6..c1fa847 100644 --- a/src/modules/PlayController.ts +++ b/src/modules/PlayController.ts @@ -97,6 +97,9 @@ class PlayController { case 'plexPlayer': await this.playViaPlexPlayer(entity.value, data.key.split('/')[3]); break; + case 'cast': + this.playViaCast(entity.value, data.Media[0].Part[0].key); + break; default: throw Error(`No service available to play ${data.title}!`); } @@ -197,6 +200,17 @@ class PlayController { } }; + private playViaCast = (entityName: string, mediaLink: string): void => { + this.hass.callService('media_player', 'play_media', { + // eslint-disable-next-line @typescript-eslint/camelcase + entity_id: entityName, + // eslint-disable-next-line @typescript-eslint/camelcase + media_content_type: 'video', + // eslint-disable-next-line @typescript-eslint/camelcase + media_content_id: this.plex.authorizeURL(`${this.plex.getBasicURL()}${mediaLink}`) + }); + }; + private playViaAndroidTV = async (entityName: string, mediaID: number, instantPlay = false): Promise => { const serverID = await this.plex.getServerID(); let command = `am start`; @@ -230,7 +244,8 @@ class PlayController { if ( (key === 'kodi' && this.isKodiSupported(entity)) || (key === 'androidtv' && this.isAndroidTVSupported(entity)) || - (key === 'plexPlayer' && this.isPlexPlayerSupported(entity)) + (key === 'plexPlayer' && this.isPlexPlayerSupported(entity)) || + (key === 'cast' && this.isCastSupported(entity)) ) { service = { key, value: entity }; return false; @@ -241,7 +256,8 @@ class PlayController { if ( (key === 'kodi' && this.isKodiSupported(entityVal)) || (key === 'androidtv' && this.isAndroidTVSupported(entityVal)) || - (key === 'plexPlayer' && this.isPlexPlayerSupported(entityVal)) + (key === 'plexPlayer' && this.isPlexPlayerSupported(entityVal)) || + (key === 'cast' && this.isCastSupported(entityVal)) ) { service = { key, value: entityVal }; return false; @@ -294,6 +310,14 @@ class PlayController { return false; }; + private isCastSupported = (entityName: string): boolean => { + return ( + this.hass.states[entityName] && + !_.isNil(this.hass.states[entityName].attributes) && + this.hass.states[entityName].state !== 'unavailable' + ); + }; + private isAndroidTVSupported = (entityName: string): boolean => { return ( this.hass.states[entityName] &&