From 308b03a6ea3504aadee7609b66b9f07fdd2b8eb9 Mon Sep 17 00:00:00 2001 From: Autli Date: Wed, 28 Oct 2026 17:04:51 +0500 Subject: [PATCH] release: v0.0.13 - fix player play/pause script, multi-protocol postMessage dispatcher, and balancer autoplay --- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- src/components/UpdaterBanner.tsx | 2 +- src/pages/cinema/Cinema.tsx | 204 ++++++++++++++++++++++++------- 5 files changed, 165 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index dba52b4..0321c1e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "has", "private": true, - "version": "0.0.12", + "version": "0.0.13", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0300fb0..b61679d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "has" -version = "0.0.12" +version = "0.0.13" description = "has" authors = ["autli"] edition = "2024" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 297510f..26b6d73 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "has", - "version": "0.0.12", + "version": "0.0.13", "identifier": "com.has.tauri-app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/components/UpdaterBanner.tsx b/src/components/UpdaterBanner.tsx index 53f3b67..f0359f1 100644 --- a/src/components/UpdaterBanner.tsx +++ b/src/components/UpdaterBanner.tsx @@ -88,7 +88,7 @@ const isNewerSemver = (remote: string, current: string): boolean => { export function UpdaterBanner({ isTvEnvironment }: UpdaterBannerProps = {}) { const [isTv, setIsTv] = useState(() => isTvEnvironment ?? detectTvEnvironment()); - const [currentAppVersion, setCurrentAppVersion] = useState("0.0.12"); + const [currentAppVersion, setCurrentAppVersion] = useState("0.0.13"); const [updateAvailable, setUpdateAvailable] = useState(false); const [updateInfo, setUpdateInfo] = useState(null); const [downloading, setDownloading] = useState(false); diff --git a/src/pages/cinema/Cinema.tsx b/src/pages/cinema/Cinema.tsx index b3e2601..b37f53a 100644 --- a/src/pages/cinema/Cinema.tsx +++ b/src/pages/cinema/Cinema.tsx @@ -654,31 +654,103 @@ export default function Cinema({ const [isPlayingState, setIsPlayingState] = useState(true); + // Dedicated multi-protocol command dispatcher to balancer iframes + const sendIframePlaybackCommand = useCallback((command: "play" | "pause" | "toggle") => { + const iframe = document.querySelector(".has-cinema-iframe-elem"); + if (!iframe || !iframe.contentWindow) return; + + const win = iframe.contentWindow; + + // Focus iframe element & contentWindow so remote/keyboard events route into it + try { + iframe.focus(); + win.focus(); + } catch {} + + const post = (payload: any) => { + try { + win.postMessage(payload, "*"); + } catch {} + try { + win.postMessage(JSON.stringify(payload), "*"); + } catch {} + }; + + const isPlay = command === "play"; + const isToggle = command === "toggle"; + + // 1. Kodik Player API (Supports both object and stringified commands) + post({ key: "kodik_player_api", value: { method: isToggle ? "toggle" : (isPlay ? "play" : "pause") } }); + if (!isToggle) { + post({ key: "kodik_player_api", value: { method: isPlay ? "play" : "pause" } }); + } + + // 2. Playerjs API (Used by Alloha, Kinoplay, Collaps, VideoCDN, Ashdi, HDRezka) + post({ api: isToggle ? "toggle" : (isPlay ? "play" : "pause") }); + if (!isToggle) { + post({ api: isPlay ? "play" : "pause" }); + } + + // 3. Alloha API + post({ action: isPlay ? "play" : "pause" }); + post({ method: isPlay ? "play" : "pause" }); + + // 4. VidLink Pro / Video.js API + post({ type: isPlay ? "PLAYER_PLAY" : "PLAYER_PAUSE" }); + post({ type: isPlay ? "play" : "pause" }); + post({ action: isPlay ? "play" : "pause" }); + + // 5. YouTube / VidSrc / AutoEmbed HTML5 API + post({ event: "command", func: isPlay ? "playVideo" : "pauseVideo", args: "" }); + + // 6. Generic HTML5 postMessage + post({ method: isPlay ? "play" : "pause" }); + post({ command: isPlay ? "play" : "pause" }); + + // 7. Spacebar / K keyboard event simulation into iframe + try { + const spaceDown = new KeyboardEvent("keydown", { key: " ", code: "Space", keyCode: 32, which: 32, bubbles: true }); + iframe.dispatchEvent(spaceDown); + const kDown = new KeyboardEvent("keydown", { key: "k", code: "KeyK", keyCode: 75, which: 75, bubbles: true }); + iframe.dispatchEvent(kDown); + } catch {} + }, []); + + const triggerPlayerPlay = useCallback(() => { + if (videoRef.current) { + videoRef.current.play().catch(() => {}); + } else { + sendIframePlaybackCommand("play"); + } + setIsPlayingState(true); + showTvToast("Воспроизведение", "▶", 1200); + }, [sendIframePlaybackCommand, showTvToast]); + + const triggerPlayerPause = useCallback(() => { + if (videoRef.current) { + videoRef.current.pause(); + } else { + sendIframePlaybackCommand("pause"); + } + setIsPlayingState(false); + showTvToast("Пауза", "⏸", 1200); + }, [sendIframePlaybackCommand, showTvToast]); + const triggerPlayerPlayPause = useCallback(() => { if (videoRef.current) { if (videoRef.current.paused) { - videoRef.current.play().catch(() => {}); - setIsPlayingState(true); - showTvToast("Воспроизведение", "▶", 1200); + triggerPlayerPlay(); } else { - videoRef.current.pause(); - setIsPlayingState(false); - showTvToast("Пауза", "⏸", 1200); + triggerPlayerPause(); } } else { - const iframe = document.querySelector(".has-cinema-iframe-elem"); - if (iframe && iframe.contentWindow) { - setIsPlayingState((prev) => { - const next = !prev; - iframe.contentWindow?.postMessage({ key: "kodik_player_api", value: { method: next ? "play" : "pause" } }, "*"); - iframe.contentWindow?.postMessage({ api: next ? "play" : "pause" }, "*"); - iframe.contentWindow?.postMessage({ type: next ? "PLAY" : "PAUSE" }, "*"); - showTvToast(next ? "Воспроизведение" : "Пауза", next ? "▶" : "⏸", 1200); - return next; - }); + if (isPlayingState) { + triggerPlayerPause(); + } else { + triggerPlayerPlay(); } } - }, [showTvToast]); + }, [isPlayingState, triggerPlayerPlay, triggerPlayerPause]); const triggerPlayerSeek = useCallback((secondsDelta: number) => { if (videoRef.current) { @@ -689,8 +761,16 @@ export default function Cinema({ } else { const iframe = document.querySelector(".has-cinema-iframe-elem"); if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage({ key: "kodik_player_api", value: { method: "seek_delta", time: secondsDelta } }, "*"); - iframe.contentWindow.postMessage({ api: "seek", delta: secondsDelta }, "*"); + const win = iframe.contentWindow; + const post = (payload: any) => { + try { win.postMessage(payload, "*"); } catch {} + try { win.postMessage(JSON.stringify(payload), "*"); } catch {} + }; + post({ key: "kodik_player_api", value: { method: "seek_delta", time: secondsDelta } }); + post({ api: "seek", delta: secondsDelta }); + post({ api: "seek_delta", time: secondsDelta }); + post({ type: "SEEK_DELTA", delta: secondsDelta }); + post({ event: "command", func: "seekTo", args: [Math.max(0, currentPlaybackRef.current.currentTime + secondsDelta), true] }); } } showTvToast(secondsDelta > 0 ? `+${secondsDelta} сек` : `${secondsDelta} сек`, secondsDelta > 0 ? "⏩" : "⏪", 1000); @@ -1469,7 +1549,14 @@ export default function Cinema({ mirrorWatchdogTimerRef.current = null; } setIsMirrorConnecting(false); - }, []); + // Auto-trigger play once iframe scripts initialize + setTimeout(() => { + sendIframePlaybackCommand("play"); + }, 500); + setTimeout(() => { + sendIframePlaybackCommand("play"); + }, 1500); + }, [sendIframePlaybackCommand]); // Start playback for a media item (movie or specific series episode) const startPlayback = (media: CinemaMediaItem, seasonNum: number = 1, episode?: CinemaEpisode) => { @@ -1479,6 +1566,7 @@ export default function Cinema({ setRussianDubNotice(null); setAnilibriaStreamUrl(null); setIsHudVisible(true); + setIsPlayingState(true); setShowPlayerSettings(false); setShowPlayerEpisodes(false); resetHudTimer(); @@ -1908,6 +1996,25 @@ export default function Cinema({ } if (!data || typeof data !== "object") return; + // Update play/pause status from player messages + if ( + data.event === "play" || + data.type === "play" || + data.type === "PLAY" || + data.key === "kodik_player_play" || + (data.type === "PLAYER_EVENT" && data.data?.event === "play") + ) { + setIsPlayingState(true); + } else if ( + data.event === "pause" || + data.type === "pause" || + data.type === "PAUSE" || + data.key === "kodik_player_pause" || + (data.type === "PLAYER_EVENT" && data.data?.event === "pause") + ) { + setIsPlayingState(false); + } + // VidLink postMessage if (data.type === "MEDIA_DATA" && data.data) { if (typeof data.data.currentTime === "number" && data.data.currentTime > 0) { @@ -2709,7 +2816,7 @@ export default function Cinema({ const currentSeasonData: CinemaSeason = selectedMedia.seasons.find((s) => s.season_number === selectedSeason) || selectedMedia.seasons[0]; - // Balancer embed URL with multiple verified mirror selection + // Balancer embed URL with multiple verified mirror selection & autoplay flag const getBalancerEmbedUrl = (): string => { const media = playingMedia || selectedMedia; const tmdb = media.tmdb_id; @@ -2719,62 +2826,72 @@ export default function Cinema({ const kpId = currentKpId || (tmdb ? KNOWN_KP_MAP[tmdb] : null); const imdbId = currentImdbId; + const appendAutoplay = (url: string): string => { + if (!url) return url; + if (url.includes("autoplay=")) return url; + const joiner = url.includes("?") ? "&" : "?"; + const val = url.includes("vidlink.pro") ? "true" : "1"; + return `${url}${joiner}autoplay=${val}`; + }; + switch (selectedMirror) { case "vidlink_pro": return isTv - ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?primaryColor=6366f1` - : `https://vidlink.pro/movie/${tmdb || 693134}?primaryColor=6366f1`; + ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=true&primaryColor=6366f1` + : `https://vidlink.pro/movie/${tmdb || 693134}?autoplay=true&primaryColor=6366f1`; case "kodik_ru": if (selectedKodikLink) { - return selectedKodikLink.startsWith("//") ? `https:${selectedKodikLink}` : selectedKodikLink; + const l = selectedKodikLink.startsWith("//") ? `https:${selectedKodikLink}` : selectedKodikLink; + return appendAutoplay(l); } if (kodikResults.length > 0 && kodikResults[0]?.link) { - const l = kodikResults[0].link; - return l.startsWith("//") ? `https:${l}` : l; + const l = kodikResults[0].link.startsWith("//") ? `https:${kodikResults[0].link}` : kodikResults[0].link; + return appendAutoplay(l); } return isTv - ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?primaryColor=6366f1` - : `https://vidlink.pro/movie/${tmdb || 693134}?primaryColor=6366f1`; + ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=true&primaryColor=6366f1` + : `https://vidlink.pro/movie/${tmdb || 693134}?autoplay=true&primaryColor=6366f1`; case "autoembed": return isTv - ? `https://player.autoembed.co/embed/tv/${tmdb || 693134}/${sNum}/${epNum}` - : `https://player.autoembed.co/embed/movie/${tmdb || 693134}`; + ? `https://player.autoembed.co/embed/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=1` + : `https://player.autoembed.co/embed/movie/${tmdb || 693134}?autoplay=1`; case "vidsrc_pm": return isTv - ? `https://vidsrc.pm/embed/tv/${tmdb || 693134}/${sNum}/${epNum}` - : `https://vidsrc.pm/embed/movie/${tmdb || 693134}`; + ? `https://vidsrc.pm/embed/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=1` + : `https://vidsrc.pm/embed/movie/${tmdb || 693134}?autoplay=1`; case "multiembed": return isTv - ? `https://multiembed.mov/?video_id=${tmdb || 693134}&tmdb=1&s=${sNum}&e=${epNum}` - : `https://multiembed.mov/?video_id=${tmdb || 693134}&tmdb=1`; + ? `https://multiembed.mov/?video_id=${tmdb || 693134}&tmdb=1&s=${sNum}&e=${epNum}&autoplay=1` + : `https://multiembed.mov/?video_id=${tmdb || 693134}&tmdb=1&autoplay=1`; case "vidsrc_to": return isTv - ? `https://vidsrc.to/embed/tv/${tmdb || 693134}/${sNum}/${epNum}` - : `https://vidsrc.to/embed/movie/${tmdb || 693134}`; + ? `https://vidsrc.to/embed/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=1` + : `https://vidsrc.to/embed/movie/${tmdb || 693134}?autoplay=1`; case "alloha_ru": if (currentAllohaUrl) { - return currentAllohaUrl.startsWith("//") ? `https:${currentAllohaUrl}` : currentAllohaUrl; + const l = currentAllohaUrl.startsWith("//") ? `https:${currentAllohaUrl}` : currentAllohaUrl; + return appendAutoplay(l); } if (kpId) { - return `https://theatre.stravers.live/?kp=${kpId}`; + return `https://theatre.stravers.live/?kp=${kpId}&autoplay=1`; } if (imdbId) { - return `https://theatre.stravers.live/?imdb=${imdbId}`; + return `https://theatre.stravers.live/?imdb=${imdbId}&autoplay=1`; } return isTv - ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?primaryColor=6366f1` - : `https://vidlink.pro/movie/${tmdb || 693134}?primaryColor=6366f1`; + ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=true&primaryColor=6366f1` + : `https://vidlink.pro/movie/${tmdb || 693134}?autoplay=true&primaryColor=6366f1`; default: return isTv - ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?primaryColor=6366f1` - : `https://vidlink.pro/movie/${tmdb || 693134}?primaryColor=6366f1`; + ? `https://vidlink.pro/tv/${tmdb || 693134}/${sNum}/${epNum}?autoplay=true&primaryColor=6366f1` + : `https://vidlink.pro/movie/${tmdb || 693134}?autoplay=true&primaryColor=6366f1`; } }; @@ -4585,6 +4702,7 @@ export default function Cinema({ allow="autoplay *; fullscreen *; encrypted-media *; picture-in-picture *; clipboard-write *" referrerPolicy="no-referrer-when-downgrade" allowFullScreen + tabIndex={0} onLoad={handleIframeLoaded} onError={() => handleAutoSwitchNextMirror("Сервер вернул ошибку")} {...{ webkitallowfullscreen: "true", mozallowfullscreen: "true" }}