From ee4b1a3e32fdaef62830d91ae635dc3d12e6ce5a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 17 Jul 2026 09:32:16 +0200 Subject: [PATCH] feat(cast): allow custom cast data and custom receivers --- src/index.ts | 1 + src/player.web.tsx | 3 ++- src/provider.tsx | 4 +++- src/provider.web.tsx | 18 +++++++++++++++--- src/types/source.ts | 7 +++++++ src/view.web.tsx | 40 ++++++++++++++++++++-------------------- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/index.ts b/src/index.ts index 43a52ec..47a9deb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export type { Track, } from "./types/player"; export type { + CastOptions, Metadata, MixAudioMode, Source, diff --git a/src/player.web.tsx b/src/player.web.tsx index 83e6cbf..cf76e56 100644 --- a/src/player.web.tsx +++ b/src/player.web.tsx @@ -13,7 +13,7 @@ import type { Rendition, Track, } from "./types/player"; -import type { Source, Subtitle } from "./types/source"; +import type { CastOptions, Source, Subtitle } from "./types/source"; export type SubtitleFormat = "vtt" | "ass" | "pgs" | "native"; @@ -60,6 +60,7 @@ export class WebOmniPlayer implements OmniPlayer { this._store.state.toggleRemotePlayback(); } + castOptions: CastOptions | null = null; _source: Source | null = null; private _showNotification = false; diff --git a/src/provider.tsx b/src/provider.tsx index d7e9561..6756249 100755 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -2,7 +2,7 @@ import { createContext, type ReactNode, useContext, useEffect } from "react"; import { NitroModules } from "react-native-nitro-modules"; import type { OmniPlayerFactory } from "./specs/omni-player.nitro"; import type { OmniPlayer } from "./types/player"; -import type { Source } from "./types/source"; +import type { CastOptions, Source } from "./types/source"; import { useLazyRef } from "./utils/lazy-ref"; const ProviderFactory = @@ -14,8 +14,10 @@ export const OmniProvider = ({ children, source, showNotification = false, + cast: _, }: { source: Source; + cast?: CastOptions; children: ReactNode; showNotification?: boolean; }) => { diff --git a/src/provider.web.tsx b/src/provider.web.tsx index 62dc528..4632bbf 100644 --- a/src/provider.web.tsx +++ b/src/provider.web.tsx @@ -9,7 +9,7 @@ import { } from "react"; import { WebOmniPlayer } from "./player.web"; import type { OmniPlayer } from "./types/player"; -import type { Source } from "./types/source"; +import type { CastOptions, Source } from "./types/source"; import { useLazyRef } from "./utils/lazy-ref"; export const VideoPlayer = createPlayer({ features: videoFeatures }); @@ -19,15 +19,21 @@ const PlayerCtx = createContext(null!); export const OmniProvider = ({ children, source, + cast, showNotification = false, }: { source: Source; + cast?: CastOptions; children: ReactNode; showNotification?: boolean; }) => { return ( - + {children} @@ -37,10 +43,12 @@ export const OmniProvider = ({ const PlayerInitializer = ({ children, source, + cast, showNotification, }: { children: ReactNode; source: Source; + cast?: CastOptions; showNotification: boolean; }) => { const store = VideoPlayer.usePlayer(); @@ -52,10 +60,14 @@ const PlayerInitializer = ({ const uri = source.src[0]?.uri; if (uri !== seekedForSrc.current) { seekedForSrc.current = uri; - if (source.startTime) store.seek(source.startTime) + if (source.startTime) store.seek(source.startTime); } }, [source, store]); + useEffect(() => { + player.castOptions = cast ?? null; + }, [cast]); + useEffect(() => { player.showNotification = showNotification; }, [showNotification]); diff --git a/src/types/source.ts b/src/types/source.ts index cf8f7d7..f87eba6 100644 --- a/src/types/source.ts +++ b/src/types/source.ts @@ -7,6 +7,13 @@ export interface Source { fonts?: string[]; metadata?: Metadata; mixAudio?: MixAudioMode; + // Opaque, consumer-defined payload forwarded verbatim to the cast receiver + // as the load request's customData. omni does not interpret it. + castData?: unknown; +} + +export interface CastOptions { + receiverApplicationId?: string; } export interface VideoSrc { diff --git a/src/view.web.tsx b/src/view.web.tsx index 955457a..f679006 100644 --- a/src/view.web.tsx +++ b/src/view.web.tsx @@ -1,6 +1,5 @@ import type { HlsMediaConfig } from "@videojs/core/dom/media/hls-js"; import { HlsJsVideo } from "@videojs/react/media/hlsjs-video"; -import { Video } from "@videojs/react/video"; import { type CSSProperties, type RefObject, @@ -108,28 +107,29 @@ export const OmniView = ({ const ref = useRef(undefined!); const src = player.source?.src[0]; - const isHls = - src?.mimeType?.toLowerCase().includes("mpegurl") || - src?.uri.split(/[?#]/)[0]?.toLowerCase().endsWith(".m3u8") || - false; - const Tech = isHls ? HlsJsVideo : Video; const headersRef = useRef(src?.headers); headersRef.current = src?.headers; + const castData = player.source?.castData; const config = useMemo( - () => ({ - hlsJs: { - xhrSetup: (xhr: XMLHttpRequest) => { - const headers = headersRef.current; - if (!headers) return; - for (const [key, value] of Object.entries(headers)) { - if (value) xhr.setRequestHeader(key, value); - } + () => + ({ + hlsJs: { + xhrSetup: (xhr: XMLHttpRequest) => { + const headers = headersRef.current; + if (!headers) return; + for (const [key, value] of Object.entries(headers)) { + if (value) xhr.setRequestHeader(key, value); + } + }, }, - }, - }), - [], + googleCast: { + receiver: player.castOptions?.receiverApplicationId, + customData: castData ?? null, + }, + }) as HlsMediaConfig, + [player.castOptions, castData], ); return ( @@ -138,10 +138,10 @@ export const OmniView = ({ style={{ position: "relative", ...style }} > {src && ( - ))} - + )}