From 3cc98ebda597369c359b163bfc689b56795f9119 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Thu, 25 Dec 2025 12:56:23 -0700 Subject: [PATCH 1/2] Update VideoTesterView.tsx --- src/pages/developer/VideoTesterView.tsx | 113 ++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/src/pages/developer/VideoTesterView.tsx b/src/pages/developer/VideoTesterView.tsx index ef218383..7c234c54 100644 --- a/src/pages/developer/VideoTesterView.tsx +++ b/src/pages/developer/VideoTesterView.tsx @@ -6,6 +6,7 @@ import { Toggle } from "@/components/buttons/Toggle"; import { Dropdown } from "@/components/form/Dropdown"; import { Icon, Icons } from "@/components/Icon"; import { usePlayer } from "@/components/player/hooks/usePlayer"; +import { convertProviderCaption } from "@/components/player/utils/captions"; import { Title } from "@/components/text/Title"; import { AuthInputBox } from "@/components/text-inputs/AuthInputBox"; import { TextInputControl } from "@/components/text-inputs/TextInputControl"; @@ -140,6 +141,101 @@ export default function VideoTesterView() { [playMedia, setMeta, headersEnabled, headers, extensionState], ); + const startFromCli = useCallback(async () => { + try { + const clipboardText = await navigator.clipboard.readText(); + + // Parse JavaScript object notation by evaluating it safely + let cliData; + try { + // Try to parse as JSON first (in case it's already valid JSON) + cliData = JSON.parse(clipboardText); + } catch { + // If JSON parsing fails, try to evaluate as JavaScript object + try { + // Use Function constructor to safely evaluate the JavaScript object + // eslint-disable-next-line no-new-func + cliData = new Function(`return (${clipboardText})`)(); + } catch { + throw new Error( + "Invalid JavaScript object format. Please ensure the CLI output is properly formatted.", + ); + } + } + + if ( + !cliData.stream || + !Array.isArray(cliData.stream) || + cliData.stream.length === 0 + ) { + throw new Error("Invalid CLI output: no stream data found"); + } + + const streamData = cliData.stream[0]; // Take the first stream + + let source: SourceSliceSource; + if (streamData.type === "hls") { + source = { + type: "hls", + url: streamData.playlist, + ...(streamData.headers && { headers: streamData.headers }), + }; + } else if (streamData.type === "file") { + // Handle file type streams + const qualities = streamData.qualities || {}; + const qualityKeys = Object.keys(qualities); + if (qualityKeys.length === 0) { + throw new Error("Invalid file stream: no qualities found"); + } + source = { + type: "file", + qualities, + ...(streamData.headers && { headers: streamData.headers }), + }; + } else { + throw new Error(`Unsupported stream type: ${streamData.type}`); + } + + // Convert captions + const captions = streamData.captions + ? convertProviderCaption(streamData.captions) + : []; + + // Prepare stream headers if extension is active and headers are present + if ( + extensionState === "success" && + streamData.headers && + Object.keys(streamData.headers).length > 0 + ) { + try { + await prepareStream(streamData); + } catch (error) { + console.warn("Failed to prepare stream headers:", error); + } + } + + setMeta(testMeta); + playMedia(source, captions, streamData.id); + } catch (error) { + console.error("Failed to parse CLI data:", error); + + let errorMessage = + error instanceof Error ? error.message : "Unknown error"; + + // Check for common JSON/JavaScript formatting issues + if ( + errorMessage.includes("Expected property name") || + errorMessage.includes("Unexpected token") + ) { + errorMessage += + "\n\nThe CLI output should be in JavaScript object format. Make sure you're copying the complete output from your CLI tool."; + } + + // eslint-disable-next-line no-alert + alert(`Failed to parse CLI data: ${errorMessage}`); + } + }, [playMedia, setMeta, extensionState]); + return ( {status === playerStatus.IDLE ? ( @@ -172,7 +268,7 @@ export default function VideoTesterView() {
-

Headers (Beta)

+

Headers

)} - +
+ + +
Preset tests From 2c9015dd57af46b725628feaad75553ae81742f0 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Thu, 25 Dec 2025 13:00:59 -0700 Subject: [PATCH 2/2] hide WatchPartyInputLink if backend requirement isnt met --- src/components/LinksDropdown.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index 50ae38c0..21573dc7 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -2,8 +2,10 @@ import classNames from "classnames"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; +import { useAsync } from "react-use"; import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto"; +import { getBackendMeta } from "@/backend/accounts/meta"; import { getRoomStatuses } from "@/backend/player/status"; import { UserAvatar } from "@/components/Avatar"; import { Icon, Icons } from "@/components/Icon"; @@ -213,6 +215,17 @@ export function LinksDropdown(props: { children: React.ReactNode }) { [seed], ); const { logout } = useAuth(); + const backendUrl = useBackendUrl(); + + // Check backend compatibility for watch party + const backendMeta = useAsync(async () => { + if (!backendUrl) return; + return getBackendMeta(backendUrl); + }, [backendUrl]); + + const backendSupportsWatchParty = backendMeta?.value?.version + ? backendMeta.value.version >= "2.0.1" + : false; useEffect(() => { function onWindowClick(evt: MouseEvent) { @@ -291,7 +304,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) { {t("navigation.menu.discover")} )} - + {backendSupportsWatchParty && } {deviceName ? (