Enhancement: additional tautulli jellyfin emby configuration options (#3350)

pull/3354/head
Ameer Abdallah 2 weeks ago committed by GitHub
parent f4fc30cd9f
commit 312e97d18b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,4 +17,6 @@ widget:
enableBlocks: true # optional, defaults to false
enableNowPlaying: true # optional, defaults to true
enableUser: true # optional, defaults to false
showEpisodeNumber: true # optional, defaults to false
expandOneStreamToTwoRows: false # optional, defaults to true
```

@ -16,4 +16,7 @@ widget:
key: apikeyapikeyapikeyapikeyapikey
enableBlocks: true # optional, defaults to false
enableNowPlaying: true # optional, defaults to true
enableUser: true # optional, defaults to false
showEpisodeNumber: true # optional, defaults to false
expandOneStreamToTwoRows: false # optional, defaults to true
```

@ -15,4 +15,6 @@ widget:
url: http://tautulli.host.or.ip
key: apikeyapikeyapikeyapikeyapikey
enableUser: true # optional, defaults to false
showEpisodeNumber: true # optional, defaults to false
expandOneStreamToTwoRows: false # optional, defaults to true
```

@ -395,6 +395,8 @@ export function cleanServiceGroups(groups) {
// emby, jellyfin, tautulli
enableUser,
expandOneStreamToTwoRows,
showEpisodeNumber,
// glances, pihole
version,
@ -520,14 +522,13 @@ export function cleanServiceGroups(groups) {
if (["emby", "jellyfin"].includes(type)) {
if (enableBlocks !== undefined) cleanedService.widget.enableBlocks = JSON.parse(enableBlocks);
if (enableNowPlaying !== undefined) cleanedService.widget.enableNowPlaying = JSON.parse(enableNowPlaying);
if (enableUser !== undefined) {
cleanedService.widget.enableUser = !!JSON.parse(enableUser);
}
}
if (["tautulli"].includes(type)) {
if (enableUser !== undefined) {
cleanedService.widget.enableUser = !!JSON.parse(enableUser);
}
if (["emby", "jellyfin", "tautulli"].includes(type)) {
if (expandOneStreamToTwoRows !== undefined)
cleanedService.widget.expandOneStreamToTwoRows = !!JSON.parse(expandOneStreamToTwoRows);
if (showEpisodeNumber !== undefined)
cleanedService.widget.showEpisodeNumber = !!JSON.parse(showEpisodeNumber);
if (enableUser !== undefined) cleanedService.widget.enableUser = !!JSON.parse(enableUser);
}
if (["sonarr", "radarr"].includes(type)) {
if (enableQueue !== undefined) cleanedService.widget.enableQueue = JSON.parse(enableQueue);

@ -27,12 +27,28 @@ function ticksToString(ticks) {
return parts.map((part) => part.toString().padStart(2, "0")).join(":");
}
function SingleSessionEntry({ playCommand, session, enableUser }) {
function generateStreamTitle(session, enableUser, showEpisodeNumber) {
const {
NowPlayingItem: { Name, SeriesName },
PlayState: { PositionTicks, IsPaused, IsMuted },
NowPlayingItem: { Name, SeriesName, Type, ParentIndexNumber, IndexNumber },
UserName,
} = session;
let streamTitle = "";
if (Type === "Episode" && showEpisodeNumber) {
const seasonStr = `S${ParentIndexNumber.toString().padStart(2, "0")}`;
const episodeStr = `E${IndexNumber.toString().padStart(2, "0")}`;
streamTitle = `${SeriesName}: ${seasonStr} · ${episodeStr} - ${Name}`;
} else {
streamTitle = `${Name}${SeriesName ? ` - ${SeriesName}` : ""}`;
}
return enableUser ? `${streamTitle} (${UserName})` : streamTitle;
}
function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumber }) {
const {
PlayState: { PositionTicks, IsPaused, IsMuted },
} = session;
const RunTimeTicks =
session.NowPlayingItem?.RunTimeTicks ?? session.NowPlayingItem?.CurrentProgram?.RunTimeTicks ?? 0;
@ -43,14 +59,13 @@ function SingleSessionEntry({ playCommand, session, enableUser }) {
const percent = Math.min(1, PositionTicks / RunTimeTicks) * 100;
const streamTitle = generateStreamTitle(session, enableUser, showEpisodeNumber);
return (
<>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div className="grow text-xs z-10 self-center ml-2 relative w-full h-4 mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">
{Name}
{SeriesName && ` - ${SeriesName}`}
{enableUser && ` (${UserName})`}
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden" title={streamTitle}>
{streamTitle}
</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1">
@ -99,11 +114,9 @@ function SingleSessionEntry({ playCommand, session, enableUser }) {
);
}
function SessionEntry({ playCommand, session, enableUser }) {
function SessionEntry({ playCommand, session, enableUser, showEpisodeNumber }) {
const {
NowPlayingItem: { Name, SeriesName },
PlayState: { PositionTicks, IsPaused, IsMuted },
UserName,
} = session;
const RunTimeTicks =
@ -113,6 +126,8 @@ function SessionEntry({ playCommand, session, enableUser }) {
IsVideoDirect: true,
}; // if no transcodinginfo its videodirect
const streamTitle = generateStreamTitle(session, enableUser, showEpisodeNumber);
const percent = Math.min(1, PositionTicks / RunTimeTicks) * 100;
return (
@ -142,10 +157,8 @@ function SessionEntry({ playCommand, session, enableUser }) {
)}
</div>
<div className="grow text-xs z-10 self-center relative w-full h-4">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">
{Name}
{SeriesName && ` - ${SeriesName}`}
{enableUser && ` (${UserName})`}
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden" title={streamTitle}>
{streamTitle}
</div>
</div>
<div className="self-center text-xs flex justify-end mr-1 z-10">{IsMuted && <BsVolumeMuteFill />}</div>
@ -219,7 +232,9 @@ export default function Component({ service }) {
const enableBlocks = service.widget?.enableBlocks;
const enableNowPlaying = service.widget?.enableNowPlaying ?? true;
const enableUser = !!service.widget?.enableUser;
const enableUser = !!service.widget?.enableUser; // default is false
const expandOneStreamToTwoRows = service.widget?.expandOneStreamToTwoRows !== false; // default is true
const showEpisodeNumber = !!service.widget?.showEpisodeNumber; // default is false
if (!sessionsData || !countData) {
return (
@ -230,9 +245,11 @@ export default function Component({ service }) {
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
{expandOneStreamToTwoRows && (
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
)}
</div>
)}
</>
@ -260,15 +277,17 @@ export default function Component({ service }) {
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">{t("emby.no_active")}</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
{expandOneStreamToTwoRows && (
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
)}
</div>
</>
);
}
if (playing.length === 1) {
if (expandOneStreamToTwoRows && playing.length === 1) {
const session = playing[0];
return (
<>
@ -278,28 +297,29 @@ export default function Component({ service }) {
playCommand={(currentSession, command) => handlePlayCommand(currentSession, command)}
session={session}
enableUser={enableUser}
showEpisodeNumber={showEpisodeNumber}
/>
</div>
</>
);
}
if (playing.length > 0)
return (
<>
{enableBlocks && <CountBlocks service={service} countData={countData} />}
<div className="flex flex-col pb-1 mx-1">
{playing.map((session) => (
<SessionEntry
key={session.Id}
playCommand={(currentSession, command) => handlePlayCommand(currentSession, command)}
session={session}
enableUser={enableUser}
/>
))}
</div>
</>
);
return (
<>
{enableBlocks && <CountBlocks service={service} countData={countData} />}
<div className="flex flex-col pb-1 mx-1">
{playing.map((session) => (
<SessionEntry
key={session.Id}
playCommand={(currentSession, command) => handlePlayCommand(currentSession, command)}
session={session}
enableUser={enableUser}
showEpisodeNumber={showEpisodeNumber}
/>
))}
</div>
</>
);
}
if (enableBlocks) {

@ -25,17 +25,31 @@ function millisecondsToString(milliseconds) {
return parts.map((part) => part.toString().padStart(2, "0")).join(":");
}
function SingleSessionEntry({ session, enableUser }) {
const { full_title, duration, view_offset, progress_percent, state, video_decision, audio_decision, username } =
session;
function generateStreamTitle(session, enableUser, showEpisodeNumber) {
let stream_title = "";
const { media_type, parent_media_index, media_index, title, grandparent_title, full_title, friendly_name } = session;
if (media_type === "episode" && showEpisodeNumber) {
const season_str = `S${parent_media_index.toString().padStart(2, "0")}`;
const episode_str = `E${media_index.toString().padStart(2, "0")}`;
stream_title = `${grandparent_title}: ${season_str} · ${episode_str} - ${title}`;
} else {
stream_title = full_title;
}
return enableUser ? `${stream_title} (${friendly_name})` : stream_title;
}
function SingleSessionEntry({ session, enableUser, showEpisodeNumber }) {
const { duration, view_offset, progress_percent, state, video_decision, audio_decision } = session;
const stream_title = generateStreamTitle(session, enableUser, showEpisodeNumber);
return (
<>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div className="text-xs z-10 self-center ml-2 relative w-full h-4 grow mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">
{full_title}
{enableUser && ` (${username})`}
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden" title={stream_title}>
{stream_title}
</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1">
@ -78,8 +92,10 @@ function SingleSessionEntry({ session, enableUser }) {
);
}
function SessionEntry({ session, enableUser }) {
const { full_title, view_offset, progress_percent, state, video_decision, audio_decision, username } = session;
function SessionEntry({ session, enableUser, showEpisodeNumber }) {
const { view_offset, progress_percent, state, video_decision, audio_decision } = session;
const stream_title = generateStreamTitle(session, enableUser, showEpisodeNumber);
return (
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
@ -98,9 +114,8 @@ function SessionEntry({ session, enableUser }) {
)}
</div>
<div className="text-xs z-10 self-center ml-2 relative w-full h-4 grow mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">
{full_title}
{enableUser && ` (${username})`}
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden" title={stream_title}>
{stream_title}
</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10">
@ -129,6 +144,10 @@ export default function Component({ service }) {
refreshInterval: 5000,
});
const enableUser = !!service.widget?.enableUser; // default is false
const expandOneStreamToTwoRows = service.widget?.expandOneStreamToTwoRows !== false; // default is true
const showEpisodeNumber = !!service.widget?.showEpisodeNumber; // default is false
if (activityError || (activityData && Object.keys(activityData.response.data).length === 0)) {
return <Container service={service} error={activityError ?? { message: t("tautulli.plex_connection_error") }} />;
}
@ -139,9 +158,11 @@ export default function Component({ service }) {
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
{expandOneStreamToTwoRows && (
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
)}
</div>
);
}
@ -162,20 +183,20 @@ export default function Component({ service }) {
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">{t("tautulli.no_active")}</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
{expandOneStreamToTwoRows && (
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
)}
</div>
);
}
const enableUser = !!service.widget?.enableUser;
if (playing.length === 1) {
if (expandOneStreamToTwoRows && playing.length === 1) {
const session = playing[0];
return (
<div className="flex flex-col pb-1 mx-1">
<SingleSessionEntry session={session} enableUser={enableUser} />
<SingleSessionEntry session={session} enableUser={enableUser} showEpisodeNumber={showEpisodeNumber} />
</div>
);
}
@ -183,7 +204,12 @@ export default function Component({ service }) {
return (
<div className="flex flex-col pb-1 mx-1">
{playing.map((session) => (
<SessionEntry key={session.Id} session={session} enableUser={enableUser} />
<SessionEntry
key={session.Id}
session={session}
enableUser={enableUser}
showEpisodeNumber={showEpisodeNumber}
/>
))}
</div>
);

Loading…
Cancel
Save