Merge pull request #16 from JurajNyiri/support_shared_servers

Support shared servers
pull/30/head 1.3
Juraj Nyíri 4 years ago committed by GitHub
commit b0c208b12b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -140,7 +140,9 @@ Play button is only visible if all the conditions inside Availability section of
- Provided entity ID needs to have attributes
- Provided entity ID needs to have attribute adb_response
**Supported play types**:
**Supported**:
✅ Shared Plex servers
✅ Movies
@ -180,7 +182,9 @@ Play button is only visible if all the conditions inside Availability section of
- State of both entities cannot be 'unavailable'
- State of kodi cannot be 'off'
**Supported play types**:
**Supported**:
✅ Shared Plex servers _\*if content available in kodi_
✅ Movies
@ -204,7 +208,9 @@ Play button is only visible if all the conditions inside Availability section of
- Media player entity cannot be `unavailable`
**Supported play types**:
**Supported**:
✅ Shared Plex servers
✅ Movies
@ -263,7 +269,9 @@ entity:
- Plex needs to run on the defined device
**Supported play types**:
**Supported**:
✅ Shared Plex servers _\*requires additional configuration, see below_
✅ Movies
@ -273,6 +281,63 @@ entity:
✅ Episodes
**Shared Plex servers configuration**
plexPlayer can be configured in multiple ways, achieving the same thing:
```
entity:
plexPlayer: TV 2020
```
```
entity:
plexPlayer:
- TV 2020
```
```
entity:
plexPlayer:
identifier: TV 2020
```
```
entity:
plexPlayer:
- identifier: TV 2020
```
As can be seen from the last two examples, it is possible to configure it as an object having key "identifier".
That is useful, if you want to stream media from shared or remote Plex server. Add information about your local Plex server which sees your device on which you wish to play content. This is done by including a new key, "server" having additional keys:
Example 1:
```
entity:
plexPlayer:
- identifier: TV 2020
server:
ip: 192.168.13.37 # Mandatory
token: QWdsqEXAMPLETOKENqwerty # Mandatory
port: 32400
protocol: http
```
Example 2:
```
entity:
plexPlayer:
identifier: TV 2020
server:
ip: 192.168.13.37 # Mandatory
token: QWdsqEXAMPLETOKENqwerty # Mandatory
port: 32400
protocol: http
```
## Sorting
You can use _:desc_ or _:asc_ after every value to change the order from ascending to descending. For example, titlesort would become titleSort:asc, or titleSort:desc.

@ -18672,7 +18672,7 @@ class Plex {
constructor(ip, port = false, token, protocol = 'http', sort = 'titleSort:asc') {
this.serverInfo = {};
this.clients = [];
this.requestTimeout = 5000;
this.requestTimeout = 10000;
this.sections = [];
this.init = async () => {
await this.getClients();
@ -18917,13 +18917,13 @@ class PlayController {
await this.hass.callService(this.runAfter[0], this.runAfter[1], {});
}
};
this.plexPlayerCreateQueue = async (movieID) => {
const url = `${this.plex.protocol}://${this.plex.ip}:${this.plex.port}/playQueues?type=video&shuffle=0&repeat=0&continuous=1&own=1&uri=server://${await this.plex.getServerID()}/com.plexapp.plugins.library/library/metadata/${movieID}`;
this.plexPlayerCreateQueue = async (movieID, plex) => {
const url = `${plex.getBasicURL()}/playQueues?type=video&shuffle=0&repeat=0&continuous=1&own=1&uri=server://${await plex.getServerID()}/com.plexapp.plugins.library/library/metadata/${movieID}`;
const plexResponse = await axios({
method: 'post',
url,
headers: {
'X-Plex-Token': this.plex.token,
'X-Plex-Token': plex.token,
'X-Plex-Client-Identifier': 'PlexMeetsHomeAssistant'
}
});
@ -18935,10 +18935,27 @@ class PlayController {
playQueueSelectedMetadataItemID: plexResponse.data.MediaContainer.playQueueSelectedMetadataItemID
};
};
this.playViaPlexPlayer = async (entityName, movieID) => {
const machineID = this.getPlexPlayerMachineIdentifier(entityName);
const { playQueueID, playQueueSelectedMetadataItemID } = await this.plexPlayerCreateQueue(movieID);
const url = `${this.plex.protocol}://${this.plex.ip}:${this.plex.port}/player/playback/playMedia?address=${this.plex.ip}&commandID=1&containerKey=/playQueues/${playQueueID}?window=100%26own=1&key=/library/metadata/${playQueueSelectedMetadataItemID}&machineIdentifier=${await this.plex.getServerID()}&offset=0&port=${this.plex.port}&token=${this.plex.token}&type=video&protocol=${this.plex.protocol}`;
this.playViaPlexPlayer = async (entity, movieID) => {
const machineID = this.getPlexPlayerMachineIdentifier(entity);
let { plex } = this;
if (lodash.isObject(entity) && !lodash.isNil(entity.plex)) {
plex = entity.plex;
}
const { playQueueID, playQueueSelectedMetadataItemID } = await this.plexPlayerCreateQueue(movieID, this.plex);
let url = plex.getBasicURL();
url += `/player/playback/playMedia`;
url += `?type=video`;
url += `&commandID=1`;
url += `&providerIdentifier=com.plexapp.plugins.library`;
url += `&containerKey=/playQueues/${playQueueID}`;
url += `&key=/library/metadata/${playQueueSelectedMetadataItemID}`;
url += `&offset=0`;
url += `&machineIdentifier=${await this.plex.getServerID()}`;
url += `&protocol=${this.plex.protocol}`;
url += `&address=${this.plex.ip}`;
url += `&port=${this.plex.port}`;
url += `&token=${this.plex.token}`;
url = plex.authorizeURL(url);
try {
const plexResponse = await axios({
method: 'post',
@ -19061,9 +19078,58 @@ class PlayController {
});
return service;
};
this.getPlexPlayerMachineIdentifier = (entityName) => {
this.init = async () => {
if (!lodash.isNil(this.entity.plexPlayer)) {
if (lodash.isArray(this.entity.plexPlayer)) {
for (let i = 0; i < this.entity.plexPlayer.length; i += 1) {
if (lodash.isObjectLike(this.entity.plexPlayer[i]) && !lodash.isNil(this.entity.plexPlayer[i].server)) {
let port = false;
if (!lodash.isNil(this.entity.plexPlayer[i].server.port)) {
port = this.entity.plexPlayer[i].server.port;
}
let protocol = 'http';
if (!lodash.isNil(this.entity.plexPlayer[i].server.protocol)) {
protocol = this.entity.plexPlayer[i].server.protocol;
}
// eslint-disable-next-line no-param-reassign
this.entity.plexPlayer[i].plex = new Plex(this.entity.plexPlayer[i].server.ip, port, this.entity.plexPlayer[i].server.token, protocol);
// eslint-disable-next-line no-await-in-loop
await this.entity.plexPlayer[i].plex.getClients();
}
}
}
else if (!lodash.isNil(this.entity.plexPlayer.server) &&
!lodash.isNil(this.entity.plexPlayer.server.ip) &&
!lodash.isNil(this.entity.plexPlayer.server.token)) {
let port = false;
if (!lodash.isNil(this.entity.plexPlayer.server.port)) {
port = this.entity.plexPlayer.server.port;
}
let protocol = 'http';
if (!lodash.isNil(this.entity.plexPlayer.server.protocol)) {
protocol = this.entity.plexPlayer.server.protocol;
}
// eslint-disable-next-line no-param-reassign
this.entity.plexPlayer.plex = new Plex(this.entity.plexPlayer.server.ip, port, this.entity.plexPlayer.server.token, protocol);
// eslint-disable-next-line no-await-in-loop
await this.entity.plexPlayer.plex.getClients();
}
}
};
this.getPlexPlayerMachineIdentifier = (entity) => {
let machineIdentifier = '';
lodash.forEach(this.plex.clients, plexClient => {
let { plex } = this;
let entityName = '';
if (lodash.isString(entity)) {
entityName = entity;
}
else if (lodash.isObjectLike(entity) && !lodash.isNil(entity.identifier)) {
entityName = entity.identifier;
if (!lodash.isNil(entity.plex) && entity.plex) {
plex = entity.plex;
}
}
lodash.forEach(plex.clients, plexClient => {
if (lodash.isEqual(plexClient.machineIdentifier, entityName) ||
lodash.isEqual(plexClient.product, entityName) ||
lodash.isEqual(plexClient.name, entityName) ||
@ -19078,9 +19144,9 @@ class PlayController {
this.isPlaySupported = (data) => {
return !lodash.isEmpty(this.getPlayService(data));
};
this.isPlexPlayerSupported = (entityName) => {
this.isPlexPlayerSupported = (entity) => {
let found = false;
if (this.getPlexPlayerMachineIdentifier(entityName)) {
if (this.getPlexPlayerMachineIdentifier(entity)) {
found = true;
}
return found || !lodash.isEqual(this.runBefore, false);
@ -19205,6 +19271,47 @@ const findTrailerURL = (movieData) => {
}
return foundURL;
};
const clickHandler = (elem, clickFunction, holdFunction) => {
let longpress = false;
let presstimer = null;
const cancel = (e) => {
e.stopPropagation();
if (presstimer !== null) {
clearTimeout(presstimer);
presstimer = null;
}
};
const click = (e) => {
e.stopPropagation();
if (presstimer !== null) {
clearTimeout(presstimer);
presstimer = null;
}
if (longpress) {
return false;
}
clickFunction(e);
return true;
};
const start = (e) => {
e.stopPropagation();
if (e.type === 'click' && e.button !== 0) {
return;
}
longpress = false;
presstimer = setTimeout(() => {
holdFunction(e);
longpress = true;
}, 1000);
};
elem.addEventListener('mousedown', start);
elem.addEventListener('touchstart', start);
elem.addEventListener('click', click);
elem.addEventListener('mouseout', cancel);
elem.addEventListener('touchend', cancel);
elem.addEventListener('touchleave', cancel);
elem.addEventListener('touchcancel', cancel);
};
const createEpisodesView = (playController, plex, data) => {
const episodeContainer = document.createElement('div');
episodeContainer.className = 'episodeContainer';
@ -20100,6 +20207,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.renderPage();
try {
if (this.plex) {
if (this.hassObj) {
const entityConfig = JSON.parse(JSON.stringify(this.config.entity)); // todo: find a nicer solution
this.playController = new PlayController(this.hassObj, this.plex, entityConfig, this.runBefore, this.runAfter);
if (this.playController) {
await this.playController.init();
}
}
await this.plex.init();
try {
const onDeck = await this.plex.getOnDeck();
@ -21140,11 +21254,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
interactiveArea.append(playButton);
}
movieElem.append(interactiveArea);
playButton.addEventListener('click', event => {
clickHandler(playButton, (event) => {
event.stopPropagation();
if (this.hassObj && this.playController) {
this.playController.play(data, true);
}
}, (event) => {
console.log('Play version... will be here!');
event.stopPropagation();
});
const titleElem = document.createElement('div');
if (lodash.isEqual(data.type, 'episode')) {
@ -21240,9 +21357,6 @@ class PlexMeetsHomeAssistant extends HTMLElement {
}
set hass(hass) {
this.hassObj = hass;
if (this.plex) {
this.playController = new PlayController(this.hassObj, this.plex, this.config.entity, this.runBefore, this.runAfter);
}
if (!this.content) {
this.error = '';
if (!this.loading) {

@ -121,19 +121,18 @@ class PlayController {
}
};
private plexPlayerCreateQueue = async (movieID: number): Promise<Record<string, number>> => {
const url = `${this.plex.protocol}://${this.plex.ip}:${
this.plex.port
}/playQueues?type=video&shuffle=0&repeat=0&continuous=1&own=1&uri=server://${await this.plex.getServerID()}/com.plexapp.plugins.library/library/metadata/${movieID}`;
private plexPlayerCreateQueue = async (movieID: number, plex: Plex): Promise<Record<string, number>> => {
const url = `${plex.getBasicURL()}/playQueues?type=video&shuffle=0&repeat=0&continuous=1&own=1&uri=server://${await plex.getServerID()}/com.plexapp.plugins.library/library/metadata/${movieID}`;
const plexResponse = await axios({
method: 'post',
url,
headers: {
'X-Plex-Token': this.plex.token,
'X-Plex-Token': plex.token,
'X-Plex-Client-Identifier': 'PlexMeetsHomeAssistant'
}
});
if (plexResponse.status !== 200) {
throw Error('Error reaching Plex to generate queue');
}
@ -143,15 +142,30 @@ class PlayController {
};
};
private playViaPlexPlayer = async (entityName: string, movieID: number): Promise<void> => {
const machineID = this.getPlexPlayerMachineIdentifier(entityName);
const { playQueueID, playQueueSelectedMetadataItemID } = await this.plexPlayerCreateQueue(movieID);
private playViaPlexPlayer = async (entity: string | Record<string, any>, movieID: number): Promise<void> => {
const machineID = this.getPlexPlayerMachineIdentifier(entity);
let { plex } = this;
if (_.isObject(entity) && !_.isNil(entity.plex)) {
plex = entity.plex;
}
const { playQueueID, playQueueSelectedMetadataItemID } = await this.plexPlayerCreateQueue(movieID, this.plex);
let url = plex.getBasicURL();
url += `/player/playback/playMedia`;
url += `?type=video`;
url += `&commandID=1`;
url += `&providerIdentifier=com.plexapp.plugins.library`;
url += `&containerKey=/playQueues/${playQueueID}`;
url += `&key=/library/metadata/${playQueueSelectedMetadataItemID}`;
url += `&offset=0`;
url += `&machineIdentifier=${await this.plex.getServerID()}`;
url += `&protocol=${this.plex.protocol}`;
url += `&address=${this.plex.ip}`;
url += `&port=${this.plex.port}`;
url += `&token=${this.plex.token}`;
url = plex.authorizeURL(url);
const url = `${this.plex.protocol}://${this.plex.ip}:${this.plex.port}/player/playback/playMedia?address=${
this.plex.ip
}&commandID=1&containerKey=/playQueues/${playQueueID}?window=100%26own=1&key=/library/metadata/${playQueueSelectedMetadataItemID}&machineIdentifier=${await this.plex.getServerID()}&offset=0&port=${
this.plex.port
}&token=${this.plex.token}&type=video&protocol=${this.plex.protocol}`;
try {
const plexResponse = await axios({
method: 'post',
@ -284,9 +298,71 @@ class PlayController {
return service;
};
private getPlexPlayerMachineIdentifier = (entityName: string): string => {
init = async (): Promise<void> => {
if (!_.isNil(this.entity.plexPlayer)) {
if (_.isArray(this.entity.plexPlayer)) {
for (let i = 0; i < this.entity.plexPlayer.length; i += 1) {
if (_.isObjectLike(this.entity.plexPlayer[i]) && !_.isNil(this.entity.plexPlayer[i].server)) {
let port: number | false = false;
if (!_.isNil(this.entity.plexPlayer[i].server.port)) {
port = this.entity.plexPlayer[i].server.port;
}
let protocol: 'http' | 'https' = 'http';
if (!_.isNil(this.entity.plexPlayer[i].server.protocol)) {
protocol = this.entity.plexPlayer[i].server.protocol;
}
// eslint-disable-next-line no-param-reassign
this.entity.plexPlayer[i].plex = new Plex(
this.entity.plexPlayer[i].server.ip,
port,
this.entity.plexPlayer[i].server.token,
protocol
);
// eslint-disable-next-line no-await-in-loop
await this.entity.plexPlayer[i].plex.getClients();
}
}
} else if (
!_.isNil(this.entity.plexPlayer.server) &&
!_.isNil(this.entity.plexPlayer.server.ip) &&
!_.isNil(this.entity.plexPlayer.server.token)
) {
let port: number | false = false;
if (!_.isNil(this.entity.plexPlayer.server.port)) {
port = this.entity.plexPlayer.server.port;
}
let protocol: 'http' | 'https' = 'http';
if (!_.isNil(this.entity.plexPlayer.server.protocol)) {
protocol = this.entity.plexPlayer.server.protocol;
}
// eslint-disable-next-line no-param-reassign
this.entity.plexPlayer.plex = new Plex(
this.entity.plexPlayer.server.ip,
port,
this.entity.plexPlayer.server.token,
protocol
);
// eslint-disable-next-line no-await-in-loop
await this.entity.plexPlayer.plex.getClients();
}
}
};
private getPlexPlayerMachineIdentifier = (entity: string | Record<string, any>): string => {
let machineIdentifier = '';
_.forEach(this.plex.clients, plexClient => {
let { plex } = this;
let entityName = '';
if (_.isString(entity)) {
entityName = entity;
} else if (_.isObjectLike(entity) && !_.isNil(entity.identifier)) {
entityName = entity.identifier;
if (!_.isNil(entity.plex) && entity.plex) {
plex = entity.plex;
}
}
_.forEach(plex.clients, plexClient => {
if (
_.isEqual(plexClient.machineIdentifier, entityName) ||
_.isEqual(plexClient.product, entityName) ||
@ -305,11 +381,12 @@ class PlayController {
return !_.isEmpty(this.getPlayService(data));
};
private isPlexPlayerSupported = (entityName: string): boolean => {
private isPlexPlayerSupported = (entity: string | Record<string, any>): boolean => {
let found = false;
if (this.getPlexPlayerMachineIdentifier(entityName)) {
if (this.getPlexPlayerMachineIdentifier(entity)) {
found = true;
}
return found || !_.isEqual(this.runBefore, false);
};

@ -15,7 +15,7 @@ class Plex {
clients: Array<Record<string, any>> = [];
requestTimeout = 5000;
requestTimeout = 10000;
sort: string;

@ -104,6 +104,54 @@ const findTrailerURL = (movieData: Record<string, any>): string => {
}
return foundURL;
};
const clickHandler = (elem: HTMLButtonElement, clickFunction: Function, holdFunction: Function): void => {
let longpress = false;
let presstimer: any = null;
const cancel = (e: any): void => {
e.stopPropagation();
if (presstimer !== null) {
clearTimeout(presstimer);
presstimer = null;
}
};
const click = (e: any): boolean => {
e.stopPropagation();
if (presstimer !== null) {
clearTimeout(presstimer);
presstimer = null;
}
if (longpress) {
return false;
}
clickFunction(e);
return true;
};
const start = (e: any): void => {
e.stopPropagation();
if (e.type === 'click' && e.button !== 0) {
return;
}
longpress = false;
presstimer = setTimeout(() => {
holdFunction(e);
longpress = true;
}, 1000);
};
elem.addEventListener('mousedown', start);
elem.addEventListener('touchstart', start);
elem.addEventListener('click', click);
elem.addEventListener('mouseout', cancel);
elem.addEventListener('touchend', cancel);
elem.addEventListener('touchleave', cancel);
elem.addEventListener('touchcancel', cancel);
};
const createEpisodesView = (playController: PlayController, plex: Plex, data: Record<string, any>): HTMLElement => {
const episodeContainer = document.createElement('div');
@ -206,5 +254,6 @@ export {
hasEpisodes,
getOldPlexServerErrorMessage,
getWidth,
getDetailsBottom
getDetailsBottom,
clickHandler
};

@ -15,7 +15,8 @@ import {
isVideoFullScreen,
hasEpisodes,
getOldPlexServerErrorMessage,
getDetailsBottom
getDetailsBottom,
clickHandler
} from './modules/utils';
import style from './modules/style';
@ -116,15 +117,6 @@ class PlexMeetsHomeAssistant extends HTMLElement {
set hass(hass: HomeAssistant) {
this.hassObj = hass;
if (this.plex) {
this.playController = new PlayController(
this.hassObj,
this.plex,
this.config.entity,
this.runBefore,
this.runAfter
);
}
if (!this.content) {
this.error = '';
@ -210,6 +202,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
}
this.renderNewElementsIfNeeded();
});
if (this.card) {
this.previousPageWidth = this.card.offsetWidth;
}
@ -218,6 +211,19 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.renderPage();
try {
if (this.plex) {
if (this.hassObj) {
const entityConfig: Record<string, any> = JSON.parse(JSON.stringify(this.config.entity)); // todo: find a nicer solution
this.playController = new PlayController(
this.hassObj,
this.plex,
entityConfig,
this.runBefore,
this.runAfter
);
if (this.playController) {
await this.playController.init();
}
}
await this.plex.init();
try {
@ -1362,13 +1368,20 @@ class PlexMeetsHomeAssistant extends HTMLElement {
movieElem.append(interactiveArea);
playButton.addEventListener('click', event => {
event.stopPropagation();
clickHandler(
playButton,
(event: any): void => {
event.stopPropagation();
if (this.hassObj && this.playController) {
this.playController.play(data, true);
if (this.hassObj && this.playController) {
this.playController.play(data, true);
}
},
(event: any): void => {
console.log('Play version... will be here!');
event.stopPropagation();
}
});
);
const titleElem = document.createElement('div');
if (_.isEqual(data.type, 'episode')) {

Loading…
Cancel
Save