diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 7aa0d3d2..dd2d9fbe 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -565,6 +565,11 @@ "copyHlsPlaylist": "Copy HLS playlist link", "downloadSubtitle": "Download current subtitle", "downloadVideo": "Download video", + "openIn": "Open in...", + "vlc": "VLC", + "iina": "IINA", + "outplayer": "Outplayer", + "hlsOpenInDisclaimer": "Opening HLS streams in an external player MAY NOT WORK!", "hlsDisclaimer": "Downloads are taken directly from the provider. P-Stream does not have control over how the downloads are provided.

Please note you are downloading an HLS playlist, it is not recommended to download if you are not familiar with advanced streaming formats. Try different sources for different formats.", "onAndroid": { "1": "To download on Android, click the download button then, on the new page, tap and hold on the video, then select save.", diff --git a/src/components/player/atoms/settings/Downloads.tsx b/src/components/player/atoms/settings/Downloads.tsx index 77294727..43b0d44c 100644 --- a/src/components/player/atoms/settings/Downloads.tsx +++ b/src/components/player/atoms/settings/Downloads.tsx @@ -1,15 +1,75 @@ +import { Listbox } from "@headlessui/react"; import { useCallback, useMemo } from "react"; import { Trans, useTranslation } from "react-i18next"; import { useCopyToClipboard } from "react-use"; import { Button } from "@/components/buttons/Button"; +import { OptionItem } from "@/components/form/Dropdown"; 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 { Transition } from "@/components/utils/Transition"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; +function PlayerDropdown({ + options, + onSelectOption, +}: { + options: OptionItem[]; + onSelectOption: (option: OptionItem) => void; +}) { + const { t } = useTranslation(); + const defaultLabel = t("player.menus.downloads.openIn"); + + return ( +
+ + {({ open }) => ( + <> + + + {defaultLabel} + + + + + + + + {options.map((opt) => ( + + `cursor-pointer flex gap-4 items-center relative select-none py-2 px-4 mx-1 rounded-lg ${ + active + ? "bg-background-secondaryHover text-type-link" + : "text-type-secondary" + }` + } + key={opt.id} + value={opt} + > + {opt.leftIcon ? opt.leftIcon : null} + {opt.name} + + ))} + + + + )} + +
+ ); +} + export function useDownloadLink() { const source = usePlayerStore((s) => s.source); const currentQuality = usePlayerStore((s) => s.currentQuality); @@ -72,6 +132,42 @@ export function DownloadView({ id }: { id: string }) { window.open(dataUrl); }, [selectedCaption]); + const playerOptions = useMemo( + () => [ + { id: "vlc", name: t("player.menus.downloads.vlc") }, + { id: "iina", name: t("player.menus.downloads.iina") }, + { id: "outplayer", name: t("player.menus.downloads.outplayer") }, + ], + [t], + ); + + const openInExternalPlayer = useCallback( + (option: OptionItem) => { + if (!downloadUrl) return; + + let externalUrl = ""; + + switch (option.id) { + case "vlc": + externalUrl = `vlc://${downloadUrl}`; + break; + case "iina": + externalUrl = `iina://weblink?url=${encodeURIComponent(downloadUrl)}`; + break; + case "outplayer": + externalUrl = `outplayer://${downloadUrl}`; + break; + default: + break; + } + + if (externalUrl) { + window.open(externalUrl); + } + }, + [downloadUrl], + ); + if (!downloadUrl) return null; return ( @@ -86,6 +182,15 @@ export function DownloadView({ id }: { id: string }) { + + +

+ +

+ @@ -141,6 +246,11 @@ export function DownloadView({ id }: { id: string }) { + +