mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-11 09:45:33 +00:00
add downloading feature for desktop (#93)
Co-authored-by: Duplicake-fyi <duplicake@pstream.mov>
This commit is contained in:
parent
f1af25bf7b
commit
a53a89a5f0
3 changed files with 156 additions and 25 deletions
|
|
@ -294,16 +294,24 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
|
|||
{t("navigation.menu.settings")}
|
||||
</DropdownLink>
|
||||
{isDesktopApp && (
|
||||
<DropdownLink
|
||||
onClick={() =>
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("pstream-desktop-settings"),
|
||||
)
|
||||
}
|
||||
icon={Icons.GEAR}
|
||||
>
|
||||
{t("navigation.menu.desktop")}
|
||||
</DropdownLink>
|
||||
<>
|
||||
<DropdownLink
|
||||
onClick={() =>
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("pstream-desktop-settings"),
|
||||
)
|
||||
}
|
||||
icon={Icons.GEAR}
|
||||
>
|
||||
{t("navigation.menu.desktop")}
|
||||
</DropdownLink>
|
||||
<DropdownLink
|
||||
onClick={() => window.desktopApi?.openOffline()}
|
||||
icon={Icons.DOWNLOAD}
|
||||
>
|
||||
Offline Downloads
|
||||
</DropdownLink>
|
||||
</>
|
||||
)}
|
||||
<DropdownLink href="/watch-history" icon={Icons.CLOCK}>
|
||||
{t("home.watchHistory.sectionTitle")}
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
|||
<div className="mb-4">
|
||||
{sourceType === "hls" ? (
|
||||
<div className="mb-6">
|
||||
<Menu.Paragraph marginClass="mb-6">
|
||||
<StyleTrans k="player.menus.downloads.hlsDisclaimer" />
|
||||
</Menu.Paragraph>
|
||||
{isDesktopApp ? (
|
||||
<>
|
||||
<Menu.Paragraph marginClass="mb-6">
|
||||
<Trans i18nKey="player.menus.downloads.desktopDisclaimer">
|
||||
Download this video directly to your app for offline
|
||||
playback.
|
||||
</Trans>
|
||||
</Menu.Paragraph>
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
theme="purple"
|
||||
onClick={startOfflineDownload}
|
||||
>
|
||||
{t(
|
||||
"player.menus.downloads.offlineButton",
|
||||
"Download for Offline Use",
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Menu.Paragraph marginClass="mb-6">
|
||||
<StyleTrans k="player.menus.downloads.hlsDisclaimer" />
|
||||
</Menu.Paragraph>
|
||||
|
||||
<Button className="w-full mt-2" theme="purple" href={hlsDownload}>
|
||||
{t("player.menus.downloads.button")}
|
||||
</Button>
|
||||
<p className="text-xs py-4">
|
||||
<Trans i18nKey="player.menus.downloads.hlsDownloader">
|
||||
<a
|
||||
className="text-type-link"
|
||||
href="https://hls-downloader.pstream.mov/"
|
||||
/>
|
||||
</Trans>
|
||||
</p>
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
theme="purple"
|
||||
href={hlsDownload}
|
||||
>
|
||||
{t("player.menus.downloads.button")}
|
||||
</Button>
|
||||
<p className="text-xs py-4">
|
||||
<Trans i18nKey="player.menus.downloads.hlsDownloader">
|
||||
<a
|
||||
className="text-type-link"
|
||||
href="https://hls-downloader.pstream.mov/"
|
||||
/>
|
||||
</Trans>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
theme="secondary"
|
||||
|
|
@ -120,6 +197,42 @@ export function DownloadView({ id }: { id: string }) {
|
|||
{t("player.menus.downloads.downloadSubtitle")}
|
||||
</Button>
|
||||
</div>
|
||||
) : sourceType === "file" ? (
|
||||
<div className="mb-6">
|
||||
{isDesktopApp ? (
|
||||
<>
|
||||
<Menu.Paragraph marginClass="mb-6">
|
||||
<Trans i18nKey="player.menus.downloads.desktopDisclaimer">
|
||||
Download this video directly to your app for offline
|
||||
playback.
|
||||
</Trans>
|
||||
</Menu.Paragraph>
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
theme="purple"
|
||||
onClick={startOfflineDownload}
|
||||
>
|
||||
{t(
|
||||
"player.menus.downloads.offlineButton",
|
||||
"Download for Offline Use",
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button className="w-full" href={downloadUrl} theme="purple">
|
||||
{t("player.menus.downloads.downloadVideo")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
onClick={openSubtitleDownload}
|
||||
disabled={!selectedCaption}
|
||||
theme="secondary"
|
||||
download="subtitles.srt"
|
||||
>
|
||||
{t("player.menus.downloads.downloadSubtitle")}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Menu.ChevronLink onClick={() => router.navigate("/download/pc")}>
|
||||
|
|
@ -141,7 +254,6 @@ export function DownloadView({ id }: { id: string }) {
|
|||
<Menu.Paragraph marginClass="my-6">
|
||||
<StyleTrans k="player.menus.downloads.disclaimer" />
|
||||
</Menu.Paragraph>
|
||||
|
||||
<Button className="w-full" href={downloadUrl} theme="purple">
|
||||
{t("player.menus.downloads.downloadVideo")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue