From a53a89a5f0111709ac7fa2b5fd79305f03b91edf Mon Sep 17 00:00:00 2001 From: Duplicake Date: Mon, 2 Mar 2026 23:11:38 +0000 Subject: [PATCH] add downloading feature for desktop (#93) Co-authored-by: Duplicake-fyi --- src/components/LinksDropdown.tsx | 28 ++-- .../player/atoms/settings/Downloads.tsx | 142 ++++++++++++++++-- src/hooks/useIsDesktopApp.ts | 11 ++ 3 files changed, 156 insertions(+), 25 deletions(-) diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index 9e2b604a..4bda57ab 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -294,16 +294,24 @@ export function LinksDropdown(props: { children: React.ReactNode }) { {t("navigation.menu.settings")} {isDesktopApp && ( - - window.dispatchEvent( - new CustomEvent("pstream-desktop-settings"), - ) - } - icon={Icons.GEAR} - > - {t("navigation.menu.desktop")} - + <> + + window.dispatchEvent( + new CustomEvent("pstream-desktop-settings"), + ) + } + icon={Icons.GEAR} + > + {t("navigation.menu.desktop")} + + window.desktopApi?.openOffline()} + icon={Icons.DOWNLOAD} + > + Offline Downloads + + )} {t("home.watchHistory.sectionTitle")} diff --git a/src/components/player/atoms/settings/Downloads.tsx b/src/components/player/atoms/settings/Downloads.tsx index ef94fd2c..9b3fdc6d 100644 --- a/src/components/player/atoms/settings/Downloads.tsx +++ b/src/components/player/atoms/settings/Downloads.tsx @@ -2,11 +2,13 @@ import { useCallback, useMemo } from "react"; import { Trans, useTranslation } from "react-i18next"; import { useCopyToClipboard } from "react-use"; +import { downloadCaption } from "@/backend/helpers/subs"; import { Button } from "@/components/buttons/Button"; import { Icon, Icons } from "@/components/Icon"; import { OverlayPage } from "@/components/overlays/OverlayPage"; import { Menu } from "@/components/player/internals/ContextMenu"; import { convertSubtitlesToSrtDataurl } from "@/components/player/utils/captions"; +import { useIsDesktopApp } from "@/hooks/useIsDesktopApp"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; @@ -64,6 +66,54 @@ export function DownloadView({ id }: { id: string }) { const sourceType = usePlayerStore((s) => s.source?.type); const selectedCaption = usePlayerStore((s) => s.caption?.selected); + const captionList = usePlayerStore((s) => s.captionList); + const meta = usePlayerStore((s) => s.meta); + const duration = usePlayerStore((s) => s.progress.duration); + const isDesktopApp = useIsDesktopApp(); + + const startOfflineDownload = useCallback(async () => { + if (!downloadUrl) return; + const title = meta?.title ? meta.title : "Video"; + const poster = meta?.poster; + let subtitleText = null; + + if (selectedCaption?.srtData) { + subtitleText = selectedCaption.srtData; + } else if (captionList.length > 0) { + // Auto-fetch the first English caption, or the first available one + const defaultCaption = + captionList.find((c) => c.language === "en") ?? captionList[0]; + try { + subtitleText = await downloadCaption(defaultCaption); + } catch { + // Continue without subtitles if fetch fails + } + } + + window.desktopApi?.startDownload({ + url: downloadUrl, + title, + poster, + subtitleText, + duration, + type: sourceType, + }); + + if (window.desktopApi?.openOffline) { + window.desktopApi.openOffline(); + } else { + router.navigate("/"); + } + }, [ + downloadUrl, + meta, + selectedCaption, + captionList, + duration, + router, + sourceType, + ]); + const openSubtitleDownload = useCallback(() => { const dataUrl = selectedCaption ? convertSubtitlesToSrtDataurl(selectedCaption?.srtData) @@ -83,21 +133,48 @@ export function DownloadView({ id }: { id: string }) {
{sourceType === "hls" ? (
- - - + {isDesktopApp ? ( + <> + + + Download this video directly to your app for offline + playback. + + + + + ) : ( + <> + + + - -

- - - -

+ +

+ + + +

+ + )}
+ ) : sourceType === "file" ? ( +
+ {isDesktopApp ? ( + <> + + + Download this video directly to your app for offline + playback. + + + + + ) : ( + + )} + +
) : ( <> router.navigate("/download/pc")}> @@ -141,7 +254,6 @@ export function DownloadView({ id }: { id: string }) { - diff --git a/src/hooks/useIsDesktopApp.ts b/src/hooks/useIsDesktopApp.ts index 6245cf3d..c3b6a355 100644 --- a/src/hooks/useIsDesktopApp.ts +++ b/src/hooks/useIsDesktopApp.ts @@ -2,6 +2,17 @@ declare global { interface Window { __PSTREAM_DESKTOP__?: boolean; + desktopApi?: { + startDownload(data: { + url: string; + title: string; + poster?: string; + subtitleText?: string; + duration?: number; + type?: string; + }): void; + openOffline(): void; + }; } }