From fa37fbda003a7585f84367730ab255b6b6c8b723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Wed, 1 Sep 2021 00:57:48 +0200 Subject: [PATCH 1/9] Add: Getting providers and live tv channels --- dist/plex-meets-homeassistant.js | 36 ++++++++++++++++++++++++++++ src/modules/Plex.ts | 41 ++++++++++++++++++++++++++++++++ src/plex-meets-homeassistant.ts | 2 ++ 3 files changed, 79 insertions(+) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 6c5964d..2ff3a0f 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -18674,6 +18674,8 @@ class Plex { this.clients = []; this.requestTimeout = 10000; this.sections = []; + this.providers = []; + this.livetv = {}; this.collections = false; this.playlists = []; this.init = async () => { @@ -18692,6 +18694,39 @@ class Plex { } return this.clients; }; + this.getProviders = async () => { + if (lodash.isEmpty(this.providers)) { + const url = this.authorizeURL(`${this.getBasicURL()}/media/providers`); + const providersData = await axios.get(url, { + timeout: this.requestTimeout + }); + this.providers = providersData.data.MediaContainer.MediaProvider; + } + return this.providers; + }; + this.getLiveTV = async () => { + if (lodash.isEmpty(this.livetv)) { + const returnData = {}; + const providers = await this.getProviders(); + const liveTVRequests = []; + const liveTVRequestsNames = []; + lodash.forEach(providers, provider => { + if (lodash.isEqual(provider.protocols, 'livetv')) { + const url = this.authorizeURL(`${this.getBasicURL()}/${provider.identifier}/tags?type=310`); + liveTVRequests.push(axios.get(url, { + timeout: this.requestTimeout + })); + liveTVRequestsNames.push(provider.title); + } + }); + const allResults = await Promise.all(liveTVRequests); + lodash.forEach(allResults, (result, key) => { + returnData[liveTVRequestsNames[key]] = result.data.MediaContainer.Directory; + }); + this.livetv = returnData; + } + return this.livetv; + }; this.getServerID = async () => { if (lodash.isEmpty(this.serverInfo)) { await this.getServerInfo(); @@ -21108,6 +21143,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { } await this.plex.init(); const plexAllSections = await this.plex.getSections(); + console.log(await this.plex.getLiveTV()); const getOnDeck = async () => { if (this.plex) { try { diff --git a/src/modules/Plex.ts b/src/modules/Plex.ts index cfefa4e..242adad 100644 --- a/src/modules/Plex.ts +++ b/src/modules/Plex.ts @@ -21,6 +21,10 @@ class Plex { sections: Array> = []; + providers: Array> = []; + + livetv: Record = {}; + collections: Array> | false = false; playlists: Array> = []; @@ -56,6 +60,43 @@ class Plex { return this.clients; }; + getProviders = async (): Promise => { + if (_.isEmpty(this.providers)) { + const url = this.authorizeURL(`${this.getBasicURL()}/media/providers`); + const providersData = await axios.get(url, { + timeout: this.requestTimeout + }); + this.providers = providersData.data.MediaContainer.MediaProvider; + } + return this.providers; + }; + + getLiveTV = async (): Promise> => { + if (_.isEmpty(this.livetv)) { + const returnData: Record = {}; + const providers = await this.getProviders(); + const liveTVRequests: Array> = []; + const liveTVRequestsNames: Array = []; + _.forEach(providers, provider => { + if (_.isEqual(provider.protocols, 'livetv')) { + const url = this.authorizeURL(`${this.getBasicURL()}/${provider.identifier}/tags?type=310`); + liveTVRequests.push( + axios.get(url, { + timeout: this.requestTimeout + }) + ); + liveTVRequestsNames.push(provider.title); + } + }); + const allResults = await Promise.all(liveTVRequests); + _.forEach(allResults, (result, key) => { + returnData[liveTVRequestsNames[key]] = result.data.MediaContainer.Directory; + }); + this.livetv = returnData; + } + return this.livetv; + }; + getServerID = async (): Promise => { if (_.isEmpty(this.serverInfo)) { await this.getServerInfo(); diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 08ee42a..6df843f 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -333,6 +333,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { await this.plex.init(); const plexAllSections = await this.plex.getSections(); + console.log(await this.plex.getLiveTV()); + const getOnDeck = async (): Promise => { if (this.plex) { try { From 4dda13c2dc8528ad7033b659050689509dd17dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Wed, 1 Sep 2021 01:02:10 +0200 Subject: [PATCH 2/9] Add: Live tv into editor UI --- dist/plex-meets-homeassistant.js | 8 ++++++++ src/editor.ts | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 2ff3a0f..7f2711b 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -19646,6 +19646,7 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.entitiesRegistry = false; this.plexValidSection = document.createElement('div'); this.loaded = false; + this.livetv = {}; this.fireEvent = (node, type, detail, options = {}) => { // eslint-disable-next-line no-param-reassign detail = detail === null || detail === undefined ? {} : detail; @@ -19859,6 +19860,7 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.appendChild(this.content); this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort); this.sections = await this.plex.getSections(); + this.livetv = await this.plex.getLiveTV(); this.collections = await this.plex.getCollections(); this.playlists = await this.plex.getPlaylists(); this.clients = await this.plex.getClients(); @@ -20068,6 +20070,12 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.runAfter.addEventListener('value-changed', this.valueUpdated); this.runAfter.value = this.config.runAfter; this.plexValidSection.appendChild(this.runAfter); + if (!lodash.isEmpty(this.livetv)) { + libraryItems.appendChild(addDropdownItem('Live TV', true)); + lodash.forEach(lodash.keys(this.livetv), (livetv) => { + libraryItems.appendChild(addDropdownItem(livetv)); + }); + } if (!lodash.isEmpty(this.sections)) { libraryItems.appendChild(addDropdownItem('Libraries', true)); lodash.forEach(this.sections, (section) => { diff --git a/src/editor.ts b/src/editor.ts index b25f844..3ff32ad 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -72,6 +72,8 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { loaded = false; + livetv: Record = {}; + fireEvent = ( node: HTMLElement, type: string, @@ -300,6 +302,7 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort); this.sections = await this.plex.getSections(); + this.livetv = await this.plex.getLiveTV(); this.collections = await this.plex.getCollections(); this.playlists = await this.plex.getPlaylists(); this.clients = await this.plex.getClients(); @@ -519,6 +522,12 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.runAfter.value = this.config.runAfter; this.plexValidSection.appendChild(this.runAfter); + if (!_.isEmpty(this.livetv)) { + libraryItems.appendChild(addDropdownItem('Live TV', true)); + _.forEach(_.keys(this.livetv), (livetv: string) => { + libraryItems.appendChild(addDropdownItem(livetv)); + }); + } if (!_.isEmpty(this.sections)) { libraryItems.appendChild(addDropdownItem('Libraries', true)); _.forEach(this.sections, (section: Record) => { From f8b8ddb6d22b5e78d0644a15dc6a2b8eda66646e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Wed, 1 Sep 2021 01:09:21 +0200 Subject: [PATCH 3/9] Add: Live TV loaded in renderer --- dist/plex-meets-homeassistant.js | 10 +++++++++- src/plex-meets-homeassistant.ts | 13 +++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 7f2711b..27006e5 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -21151,7 +21151,6 @@ class PlexMeetsHomeAssistant extends HTMLElement { } await this.plex.init(); const plexAllSections = await this.plex.getSections(); - console.log(await this.plex.getLiveTV()); const getOnDeck = async () => { if (this.plex) { try { @@ -21229,6 +21228,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { } } }; + const getLiveTV = async () => { + if (this.plex) { + const liveTV = await this.plex.getLiveTV(); + lodash.forEach(liveTV, (data, key) => { + this.data[key] = data; + }); + } + }; let sectionKey = 0; lodash.forEach(plexAllSections, (section) => { if (lodash.isEqual(section.title, this.config.libraryName)) { @@ -21253,6 +21260,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { else if (lodash.isEqual(this.config.libraryName, 'Recently Added')) { loadDataRequests.push(getRecentyAdded()); } + loadDataRequests.push(getLiveTV()); const [plexSections] = await Promise.all(loadDataRequests); if (plexSections && sectionKey) { lodash.forEach(plexSections, section => { diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 6df843f..6262e2e 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -333,8 +333,6 @@ class PlexMeetsHomeAssistant extends HTMLElement { await this.plex.init(); const plexAllSections = await this.plex.getSections(); - console.log(await this.plex.getLiveTV()); - const getOnDeck = async (): Promise => { if (this.plex) { try { @@ -405,6 +403,15 @@ class PlexMeetsHomeAssistant extends HTMLElement { } }; + const getLiveTV = async (): Promise => { + if (this.plex) { + const liveTV = await this.plex.getLiveTV(); + _.forEach(liveTV, (data, key) => { + this.data[key] = data; + }); + } + }; + let sectionKey: number | false = 0; _.forEach(plexAllSections, (section: Record) => { if (_.isEqual(section.title, this.config.libraryName)) { @@ -427,6 +434,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { loadDataRequests.push(getRecentyAdded()); } + loadDataRequests.push(getLiveTV()); + const [plexSections] = await Promise.all(loadDataRequests); if (plexSections && sectionKey) { From b9ed39b62962044329f0000626b825d95bdae17c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Wed, 1 Sep 2021 01:31:52 +0200 Subject: [PATCH 4/9] Add: WIP Nicer design for live TV view --- dist/plex-meets-homeassistant.js | 230 +++++++++++++++++------------ src/plex-meets-homeassistant.ts | 246 ++++++++++++++++++------------- 2 files changed, 280 insertions(+), 196 deletions(-) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 27006e5..1a167b3 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -21714,8 +21714,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.activeMovieElem = undefined; for (let i = 0; i < this.movieElems.length; i += 1) { if (parseInt(this.movieElems[i].style.width, 10) > CSS_STYLE.width) { - this.movieElems[i].style.width = `${CSS_STYLE.width}px`; - this.movieElems[i].style.height = `${CSS_STYLE.height}px`; + if (lodash.isEqual(this.movieElems[i].style.width, this.movieElems[i].style.height)) { + this.movieElems[i].style.width = `${CSS_STYLE.width}px`; + this.movieElems[i].style.height = `${CSS_STYLE.width}px`; + } + else { + this.movieElems[i].style.width = `${CSS_STYLE.width}px`; + this.movieElems[i].style.height = `${CSS_STYLE.height}px`; + } this.movieElems[i].style['z-index'] = 1; this.movieElems[i].style.position = 'absolute'; this.movieElems[i].style.left = `${this.movieElems[i].dataset.left}px`; @@ -21801,6 +21807,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { fullscreenTrailer.style.visibility = 'hidden'; }; this.showDetails = async (data) => { + console.log(data); this.detailsShown = true; const top = this.getTop(); if (this.detailElem) { @@ -21862,7 +21869,12 @@ class PlexMeetsHomeAssistant extends HTMLElement { genreElem.parentElement.style.display = 'none'; } } - this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.title); + if (!lodash.isNil(mainData.channelCallSign)) { + this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.channelCallSign); + } + else { + this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.title); + } this.getElementsByClassName('detailsYear')[0].innerHTML = escapeHtml(mainData.year); this.getElementsByClassName('metaInfo')[0].innerHTML = `${(mainData.duration !== undefined ? `${Math.round(parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000)} min` @@ -21895,97 +21907,100 @@ class PlexMeetsHomeAssistant extends HTMLElement { else if (data.childCount > 0) { seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]); } - const dataDetails = await this.plex.getDetails(data.key.split('/')[3]); - if (this.videoElem) { - const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art); - const trailerURL = findTrailerURL(dataDetails); - if (trailerURL !== '' && !lodash.isEqual(this.playTrailer, false)) { - const videoPlayer = this.getElementsByClassName('videoPlayer')[0]; - const video = document.createElement('video'); - video.style.height = '100%'; - video.style.width = '100%'; - video.controls = false; - if (lodash.isEqual(this.playTrailer, 'muted')) { - video.muted = true; - } - const source = document.createElement('source'); - source.type = 'video/mp4'; - source.src = this.plex.authorizeURL(`${this.plex.getBasicURL()}${dataDetails.Extras.Metadata[0].Media[0].Part[0].key}`); - video.appendChild(source); - videoPlayer.appendChild(video); - video.load(); - video.play(); - let playingFired = false; - const videobgs1 = this.getElementsByClassName('videobg1'); - const videobgs2 = this.getElementsByClassName('videobg2'); - video.addEventListener('click', event => { - if (isVideoFullScreen(this)) { - event.stopPropagation(); + let dataDetails = {}; + if (!lodash.isNil(data.key)) { + dataDetails = await this.plex.getDetails(data.key.split('/')[3]); + if (this.videoElem) { + const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art); + const trailerURL = findTrailerURL(dataDetails); + if (trailerURL !== '' && !lodash.isEqual(this.playTrailer, false)) { + const videoPlayer = this.getElementsByClassName('videoPlayer')[0]; + const video = document.createElement('video'); + video.style.height = '100%'; + video.style.width = '100%'; + video.controls = false; + if (lodash.isEqual(this.playTrailer, 'muted')) { + video.muted = true; } - }); - const fullScreenChangeAction = () => { - if (this.videoElem) { + const source = document.createElement('source'); + source.type = 'video/mp4'; + source.src = this.plex.authorizeURL(`${this.plex.getBasicURL()}${dataDetails.Extras.Metadata[0].Media[0].Part[0].key}`); + video.appendChild(source); + videoPlayer.appendChild(video); + video.load(); + video.play(); + let playingFired = false; + const videobgs1 = this.getElementsByClassName('videobg1'); + const videobgs2 = this.getElementsByClassName('videobg2'); + video.addEventListener('click', event => { if (isVideoFullScreen(this)) { - // eslint-disable-next-line no-restricted-syntax - for (const videobg1 of videobgs1) { - videobg1.classList.add('transparent'); - } - // eslint-disable-next-line no-restricted-syntax - for (const videobg2 of videobgs2) { - videobg2.classList.add('transparent'); - } - this.videoElem.classList.add('maxZIndex'); - video.controls = true; - video.muted = false; + event.stopPropagation(); } - else { - // eslint-disable-next-line no-restricted-syntax - for (const videobg1 of videobgs1) { - videobg1.classList.remove('transparent'); - } - // eslint-disable-next-line no-restricted-syntax - for (const videobg2 of videobgs2) { - videobg2.classList.remove('transparent'); + }); + const fullScreenChangeAction = () => { + if (this.videoElem) { + if (isVideoFullScreen(this)) { + // eslint-disable-next-line no-restricted-syntax + for (const videobg1 of videobgs1) { + videobg1.classList.add('transparent'); + } + // eslint-disable-next-line no-restricted-syntax + for (const videobg2 of videobgs2) { + videobg2.classList.add('transparent'); + } + this.videoElem.classList.add('maxZIndex'); + video.controls = true; + video.muted = false; } - this.videoElem.classList.remove('maxZIndex'); - video.controls = false; - window.scroll({ - top: getOffset(this.activeMovieElem).top - 70, - behavior: 'smooth' - }); - if (lodash.isEqual(this.playTrailer, 'muted')) { - video.muted = true; + else { + // eslint-disable-next-line no-restricted-syntax + for (const videobg1 of videobgs1) { + videobg1.classList.remove('transparent'); + } + // eslint-disable-next-line no-restricted-syntax + for (const videobg2 of videobgs2) { + videobg2.classList.remove('transparent'); + } + this.videoElem.classList.remove('maxZIndex'); + video.controls = false; + window.scroll({ + top: getOffset(this.activeMovieElem).top - 70, + behavior: 'smooth' + }); + if (lodash.isEqual(this.playTrailer, 'muted')) { + video.muted = true; + } } } - } - }; - video.addEventListener('fullscreenchange', fullScreenChangeAction); - video.addEventListener('mozfullscreenchange', fullScreenChangeAction); - video.addEventListener('webkitfullscreenchange', fullScreenChangeAction); - video.addEventListener('msfullscreenchange', fullScreenChangeAction); - video.addEventListener('playing', () => { - if (this.videoElem && !playingFired) { - const contentbg = this.getElementsByClassName('contentbg')[0]; - const fullscreenTrailer = this.getElementsByClassName('detailPlayAction')[0]; - fullscreenTrailer.style.visibility = 'visible'; - contentbg.classList.add('no-transparency'); - playingFired = true; - this.videoElem.style.width = `${this.getElementsByClassName('searchContainer')[0].offsetWidth}px`; - this.videoElem.style.visibility = 'visible'; - this.videoElem.style.top = `${top}px`; - } - }); - } - else if (!lodash.isEmpty(art)) { - const contentArt = this.getElementsByClassName('contentArt')[0]; - const contentbg = this.getElementsByClassName('contentbg')[0]; - contentArt.style.width = `${window.innerWidth}px`; - contentArt.style.height = `${window.innerHeight}px`; - contentArt.style.backgroundImage = `url('${art}')`; - contentArt.style.top = `${top - 8}px`; - contentArt.style.transition = '0.5s'; - contentArt.style.display = 'block'; - contentbg.classList.add('no-transparency'); + }; + video.addEventListener('fullscreenchange', fullScreenChangeAction); + video.addEventListener('mozfullscreenchange', fullScreenChangeAction); + video.addEventListener('webkitfullscreenchange', fullScreenChangeAction); + video.addEventListener('msfullscreenchange', fullScreenChangeAction); + video.addEventListener('playing', () => { + if (this.videoElem && !playingFired) { + const contentbg = this.getElementsByClassName('contentbg')[0]; + const fullscreenTrailer = this.getElementsByClassName('detailPlayAction')[0]; + fullscreenTrailer.style.visibility = 'visible'; + contentbg.classList.add('no-transparency'); + playingFired = true; + this.videoElem.style.width = `${this.getElementsByClassName('searchContainer')[0].offsetWidth}px`; + this.videoElem.style.visibility = 'visible'; + this.videoElem.style.top = `${top}px`; + } + }); + } + else if (!lodash.isEmpty(art)) { + const contentArt = this.getElementsByClassName('contentArt')[0]; + const contentbg = this.getElementsByClassName('contentbg')[0]; + contentArt.style.width = `${window.innerWidth}px`; + contentArt.style.height = `${window.innerHeight}px`; + contentArt.style.backgroundImage = `url('${art}')`; + contentArt.style.top = `${top - 8}px`; + contentArt.style.transition = '0.5s'; + contentArt.style.display = 'block'; + contentbg.classList.add('no-transparency'); + } } } if (!lodash.isEmpty(seasonsData)) { @@ -22167,7 +22182,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { } }); } - else if (this.showExtras) { + else if (this.showExtras && !lodash.isNil(dataDetails.Extras)) { const extras = dataDetails.Extras.Metadata; lodash.forEach(extras, extrasData => { if (this.episodesElem && this.playController && this.plex) { @@ -22227,8 +22242,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.minimizeAll(); this.activeMovieElem = undefined; this.hideDetails(); - movieElemLocal.style.width = `${CSS_STYLE.width}px`; - movieElemLocal.style.height = `${CSS_STYLE.height}px`; + if (lodash.isEqual(movieElem.style.width, movieElem.style.height)) { + movieElemLocal.style.width = `${CSS_STYLE.width}px`; + movieElemLocal.style.height = `${CSS_STYLE.width}px`; + } + else { + movieElemLocal.style.width = `${CSS_STYLE.width}px`; + movieElemLocal.style.height = `${CSS_STYLE.height}px`; + } movieElemLocal.style.zIndex = '1'; movieElemLocal.style.top = `${movieElem.dataset.top}px`; movieElemLocal.style.left = `${movieElem.dataset.left}px`; @@ -22241,8 +22262,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { const top = this.getTop(); this.showDetails(this.activeMovieElemData); this.showBackground(); - movieElemLocal.style.width = `${CSS_STYLE.expandedWidth}px`; - movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`; + if (lodash.isEqual(movieElem.style.width, movieElem.style.height)) { + movieElemLocal.style.width = `${CSS_STYLE.expandedWidth}px`; + movieElemLocal.style.height = `${CSS_STYLE.expandedWidth}px`; + } + else { + movieElemLocal.style.width = `${CSS_STYLE.expandedWidth}px`; + movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`; + } movieElemLocal.style.zIndex = '3'; movieElemLocal.style.left = '16px'; movieElemLocal.style.top = `${top + 16}px`; @@ -22285,6 +22312,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { movieElem.className = 'movieElem'; movieElem.style.width = `${CSS_STYLE.width}px`; movieElem.style.height = `${CSS_STYLE.height}px`; + if (!lodash.isNil(data.channelCallSign)) { + movieElem.style.backgroundSize = '80%'; + movieElem.style.backgroundColor = 'rgba(0,0,0,0.2)'; + movieElem.style.backgroundPosition = 'center'; + container.style.height = container.style.width; + movieElem.style.height = `${CSS_STYLE.width}px`; + } movieElem.style.backgroundImage = `url('${thumbURL}')`; if (this.playController && !this.playController.isPlaySupported(data)) { movieElem.style.cursor = 'pointer'; @@ -22330,11 +22364,19 @@ class PlexMeetsHomeAssistant extends HTMLElement { if (lodash.isEqual(data.type, 'episode')) { titleElem.innerHTML = escapeHtml(data.grandparentTitle); } + else if (!lodash.isNil(data.channelCallSign)) { + titleElem.innerHTML = escapeHtml(data.channelCallSign); + } else { titleElem.innerHTML = escapeHtml(data.title); } titleElem.className = 'titleElem'; - titleElem.style.marginTop = `${CSS_STYLE.height}px`; + if (!lodash.isNil(data.channelCallSign)) { + titleElem.style.marginTop = `${CSS_STYLE.width}px`; + } + else { + titleElem.style.marginTop = `${CSS_STYLE.height}px`; + } const yearElem = document.createElement('div'); if (lodash.isEqual(data.type, 'episode')) { yearElem.innerHTML = escapeHtml(data.title); diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 6262e2e..100b974 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -940,8 +940,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.activeMovieElem = undefined; for (let i = 0; i < this.movieElems.length; i += 1) { if (parseInt(this.movieElems[i].style.width, 10) > CSS_STYLE.width) { - this.movieElems[i].style.width = `${CSS_STYLE.width}px`; - this.movieElems[i].style.height = `${CSS_STYLE.height}px`; + if (_.isEqual(this.movieElems[i].style.width, this.movieElems[i].style.height)) { + this.movieElems[i].style.width = `${CSS_STYLE.width}px`; + this.movieElems[i].style.height = `${CSS_STYLE.width}px`; + } else { + this.movieElems[i].style.width = `${CSS_STYLE.width}px`; + this.movieElems[i].style.height = `${CSS_STYLE.height}px`; + } + this.movieElems[i].style['z-index'] = 1; this.movieElems[i].style.position = 'absolute'; this.movieElems[i].style.left = `${this.movieElems[i].dataset.left}px`; @@ -1032,6 +1038,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { }; showDetails = async (data: any): Promise => { + console.log(data); this.detailsShown = true; const top = this.getTop(); if (this.detailElem) { @@ -1091,7 +1098,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { genreElem.parentElement.style.display = 'none'; } } - (this.getElementsByClassName('detailsTitle')[0] as HTMLElement).innerHTML = escapeHtml(mainData.title); + if (!_.isNil(mainData.channelCallSign)) { + (this.getElementsByClassName('detailsTitle')[0] as HTMLElement).innerHTML = escapeHtml( + mainData.channelCallSign + ); + } else { + (this.getElementsByClassName('detailsTitle')[0] as HTMLElement).innerHTML = escapeHtml(mainData.title); + } + (this.getElementsByClassName('detailsYear')[0] as HTMLElement).innerHTML = escapeHtml(mainData.year); (this.getElementsByClassName('metaInfo')[0] as HTMLElement).innerHTML = `${(mainData.duration !== undefined ? `${Math.round( @@ -1129,105 +1143,108 @@ class PlexMeetsHomeAssistant extends HTMLElement { } else if (data.childCount > 0) { seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]); } - const dataDetails = await this.plex.getDetails(data.key.split('/')[3]); - if (this.videoElem) { - const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art); - const trailerURL = findTrailerURL(dataDetails); - if (trailerURL !== '' && !_.isEqual(this.playTrailer, false)) { - const videoPlayer = this.getElementsByClassName('videoPlayer')[0] as HTMLElement; - const video = document.createElement('video'); - video.style.height = '100%'; - video.style.width = '100%'; - video.controls = false; - if (_.isEqual(this.playTrailer, 'muted')) { - video.muted = true; - } - const source = document.createElement('source'); - source.type = 'video/mp4'; - source.src = this.plex.authorizeURL( - `${this.plex.getBasicURL()}${dataDetails.Extras.Metadata[0].Media[0].Part[0].key}` - ); - video.appendChild(source); - videoPlayer.appendChild(video); - - video.load(); - video.play(); - let playingFired = false; - - const videobgs1 = this.getElementsByClassName('videobg1'); - const videobgs2 = this.getElementsByClassName('videobg2'); - video.addEventListener('click', event => { - if (isVideoFullScreen(this)) { - event.stopPropagation(); + let dataDetails: Record = {}; + if (!_.isNil(data.key)) { + dataDetails = await this.plex.getDetails(data.key.split('/')[3]); + if (this.videoElem) { + const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art); + const trailerURL = findTrailerURL(dataDetails); + if (trailerURL !== '' && !_.isEqual(this.playTrailer, false)) { + const videoPlayer = this.getElementsByClassName('videoPlayer')[0] as HTMLElement; + const video = document.createElement('video'); + video.style.height = '100%'; + video.style.width = '100%'; + video.controls = false; + if (_.isEqual(this.playTrailer, 'muted')) { + video.muted = true; } - }); - const fullScreenChangeAction = (): void => { - if (this.videoElem) { + const source = document.createElement('source'); + source.type = 'video/mp4'; + source.src = this.plex.authorizeURL( + `${this.plex.getBasicURL()}${dataDetails.Extras.Metadata[0].Media[0].Part[0].key}` + ); + video.appendChild(source); + videoPlayer.appendChild(video); + + video.load(); + video.play(); + let playingFired = false; + + const videobgs1 = this.getElementsByClassName('videobg1'); + const videobgs2 = this.getElementsByClassName('videobg2'); + video.addEventListener('click', event => { if (isVideoFullScreen(this)) { - // eslint-disable-next-line no-restricted-syntax - for (const videobg1 of videobgs1) { - videobg1.classList.add('transparent'); - } - // eslint-disable-next-line no-restricted-syntax - for (const videobg2 of videobgs2) { - videobg2.classList.add('transparent'); - } + event.stopPropagation(); + } + }); + const fullScreenChangeAction = (): void => { + if (this.videoElem) { + if (isVideoFullScreen(this)) { + // eslint-disable-next-line no-restricted-syntax + for (const videobg1 of videobgs1) { + videobg1.classList.add('transparent'); + } + // eslint-disable-next-line no-restricted-syntax + for (const videobg2 of videobgs2) { + videobg2.classList.add('transparent'); + } - this.videoElem.classList.add('maxZIndex'); - video.controls = true; - video.muted = false; - } else { - // eslint-disable-next-line no-restricted-syntax - for (const videobg1 of videobgs1) { - videobg1.classList.remove('transparent'); - } - // eslint-disable-next-line no-restricted-syntax - for (const videobg2 of videobgs2) { - videobg2.classList.remove('transparent'); - } + this.videoElem.classList.add('maxZIndex'); + video.controls = true; + video.muted = false; + } else { + // eslint-disable-next-line no-restricted-syntax + for (const videobg1 of videobgs1) { + videobg1.classList.remove('transparent'); + } + // eslint-disable-next-line no-restricted-syntax + for (const videobg2 of videobgs2) { + videobg2.classList.remove('transparent'); + } - this.videoElem.classList.remove('maxZIndex'); - video.controls = false; - window.scroll({ - top: getOffset(this.activeMovieElem as Element).top - 70, - behavior: 'smooth' - }); - if (_.isEqual(this.playTrailer, 'muted')) { - video.muted = true; + this.videoElem.classList.remove('maxZIndex'); + video.controls = false; + window.scroll({ + top: getOffset(this.activeMovieElem as Element).top - 70, + behavior: 'smooth' + }); + if (_.isEqual(this.playTrailer, 'muted')) { + video.muted = true; + } } } - } - }; - video.addEventListener('fullscreenchange', fullScreenChangeAction); - video.addEventListener('mozfullscreenchange', fullScreenChangeAction); - video.addEventListener('webkitfullscreenchange', fullScreenChangeAction); - video.addEventListener('msfullscreenchange', fullScreenChangeAction); - - video.addEventListener('playing', () => { - if (this.videoElem && !playingFired) { - const contentbg = this.getElementsByClassName('contentbg')[0] as HTMLElement; - const fullscreenTrailer = this.getElementsByClassName('detailPlayAction')[0] as HTMLElement; - fullscreenTrailer.style.visibility = 'visible'; - contentbg.classList.add('no-transparency'); - playingFired = true; - this.videoElem.style.width = `${ - (this.getElementsByClassName('searchContainer')[0] as HTMLElement).offsetWidth - }px`; - this.videoElem.style.visibility = 'visible'; - this.videoElem.style.top = `${top}px`; - } - }); - } else if (!_.isEmpty(art)) { - const contentArt = this.getElementsByClassName('contentArt')[0] as HTMLElement; - const contentbg = this.getElementsByClassName('contentbg')[0] as HTMLElement; - contentArt.style.width = `${window.innerWidth}px`; - contentArt.style.height = `${window.innerHeight}px`; - contentArt.style.backgroundImage = `url('${art}')`; - contentArt.style.top = `${top - 8}px`; - contentArt.style.transition = '0.5s'; - - contentArt.style.display = 'block'; - contentbg.classList.add('no-transparency'); + }; + video.addEventListener('fullscreenchange', fullScreenChangeAction); + video.addEventListener('mozfullscreenchange', fullScreenChangeAction); + video.addEventListener('webkitfullscreenchange', fullScreenChangeAction); + video.addEventListener('msfullscreenchange', fullScreenChangeAction); + + video.addEventListener('playing', () => { + if (this.videoElem && !playingFired) { + const contentbg = this.getElementsByClassName('contentbg')[0] as HTMLElement; + const fullscreenTrailer = this.getElementsByClassName('detailPlayAction')[0] as HTMLElement; + fullscreenTrailer.style.visibility = 'visible'; + contentbg.classList.add('no-transparency'); + playingFired = true; + this.videoElem.style.width = `${ + (this.getElementsByClassName('searchContainer')[0] as HTMLElement).offsetWidth + }px`; + this.videoElem.style.visibility = 'visible'; + this.videoElem.style.top = `${top}px`; + } + }); + } else if (!_.isEmpty(art)) { + const contentArt = this.getElementsByClassName('contentArt')[0] as HTMLElement; + const contentbg = this.getElementsByClassName('contentbg')[0] as HTMLElement; + contentArt.style.width = `${window.innerWidth}px`; + contentArt.style.height = `${window.innerHeight}px`; + contentArt.style.backgroundImage = `url('${art}')`; + contentArt.style.top = `${top - 8}px`; + contentArt.style.transition = '0.5s'; + + contentArt.style.display = 'block'; + contentbg.classList.add('no-transparency'); + } } } if (!_.isEmpty(seasonsData)) { @@ -1430,7 +1447,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.episodesElem.append(createEpisodesView(this.playController, this.plex, episodeData)); } }); - } else if (this.showExtras) { + } else if (this.showExtras && !_.isNil(dataDetails.Extras)) { const extras = dataDetails.Extras.Metadata; _.forEach(extras, extrasData => { if (this.episodesElem && this.playController && this.plex) { @@ -1496,8 +1513,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.minimizeAll(); this.activeMovieElem = undefined; this.hideDetails(); - movieElemLocal.style.width = `${CSS_STYLE.width}px`; - movieElemLocal.style.height = `${CSS_STYLE.height}px`; + if (_.isEqual(movieElem.style.width, movieElem.style.height)) { + movieElemLocal.style.width = `${CSS_STYLE.width}px`; + movieElemLocal.style.height = `${CSS_STYLE.width}px`; + } else { + movieElemLocal.style.width = `${CSS_STYLE.width}px`; + movieElemLocal.style.height = `${CSS_STYLE.height}px`; + } movieElemLocal.style.zIndex = '1'; movieElemLocal.style.top = `${movieElem.dataset.top}px`; movieElemLocal.style.left = `${movieElem.dataset.left}px`; @@ -1511,8 +1533,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { const top = this.getTop(); this.showDetails(this.activeMovieElemData); this.showBackground(); - movieElemLocal.style.width = `${CSS_STYLE.expandedWidth}px`; - movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`; + if (_.isEqual(movieElem.style.width, movieElem.style.height)) { + movieElemLocal.style.width = `${CSS_STYLE.expandedWidth}px`; + movieElemLocal.style.height = `${CSS_STYLE.expandedWidth}px`; + } else { + movieElemLocal.style.width = `${CSS_STYLE.expandedWidth}px`; + movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`; + } movieElemLocal.style.zIndex = '3'; movieElemLocal.style.left = '16px'; movieElemLocal.style.top = `${top + 16}px`; @@ -1562,6 +1589,15 @@ class PlexMeetsHomeAssistant extends HTMLElement { movieElem.style.width = `${CSS_STYLE.width}px`; movieElem.style.height = `${CSS_STYLE.height}px`; + + if (!_.isNil(data.channelCallSign)) { + movieElem.style.backgroundSize = '80%'; + movieElem.style.backgroundColor = 'rgba(0,0,0,0.2)'; + movieElem.style.backgroundPosition = 'center'; + container.style.height = container.style.width; + movieElem.style.height = `${CSS_STYLE.width}px`; + } + movieElem.style.backgroundImage = `url('${thumbURL}')`; if (this.playController && !this.playController.isPlaySupported(data)) { movieElem.style.cursor = 'pointer'; @@ -1620,11 +1656,17 @@ class PlexMeetsHomeAssistant extends HTMLElement { const titleElem = document.createElement('div'); if (_.isEqual(data.type, 'episode')) { titleElem.innerHTML = escapeHtml(data.grandparentTitle); + } else if (!_.isNil(data.channelCallSign)) { + titleElem.innerHTML = escapeHtml(data.channelCallSign); } else { titleElem.innerHTML = escapeHtml(data.title); } titleElem.className = 'titleElem'; - titleElem.style.marginTop = `${CSS_STYLE.height}px`; + if (!_.isNil(data.channelCallSign)) { + titleElem.style.marginTop = `${CSS_STYLE.width}px`; + } else { + titleElem.style.marginTop = `${CSS_STYLE.height}px`; + } const yearElem = document.createElement('div'); if (_.isEqual(data.type, 'episode')) { From 73d8b030f77f224fbf037c1b21246a8cbaf0f544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Wed, 1 Sep 2021 01:44:53 +0200 Subject: [PATCH 5/9] Add: Get EPG WIP --- dist/plex-meets-homeassistant.js | 30 +++++++++++++++++++++++++++++ src/modules/Plex.ts | 33 ++++++++++++++++++++++++++++++++ src/plex-meets-homeassistant.ts | 1 + 3 files changed, 64 insertions(+) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 1a167b3..4064250 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -18676,6 +18676,7 @@ class Plex { this.sections = []; this.providers = []; this.livetv = {}; + this.livetvepg = {}; this.collections = false; this.playlists = []; this.init = async () => { @@ -18727,6 +18728,34 @@ class Plex { } return this.livetv; }; + this.getEPG = async () => { + if (lodash.isEmpty(this.livetvepg)) { + const returnData = {}; + const providers = await this.getProviders(); + const liveTVRequests = []; + const liveTVRequestsNames = []; + lodash.forEach(providers, provider => { + if (lodash.isEqual(provider.protocols, 'livetv')) { + let url = this.authorizeURL(`${this.getBasicURL()}/${provider.identifier}/grid?type=1&sort=beginsAt`); + url += `&endsAt>=${Math.floor(Date.now() / 1000)}`; + url += `&beginsAt<=${Math.floor(Date.now() / 1000)}`; + liveTVRequests.push(axios.get(url, { + timeout: this.requestTimeout + })); + liveTVRequestsNames.push(provider.title); + } + }); + const allResults = await Promise.all(liveTVRequests); + lodash.forEach(allResults, (result, key) => { + returnData[liveTVRequestsNames[key]] = {}; + lodash.forEach(result.data.MediaContainer.Metadata, data => { + returnData[liveTVRequestsNames[key]][data.Media[0].channelCallSign] = data; + }); + }); + this.livetvepg = returnData; + } + return this.livetvepg; + }; this.getServerID = async () => { if (lodash.isEmpty(this.serverInfo)) { await this.getServerInfo(); @@ -21231,6 +21260,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { const getLiveTV = async () => { if (this.plex) { const liveTV = await this.plex.getLiveTV(); + console.log(await this.plex.getEPG()); lodash.forEach(liveTV, (data, key) => { this.data[key] = data; }); diff --git a/src/modules/Plex.ts b/src/modules/Plex.ts index 242adad..2bea9bf 100644 --- a/src/modules/Plex.ts +++ b/src/modules/Plex.ts @@ -25,6 +25,8 @@ class Plex { livetv: Record = {}; + livetvepg: Record = {}; + collections: Array> | false = false; playlists: Array> = []; @@ -97,6 +99,37 @@ class Plex { return this.livetv; }; + getEPG = async (): Promise> => { + if (_.isEmpty(this.livetvepg)) { + const returnData: Record = {}; + const providers = await this.getProviders(); + const liveTVRequests: Array> = []; + const liveTVRequestsNames: Array = []; + _.forEach(providers, provider => { + if (_.isEqual(provider.protocols, 'livetv')) { + let url = this.authorizeURL(`${this.getBasicURL()}/${provider.identifier}/grid?type=1&sort=beginsAt`); + url += `&endsAt>=${Math.floor(Date.now() / 1000)}`; + url += `&beginsAt<=${Math.floor(Date.now() / 1000)}`; + liveTVRequests.push( + axios.get(url, { + timeout: this.requestTimeout + }) + ); + liveTVRequestsNames.push(provider.title); + } + }); + const allResults = await Promise.all(liveTVRequests); + _.forEach(allResults, (result, key) => { + returnData[liveTVRequestsNames[key]] = {}; + _.forEach(result.data.MediaContainer.Metadata, data => { + returnData[liveTVRequestsNames[key]][data.Media[0].channelCallSign] = data; + }); + }); + this.livetvepg = returnData; + } + return this.livetvepg; + }; + getServerID = async (): Promise => { if (_.isEmpty(this.serverInfo)) { await this.getServerInfo(); diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 100b974..1e3a0c4 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -406,6 +406,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { const getLiveTV = async (): Promise => { if (this.plex) { const liveTV = await this.plex.getLiveTV(); + console.log(await this.plex.getEPG()); _.forEach(liveTV, (data, key) => { this.data[key] = data; }); From 47d45ac16c91c7974342a956ef37eb29265a117b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Thu, 2 Sep 2021 15:31:42 +0200 Subject: [PATCH 6/9] Add: EPG into data, use title and description in detailed view --- dist/plex-meets-homeassistant.js | 40 ++++++++++++++++++++++++++++---- src/plex-meets-homeassistant.ts | 39 +++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 4064250..7590953 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -20983,6 +20983,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.searchInputElem = document.createElement('input'); this.plexProtocol = 'http'; this.plexPort = false; + this.epgData = {}; this.detailsShown = false; this.entityRegistry = []; this.runBefore = ''; @@ -21260,12 +21261,16 @@ class PlexMeetsHomeAssistant extends HTMLElement { const getLiveTV = async () => { if (this.plex) { const liveTV = await this.plex.getLiveTV(); - console.log(await this.plex.getEPG()); lodash.forEach(liveTV, (data, key) => { this.data[key] = data; }); } }; + const getEPG = async () => { + if (this.plex) { + this.epgData = await this.plex.getEPG(); + } + }; let sectionKey = 0; lodash.forEach(plexAllSections, (section) => { if (lodash.isEqual(section.title, this.config.libraryName)) { @@ -21291,7 +21296,15 @@ class PlexMeetsHomeAssistant extends HTMLElement { loadDataRequests.push(getRecentyAdded()); } loadDataRequests.push(getLiveTV()); + loadDataRequests.push(getEPG()); const [plexSections] = await Promise.all(loadDataRequests); + lodash.forEach(this.epgData, (value, key) => { + lodash.forEach(this.data[key], (libraryData, libraryKey) => { + if (!lodash.isNil(this.epgData[key][libraryData.channelCallSign])) { + this.data[key][libraryKey].epg = this.epgData[key][libraryData.channelCallSign]; + } + }); + }); if (plexSections && sectionKey) { lodash.forEach(plexSections, section => { this.data[section.title1] = section.Metadata; @@ -21905,7 +21918,15 @@ class PlexMeetsHomeAssistant extends HTMLElement { else { this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.title); } - this.getElementsByClassName('detailsYear')[0].innerHTML = escapeHtml(mainData.year); + if (!lodash.isNil(mainData.year)) { + this.getElementsByClassName('detailsYear')[0].innerHTML = escapeHtml(mainData.year); + } + else if (!lodash.isNil(mainData.epg) && !lodash.isNil(mainData.epg.title)) { + this.getElementsByClassName('detailsYear')[0].innerHTML = escapeHtml(mainData.epg.title); + } + else { + this.getElementsByClassName('detailsYear')[0].innerHTML = ''; + } this.getElementsByClassName('metaInfo')[0].innerHTML = `${(mainData.duration !== undefined ? `${Math.round(parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000)} min` : '') + @@ -21915,7 +21936,15 @@ class PlexMeetsHomeAssistant extends HTMLElement { (mainData.rating !== undefined ? `${mainData.rating < 5 ? '🗑' : '⭐'} ${Math.round(parseFloat(escapeHtml(mainData.rating)) * 10) / 10}` : '')}
`; - this.getElementsByClassName('detailDesc')[0].innerHTML = escapeHtml(mainData.summary); + if (!lodash.isNil(mainData.summary)) { + this.getElementsByClassName('detailDesc')[0].innerHTML = escapeHtml(mainData.summary); + } + else if (!lodash.isNil(mainData.epg) && !lodash.isNil(mainData.epg.summary)) { + this.getElementsByClassName('detailDesc')[0].innerHTML = escapeHtml(mainData.epg.summary); + } + else { + this.getElementsByClassName('detailDesc')[0].innerHTML = ''; + } /* todo temp disabled if (data.type === 'movie') { (this.detailElem.children[5] as HTMLElement).style.visibility = 'visible'; @@ -22411,9 +22440,12 @@ class PlexMeetsHomeAssistant extends HTMLElement { if (lodash.isEqual(data.type, 'episode')) { yearElem.innerHTML = escapeHtml(data.title); } - else { + else if (!lodash.isNil(data.year)) { yearElem.innerHTML = escapeHtml(data.year); } + else if (!lodash.isNil(data.epg)) { + yearElem.innerHTML = escapeHtml(data.epg.title); + } yearElem.className = 'yearElem'; const additionalElem = document.createElement('div'); if (lodash.isEqual(data.type, 'episode')) { diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 1e3a0c4..082bc17 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -35,6 +35,8 @@ class PlexMeetsHomeAssistant extends HTMLElement { plexPort: number | false = false; + epgData: Record = {}; + detailsShown = false; entityRegistry: Array> = []; @@ -406,13 +408,18 @@ class PlexMeetsHomeAssistant extends HTMLElement { const getLiveTV = async (): Promise => { if (this.plex) { const liveTV = await this.plex.getLiveTV(); - console.log(await this.plex.getEPG()); _.forEach(liveTV, (data, key) => { this.data[key] = data; }); } }; + const getEPG = async (): Promise => { + if (this.plex) { + this.epgData = await this.plex.getEPG(); + } + }; + let sectionKey: number | false = 0; _.forEach(plexAllSections, (section: Record) => { if (_.isEqual(section.title, this.config.libraryName)) { @@ -436,8 +443,16 @@ class PlexMeetsHomeAssistant extends HTMLElement { } loadDataRequests.push(getLiveTV()); + loadDataRequests.push(getEPG()); const [plexSections] = await Promise.all(loadDataRequests); + _.forEach(this.epgData, (value, key) => { + _.forEach(this.data[key], (libraryData, libraryKey) => { + if (!_.isNil(this.epgData[key][libraryData.channelCallSign])) { + this.data[key][libraryKey].epg = this.epgData[key][libraryData.channelCallSign]; + } + }); + }); if (plexSections && sectionKey) { _.forEach(plexSections, section => { @@ -1107,7 +1122,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { (this.getElementsByClassName('detailsTitle')[0] as HTMLElement).innerHTML = escapeHtml(mainData.title); } - (this.getElementsByClassName('detailsYear')[0] as HTMLElement).innerHTML = escapeHtml(mainData.year); + if (!_.isNil(mainData.year)) { + (this.getElementsByClassName('detailsYear')[0] as HTMLElement).innerHTML = escapeHtml(mainData.year); + } else if (!_.isNil(mainData.epg) && !_.isNil(mainData.epg.title)) { + (this.getElementsByClassName('detailsYear')[0] as HTMLElement).innerHTML = escapeHtml(mainData.epg.title); + } else { + (this.getElementsByClassName('detailsYear')[0] as HTMLElement).innerHTML = ''; + } + (this.getElementsByClassName('metaInfo')[0] as HTMLElement).innerHTML = `${(mainData.duration !== undefined ? `${Math.round( parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000 @@ -1121,7 +1143,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { parseFloat(escapeHtml(mainData.rating)) * 10 ) / 10}` : '')}
`; - (this.getElementsByClassName('detailDesc')[0] as HTMLElement).innerHTML = escapeHtml(mainData.summary); + + if (!_.isNil(mainData.summary)) { + (this.getElementsByClassName('detailDesc')[0] as HTMLElement).innerHTML = escapeHtml(mainData.summary); + } else if (!_.isNil(mainData.epg) && !_.isNil(mainData.epg.summary)) { + (this.getElementsByClassName('detailDesc')[0] as HTMLElement).innerHTML = escapeHtml(mainData.epg.summary); + } else { + (this.getElementsByClassName('detailDesc')[0] as HTMLElement).innerHTML = ''; + } /* todo temp disabled if (data.type === 'movie') { @@ -1672,8 +1701,10 @@ class PlexMeetsHomeAssistant extends HTMLElement { const yearElem = document.createElement('div'); if (_.isEqual(data.type, 'episode')) { yearElem.innerHTML = escapeHtml(data.title); - } else { + } else if (!_.isNil(data.year)) { yearElem.innerHTML = escapeHtml(data.year); + } else if (!_.isNil(data.epg)) { + yearElem.innerHTML = escapeHtml(data.epg.title); } yearElem.className = 'yearElem'; From 0bac7f089e98107e8725bf88848b860c943f9dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Thu, 2 Sep 2021 15:46:14 +0200 Subject: [PATCH 7/9] Add: WIP play live TV --- dist/plex-meets-homeassistant.js | 49 +++++++++++++++++++------------- src/const.ts | 6 ++-- src/modules/PlayController.ts | 48 +++++++++++++++++++++---------- src/plex-meets-homeassistant.ts | 2 +- 4 files changed, 67 insertions(+), 38 deletions(-) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 7590953..f6d9c1a 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -17205,9 +17205,9 @@ const CSS_STYLE = { }; const supported = { kodi: ['movie', 'episode'], - androidtv: ['movie', 'show', 'season', 'episode', 'clip'], - plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'], - cast: ['movie', 'episode'] + androidtv: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], + plexPlayer: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], + cast: ['movie', 'episode', 'epg'] }; var bind = function bind(fn, thisArg) { @@ -19283,6 +19283,7 @@ class PlayController { return foundResult; }; this.play = async (data, instantPlay = false) => { + console.log('play'); if (lodash.isArray(this.runBefore)) { const entityID = `${this.runBefore[0]}.${this.runBefore[1]}`; await this.hass.callService(this.runBefore[0], this.runBefore[1], {}); @@ -19292,26 +19293,35 @@ class PlayController { } } const entity = this.getPlayService(data); + let processData = data; + let provider; + if (!lodash.isNil(data.epg)) { + processData = data.epg; + provider = ''; + } + console.log(processData); switch (entity.key) { case 'kodi': - await this.playViaKodi(entity.value, data, data.type); + await this.playViaKodi(entity.value, processData, processData.type); break; case 'androidtv': - await this.playViaAndroidTV(entity.value, data.key.split('/')[3], instantPlay); + await this.playViaAndroidTV(entity.value, processData.key, instantPlay, provider); break; case 'plexPlayer': - await this.playViaPlexPlayer(entity.value, data.key.split('/')[3]); + await this.playViaPlexPlayer(entity.value, processData.key.split('/')[3]); break; case 'cast': if (this.hass.services.plex) { - const libraryName = lodash.isNil(data.librarySectionTitle) ? this.libraryName : data.librarySectionTitle; + const libraryName = lodash.isNil(processData.librarySectionTitle) + ? this.libraryName + : processData.librarySectionTitle; try { - switch (data.type) { + switch (processData.type) { case 'movie': await this.playViaCastPlex(entity.value, 'movie', `plex://${JSON.stringify({ // eslint-disable-next-line @typescript-eslint/camelcase library_name: libraryName, - title: data.title + title: processData.title })}`); break; case 'episode': @@ -19319,28 +19329,28 @@ class PlayController { // eslint-disable-next-line @typescript-eslint/camelcase library_name: libraryName, // eslint-disable-next-line @typescript-eslint/camelcase - show_name: data.grandparentTitle, + show_name: processData.grandparentTitle, // eslint-disable-next-line @typescript-eslint/camelcase - season_number: data.parentIndex, + season_number: processData.parentIndex, // eslint-disable-next-line @typescript-eslint/camelcase - episode_number: data.index + episode_number: processData.index })}`); break; default: - this.playViaCast(entity.value, data.Media[0].Part[0].key); + this.playViaCast(entity.value, processData.Media[0].Part[0].key); } } catch (err) { console.log(err); - this.playViaCast(entity.value, data.Media[0].Part[0].key); + this.playViaCast(entity.value, processData.Media[0].Part[0].key); } } else { - this.playViaCast(entity.value, data.Media[0].Part[0].key); + this.playViaCast(entity.value, processData.Media[0].Part[0].key); } break; default: - throw Error(`No service available to play ${data.title}!`); + throw Error(`No service available to play ${processData.title}!`); } if (lodash.isArray(this.runAfter)) { await this.hass.callService(this.runAfter[0], this.runAfter[1], {}); @@ -19481,13 +19491,14 @@ class PlayController { media_content_id: mediaLink }); }; - this.playViaAndroidTV = async (entityName, mediaID, instantPlay = false) => { + this.playViaAndroidTV = async (entityName, mediaID, instantPlay = false, provider = 'com.plexapp.plugins.library') => { const serverID = await this.plex.getServerID(); let command = `am start`; if (instantPlay) { command += ' --ez "android.intent.extra.START_PLAYBACK" true'; } - command += ` -a android.intent.action.VIEW 'plex://server://${serverID}/com.plexapp.plugins.library/library/metadata/${mediaID}'`; + command += ` -a android.intent.action.VIEW 'plex://server://${serverID}/${provider}${mediaID}'`; + console.log(command); this.hass.callService('androidtv', 'adb_command', { // eslint-disable-next-line @typescript-eslint/camelcase entity_id: entityName, @@ -21302,6 +21313,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { lodash.forEach(this.data[key], (libraryData, libraryKey) => { if (!lodash.isNil(this.epgData[key][libraryData.channelCallSign])) { this.data[key][libraryKey].epg = this.epgData[key][libraryData.channelCallSign]; + this.data[key][libraryKey].type = 'epg'; } }); }); @@ -21850,7 +21862,6 @@ class PlexMeetsHomeAssistant extends HTMLElement { fullscreenTrailer.style.visibility = 'hidden'; }; this.showDetails = async (data) => { - console.log(data); this.detailsShown = true; const top = this.getTop(); if (this.detailElem) { diff --git a/src/const.ts b/src/const.ts index 12de975..1c65a35 100644 --- a/src/const.ts +++ b/src/const.ts @@ -10,9 +10,9 @@ const CSS_STYLE: any = { const supported: any = { kodi: ['movie', 'episode'], - androidtv: ['movie', 'show', 'season', 'episode', 'clip'], - plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'], - cast: ['movie', 'episode'] + androidtv: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], + plexPlayer: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], + cast: ['movie', 'episode', 'epg'] }; 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 f63dc46..88baf99 100644 --- a/src/modules/PlayController.ts +++ b/src/modules/PlayController.ts @@ -103,6 +103,7 @@ class PlayController { }; play = async (data: Record, instantPlay = false): Promise => { + console.log('play'); if (_.isArray(this.runBefore)) { const entityID = `${this.runBefore[0]}.${this.runBefore[1]}`; await this.hass.callService(this.runBefore[0], this.runBefore[1], {}); @@ -113,21 +114,31 @@ class PlayController { } } const entity = this.getPlayService(data); + + let processData = data; + let provider; + if (!_.isNil(data.epg)) { + processData = data.epg; + provider = ''; + } + console.log(processData); switch (entity.key) { case 'kodi': - await this.playViaKodi(entity.value, data, data.type); + await this.playViaKodi(entity.value, processData, processData.type); break; case 'androidtv': - await this.playViaAndroidTV(entity.value, data.key.split('/')[3], instantPlay); + await this.playViaAndroidTV(entity.value, processData.key, instantPlay, provider); break; case 'plexPlayer': - await this.playViaPlexPlayer(entity.value, data.key.split('/')[3]); + await this.playViaPlexPlayer(entity.value, processData.key.split('/')[3]); break; case 'cast': if (this.hass.services.plex) { - const libraryName = _.isNil(data.librarySectionTitle) ? this.libraryName : data.librarySectionTitle; + const libraryName = _.isNil(processData.librarySectionTitle) + ? this.libraryName + : processData.librarySectionTitle; try { - switch (data.type) { + switch (processData.type) { case 'movie': await this.playViaCastPlex( entity.value, @@ -135,7 +146,7 @@ class PlayController { `plex://${JSON.stringify({ // eslint-disable-next-line @typescript-eslint/camelcase library_name: libraryName, - title: data.title + title: processData.title })}` ); break; @@ -147,27 +158,27 @@ class PlayController { // eslint-disable-next-line @typescript-eslint/camelcase library_name: libraryName, // eslint-disable-next-line @typescript-eslint/camelcase - show_name: data.grandparentTitle, + show_name: processData.grandparentTitle, // eslint-disable-next-line @typescript-eslint/camelcase - season_number: data.parentIndex, + season_number: processData.parentIndex, // eslint-disable-next-line @typescript-eslint/camelcase - episode_number: data.index + episode_number: processData.index })}` ); break; default: - this.playViaCast(entity.value, data.Media[0].Part[0].key); + this.playViaCast(entity.value, processData.Media[0].Part[0].key); } } catch (err) { console.log(err); - this.playViaCast(entity.value, data.Media[0].Part[0].key); + this.playViaCast(entity.value, processData.Media[0].Part[0].key); } } else { - this.playViaCast(entity.value, data.Media[0].Part[0].key); + this.playViaCast(entity.value, processData.Media[0].Part[0].key); } break; default: - throw Error(`No service available to play ${data.title}!`); + throw Error(`No service available to play ${processData.title}!`); } if (_.isArray(this.runAfter)) { await this.hass.callService(this.runAfter[0], this.runAfter[1], {}); @@ -317,7 +328,12 @@ class PlayController { }); }; - private playViaAndroidTV = async (entityName: string, mediaID: number, instantPlay = false): Promise => { + private playViaAndroidTV = async ( + entityName: string, + mediaID: string, + instantPlay = false, + provider = 'com.plexapp.plugins.library' + ): Promise => { const serverID = await this.plex.getServerID(); let command = `am start`; @@ -325,7 +341,9 @@ class PlayController { command += ' --ez "android.intent.extra.START_PLAYBACK" true'; } - command += ` -a android.intent.action.VIEW 'plex://server://${serverID}/com.plexapp.plugins.library/library/metadata/${mediaID}'`; + command += ` -a android.intent.action.VIEW 'plex://server://${serverID}/${provider}${mediaID}'`; + + console.log(command); this.hass.callService('androidtv', 'adb_command', { // eslint-disable-next-line @typescript-eslint/camelcase diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 082bc17..8c7ecf4 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -450,6 +450,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { _.forEach(this.data[key], (libraryData, libraryKey) => { if (!_.isNil(this.epgData[key][libraryData.channelCallSign])) { this.data[key][libraryKey].epg = this.epgData[key][libraryData.channelCallSign]; + this.data[key][libraryKey].type = 'epg'; } }); }); @@ -1054,7 +1055,6 @@ class PlexMeetsHomeAssistant extends HTMLElement { }; showDetails = async (data: any): Promise => { - console.log(data); this.detailsShown = true; const top = this.getTop(); if (this.detailElem) { From 0a5705c61a60f046263f88d9f7670fbd2f9ea5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Thu, 2 Sep 2021 15:57:49 +0200 Subject: [PATCH 8/9] Update: Cleanup --- dist/plex-meets-homeassistant.js | 12 ++++++------ src/const.ts | 6 +++--- src/modules/PlayController.ts | 4 ---- src/plex-meets-homeassistant.ts | 3 +++ 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index f6d9c1a..057730d 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -17205,9 +17205,9 @@ const CSS_STYLE = { }; const supported = { kodi: ['movie', 'episode'], - androidtv: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], - plexPlayer: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], - cast: ['movie', 'episode', 'epg'] + androidtv: ['movie', 'show', 'season', 'episode', 'clip'], + plexPlayer: ['movie', 'show', 'season', 'episode', 'clip'], + cast: ['movie', 'episode'] }; var bind = function bind(fn, thisArg) { @@ -19283,7 +19283,6 @@ class PlayController { return foundResult; }; this.play = async (data, instantPlay = false) => { - console.log('play'); if (lodash.isArray(this.runBefore)) { const entityID = `${this.runBefore[0]}.${this.runBefore[1]}`; await this.hass.callService(this.runBefore[0], this.runBefore[1], {}); @@ -19299,7 +19298,6 @@ class PlayController { processData = data.epg; provider = ''; } - console.log(processData); switch (entity.key) { case 'kodi': await this.playViaKodi(entity.value, processData, processData.type); @@ -19498,7 +19496,6 @@ class PlayController { command += ' --ez "android.intent.extra.START_PLAYBACK" true'; } command += ` -a android.intent.action.VIEW 'plex://server://${serverID}/${provider}${mediaID}'`; - console.log(command); this.hass.callService('androidtv', 'adb_command', { // eslint-disable-next-line @typescript-eslint/camelcase entity_id: entityName, @@ -22378,6 +22375,9 @@ class PlexMeetsHomeAssistant extends HTMLElement { else { container.style.height = `${CSS_STYLE.height + 30}px`; } + if (!lodash.isNil(data.channelCallSign)) { + container.style.marginBottom = '50px'; + } const movieElem = document.createElement('div'); movieElem.className = 'movieElem'; movieElem.style.width = `${CSS_STYLE.width}px`; diff --git a/src/const.ts b/src/const.ts index 1c65a35..12de975 100644 --- a/src/const.ts +++ b/src/const.ts @@ -10,9 +10,9 @@ const CSS_STYLE: any = { const supported: any = { kodi: ['movie', 'episode'], - androidtv: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], - plexPlayer: ['movie', 'show', 'season', 'episode', 'clip', 'epg'], - cast: ['movie', 'episode', 'epg'] + androidtv: ['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 88baf99..91e04ae 100644 --- a/src/modules/PlayController.ts +++ b/src/modules/PlayController.ts @@ -103,7 +103,6 @@ class PlayController { }; play = async (data: Record, instantPlay = false): Promise => { - console.log('play'); if (_.isArray(this.runBefore)) { const entityID = `${this.runBefore[0]}.${this.runBefore[1]}`; await this.hass.callService(this.runBefore[0], this.runBefore[1], {}); @@ -121,7 +120,6 @@ class PlayController { processData = data.epg; provider = ''; } - console.log(processData); switch (entity.key) { case 'kodi': await this.playViaKodi(entity.value, processData, processData.type); @@ -343,8 +341,6 @@ class PlayController { command += ` -a android.intent.action.VIEW 'plex://server://${serverID}/${provider}${mediaID}'`; - console.log(command); - this.hass.callService('androidtv', 'adb_command', { // eslint-disable-next-line @typescript-eslint/camelcase entity_id: entityName, diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 8c7ecf4..0a39bc0 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -1613,6 +1613,9 @@ class PlexMeetsHomeAssistant extends HTMLElement { } else { container.style.height = `${CSS_STYLE.height + 30}px`; } + if (!_.isNil(data.channelCallSign)) { + container.style.marginBottom = '50px'; + } const movieElem = document.createElement('div'); movieElem.className = 'movieElem'; From f34051c44e01ae1dcafae2ad1c0832f36aaa3d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Thu, 2 Sep 2021 16:04:06 +0200 Subject: [PATCH 9/9] Add: Warning about live TV play action not supported by Plex --- dist/plex-meets-homeassistant.js | 6 ++++++ src/editor.ts | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 057730d..e83cc03 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -19893,7 +19893,10 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.libraryName.appendChild(libraryItems); this.libraryName.style.width = '100%'; this.libraryName.addEventListener('value-changed', this.valueUpdated); + const warningLibrary = document.createElement('div'); + warningLibrary.style.color = 'red'; this.content.appendChild(this.libraryName); + this.content.appendChild(warningLibrary); this.appendChild(this.content); this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort); this.sections = await this.plex.getSections(); @@ -20110,6 +20113,9 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { if (!lodash.isEmpty(this.livetv)) { libraryItems.appendChild(addDropdownItem('Live TV', true)); lodash.forEach(lodash.keys(this.livetv), (livetv) => { + if (lodash.isEqual(this.config.libraryName, livetv)) { + warningLibrary.textContent = `Warning: ${this.config.libraryName} play action currently not supported by Plex.`; + } libraryItems.appendChild(addDropdownItem(livetv)); }); } diff --git a/src/editor.ts b/src/editor.ts index 3ff32ad..0904995 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -296,7 +296,11 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { this.libraryName.appendChild(libraryItems); this.libraryName.style.width = '100%'; this.libraryName.addEventListener('value-changed', this.valueUpdated); + + const warningLibrary = document.createElement('div'); + warningLibrary.style.color = 'red'; this.content.appendChild(this.libraryName); + this.content.appendChild(warningLibrary); this.appendChild(this.content); @@ -525,6 +529,9 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement { if (!_.isEmpty(this.livetv)) { libraryItems.appendChild(addDropdownItem('Live TV', true)); _.forEach(_.keys(this.livetv), (livetv: string) => { + if (_.isEqual(this.config.libraryName, livetv)) { + warningLibrary.textContent = `Warning: ${this.config.libraryName} play action currently not supported by Plex.`; + } libraryItems.appendChild(addDropdownItem(livetv)); }); }