From 47653c2906fb12e32870b175f16cbc903aab7f74 Mon Sep 17 00:00:00 2001 From: Chris <134489068+Chris-tian123@users.noreply.github.com> Date: Sun, 12 Oct 2025 20:16:16 +0300 Subject: [PATCH] Fix casting (#49) * Tbh i tried adding it and it works but i cant fix the dual casting * clean up chromecasting * Apply Chromecast fixes * Update Icon.tsx --------- Co-authored-by: Pas <74743263+Pasithea0@users.noreply.github.com> --- src/assets/locales/en.json | 2 + src/components/Icon.tsx | 20 +---- src/components/player/Player.tsx | 1 + .../player/atoms/CastingNotification.tsx | 10 ++- src/components/player/atoms/Chromecast.tsx | 87 ++++++------------- .../player/internals/CastingInternal.tsx | 52 ++++++----- src/hooks/useChromecastAvailable.ts | 14 ++- src/pages/parts/player/PlayerPart.tsx | 14 ++- src/setup/chromecast.ts | 40 +++++---- 9 files changed, 105 insertions(+), 135 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index b590d556..76f4a155 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -594,6 +594,8 @@ "short": "Back" }, "casting": { + "to": "Casting to {{device}} 📺", + "device": "device", "enabled": "Casting to device 🎬" }, "menus": { diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index 58c1eb0a..eb567e98 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -1,5 +1,5 @@ import classNames from "classnames"; -import { memo, useEffect, useRef } from "react"; +import { memo } from "react"; export enum Icons { SEARCH = "search", @@ -128,7 +128,7 @@ const iconList: Record = { captions: ``, link: ``, circle_exclamation: ``, - casting: "", + casting: "", // leave blank because Chrome imports it's own icon download: ``, gear: ``, watch_party: ``, @@ -183,23 +183,7 @@ const iconList: Record = { repeat: ``, }; -function ChromeCastButton() { - const ref = useRef(null); - - useEffect(() => { - const tag = document.createElement("google-cast-launcher"); - tag.setAttribute("id", "castbutton"); - ref.current?.appendChild(tag); - }, []); - - return
; -} - export const Icon = memo((props: IconProps) => { - if (props.icon === Icons.CASTING) { - return ; - } - const flipClass = props.icon === Icons.ARROW_LEFT || props.icon === Icons.ARROW_RIGHT || diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index eebf459e..89ec5cde 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -11,3 +11,4 @@ export * from "./base/SubtitleView"; export * from "./internals/BookmarkButton"; export * from "./internals/InfoButton"; export * from "./internals/SkipEpisodeButton"; +export * from "./atoms/Chromecast"; diff --git a/src/components/player/atoms/CastingNotification.tsx b/src/components/player/atoms/CastingNotification.tsx index 9c4e423b..b20c91d3 100644 --- a/src/components/player/atoms/CastingNotification.tsx +++ b/src/components/player/atoms/CastingNotification.tsx @@ -8,15 +8,23 @@ export function CastingNotification() { const isLoading = usePlayerStore((s) => s.mediaPlaying.isLoading); const display = usePlayerStore((s) => s.display); const isCasting = display?.getType() === "casting"; + const remotePlayer = usePlayerStore((s) => s.casting.player); if (isLoading || !isCasting) return null; + let deviceName = remotePlayer?.displayName || t("player.casting.device"); + if (deviceName === "Default Media Receiver") { + deviceName = t("player.casting.device"); // e.g., "your TV" + } + return (
-

{t("player.casting.enabled")}

+

+ {t("player.casting.to", { device: deviceName })} +

); } diff --git a/src/components/player/atoms/Chromecast.tsx b/src/components/player/atoms/Chromecast.tsx index e73fdb8a..bb430db7 100644 --- a/src/components/player/atoms/Chromecast.tsx +++ b/src/components/player/atoms/Chromecast.tsx @@ -1,10 +1,11 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +/// + +import { useEffect, useRef, useState } from "react"; -import { Icons } from "@/components/Icon"; import { VideoPlayerButton } from "@/components/player/internals/Button"; import { usePlayerStore } from "@/stores/player/store"; -// Allow the custom element in TSX without adding a global d.ts file +// Allow the custom element in TSX /* eslint-disable @typescript-eslint/no-namespace */ declare global { namespace JSX { @@ -19,89 +20,55 @@ export interface ChromecastProps { className?: string; } -export function Chromecast(props: ChromecastProps) { - const [hidden, setHidden] = useState(false); +export function Chromecast({ className }: ChromecastProps) { const [castHidden, setCastHidden] = useState(false); const isCasting = usePlayerStore((s) => s.interface.isCasting); - const ref = useRef(null); - - const setButtonVisibility = useCallback( - (tag: HTMLElement) => { - const isVisible = (tag.getAttribute("style") ?? "").includes("inline"); - setHidden(!isVisible); - }, - [setHidden], - ); + const launcherRef = useRef(null); useEffect(() => { - const tag = ref.current?.querySelector("google-cast-launcher"); - if (!tag) return; + const w = window as unknown as { cast?: typeof cast }; + const castFramework = w.cast?.framework; + if (!castFramework) return; - const observer = new MutationObserver(() => { - setButtonVisibility(tag); - }); - - observer.observe(tag, { attributes: true, attributeFilter: ["style"] }); - setButtonVisibility(tag); - - return () => { - observer.disconnect(); - }; - }, [setButtonVisibility]); - - // Hide the button when there are no cast devices available according to CAF - useEffect(() => { - const w = window as any; - const cast = w?.cast; - if (!cast?.framework) return; - - const context = cast.framework.CastContext.getInstance(); - const update = () => { + const context = castFramework.CastContext.getInstance(); + const updateVisibility = () => { const state = context.getCastState(); - setCastHidden(state === cast.framework.CastState.NO_DEVICES_AVAILABLE); + setCastHidden(state === castFramework.CastState.NO_DEVICES_AVAILABLE); }; - const handler = () => update(); + const handler = () => updateVisibility(); context.addEventListener( - cast.framework.CastContextEventType.CAST_STATE_CHANGED, + castFramework.CastContextEventType.CAST_STATE_CHANGED, handler, ); - update(); + updateVisibility(); return () => { context.removeEventListener( - cast.framework.CastContextEventType.CAST_STATE_CHANGED, + castFramework.CastContextEventType.CAST_STATE_CHANGED, handler, ); }; }, []); + useEffect(() => { + if (!launcherRef.current || launcherRef.current.children.length > 0) return; + + const launcher = document.createElement("google-cast-launcher"); + launcherRef.current.appendChild(launcher); + }, []); + return (