From 4a7c9a7c1a83e00210dec64a7c0ca1f237d14ed6 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 23 Dec 2025 12:22:55 -0800 Subject: [PATCH] Actually shit, lets just split the widgets now --- public/locales/en/common.json | 10 + src/components/services/widget/container.jsx | 1 - src/widgets/emby/component.jsx | 15 +- src/widgets/jellyfin/component.jsx | 346 ++++++++++++++++++- 4 files changed, 357 insertions(+), 15 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 2d237a23c..f9da2791d 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -107,6 +107,16 @@ "episodes": "Episodes", "songs": "Songs" }, + "jellyfin": { + "playing": "Playing", + "transcoding": "Transcoding", + "bitrate": "Bitrate", + "no_active": "No Active Streams", + "movies": "Movies", + "series": "Series", + "episodes": "Episodes", + "songs": "Songs" + }, "esphome": { "offline": "Offline", "offline_alt": "Offline", diff --git a/src/components/services/widget/container.jsx b/src/components/services/widget/container.jsx index 66434cfc2..e5962e445 100644 --- a/src/components/services/widget/container.jsx +++ b/src/components/services/widget/container.jsx @@ -9,7 +9,6 @@ import { buildHighlightConfig } from "utils/highlights"; const ALIASED_WIDGETS = { pialert: "netalertx", hoarder: "karakeep", - jellyfin: "emby", }; export default function Container({ error = false, children, service }) { diff --git a/src/widgets/emby/component.jsx b/src/widgets/emby/component.jsx index 1f6c9b4dc..39a347185 100644 --- a/src/widgets/emby/component.jsx +++ b/src/widgets/emby/component.jsx @@ -202,31 +202,22 @@ export default function Component({ service }) { const { t } = useTranslation(); const { widget } = service; - const version = widget?.version ?? 1; - const useJellyfinV2 = widget?.type === "jellyfin" && version === 2; - const sessionsEndpoint = useJellyfinV2 ? "SessionsV2" : "Sessions"; - const countEndpoint = useJellyfinV2 ? "CountV2" : "Count"; - const commandMap = { - Pause: useJellyfinV2 ? "PauseV2" : "Pause", - Unpause: useJellyfinV2 ? "UnpauseV2" : "Unpause", - }; const enableNowPlaying = service.widget?.enableNowPlaying ?? true; const { data: sessionsData, error: sessionsError, mutate: sessionMutate, - } = useWidgetAPI(widget, enableNowPlaying ? sessionsEndpoint : "", { + } = useWidgetAPI(widget, enableNowPlaying ? "Sessions" : "", { refreshInterval: enableNowPlaying ? 5000 : undefined, }); - const { data: countData, error: countError } = useWidgetAPI(widget, countEndpoint, { + const { data: countData, error: countError } = useWidgetAPI(widget, "Count", { refreshInterval: 60000, }); async function handlePlayCommand(session, command) { - const mappedCommand = commandMap[command] ?? command; - const params = getURLSearchParams(widget, mappedCommand); + const params = getURLSearchParams(widget, command); params.append( "segments", JSON.stringify({ diff --git a/src/widgets/jellyfin/component.jsx b/src/widgets/jellyfin/component.jsx index e88dae0da..591ee73de 100644 --- a/src/widgets/jellyfin/component.jsx +++ b/src/widgets/jellyfin/component.jsx @@ -1,3 +1,345 @@ -import EmbyComponent from "../emby/component"; +import Block from "components/services/widget/block"; +import Container from "components/services/widget/container"; +import { useTranslation } from "next-i18next"; +import { BsCpu, BsFillCpuFill, BsFillPlayFill, BsPauseFill, BsVolumeMuteFill } from "react-icons/bs"; +import { MdOutlineSmartDisplay } from "react-icons/md"; -export default EmbyComponent; +import { getURLSearchParams } from "utils/proxy/api-helpers"; +import useWidgetAPI from "utils/proxy/use-widget-api"; + +function ticksToTime(ticks) { + const milliseconds = ticks / 10000; + const seconds = Math.floor((milliseconds / 1000) % 60); + const minutes = Math.floor((milliseconds / (1000 * 60)) % 60); + const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24); + return { hours, minutes, seconds }; +} + +function ticksToString(ticks) { + const { hours, minutes, seconds } = ticksToTime(ticks); + const parts = []; + if (hours > 0) { + parts.push(hours); + } + parts.push(minutes); + parts.push(seconds); + + return parts.map((part) => part.toString().padStart(2, "0")).join(":"); +} + +function generateStreamTitle(session, enableUser, showEpisodeNumber) { + const { + NowPlayingItem: { Name, SeriesName, Type, ParentIndexNumber, IndexNumber, AlbumArtist, Album }, + UserName, + } = session; + let streamTitle = ""; + + if (Type === "Episode" && showEpisodeNumber) { + const seasonStr = ParentIndexNumber ? `S${ParentIndexNumber.toString().padStart(2, "0")}` : ""; + const episodeStr = IndexNumber ? `E${IndexNumber.toString().padStart(2, "0")}` : ""; + streamTitle = `${SeriesName}: ${seasonStr} ยท ${episodeStr} - ${Name}`; + } else if (Type === "Audio") { + streamTitle = `${AlbumArtist} - ${Album} - ${Name}`; + } else { + streamTitle = `${Name}${SeriesName ? ` - ${SeriesName}` : ""}`; + } + + return enableUser ? `${streamTitle} (${UserName})` : streamTitle; +} + +function SingleSessionEntry({ playCommand, session, enableUser, showEpisodeNumber, enableMediaControl }) { + const { + PlayState: { PositionTicks, IsPaused, IsMuted }, + } = session; + + const RunTimeTicks = + session.NowPlayingItem?.RunTimeTicks ?? session.NowPlayingItem?.CurrentProgram?.RunTimeTicks ?? 0; + + const { IsVideoDirect, VideoDecoderIsHardware, VideoEncoderIsHardware } = session?.TranscodingInfo || { + IsVideoDirect: true, + }; // if no transcodinginfo its videodirect + + const percent = Math.min(1, PositionTicks / RunTimeTicks) * 100; + + const streamTitle = generateStreamTitle(session, enableUser, showEpisodeNumber); + return ( + <> +