From 621b971b9179de80a3f17cf4f48c0c99e9765006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Sun, 16 Jan 2022 09:51:32 +0100 Subject: [PATCH] Add: Sonos support --- DETAILED_CONFIGURATION.md | 43 +++++++++++++++++++++++++++++++- README.md | 2 +- dist/plex-meets-homeassistant.js | 26 ++++++++++++++++--- src/const.ts | 3 ++- src/editor.ts | 3 ++- src/modules/PlayController.ts | 13 +++++++++- src/plex-meets-homeassistant.ts | 10 +++++++- 7 files changed, 90 insertions(+), 10 deletions(-) diff --git a/DETAILED_CONFIGURATION.md b/DETAILED_CONFIGURATION.md index e339e56..cc65cf0 100644 --- a/DETAILED_CONFIGURATION.md +++ b/DETAILED_CONFIGURATION.md @@ -26,6 +26,7 @@ You can also use Live TV library by specifying its name, usually "Live TV & DVR" - **plexPlayer**: Name or machine ID of your plex client. Use this if you do not have devices above. See [detailed instructions](#all-other-plex-clients). It is required to use detailed declaration with "plexPlayer:" property. - **cast**: Entity id of your media_player configured via [Google Cast](https://www.home-assistant.io/integrations/cast/). See [detailed instructions](#google-cast). It is also possible to use short declaration with cast. - **vlcTelnet**: Entity id of your media_player configured via [VLC media player Telnet](https://www.home-assistant.io/integrations/vlc_telnet/). See [detailed instructions](#vlc-media-player-telnet). It is also possible to use short declaration with vlcTelnet. +- **sonos**: Entity id of your media_player configured via [Sonos](https://www.home-assistant.io/integrations/sonos/). See [detailed instructions](#sonos). It is also possible to use short declaration with sonos. - **input_select**: Entity id of input select you wish to use for selecting media player to play on. State of this entity needs to be entity ID of media player of `androidtv`, `kodi` or `cast`. You can also use this with `plexPlayer`, in that case, provide name or machine ID of your plex client. You can also provide the same string as displayed in entities selection in UI editor for the card (beginning with `plexPlayer |`). - **input_text**: Entity id of input text you wish to use for selecting media player to play on. State of this entity needs to be entity ID of media player of `androidtv`, `kodi` or `cast`. You can also use this with `plexPlayer`, in that case, provide name or machine ID of your plex client. You can also provide the same string as displayed in entities selection in UI editor for the card (beginning with `plexPlayer |`). @@ -98,6 +99,7 @@ entity: - media_player.bedroom_tv # created by cast integration - media_player.kodi_123456qwe789rty # created by kodi integration - media_player.vlc_telnet # created by VLC Telnet integration + - media_player.sonos # created by Sonos integration ``` Example of card configuration using detailed definitions: @@ -119,6 +121,7 @@ entity: plexPlayer: 192.168.13.38 cast: media_player.bedroom_tv vlcTelnet: media_player.vlc_telnet + sonos: media_player.sonos ``` Complex example using detailed definitions, lists and shared plex server for plexPlayer: @@ -141,6 +144,8 @@ playTrailer: muted entity: vlcTelnet: - media_player.vlc_telnet + sonos: + - media_player.sonos kodi: - media_player.kodi_bedroom - media_player.kodi_living_room @@ -298,7 +303,7 @@ Play button is only visible if all the conditions inside Availability section of ### VLC media player Telnet -**Difficulty to setup**: Very easy +**Difficulty to setup**: Moderate **Steps**: @@ -330,6 +335,42 @@ Play button is only visible if all the conditions inside Availability section of ❌ Live TV +### Sonos + +**Difficulty to setup**: Easy + +**Steps**: + +- Have Plex Pass +- Set up [Sonos](https://www.home-assistant.io/integrations/sonos/) in Home Assistant. +- Set up [Plex](https://www.home-assistant.io/integrations/plex/) in Home Assistant. +- Use entity_id of media_player provided by Sonos integration in card, example: `cast: media_player.sonos`. +- Save card configuration and make sure the entity is not `unavailable`, if you see play buttons on music configuration was successful. + +**Availability**: + +- Media player entity cannot be `unavailable` + +**Supported**: + +✅ Shared Plex servers + +? Movies + +? Show + +? Season + +? Episodes + +✅ Artists + +✅ Albums + +✅ Tracks + +? Live TV + ### All other plex clients **Difficulty to setup**: Very Easy to Moderate diff --git a/README.md b/README.md index ec24f4d..1fbe0fb 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, Android TV, VLC via Telnet and Google Cast is also supported. +Supported are **ALL** Plex clients, some even with enhanced functionality. Kodi with PlexKodiConnect, Android TV, VLC via Telnet, Sonos and Google Cast is also supported. Video of the card: diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 72281d6..c7dfe36 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -17208,7 +17208,8 @@ const supported = { androidtv: ['movie', 'show', 'season', 'episode', 'clip', 'track', 'artist', 'album'], plexPlayer: ['movie', 'show', 'season', 'episode', 'clip', 'track', 'artist', 'album'], cast: ['movie', 'episode', 'artist', 'album', 'track'], - vlcTelnet: ['track'] + vlcTelnet: ['track'], + sonos: ['movie', 'show', 'season', 'episode', 'clip', 'track', 'artist', 'album'] // Experimental }; var bind = function bind(fn, thisArg) { @@ -19526,6 +19527,7 @@ class PlayController { await this.playViaPlexPlayer(entity.value, processData.key.split('/')[3]); break; case 'cast': + case 'sonos': if (lodash.isEqual(data.type, 'epg')) { const session = `PlexMeetsHomeAssistant-${Math.floor(Date.now() / 1000)}`; const streamURL = await this.plex.tune(data.channelIdentifier, session); @@ -19996,7 +19998,8 @@ class PlayController { (entity.key === 'androidtv' && this.isAndroidTVSupported(entity.value)) || (entity.key === 'plexPlayer' && this.isPlexPlayerSupported(entity.value)) || (entity.key === 'cast' && this.isCastSupported(entity.value)) || - (entity.key === 'vlcTelnet' && this.isVLCTelnetSupported(entity.value))) { + (entity.key === 'vlcTelnet' && this.isVLCTelnetSupported(entity.value)) || + (entity.key === 'sonos' && this.isSonosSupported(entity.value))) { service = { key: entity.key, value: entity.value }; return false; } @@ -20158,6 +20161,12 @@ class PlayController { this.entityStates[entityName].state !== 'unavailable') || !lodash.isEqual(this.runBefore, false)); }; + this.isSonosSupported = (entityName) => { + return ((this.entityStates[entityName] && + !lodash.isNil(this.entityStates[entityName].attributes) && + this.entityStates[entityName].state !== 'unavailable') || + !lodash.isEqual(this.runBefore, false)); + }; this.isCastSupported = (entityName) => { return ((this.entityStates[entityName] && !lodash.isNil(this.entityStates[entityName].attributes) && @@ -20429,7 +20438,8 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { lodash.isEqual(entityRegistry.platform, 'androidtv') || lodash.isEqual(entityRegistry.platform, 'input_select') || lodash.isEqual(entityRegistry.platform, 'input_text') || - lodash.isEqual(entityRegistry.platform, 'vlc_telnet')) { + lodash.isEqual(entityRegistry.platform, 'vlc_telnet') || + lodash.isEqual(entityRegistry.platform, 'sonos')) { const entityName = `${entityRegistry.platform} | ${entityRegistry.entity_id}`; entities.appendChild(addDropdownItem(entityName)); addedEntityStrings.push(entityName); @@ -22079,7 +22089,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { lodash.startsWith(entityString, 'cast | ') || lodash.startsWith(entityString, 'input_select | ') || lodash.startsWith(entityString, 'input_text | ') || - lodash.startsWith(entityString, 'vlc_telnet | ')) { + lodash.startsWith(entityString, 'vlc_telnet | ') || + lodash.startsWith(entityString, 'sonos | ')) { // eslint-disable-next-line prefer-destructuring realEntityString = entityString.split(' | ')[1]; isPlexPlayer = false; @@ -22137,6 +22148,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { } entityObj.vlcTelnet.push(entityInRegister.entity_id); break; + case 'sonos': + if (lodash.isNil(entityObj.sonos)) { + // eslint-disable-next-line no-param-reassign + entityObj.sonos = []; + } + entityObj.sonos.push(entityInRegister.entity_id); + break; default: console.error(`Entity ${entityInRegister.entity_id} is not supported.`); } diff --git a/src/const.ts b/src/const.ts index 50ac168..bfbab44 100644 --- a/src/const.ts +++ b/src/const.ts @@ -13,7 +13,8 @@ const supported: any = { androidtv: ['movie', 'show', 'season', 'episode', 'clip', 'track', 'artist', 'album'], plexPlayer: ['movie', 'show', 'season', 'episode', 'clip', 'track', 'artist', 'album'], cast: ['movie', 'episode', 'artist', 'album', 'track'], - vlcTelnet: ['track'] + vlcTelnet: ['track'], + sonos: ['movie', 'show', 'season', 'episode', 'clip', 'track', 'artist', 'album'] // Experimental }; 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/editor.ts b/src/editor.ts index ba36acd..82a53f0 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -297,7 +297,8 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { _.isEqual(entityRegistry.platform, 'androidtv') || _.isEqual(entityRegistry.platform, 'input_select') || _.isEqual(entityRegistry.platform, 'input_text') || - _.isEqual(entityRegistry.platform, 'vlc_telnet') + _.isEqual(entityRegistry.platform, 'vlc_telnet') || + _.isEqual(entityRegistry.platform, 'sonos') ) { const entityName = `${entityRegistry.platform} | ${entityRegistry.entity_id}`; entities.appendChild(addDropdownItem(entityName)); diff --git a/src/modules/PlayController.ts b/src/modules/PlayController.ts index 2b431c2..314cab0 100644 --- a/src/modules/PlayController.ts +++ b/src/modules/PlayController.ts @@ -174,6 +174,7 @@ class PlayController { await this.playViaPlexPlayer(entity.value, processData.key.split('/')[3]); break; case 'cast': + case 'sonos': if (_.isEqual(data.type, 'epg')) { const session = `PlexMeetsHomeAssistant-${Math.floor(Date.now() / 1000)}`; const streamURL = await this.plex.tune(data.channelIdentifier, session); @@ -680,7 +681,8 @@ class PlayController { (entity.key === 'androidtv' && this.isAndroidTVSupported(entity.value)) || (entity.key === 'plexPlayer' && this.isPlexPlayerSupported(entity.value)) || (entity.key === 'cast' && this.isCastSupported(entity.value)) || - (entity.key === 'vlcTelnet' && this.isVLCTelnetSupported(entity.value)) + (entity.key === 'vlcTelnet' && this.isVLCTelnetSupported(entity.value)) || + (entity.key === 'sonos' && this.isSonosSupported(entity.value)) ) { service = { key: entity.key, value: entity.value }; return false; @@ -871,6 +873,15 @@ class PlayController { ); }; + private isSonosSupported = (entityName: string): boolean => { + return ( + (this.entityStates[entityName] && + !_.isNil(this.entityStates[entityName].attributes) && + this.entityStates[entityName].state !== 'unavailable') || + !_.isEqual(this.runBefore, false) + ); + }; + private isCastSupported = (entityName: string): boolean => { return ( (this.entityStates[entityName] && diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 5af9715..bcdc978 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -303,7 +303,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { _.startsWith(entityString, 'cast | ') || _.startsWith(entityString, 'input_select | ') || _.startsWith(entityString, 'input_text | ') || - _.startsWith(entityString, 'vlc_telnet | ') + _.startsWith(entityString, 'vlc_telnet | ') || + _.startsWith(entityString, 'sonos | ') ) { // eslint-disable-next-line prefer-destructuring realEntityString = entityString.split(' | ')[1]; @@ -361,6 +362,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { } entityObj.vlcTelnet.push(entityInRegister.entity_id); break; + case 'sonos': + if (_.isNil(entityObj.sonos)) { + // eslint-disable-next-line no-param-reassign + entityObj.sonos = []; + } + entityObj.sonos.push(entityInRegister.entity_id); + break; default: console.error(`Entity ${entityInRegister.entity_id} is not supported.`); }