Merge branch 'live_tv' into main

live_tv_play
Juraj Nyíri 3 years ago
commit c2b109b76d

@ -18674,6 +18674,9 @@ class Plex {
this.clients = []; this.clients = [];
this.requestTimeout = 10000; this.requestTimeout = 10000;
this.sections = []; this.sections = [];
this.providers = [];
this.livetv = {};
this.livetvepg = {};
this.collections = false; this.collections = false;
this.playlists = []; this.playlists = [];
this.init = async () => { this.init = async () => {
@ -18692,6 +18695,67 @@ class Plex {
} }
return this.clients; 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.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 () => { this.getServerID = async () => {
if (lodash.isEmpty(this.serverInfo)) { if (lodash.isEmpty(this.serverInfo)) {
await this.getServerInfo(); await this.getServerInfo();
@ -19228,26 +19292,34 @@ class PlayController {
} }
} }
const entity = this.getPlayService(data); const entity = this.getPlayService(data);
let processData = data;
let provider;
if (!lodash.isNil(data.epg)) {
processData = data.epg;
provider = '';
}
switch (entity.key) { switch (entity.key) {
case 'kodi': case 'kodi':
await this.playViaKodi(entity.value, data, data.type); await this.playViaKodi(entity.value, processData, processData.type);
break; break;
case 'androidtv': case 'androidtv':
await this.playViaAndroidTV(entity.value, data.key.split('/')[3], instantPlay); await this.playViaAndroidTV(entity.value, processData.key, instantPlay, provider);
break; break;
case 'plexPlayer': case 'plexPlayer':
await this.playViaPlexPlayer(entity.value, data.key.split('/')[3]); await this.playViaPlexPlayer(entity.value, processData.key.split('/')[3]);
break; break;
case 'cast': case 'cast':
if (this.hass.services.plex) { 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 { try {
switch (data.type) { switch (processData.type) {
case 'movie': case 'movie':
await this.playViaCastPlex(entity.value, 'movie', `plex://${JSON.stringify({ await this.playViaCastPlex(entity.value, 'movie', `plex://${JSON.stringify({
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
library_name: libraryName, library_name: libraryName,
title: data.title title: processData.title
})}`); })}`);
break; break;
case 'episode': case 'episode':
@ -19255,28 +19327,28 @@ class PlayController {
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
library_name: libraryName, library_name: libraryName,
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
show_name: data.grandparentTitle, show_name: processData.grandparentTitle,
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
season_number: data.parentIndex, season_number: processData.parentIndex,
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
episode_number: data.index episode_number: processData.index
})}`); })}`);
break; break;
default: default:
this.playViaCast(entity.value, data.Media[0].Part[0].key); this.playViaCast(entity.value, processData.Media[0].Part[0].key);
} }
} }
catch (err) { catch (err) {
console.log(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 { else {
this.playViaCast(entity.value, data.Media[0].Part[0].key); this.playViaCast(entity.value, processData.Media[0].Part[0].key);
} }
break; break;
default: default:
throw Error(`No service available to play ${data.title}!`); throw Error(`No service available to play ${processData.title}!`);
} }
if (lodash.isArray(this.runAfter)) { if (lodash.isArray(this.runAfter)) {
await this.hass.callService(this.runAfter[0], this.runAfter[1], {}); await this.hass.callService(this.runAfter[0], this.runAfter[1], {});
@ -19417,13 +19489,13 @@ class PlayController {
media_content_id: mediaLink 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(); const serverID = await this.plex.getServerID();
let command = `am start`; let command = `am start`;
if (instantPlay) { if (instantPlay) {
command += ' --ez "android.intent.extra.START_PLAYBACK" true'; 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}'`;
this.hass.callService('androidtv', 'adb_command', { this.hass.callService('androidtv', 'adb_command', {
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
entity_id: entityName, entity_id: entityName,
@ -19611,6 +19683,7 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement {
this.entitiesRegistry = false; this.entitiesRegistry = false;
this.plexValidSection = document.createElement('div'); this.plexValidSection = document.createElement('div');
this.loaded = false; this.loaded = false;
this.livetv = {};
this.fireEvent = (node, type, detail, options = {}) => { this.fireEvent = (node, type, detail, options = {}) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
detail = detail === null || detail === undefined ? {} : detail; detail = detail === null || detail === undefined ? {} : detail;
@ -19820,10 +19893,14 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement {
this.libraryName.appendChild(libraryItems); this.libraryName.appendChild(libraryItems);
this.libraryName.style.width = '100%'; this.libraryName.style.width = '100%';
this.libraryName.addEventListener('value-changed', this.valueUpdated); this.libraryName.addEventListener('value-changed', this.valueUpdated);
const warningLibrary = document.createElement('div');
warningLibrary.style.color = 'red';
this.content.appendChild(this.libraryName); this.content.appendChild(this.libraryName);
this.content.appendChild(warningLibrary);
this.appendChild(this.content); this.appendChild(this.content);
this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort); this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort);
this.sections = await this.plex.getSections(); this.sections = await this.plex.getSections();
this.livetv = await this.plex.getLiveTV();
this.collections = await this.plex.getCollections(); this.collections = await this.plex.getCollections();
this.playlists = await this.plex.getPlaylists(); this.playlists = await this.plex.getPlaylists();
this.clients = await this.plex.getClients(); this.clients = await this.plex.getClients();
@ -20033,6 +20110,15 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement {
this.runAfter.addEventListener('value-changed', this.valueUpdated); this.runAfter.addEventListener('value-changed', this.valueUpdated);
this.runAfter.value = this.config.runAfter; this.runAfter.value = this.config.runAfter;
this.plexValidSection.appendChild(this.runAfter); this.plexValidSection.appendChild(this.runAfter);
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));
});
}
if (!lodash.isEmpty(this.sections)) { if (!lodash.isEmpty(this.sections)) {
libraryItems.appendChild(addDropdownItem('Libraries', true)); libraryItems.appendChild(addDropdownItem('Libraries', true));
lodash.forEach(this.sections, (section) => { lodash.forEach(this.sections, (section) => {
@ -20911,6 +20997,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.searchInputElem = document.createElement('input'); this.searchInputElem = document.createElement('input');
this.plexProtocol = 'http'; this.plexProtocol = 'http';
this.plexPort = false; this.plexPort = false;
this.epgData = {};
this.detailsShown = false; this.detailsShown = false;
this.entityRegistry = []; this.entityRegistry = [];
this.runBefore = ''; this.runBefore = '';
@ -21185,6 +21272,19 @@ 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;
});
}
};
const getEPG = async () => {
if (this.plex) {
this.epgData = await this.plex.getEPG();
}
};
let sectionKey = 0; let sectionKey = 0;
lodash.forEach(plexAllSections, (section) => { lodash.forEach(plexAllSections, (section) => {
if (lodash.isEqual(section.title, this.config.libraryName)) { if (lodash.isEqual(section.title, this.config.libraryName)) {
@ -21209,7 +21309,17 @@ class PlexMeetsHomeAssistant extends HTMLElement {
else if (lodash.isEqual(this.config.libraryName, 'Recently Added')) { else if (lodash.isEqual(this.config.libraryName, 'Recently Added')) {
loadDataRequests.push(getRecentyAdded()); loadDataRequests.push(getRecentyAdded());
} }
loadDataRequests.push(getLiveTV());
loadDataRequests.push(getEPG());
const [plexSections] = await Promise.all(loadDataRequests); 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];
this.data[key][libraryKey].type = 'epg';
}
});
});
if (plexSections && sectionKey) { if (plexSections && sectionKey) {
lodash.forEach(plexSections, section => { lodash.forEach(plexSections, section => {
this.data[section.title1] = section.Metadata; this.data[section.title1] = section.Metadata;
@ -21662,8 +21772,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.activeMovieElem = undefined; this.activeMovieElem = undefined;
for (let i = 0; i < this.movieElems.length; i += 1) { for (let i = 0; i < this.movieElems.length; i += 1) {
if (parseInt(this.movieElems[i].style.width, 10) > CSS_STYLE.width) { if (parseInt(this.movieElems[i].style.width, 10) > CSS_STYLE.width) {
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.width = `${CSS_STYLE.width}px`;
this.movieElems[i].style.height = `${CSS_STYLE.height}px`; this.movieElems[i].style.height = `${CSS_STYLE.height}px`;
}
this.movieElems[i].style['z-index'] = 1; this.movieElems[i].style['z-index'] = 1;
this.movieElems[i].style.position = 'absolute'; this.movieElems[i].style.position = 'absolute';
this.movieElems[i].style.left = `${this.movieElems[i].dataset.left}px`; this.movieElems[i].style.left = `${this.movieElems[i].dataset.left}px`;
@ -21810,8 +21926,21 @@ class PlexMeetsHomeAssistant extends HTMLElement {
genreElem.parentElement.style.display = 'none'; genreElem.parentElement.style.display = 'none';
} }
} }
if (!lodash.isNil(mainData.channelCallSign)) {
this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.channelCallSign);
}
else {
this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.title); this.getElementsByClassName('detailsTitle')[0].innerHTML = escapeHtml(mainData.title);
}
if (!lodash.isNil(mainData.year)) {
this.getElementsByClassName('detailsYear')[0].innerHTML = escapeHtml(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 this.getElementsByClassName('metaInfo')[0].innerHTML = `${(mainData.duration !== undefined
? `<span class='minutesDetail'>${Math.round(parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000)} min</span>` ? `<span class='minutesDetail'>${Math.round(parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000)} min</span>`
: '') + : '') +
@ -21821,7 +21950,15 @@ class PlexMeetsHomeAssistant extends HTMLElement {
(mainData.rating !== undefined (mainData.rating !== undefined
? `<span class='ratingDetail'>${mainData.rating < 5 ? '&#128465;' : '&#11088;'}&nbsp;${Math.round(parseFloat(escapeHtml(mainData.rating)) * 10) / 10}</span>` ? `<span class='ratingDetail'>${mainData.rating < 5 ? '&#128465;' : '&#11088;'}&nbsp;${Math.round(parseFloat(escapeHtml(mainData.rating)) * 10) / 10}</span>`
: '')}<div class='clear'></div>`; : '')}<div class='clear'></div>`;
if (!lodash.isNil(mainData.summary)) {
this.getElementsByClassName('detailDesc')[0].innerHTML = escapeHtml(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 /* todo temp disabled
if (data.type === 'movie') { if (data.type === 'movie') {
(this.detailElem.children[5] as HTMLElement).style.visibility = 'visible'; (this.detailElem.children[5] as HTMLElement).style.visibility = 'visible';
@ -21843,7 +21980,9 @@ class PlexMeetsHomeAssistant extends HTMLElement {
else if (data.childCount > 0) { else if (data.childCount > 0) {
seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]); seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]);
} }
const dataDetails = await this.plex.getDetails(data.key.split('/')[3]); let dataDetails = {};
if (!lodash.isNil(data.key)) {
dataDetails = await this.plex.getDetails(data.key.split('/')[3]);
if (this.videoElem) { if (this.videoElem) {
const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art); const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art);
const trailerURL = findTrailerURL(dataDetails); const trailerURL = findTrailerURL(dataDetails);
@ -21936,6 +22075,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
contentbg.classList.add('no-transparency'); contentbg.classList.add('no-transparency');
} }
} }
}
if (!lodash.isEmpty(seasonsData)) { if (!lodash.isEmpty(seasonsData)) {
this.seasonElemFreshlyLoaded = true; this.seasonElemFreshlyLoaded = true;
if (this.seasonsElem) { if (this.seasonsElem) {
@ -22115,7 +22255,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
} }
}); });
} }
else if (this.showExtras) { else if (this.showExtras && !lodash.isNil(dataDetails.Extras)) {
const extras = dataDetails.Extras.Metadata; const extras = dataDetails.Extras.Metadata;
lodash.forEach(extras, extrasData => { lodash.forEach(extras, extrasData => {
if (this.episodesElem && this.playController && this.plex) { if (this.episodesElem && this.playController && this.plex) {
@ -22175,8 +22315,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.minimizeAll(); this.minimizeAll();
this.activeMovieElem = undefined; this.activeMovieElem = undefined;
this.hideDetails(); this.hideDetails();
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.width = `${CSS_STYLE.width}px`;
movieElemLocal.style.height = `${CSS_STYLE.height}px`; movieElemLocal.style.height = `${CSS_STYLE.height}px`;
}
movieElemLocal.style.zIndex = '1'; movieElemLocal.style.zIndex = '1';
movieElemLocal.style.top = `${movieElem.dataset.top}px`; movieElemLocal.style.top = `${movieElem.dataset.top}px`;
movieElemLocal.style.left = `${movieElem.dataset.left}px`; movieElemLocal.style.left = `${movieElem.dataset.left}px`;
@ -22189,8 +22335,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
const top = this.getTop(); const top = this.getTop();
this.showDetails(this.activeMovieElemData); this.showDetails(this.activeMovieElemData);
this.showBackground(); this.showBackground();
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.width = `${CSS_STYLE.expandedWidth}px`;
movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`; movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`;
}
movieElemLocal.style.zIndex = '3'; movieElemLocal.style.zIndex = '3';
movieElemLocal.style.left = '16px'; movieElemLocal.style.left = '16px';
movieElemLocal.style.top = `${top + 16}px`; movieElemLocal.style.top = `${top + 16}px`;
@ -22229,10 +22381,20 @@ class PlexMeetsHomeAssistant extends HTMLElement {
else { else {
container.style.height = `${CSS_STYLE.height + 30}px`; container.style.height = `${CSS_STYLE.height + 30}px`;
} }
if (!lodash.isNil(data.channelCallSign)) {
container.style.marginBottom = '50px';
}
const movieElem = document.createElement('div'); const movieElem = document.createElement('div');
movieElem.className = 'movieElem'; movieElem.className = 'movieElem';
movieElem.style.width = `${CSS_STYLE.width}px`; movieElem.style.width = `${CSS_STYLE.width}px`;
movieElem.style.height = `${CSS_STYLE.height}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}')`; movieElem.style.backgroundImage = `url('${thumbURL}')`;
if (this.playController && !this.playController.isPlaySupported(data)) { if (this.playController && !this.playController.isPlaySupported(data)) {
movieElem.style.cursor = 'pointer'; movieElem.style.cursor = 'pointer';
@ -22278,18 +22440,29 @@ class PlexMeetsHomeAssistant extends HTMLElement {
if (lodash.isEqual(data.type, 'episode')) { if (lodash.isEqual(data.type, 'episode')) {
titleElem.innerHTML = escapeHtml(data.grandparentTitle); titleElem.innerHTML = escapeHtml(data.grandparentTitle);
} }
else if (!lodash.isNil(data.channelCallSign)) {
titleElem.innerHTML = escapeHtml(data.channelCallSign);
}
else { else {
titleElem.innerHTML = escapeHtml(data.title); titleElem.innerHTML = escapeHtml(data.title);
} }
titleElem.className = 'titleElem'; titleElem.className = 'titleElem';
if (!lodash.isNil(data.channelCallSign)) {
titleElem.style.marginTop = `${CSS_STYLE.width}px`;
}
else {
titleElem.style.marginTop = `${CSS_STYLE.height}px`; titleElem.style.marginTop = `${CSS_STYLE.height}px`;
}
const yearElem = document.createElement('div'); const yearElem = document.createElement('div');
if (lodash.isEqual(data.type, 'episode')) { if (lodash.isEqual(data.type, 'episode')) {
yearElem.innerHTML = escapeHtml(data.title); yearElem.innerHTML = escapeHtml(data.title);
} }
else { else if (!lodash.isNil(data.year)) {
yearElem.innerHTML = escapeHtml(data.year); yearElem.innerHTML = escapeHtml(data.year);
} }
else if (!lodash.isNil(data.epg)) {
yearElem.innerHTML = escapeHtml(data.epg.title);
}
yearElem.className = 'yearElem'; yearElem.className = 'yearElem';
const additionalElem = document.createElement('div'); const additionalElem = document.createElement('div');
if (lodash.isEqual(data.type, 'episode')) { if (lodash.isEqual(data.type, 'episode')) {

@ -72,6 +72,8 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement {
loaded = false; loaded = false;
livetv: Record<string, any> = {};
fireEvent = ( fireEvent = (
node: HTMLElement, node: HTMLElement,
type: string, type: string,
@ -294,12 +296,17 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement {
this.libraryName.appendChild(libraryItems); this.libraryName.appendChild(libraryItems);
this.libraryName.style.width = '100%'; this.libraryName.style.width = '100%';
this.libraryName.addEventListener('value-changed', this.valueUpdated); this.libraryName.addEventListener('value-changed', this.valueUpdated);
const warningLibrary = document.createElement('div');
warningLibrary.style.color = 'red';
this.content.appendChild(this.libraryName); this.content.appendChild(this.libraryName);
this.content.appendChild(warningLibrary);
this.appendChild(this.content); this.appendChild(this.content);
this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort); this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort);
this.sections = await this.plex.getSections(); this.sections = await this.plex.getSections();
this.livetv = await this.plex.getLiveTV();
this.collections = await this.plex.getCollections(); this.collections = await this.plex.getCollections();
this.playlists = await this.plex.getPlaylists(); this.playlists = await this.plex.getPlaylists();
this.clients = await this.plex.getClients(); this.clients = await this.plex.getClients();
@ -519,6 +526,15 @@ class PlexMeetsHomeAssistantEditor extends HTMLElement {
this.runAfter.value = this.config.runAfter; this.runAfter.value = this.config.runAfter;
this.plexValidSection.appendChild(this.runAfter); this.plexValidSection.appendChild(this.runAfter);
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));
});
}
if (!_.isEmpty(this.sections)) { if (!_.isEmpty(this.sections)) {
libraryItems.appendChild(addDropdownItem('Libraries', true)); libraryItems.appendChild(addDropdownItem('Libraries', true));
_.forEach(this.sections, (section: Record<string, any>) => { _.forEach(this.sections, (section: Record<string, any>) => {

@ -113,21 +113,30 @@ class PlayController {
} }
} }
const entity = this.getPlayService(data); const entity = this.getPlayService(data);
let processData = data;
let provider;
if (!_.isNil(data.epg)) {
processData = data.epg;
provider = '';
}
switch (entity.key) { switch (entity.key) {
case 'kodi': case 'kodi':
await this.playViaKodi(entity.value, data, data.type); await this.playViaKodi(entity.value, processData, processData.type);
break; break;
case 'androidtv': case 'androidtv':
await this.playViaAndroidTV(entity.value, data.key.split('/')[3], instantPlay); await this.playViaAndroidTV(entity.value, processData.key, instantPlay, provider);
break; break;
case 'plexPlayer': case 'plexPlayer':
await this.playViaPlexPlayer(entity.value, data.key.split('/')[3]); await this.playViaPlexPlayer(entity.value, processData.key.split('/')[3]);
break; break;
case 'cast': case 'cast':
if (this.hass.services.plex) { if (this.hass.services.plex) {
const libraryName = _.isNil(data.librarySectionTitle) ? this.libraryName : data.librarySectionTitle; const libraryName = _.isNil(processData.librarySectionTitle)
? this.libraryName
: processData.librarySectionTitle;
try { try {
switch (data.type) { switch (processData.type) {
case 'movie': case 'movie':
await this.playViaCastPlex( await this.playViaCastPlex(
entity.value, entity.value,
@ -135,7 +144,7 @@ class PlayController {
`plex://${JSON.stringify({ `plex://${JSON.stringify({
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
library_name: libraryName, library_name: libraryName,
title: data.title title: processData.title
})}` })}`
); );
break; break;
@ -147,27 +156,27 @@ class PlayController {
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
library_name: libraryName, library_name: libraryName,
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
show_name: data.grandparentTitle, show_name: processData.grandparentTitle,
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
season_number: data.parentIndex, season_number: processData.parentIndex,
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase
episode_number: data.index episode_number: processData.index
})}` })}`
); );
break; break;
default: default:
this.playViaCast(entity.value, data.Media[0].Part[0].key); this.playViaCast(entity.value, processData.Media[0].Part[0].key);
} }
} catch (err) { } catch (err) {
console.log(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 { } else {
this.playViaCast(entity.value, data.Media[0].Part[0].key); this.playViaCast(entity.value, processData.Media[0].Part[0].key);
} }
break; break;
default: default:
throw Error(`No service available to play ${data.title}!`); throw Error(`No service available to play ${processData.title}!`);
} }
if (_.isArray(this.runAfter)) { if (_.isArray(this.runAfter)) {
await this.hass.callService(this.runAfter[0], this.runAfter[1], {}); await this.hass.callService(this.runAfter[0], this.runAfter[1], {});
@ -317,7 +326,12 @@ class PlayController {
}); });
}; };
private playViaAndroidTV = async (entityName: string, mediaID: number, instantPlay = false): Promise<void> => { private playViaAndroidTV = async (
entityName: string,
mediaID: string,
instantPlay = false,
provider = 'com.plexapp.plugins.library'
): Promise<void> => {
const serverID = await this.plex.getServerID(); const serverID = await this.plex.getServerID();
let command = `am start`; let command = `am start`;
@ -325,7 +339,7 @@ class PlayController {
command += ' --ez "android.intent.extra.START_PLAYBACK" true'; 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}'`;
this.hass.callService('androidtv', 'adb_command', { this.hass.callService('androidtv', 'adb_command', {
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase

@ -21,6 +21,12 @@ class Plex {
sections: Array<Record<string, any>> = []; sections: Array<Record<string, any>> = [];
providers: Array<Record<string, any>> = [];
livetv: Record<string, any> = {};
livetvepg: Record<string, any> = {};
collections: Array<Record<string, any>> | false = false; collections: Array<Record<string, any>> | false = false;
playlists: Array<Record<string, any>> = []; playlists: Array<Record<string, any>> = [];
@ -56,6 +62,74 @@ class Plex {
return this.clients; return this.clients;
}; };
getProviders = async (): Promise<any> => {
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<Record<string, any>> => {
if (_.isEmpty(this.livetv)) {
const returnData: Record<string, any> = {};
const providers = await this.getProviders();
const liveTVRequests: Array<Promise<any>> = [];
const liveTVRequestsNames: Array<string> = [];
_.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;
};
getEPG = async (): Promise<Record<string, any>> => {
if (_.isEmpty(this.livetvepg)) {
const returnData: Record<string, any> = {};
const providers = await this.getProviders();
const liveTVRequests: Array<Promise<any>> = [];
const liveTVRequestsNames: Array<string> = [];
_.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<any> => { getServerID = async (): Promise<any> => {
if (_.isEmpty(this.serverInfo)) { if (_.isEmpty(this.serverInfo)) {
await this.getServerInfo(); await this.getServerInfo();

@ -35,6 +35,8 @@ class PlexMeetsHomeAssistant extends HTMLElement {
plexPort: number | false = false; plexPort: number | false = false;
epgData: Record<string, any> = {};
detailsShown = false; detailsShown = false;
entityRegistry: Array<Record<string, any>> = []; entityRegistry: Array<Record<string, any>> = [];
@ -403,6 +405,21 @@ class PlexMeetsHomeAssistant extends HTMLElement {
} }
}; };
const getLiveTV = async (): Promise<void> => {
if (this.plex) {
const liveTV = await this.plex.getLiveTV();
_.forEach(liveTV, (data, key) => {
this.data[key] = data;
});
}
};
const getEPG = async (): Promise<void> => {
if (this.plex) {
this.epgData = await this.plex.getEPG();
}
};
let sectionKey: number | false = 0; let sectionKey: number | false = 0;
_.forEach(plexAllSections, (section: Record<string, any>) => { _.forEach(plexAllSections, (section: Record<string, any>) => {
if (_.isEqual(section.title, this.config.libraryName)) { if (_.isEqual(section.title, this.config.libraryName)) {
@ -425,7 +442,18 @@ class PlexMeetsHomeAssistant extends HTMLElement {
loadDataRequests.push(getRecentyAdded()); loadDataRequests.push(getRecentyAdded());
} }
loadDataRequests.push(getLiveTV());
loadDataRequests.push(getEPG());
const [plexSections] = await Promise.all(loadDataRequests); 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];
this.data[key][libraryKey].type = 'epg';
}
});
});
if (plexSections && sectionKey) { if (plexSections && sectionKey) {
_.forEach(plexSections, section => { _.forEach(plexSections, section => {
@ -929,8 +957,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.activeMovieElem = undefined; this.activeMovieElem = undefined;
for (let i = 0; i < this.movieElems.length; i += 1) { for (let i = 0; i < this.movieElems.length; i += 1) {
if (parseInt(this.movieElems[i].style.width, 10) > CSS_STYLE.width) { if (parseInt(this.movieElems[i].style.width, 10) > CSS_STYLE.width) {
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.width = `${CSS_STYLE.width}px`;
this.movieElems[i].style.height = `${CSS_STYLE.height}px`; this.movieElems[i].style.height = `${CSS_STYLE.height}px`;
}
this.movieElems[i].style['z-index'] = 1; this.movieElems[i].style['z-index'] = 1;
this.movieElems[i].style.position = 'absolute'; this.movieElems[i].style.position = 'absolute';
this.movieElems[i].style.left = `${this.movieElems[i].dataset.left}px`; this.movieElems[i].style.left = `${this.movieElems[i].dataset.left}px`;
@ -1080,8 +1114,22 @@ class PlexMeetsHomeAssistant extends HTMLElement {
genreElem.parentElement.style.display = 'none'; genreElem.parentElement.style.display = 'none';
} }
} }
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('detailsTitle')[0] as HTMLElement).innerHTML = escapeHtml(mainData.title);
}
if (!_.isNil(mainData.year)) {
(this.getElementsByClassName('detailsYear')[0] as HTMLElement).innerHTML = escapeHtml(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 (this.getElementsByClassName('metaInfo')[0] as HTMLElement).innerHTML = `${(mainData.duration !== undefined
? `<span class='minutesDetail'>${Math.round( ? `<span class='minutesDetail'>${Math.round(
parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000 parseInt(escapeHtml(mainData.duration), 10) / 60 / 1000
@ -1095,7 +1143,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
parseFloat(escapeHtml(mainData.rating)) * 10 parseFloat(escapeHtml(mainData.rating)) * 10
) / 10}</span>` ) / 10}</span>`
: '')}<div class='clear'></div>`; : '')}<div class='clear'></div>`;
if (!_.isNil(mainData.summary)) {
(this.getElementsByClassName('detailDesc')[0] as HTMLElement).innerHTML = escapeHtml(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 /* todo temp disabled
if (data.type === 'movie') { if (data.type === 'movie') {
@ -1118,7 +1173,9 @@ class PlexMeetsHomeAssistant extends HTMLElement {
} else if (data.childCount > 0) { } else if (data.childCount > 0) {
seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]); seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]);
} }
const dataDetails = await this.plex.getDetails(data.key.split('/')[3]); let dataDetails: Record<string, any> = {};
if (!_.isNil(data.key)) {
dataDetails = await this.plex.getDetails(data.key.split('/')[3]);
if (this.videoElem) { if (this.videoElem) {
const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art); const art = this.plex.authorizeURL(this.plex.getBasicURL() + data.art);
const trailerURL = findTrailerURL(dataDetails); const trailerURL = findTrailerURL(dataDetails);
@ -1219,6 +1276,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
contentbg.classList.add('no-transparency'); contentbg.classList.add('no-transparency');
} }
} }
}
if (!_.isEmpty(seasonsData)) { if (!_.isEmpty(seasonsData)) {
this.seasonElemFreshlyLoaded = true; this.seasonElemFreshlyLoaded = true;
if (this.seasonsElem) { if (this.seasonsElem) {
@ -1419,7 +1477,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.episodesElem.append(createEpisodesView(this.playController, this.plex, episodeData)); 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; const extras = dataDetails.Extras.Metadata;
_.forEach(extras, extrasData => { _.forEach(extras, extrasData => {
if (this.episodesElem && this.playController && this.plex) { if (this.episodesElem && this.playController && this.plex) {
@ -1485,8 +1543,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.minimizeAll(); this.minimizeAll();
this.activeMovieElem = undefined; this.activeMovieElem = undefined;
this.hideDetails(); this.hideDetails();
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.width = `${CSS_STYLE.width}px`;
movieElemLocal.style.height = `${CSS_STYLE.height}px`; movieElemLocal.style.height = `${CSS_STYLE.height}px`;
}
movieElemLocal.style.zIndex = '1'; movieElemLocal.style.zIndex = '1';
movieElemLocal.style.top = `${movieElem.dataset.top}px`; movieElemLocal.style.top = `${movieElem.dataset.top}px`;
movieElemLocal.style.left = `${movieElem.dataset.left}px`; movieElemLocal.style.left = `${movieElem.dataset.left}px`;
@ -1500,8 +1563,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
const top = this.getTop(); const top = this.getTop();
this.showDetails(this.activeMovieElemData); this.showDetails(this.activeMovieElemData);
this.showBackground(); this.showBackground();
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.width = `${CSS_STYLE.expandedWidth}px`;
movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`; movieElemLocal.style.height = `${CSS_STYLE.expandedHeight}px`;
}
movieElemLocal.style.zIndex = '3'; movieElemLocal.style.zIndex = '3';
movieElemLocal.style.left = '16px'; movieElemLocal.style.left = '16px';
movieElemLocal.style.top = `${top + 16}px`; movieElemLocal.style.top = `${top + 16}px`;
@ -1545,12 +1613,24 @@ class PlexMeetsHomeAssistant extends HTMLElement {
} else { } else {
container.style.height = `${CSS_STYLE.height + 30}px`; container.style.height = `${CSS_STYLE.height + 30}px`;
} }
if (!_.isNil(data.channelCallSign)) {
container.style.marginBottom = '50px';
}
const movieElem = document.createElement('div'); const movieElem = document.createElement('div');
movieElem.className = 'movieElem'; movieElem.className = 'movieElem';
movieElem.style.width = `${CSS_STYLE.width}px`; movieElem.style.width = `${CSS_STYLE.width}px`;
movieElem.style.height = `${CSS_STYLE.height}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}')`; movieElem.style.backgroundImage = `url('${thumbURL}')`;
if (this.playController && !this.playController.isPlaySupported(data)) { if (this.playController && !this.playController.isPlaySupported(data)) {
movieElem.style.cursor = 'pointer'; movieElem.style.cursor = 'pointer';
@ -1609,17 +1689,25 @@ class PlexMeetsHomeAssistant extends HTMLElement {
const titleElem = document.createElement('div'); const titleElem = document.createElement('div');
if (_.isEqual(data.type, 'episode')) { if (_.isEqual(data.type, 'episode')) {
titleElem.innerHTML = escapeHtml(data.grandparentTitle); titleElem.innerHTML = escapeHtml(data.grandparentTitle);
} else if (!_.isNil(data.channelCallSign)) {
titleElem.innerHTML = escapeHtml(data.channelCallSign);
} else { } else {
titleElem.innerHTML = escapeHtml(data.title); titleElem.innerHTML = escapeHtml(data.title);
} }
titleElem.className = 'titleElem'; titleElem.className = 'titleElem';
if (!_.isNil(data.channelCallSign)) {
titleElem.style.marginTop = `${CSS_STYLE.width}px`;
} else {
titleElem.style.marginTop = `${CSS_STYLE.height}px`; titleElem.style.marginTop = `${CSS_STYLE.height}px`;
}
const yearElem = document.createElement('div'); const yearElem = document.createElement('div');
if (_.isEqual(data.type, 'episode')) { if (_.isEqual(data.type, 'episode')) {
yearElem.innerHTML = escapeHtml(data.title); yearElem.innerHTML = escapeHtml(data.title);
} else { } else if (!_.isNil(data.year)) {
yearElem.innerHTML = escapeHtml(data.year); yearElem.innerHTML = escapeHtml(data.year);
} else if (!_.isNil(data.epg)) {
yearElem.innerHTML = escapeHtml(data.epg.title);
} }
yearElem.className = 'yearElem'; yearElem.className = 'yearElem';

Loading…
Cancel
Save