diff --git a/README.md b/README.md index 4e9d7eb..adcdc4b 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ _Available special libraries:_ **entity**: You need to configure at least one supported media_player entity. -- **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). +- **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). It is also possible to use short declaration with androidtv. +- **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). It is also possible to use short declaration with 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). 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](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#google-cast). It is also possible to use short declaration with cast. **port**: _Optional_ Port of your plex sever. @@ -63,14 +63,38 @@ _Available special libraries:_ **playTrailer**: _Optional_ Specify whether to play trailer if available. Possible values: true, false, muted. Default: true -Example of card configuration: +Example of the simplest possible configuration: + +``` +type: 'custom:plex-meets-homeassistant' +token: QWdsqEXAMPLETOKENqwerty +ip: 192.168.13.37 +libraryName: Movies +entity: media_player.bedroom_tv # entity provided by cast integration +``` + +Example of the simplest possible configuration using multiple entities: ``` type: 'custom:plex-meets-homeassistant' token: QWdsqEXAMPLETOKENqwerty ip: 192.168.13.37 -port: 32400 libraryName: Movies +entity: + - media_player.living_room_nvidia_shield # created by androidtv integration + - media_player.living_room_tv # created by cast integration + - media_player.bedroom_tv # created by cast integration + - media_player.kodi_123456qwe789rty # created by kodi integration +``` + +Example of card configuration using detailed definitions: + +``` +type: 'custom:plex-meets-homeassistant' +token: QWdsqEXAMPLETOKENqwerty +ip: 192.168.13.37 +port: 32400 +libraryName: TV Shows protocol: http maxCount: 10 sort: title:desc @@ -81,19 +105,21 @@ entity: cast: media_player.bedroom_tv ``` -Example using lists: +Complex example using detailed definitions, lists and shared plex server for plexPlayer: ``` type: 'custom:plex-meets-homeassistant' token: QWdsqEXAMPLETOKENqwerty -ip: 192.168.13.37 -port: 32400 +ip: remote.plex.server.com # remote shared plex instance +port: 443 libraryName: Deck -protocol: http +protocol: https maxCount: 10 sort: title:desc runBefore: script.turn_on_tv_and_wait runAfter: script.movie_time +showExtras: true +playTrailer: muted entity: kodi: - media_player.kodi_bedroom @@ -103,15 +129,19 @@ entity: - media_player.bedroom_nvidia_shield - media_player.kithen_nvidia_shield plexPlayer: - - TV 2020 - - 192.168.13.50 - cast: - - media_player.bedroom_tv + - identifier: TV 2020 # plex client device located on local plex server network + server: + ip: local.plex.server.com # Mandatory + token: QWdsqEXAMPLETOKENqwerty # Mandatory + port: 32400 + protocol: http + - 192.168.13.50 # without definition for server, it will look for device on remote.plex.server.com network + 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. -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. +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 on local plex server and if not found, IP 192.168.13.50 on remote plex server. Finally, it tries to cast into media_player.bedroom_tv. ## Detailed configuration instructions for end devices diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 8004cb0..909cd31 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -20157,6 +20157,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.plexProtocol = 'http'; this.plexPort = false; this.detailsShown = false; + this.entityRegistry = []; this.runBefore = ''; this.playTrailer = true; this.showExtras = true; @@ -20191,7 +20192,55 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.calculatePositions(); } }; + this.fetchEntityRegistry = (conn) => conn.sendMessagePromise({ + type: 'config/entity_registry/list' + }); this.loadInitialData = async () => { + if (this.hassObj) { + this.entityRegistry = await this.fetchEntityRegistry(this.hassObj.connection); + } + let { entity } = JSON.parse(JSON.stringify(this.config)); + const processEntity = (entityObj, entityString) => { + lodash.forEach(this.entityRegistry, entityInRegister => { + if (lodash.isEqual(entityInRegister.entity_id, entityString)) { + switch (entityInRegister.platform) { + case 'cast': + if (lodash.isNil(entityObj.cast)) { + // eslint-disable-next-line no-param-reassign + entityObj.cast = []; + } + entityObj.cast.push(entityInRegister.entity_id); + break; + case 'androidtv': + if (lodash.isNil(entityObj.androidtv)) { + // eslint-disable-next-line no-param-reassign + entityObj.androidtv = []; + } + entityObj.androidtv.push(entityInRegister.entity_id); + break; + case 'kodi': + if (lodash.isNil(entityObj.kodi)) { + // eslint-disable-next-line no-param-reassign + entityObj.kodi = []; + } + entityObj.kodi.push(entityInRegister.entity_id); + break; + // pass + } + } + }); + }; + const entityOrig = entity; + if (lodash.isString(entityOrig)) { + entity = {}; + processEntity(entity, entityOrig); + } + else if (lodash.isArray(entityOrig)) { + entity = {}; + lodash.forEach(entityOrig, entityStr => { + processEntity(entity, entityStr); + }); + } window.addEventListener('scroll', () => { // todo: improve performance by calculating this when needed only if (this.detailsShown && this.activeMovieElem && !isVideoFullScreen(this)) { @@ -20259,8 +20308,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { try { if (this.plex) { if (this.hassObj) { - const entityConfig = JSON.parse(JSON.stringify(this.config.entity)); // todo: find a nicer solution - this.playController = new PlayController(this.hassObj, this.plex, entityConfig, this.runBefore, this.runAfter, this.config.libraryName); + this.playController = new PlayController(this.hassObj, this.plex, entity, this.runBefore, this.runAfter, this.config.libraryName); if (this.playController) { await this.playController.init(); } @@ -21353,10 +21401,10 @@ class PlexMeetsHomeAssistant extends HTMLElement { }; this.setConfig = (config) => { this.plexProtocol = 'http'; - if (!config.entity || config.entity.length === 0 || !lodash.isObject(config.entity)) { + if (!config.entity || config.entity.length === 0) { throw new Error('You need to define at least one entity'); } - if (lodash.isObject(config.entity)) { + if (lodash.isPlainObject(config.entity)) { let entityDefined = false; // eslint-disable-next-line consistent-return lodash.forEach(config.entity, (value, key) => { @@ -21369,6 +21417,9 @@ class PlexMeetsHomeAssistant extends HTMLElement { throw new Error('You need to define at least one supported entity'); } } + else if (!lodash.isString(config.entity) && !lodash.isArray(config.entity)) { + throw new Error('You need to define at least one supported entity'); + } if (!config.token) { throw new Error('You need to define a token'); } diff --git a/package-lock.json b/package-lock.json index 3dd0a63..2226623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1119,9 +1119,9 @@ "dev": true }, "home-assistant-js-websocket": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-5.9.0.tgz", - "integrity": "sha512-HSAhX+s2JgsE77sYKKqcNsukiO6Zm4CcCIwugq17MwHcEyLoecChsbQtgtbvg1dHctUAk+IHxuZ0JBx10B1YGQ==" + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-5.10.0.tgz", + "integrity": "sha512-QiK26d62wuQCuaXHRZ90yvaMXeHkYbQrSGQvCrF6hqxdibLXEwSXQM2uzWYUgP25Q3iKy3PLQspMBRnuBKczbQ==" }, "hosted-git-info": { "version": "2.8.9", diff --git a/package.json b/package.json index 2a9bccd..57b93a4 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@vercel/ncc": "^0.28.5", "axios": "^0.21.1", "custom-card-helpers": "^1.7.0", + "home-assistant-js-websocket": "^5.10.0", "lit-element": "^2.5.0", "lodash": "^4.17.21" }, diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 309dc3a..0e55e96 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -2,6 +2,7 @@ /* eslint-env browser */ import { HomeAssistant } from 'custom-card-helpers'; import _ from 'lodash'; +import { Connection } from 'home-assistant-js-websocket'; import { supported, CSS_STYLE } from './const'; import Plex from './modules/Plex'; import PlayController from './modules/PlayController'; @@ -27,6 +28,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { detailsShown = false; + entityRegistry: Array> = []; + runBefore = ''; playTrailer: string | boolean = true; @@ -141,7 +144,61 @@ class PlexMeetsHomeAssistant extends HTMLElement { } }; + fetchEntityRegistry = (conn: Connection): Promise>> => + conn.sendMessagePromise({ + type: 'config/entity_registry/list' + }); + loadInitialData = async (): Promise => { + if (this.hassObj) { + this.entityRegistry = await this.fetchEntityRegistry(this.hassObj.connection); + } + + let { entity } = JSON.parse(JSON.stringify(this.config)); + + const processEntity = (entityObj: Record, entityString: string): void => { + _.forEach(this.entityRegistry, entityInRegister => { + if (_.isEqual(entityInRegister.entity_id, entityString)) { + switch (entityInRegister.platform) { + case 'cast': + if (_.isNil(entityObj.cast)) { + // eslint-disable-next-line no-param-reassign + entityObj.cast = []; + } + entityObj.cast.push(entityInRegister.entity_id); + break; + case 'androidtv': + if (_.isNil(entityObj.androidtv)) { + // eslint-disable-next-line no-param-reassign + entityObj.androidtv = []; + } + entityObj.androidtv.push(entityInRegister.entity_id); + break; + case 'kodi': + if (_.isNil(entityObj.kodi)) { + // eslint-disable-next-line no-param-reassign + entityObj.kodi = []; + } + entityObj.kodi.push(entityInRegister.entity_id); + break; + default: + // pass + } + } + }); + }; + + const entityOrig = entity; + if (_.isString(entityOrig)) { + entity = {}; + processEntity(entity, entityOrig); + } else if (_.isArray(entityOrig)) { + entity = {}; + _.forEach(entityOrig, entityStr => { + processEntity(entity, entityStr); + }); + } + window.addEventListener('scroll', () => { // todo: improve performance by calculating this when needed only if (this.detailsShown && this.activeMovieElem && !isVideoFullScreen(this)) { @@ -212,11 +269,10 @@ class PlexMeetsHomeAssistant extends HTMLElement { try { if (this.plex) { if (this.hassObj) { - const entityConfig: Record = JSON.parse(JSON.stringify(this.config.entity)); // todo: find a nicer solution this.playController = new PlayController( this.hassObj, this.plex, - entityConfig, + entity, this.runBefore, this.runAfter, this.config.libraryName @@ -1428,10 +1484,10 @@ class PlexMeetsHomeAssistant extends HTMLElement { setConfig = (config: any): void => { this.plexProtocol = 'http'; - if (!config.entity || config.entity.length === 0 || !_.isObject(config.entity)) { + if (!config.entity || config.entity.length === 0) { throw new Error('You need to define at least one entity'); } - if (_.isObject(config.entity)) { + if (_.isPlainObject(config.entity)) { let entityDefined = false; // eslint-disable-next-line consistent-return _.forEach(config.entity, (value, key) => { @@ -1443,6 +1499,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { if (!entityDefined) { throw new Error('You need to define at least one supported entity'); } + } else if (!_.isString(config.entity) && !_.isArray(config.entity)) { + throw new Error('You need to define at least one supported entity'); } if (!config.token) { throw new Error('You need to define a token');