Plex and Jellyfin widget (#713)

This commit is contained in:
Manuel
2023-02-15 22:12:49 +01:00
committed by GitHub
parent ca50cffe82
commit d157e986a1
20 changed files with 1129 additions and 236 deletions

View File

@@ -0,0 +1,108 @@
import { Element, xml2js } from 'xml-js';
import {
GenericCurrentlyPlaying,
GenericSessionInfo,
} from '../../../../types/api/media-server/session-info';
export class PlexClient {
constructor(private readonly apiAddress: string, private readonly token: string) {}
async getSessions(): Promise<GenericSessionInfo[]> {
const response = await fetch(`${this.apiAddress}/status/sessions?X-Plex-Token=${this.token}`);
const body = await response.text();
// convert xml response to objects, as there is no JSON api
const data = xml2js(body);
// TODO: Investigate when there are no media containers
const mediaContainer = data.elements[0] as Element;
// no sessions are open or available
if (!mediaContainer.elements?.some((_) => true)) {
return [];
}
const videoElements = mediaContainer.elements as Element[];
const videos = videoElements
.map((videoElement): GenericSessionInfo | undefined => {
// extract the elements from the children
const userElement = this.findElement('User', videoElement.elements);
const playerElement = this.findElement('Player', videoElement.elements);
const mediaElement = this.findElement('Media', videoElement.elements);
const sessionElement = this.findElement('Session', videoElement.elements);
if (!userElement || !playerElement || !mediaElement || !sessionElement) {
return undefined;
}
const { videoCodec, videoFrameRate, audioCodec, audioChannels, height, width, bitrate } =
mediaElement;
const transcodingElement = this.findElement('TranscodeSession', videoElement.elements);
return {
id: sessionElement.id as string,
username: userElement.title as string,
userProfilePicture: userElement.thumb as string,
sessionName: `${playerElement.product} (${playerElement.title})`,
currentlyPlaying: {
name: videoElement.attributes?.title as string,
type: this.getCurrentlyPlayingType(videoElement.attributes?.type as string),
metadata: {
video: {
bitrate,
height,
videoCodec,
videoFrameRate,
width,
},
audio: {
audioChannels,
audioCodec,
},
transcoding:
transcodingElement === undefined
? undefined
: {
audioChannels: transcodingElement.audioChannels,
audioCodec: transcodingElement.audioCodec,
audioDecision: transcodingElement.audioDecision,
container: transcodingElement.container,
context: transcodingElement.context,
duration: transcodingElement.duration,
error: transcodingElement.error === 1,
height: transcodingElement.height,
sourceAudioCodec: transcodingElement.sourceAudioCodec,
sourceVideoCodec: transcodingElement.sourceVideoCodec,
timeStamp: transcodingElement.timeStamp,
transcodeHwRequested: transcodingElement.transcodeHwRequested === 1,
videoCodec: transcodingElement.videoCodec,
videoDecision: transcodingElement.videoDecision,
width: transcodingElement.width,
},
},
},
} as GenericSessionInfo;
})
.filter((x) => x !== undefined) as GenericSessionInfo[];
return videos;
}
private findElement(name: string, elements: Element[] | undefined) {
return elements?.find((x) => x.name === name)?.attributes;
}
private getCurrentlyPlayingType(type: string): GenericCurrentlyPlaying['type'] {
switch (type) {
case 'movie':
return 'movie';
case 'episode':
return 'video';
default:
return undefined;
}
}
}