From 574c97328bbcb3e33d633092281137b0a8a9481c Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Wed, 22 Jul 2026 12:01:18 +0530 Subject: [PATCH 1/2] feat(web): add dedicated VLC integration section with Linux support, protocol probe, and custom icon --- web/components/settings/SettingsScreen.tsx | 277 ++++++++++++++++++++- web/lib/externalPlayers.ts | 11 +- web/netlify.toml | 6 + web/public/version.json | 2 +- web/public/vlc-setup.sh | 70 ++++++ 5 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 web/public/vlc-setup.sh diff --git a/web/components/settings/SettingsScreen.tsx b/web/components/settings/SettingsScreen.tsx index c94daf94..82831742 100644 --- a/web/components/settings/SettingsScreen.tsx +++ b/web/components/settings/SettingsScreen.tsx @@ -5,8 +5,11 @@ import { ArrowUp, Captions, Check, + CheckCircle, ChevronDown, Cloud, + Download, + ExternalLink, Eye, EyeOff, Languages, @@ -27,7 +30,7 @@ import { User, UserCircle, } from "lucide-react"; -import { Component, useEffect, useState, type ReactNode } from "react"; +import { Component, CSSProperties, useEffect, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { defaultCatalogs, mergeCatalogs } from "@/lib/catalogs"; import { @@ -36,6 +39,14 @@ import { hasTraktConfig, getAuthPortalUrl, } from "@/lib/config"; +import { + isLinux, + setVlcProtocolReady, + triggerDownload, + vlcProtocolReady, + VLC_SETUP_SH_URL, + VLC_SETUP_URL, +} from "@/lib/externalPlayers"; import { defaultSettings, useApp } from "@/lib/store"; import type { AppSettings, @@ -47,10 +58,42 @@ import type { const settingsKey = "arvio.web.settings"; +function VlcIcon({ + size = 18, + className = "", + style = {}, +}: { + size?: number; + className?: string; + style?: CSSProperties; +}) { + return ( + + {/* Cone tip */} + + {/* Upper cone band */} + + {/* Lower cone band */} + + {/* Base stand */} + + + ); +} + const SECTIONS = [ { id: "accounts", label: "Accounts", icon: Cloud }, { id: "profiles", label: "Profiles", icon: User }, { id: "playback", label: "Playback", icon: Play }, + { id: "vlc", label: "VLC Integration", icon: VlcIcon }, { id: "language", label: "Language & Audio", icon: Languages }, { id: "subtitles", label: "Subtitles", icon: Subtitles }, { id: "ai", label: "AI Subtitles", icon: Captions }, @@ -499,6 +542,24 @@ function SectionBody({ section }: { section: SectionId }) { ]} /> + + + ; case "addons": return ; + case "vlc": + return ; default: return null; } @@ -1806,6 +1869,218 @@ function AddonsSection() { ); } +/* ---------- VLC Integration ---------- */ + +function VlcSection() { + const { setToast } = useApp(); + const [vlcReady, setVlcReady] = useState(() => vlcProtocolReady()); + const [checkStatus, setCheckStatus] = useState< + "idle" | "testing" | "working" | "not_installed" + >("idle"); + + const handleDownloadWindows = () => { + triggerDownload(VLC_SETUP_URL, "vlc-setup.bat"); + setVlcProtocolReady(true); + setVlcReady(true); + setToast( + "Downloaded vlc-setup.bat — run it once on Windows to enable direct VLC launching.", + ); + }; + + const handleDownloadLinux = () => { + triggerDownload(VLC_SETUP_SH_URL, "vlc-setup.sh"); + setVlcProtocolReady(true); + setVlcReady(true); + setToast( + "Downloaded vlc-setup.sh — run `bash vlc-setup.sh` to enable direct VLC launching on Linux.", + ); + }; + + const handleToggleReady = () => { + const next = !vlcReady; + setVlcProtocolReady(next); + setVlcReady(next); + setToast( + next + ? "VLC direct protocol handler marked as ready." + : "VLC direct protocol handler status reset.", + ); + }; + + const handleCheckIntegration = () => { + setCheckStatus("testing"); + let blurred = false; + + const onBlur = () => { + blurred = true; + }; + + window.addEventListener("blur", onBlur); + + const testUrl = + "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; + try { + const a = document.createElement("a"); + a.href = `vlc://${testUrl}`; + a.rel = "noopener"; + a.style.position = "fixed"; + a.style.left = "-9999px"; + document.body.appendChild(a); + a.click(); + setTimeout(() => a.remove(), 1000); + } catch { + /* ignore */ + } + + setTimeout(() => { + window.removeEventListener("blur", onBlur); + if (blurred) { + setCheckStatus("working"); + setVlcProtocolReady(true); + setVlcReady(true); + setToast( + "✓ VLC Integration Confirmed! VLC launched directly via vlc:// protocol.", + ); + } else { + setCheckStatus("not_installed"); + setToast( + "✕ VLC handler not detected. Please run the setup script for your OS.", + ); + } + }, 1500); + }; + + return ( + + + + + + +
+ +
+
+ + +
+ + +
+
+ +
+ + Platform Integration Guide: + +
    +
  • + Windows Desktop: Run{" "} + vlc-setup.bat once to register the vlc://{" "} + protocol handler for your user account (no administrator rights + needed). +
  • +
  • + Linux Desktop: Download{" "} + vlc-setup.sh and run bash vlc-setup.sh in + your terminal. It creates a .desktop entry and registers{" "} + x-scheme-handler/vlc via xdg-mime for + GNOME, KDE, XFCE, etc. +
  • +
  • + macOS Desktop: VLC + automatically self-registers the vlc:// protocol + handler upon installation. No setup script required. +
  • +
  • + Android: Uses native + Android intents to launch VLC directly or prompt with an app chooser + (MX Player, Just Player, VLC). +
  • +
  • + iOS / iPadOS: Launches + VLC directly using the native vlc-x-callback://{" "} + protocol. +
  • +
+
+
+ ); +} + function SubtitlePreview({ settings }: { settings: AppSettings }) { const previewClass = `subtitle-preview-text subtitle-style-${settings.subtitleStyle} subtitle-pos-${settings.subtitleOffset}`; return ( diff --git a/web/lib/externalPlayers.ts b/web/lib/externalPlayers.ts index b5f531db..8273cfe2 100644 --- a/web/lib/externalPlayers.ts +++ b/web/lib/externalPlayers.ts @@ -182,13 +182,20 @@ export function isMac() { return /Macintosh|Mac OS X/i.test(ua) && !(navigator.maxTouchPoints > 1); } +export function isLinux() { + if (typeof navigator === "undefined") return false; + const ua = navigator.userAgent; + return /Linux/i.test(ua) && !/Android/i.test(ua); +} + export function isDesktop() { return !isAppleMobile() && !isAndroid(); } -// One-time setup that registers the vlc:// handler so the desktop "Open in VLC" -// button launches VLC directly instead of downloading a .m3u. Hosted by ARVIO. +// One-time setup scripts that register the vlc:// handler so desktop "Open in VLC" +// launches VLC directly instead of downloading a .m3u. Hosted by ARVIO. export const VLC_SETUP_URL = "/vlc-setup.bat"; +export const VLC_SETUP_SH_URL = "/vlc-setup.sh"; // Whether the user has run the one-time desktop vlc:// setup. We can't detect // protocol registration from the browser, so we remember that they installed diff --git a/web/netlify.toml b/web/netlify.toml index 20f354d9..3d80ab55 100644 --- a/web/netlify.toml +++ b/web/netlify.toml @@ -44,3 +44,9 @@ [headers.values] Content-Type = "application/octet-stream" Content-Disposition = "attachment; filename=\"vlc-setup.bat\"" + +[[headers]] + for = "/vlc-setup.sh" + [headers.values] + Content-Type = "application/x-sh" + Content-Disposition = "attachment; filename=\"vlc-setup.sh\"" diff --git a/web/public/version.json b/web/public/version.json index a31b552b..45c6ad81 100644 --- a/web/public/version.json +++ b/web/public/version.json @@ -1 +1 @@ -{"v":"1784643864040"} +{"v":"1784701370858"} diff --git a/web/public/vlc-setup.sh b/web/public/vlc-setup.sh new file mode 100644 index 00000000..23e9838a --- /dev/null +++ b/web/public/vlc-setup.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# ARVIO - Enable "Open in VLC" protocol handler for Linux +set -e + +echo "================================================" +echo " ARVIO - Enable \"Open in VLC\" for Linux" +echo "================================================" +echo "" + +VLC_BIN=$(command -v vlc 2>/dev/null || true) +if [ -z "$VLC_BIN" ]; then + echo "[!] Could not find VLC on this system." + echo " Please install VLC (e.g., sudo apt install vlc / sudo dnf install vlc) and run this again." + exit 1 +fi + +echo "[+] Found VLC at: $VLC_BIN" + +BIN_DIR="$HOME/.local/bin" +APP_DIR="$HOME/.local/share/applications" +mkdir -p "$BIN_DIR" "$APP_DIR" + +SCRIPT_PATH="$BIN_DIR/arvio-vlc" + +cat << 'EOF' > "$SCRIPT_PATH" +#!/usr/bin/env bash +URL="$1" +# Strip vlc:// or vlc:/ prefix +URL="${URL#vlc://}" +URL="${URL#vlc:/}" +# Fix scheme collapse if browser stripped slashes +if [[ "$URL" =~ ^(https?)([^/].*) ]]; then + URL="${BASH_REMATCH[1]}://${BASH_REMATCH[2]}" +fi +if [ -n "$URL" ]; then + exec vlc "$URL" +fi +EOF + +chmod +x "$SCRIPT_PATH" +echo "[+] Installed handler script to: $SCRIPT_PATH" + +DESKTOP_PATH="$APP_DIR/vlc-handler.desktop" +cat << EOF > "$DESKTOP_PATH" +[Desktop Entry] +Name=VLC ARVIO Link Handler +Comment=Open vlc:// links directly in VLC +Exec=$SCRIPT_PATH %u +Type=Application +Terminal=false +MimeType=x-scheme-handler/vlc; +NoDisplay=true +EOF + +echo "[+] Created desktop entry at: $DESKTOP_PATH" + +if command -v xdg-mime >/dev/null 2>&1; then + xdg-mime default vlc-handler.desktop x-scheme-handler/vlc + echo "[+] Registered vlc:// scheme with xdg-mime." +fi + +if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "$APP_DIR" 2>/dev/null || true +fi + +echo "" +echo "================================================" +echo " Done! \"Open in VLC\" now works on web.arvio.tv" +echo "================================================" +echo "Go back to ARVIO and click \"Open in VLC\" on any source." From 9a6c7c900dc1bc533809341fa7bbb9066fe722ae Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Thu, 23 Jul 2026 11:28:39 +0530 Subject: [PATCH 2/2] fix(web): fix Linux VLC handler scheme regex, update test stream URL, and gate platform setup hints --- web/components/details/DetailsDrawer.tsx | 22 ++++-- web/components/settings/SettingsScreen.tsx | 88 +++++++++++++++------- web/public/version.json | 2 +- web/public/vlc-setup.sh | 12 +-- 4 files changed, 84 insertions(+), 40 deletions(-) diff --git a/web/components/details/DetailsDrawer.tsx b/web/components/details/DetailsDrawer.tsx index c2dbe889..ae03679b 100644 --- a/web/components/details/DetailsDrawer.tsx +++ b/web/components/details/DetailsDrawer.tsx @@ -8,7 +8,7 @@ import { RailScroller } from "@/components/media/RailScroller"; import { config } from "@/lib/config"; import { createPendingExternalPlayback } from "@/lib/externalPlayback"; import { saveProgress } from "@/lib/cloud"; -import { copyStreamUrl, downloadStreamUrl, downloadToVlc, externalLaunchMode, isAppleMobile, isDesktop, isWindows, openExternalPlayer, openInAnyPlayer, setVlcProtocolReady, triggerDownload, vlcProtocolReady, VLC_SETUP_URL } from "@/lib/externalPlayers"; +import { copyStreamUrl, downloadStreamUrl, downloadToVlc, externalLaunchMode, isAppleMobile, isDesktop, isLinux, isWindows, openExternalPlayer, openInAnyPlayer, setVlcProtocolReady, triggerDownload, vlcProtocolReady, VLC_SETUP_SH_URL, VLC_SETUP_URL } from "@/lib/externalPlayers"; import { fetchSubtitlesForItem } from "@/lib/addons"; import { cachedDebridDirectUrl, isUncachedDebridStream, parseDebridStream, prefetchDebridDirectUrl, resolveDebridDirectUrl } from "@/lib/debrid"; import { canonicalServiceName, IMDB_LOGO, serviceClearLogo } from "@/lib/serviceLogos"; @@ -381,14 +381,20 @@ function SourcePickerModal({ // macOS is excluded — VLC self-registers vlc:// there, so no installer is // needed (and the .bat wouldn't run on a Mac anyway). const [vlcReady, setVlcReady] = useState(() => vlcProtocolReady()); - const showVlcSetup = isWindows() && !vlcReady; + const showVlcSetup = (isWindows() || isLinux()) && !vlcReady; const enableVlcProtocol = () => { - // Download the tiny installer, then remember the user set it up so the VLC - // button switches to the direct vlc:// launch. - triggerDownload(VLC_SETUP_URL, "vlc-setup.bat"); - setVlcProtocolReady(true); - setVlcReady(true); - onToast("Run the downloaded vlc-setup.bat once — then Open in VLC works instantly."); + // Download the tiny installer script for the platform, then remember the user set it up + if (isLinux()) { + triggerDownload(VLC_SETUP_SH_URL, "vlc-setup.sh"); + setVlcProtocolReady(true); + setVlcReady(true); + onToast("Run `bash vlc-setup.sh` once in terminal — then Open in VLC works instantly."); + } else { + triggerDownload(VLC_SETUP_URL, "vlc-setup.bat"); + setVlcProtocolReady(true); + setVlcReady(true); + onToast("Run the downloaded vlc-setup.bat once — then Open in VLC works instantly."); + } }; // Addon subtitles for this title, fetched in the background when the panel // opens so the VLC/Infuse buttons can attach them synchronously on click. diff --git a/web/components/settings/SettingsScreen.tsx b/web/components/settings/SettingsScreen.tsx index 82831742..e144be81 100644 --- a/web/components/settings/SettingsScreen.tsx +++ b/web/components/settings/SettingsScreen.tsx @@ -41,6 +41,8 @@ import { } from "@/lib/config"; import { isLinux, + isMac, + isWindows, setVlcProtocolReady, triggerDownload, vlcProtocolReady, @@ -542,24 +544,52 @@ function SectionBody({ section }: { section: SectionId }) { ]} />
- - - + Natively supported + + ) : isLinux() ? ( + + + + ) : isWindows() ? ( + + + + ) : null}
- + {isMac() ? ( + + Natively Enabled on macOS (no setup script needed) + + ) : ( + + )}
diff --git a/web/public/version.json b/web/public/version.json index 45c6ad81..35c7ec65 100644 --- a/web/public/version.json +++ b/web/public/version.json @@ -1 +1 @@ -{"v":"1784701370858"} +{"v":"1784786206262"} \ No newline at end of file diff --git a/web/public/vlc-setup.sh b/web/public/vlc-setup.sh index 23e9838a..a208ea4d 100644 --- a/web/public/vlc-setup.sh +++ b/web/public/vlc-setup.sh @@ -25,11 +25,13 @@ SCRIPT_PATH="$BIN_DIR/arvio-vlc" cat << 'EOF' > "$SCRIPT_PATH" #!/usr/bin/env bash URL="$1" -# Strip vlc:// or vlc:/ prefix -URL="${URL#vlc://}" -URL="${URL#vlc:/}" -# Fix scheme collapse if browser stripped slashes -if [[ "$URL" =~ ^(https?)([^/].*) ]]; then +# Strip leading vlc: and any slashes following it +URL="${URL#vlc:}" +while [[ "$URL" == /* ]]; do + URL="${URL#/}" +done +# Repair mangled scheme separator (e.g. Chrome stripping colon https// -> https://, or collapsing slashes https:/ -> https://) +if [[ "$URL" =~ ^(https?)[:/]+(.*) ]]; then URL="${BASH_REMATCH[1]}://${BASH_REMATCH[2]}" fi if [ -n "$URL" ]; then