feat(cast): allow custom cast data and custom receivers

This commit is contained in:
2026-07-19 13:59:45 +02:00
parent 8656fb8068
commit ee4b1a3e32
6 changed files with 48 additions and 25 deletions
+1
View File
@@ -10,6 +10,7 @@ export type {
Track, Track,
} from "./types/player"; } from "./types/player";
export type { export type {
CastOptions,
Metadata, Metadata,
MixAudioMode, MixAudioMode,
Source, Source,
+2 -1
View File
@@ -13,7 +13,7 @@ import type {
Rendition, Rendition,
Track, Track,
} from "./types/player"; } 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"; export type SubtitleFormat = "vtt" | "ass" | "pgs" | "native";
@@ -60,6 +60,7 @@ export class WebOmniPlayer implements OmniPlayer {
this._store.state.toggleRemotePlayback(); this._store.state.toggleRemotePlayback();
} }
castOptions: CastOptions | null = null;
_source: Source | null = null; _source: Source | null = null;
private _showNotification = false; private _showNotification = false;
+3 -1
View File
@@ -2,7 +2,7 @@ import { createContext, type ReactNode, useContext, useEffect } from "react";
import { NitroModules } from "react-native-nitro-modules"; import { NitroModules } from "react-native-nitro-modules";
import type { OmniPlayerFactory } from "./specs/omni-player.nitro"; import type { OmniPlayerFactory } from "./specs/omni-player.nitro";
import type { OmniPlayer } from "./types/player"; 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"; import { useLazyRef } from "./utils/lazy-ref";
const ProviderFactory = const ProviderFactory =
@@ -14,8 +14,10 @@ export const OmniProvider = ({
children, children,
source, source,
showNotification = false, showNotification = false,
cast: _,
}: { }: {
source: Source; source: Source;
cast?: CastOptions;
children: ReactNode; children: ReactNode;
showNotification?: boolean; showNotification?: boolean;
}) => { }) => {
+15 -3
View File
@@ -9,7 +9,7 @@ import {
} from "react"; } from "react";
import { WebOmniPlayer } from "./player.web"; import { WebOmniPlayer } from "./player.web";
import type { OmniPlayer } from "./types/player"; 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"; import { useLazyRef } from "./utils/lazy-ref";
export const VideoPlayer = createPlayer({ features: videoFeatures }); export const VideoPlayer = createPlayer({ features: videoFeatures });
@@ -19,15 +19,21 @@ const PlayerCtx = createContext<OmniPlayer>(null!);
export const OmniProvider = ({ export const OmniProvider = ({
children, children,
source, source,
cast,
showNotification = false, showNotification = false,
}: { }: {
source: Source; source: Source;
cast?: CastOptions;
children: ReactNode; children: ReactNode;
showNotification?: boolean; showNotification?: boolean;
}) => { }) => {
return ( return (
<VideoPlayer.Provider> <VideoPlayer.Provider>
<PlayerInitializer source={source} showNotification={showNotification}> <PlayerInitializer
source={source}
cast={cast}
showNotification={showNotification}
>
{children} {children}
</PlayerInitializer> </PlayerInitializer>
</VideoPlayer.Provider> </VideoPlayer.Provider>
@@ -37,10 +43,12 @@ export const OmniProvider = ({
const PlayerInitializer = ({ const PlayerInitializer = ({
children, children,
source, source,
cast,
showNotification, showNotification,
}: { }: {
children: ReactNode; children: ReactNode;
source: Source; source: Source;
cast?: CastOptions;
showNotification: boolean; showNotification: boolean;
}) => { }) => {
const store = VideoPlayer.usePlayer(); const store = VideoPlayer.usePlayer();
@@ -52,10 +60,14 @@ const PlayerInitializer = ({
const uri = source.src[0]?.uri; const uri = source.src[0]?.uri;
if (uri !== seekedForSrc.current) { if (uri !== seekedForSrc.current) {
seekedForSrc.current = uri; seekedForSrc.current = uri;
if (source.startTime) store.seek(source.startTime) if (source.startTime) store.seek(source.startTime);
} }
}, [source, store]); }, [source, store]);
useEffect(() => {
player.castOptions = cast ?? null;
}, [cast]);
useEffect(() => { useEffect(() => {
player.showNotification = showNotification; player.showNotification = showNotification;
}, [showNotification]); }, [showNotification]);
+7
View File
@@ -7,6 +7,13 @@ export interface Source {
fonts?: string[]; fonts?: string[];
metadata?: Metadata; metadata?: Metadata;
mixAudio?: MixAudioMode; 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 { export interface VideoSrc {
+20 -20
View File
@@ -1,6 +1,5 @@
import type { HlsMediaConfig } from "@videojs/core/dom/media/hls-js"; import type { HlsMediaConfig } from "@videojs/core/dom/media/hls-js";
import { HlsJsVideo } from "@videojs/react/media/hlsjs-video"; import { HlsJsVideo } from "@videojs/react/media/hlsjs-video";
import { Video } from "@videojs/react/video";
import { import {
type CSSProperties, type CSSProperties,
type RefObject, type RefObject,
@@ -108,28 +107,29 @@ export const OmniView = ({
const ref = useRef<HTMLVideoElement>(undefined!); const ref = useRef<HTMLVideoElement>(undefined!);
const src = player.source?.src[0]; 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); const headersRef = useRef(src?.headers);
headersRef.current = src?.headers; headersRef.current = src?.headers;
const castData = player.source?.castData;
const config = useMemo<HlsMediaConfig>( const config = useMemo<HlsMediaConfig>(
() => ({ () =>
hlsJs: { ({
xhrSetup: (xhr: XMLHttpRequest) => { hlsJs: {
const headers = headersRef.current; xhrSetup: (xhr: XMLHttpRequest) => {
if (!headers) return; const headers = headersRef.current;
for (const [key, value] of Object.entries(headers)) { if (!headers) return;
if (value) xhr.setRequestHeader(key, value); 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 ( return (
@@ -138,10 +138,10 @@ export const OmniView = ({
style={{ position: "relative", ...style }} style={{ position: "relative", ...style }}
> >
{src && ( {src && (
<Tech <HlsJsVideo
ref={ref} ref={ref}
src={src.uri} src={src.uri}
config={isHls ? config : undefined} config={config}
autoPlay={autoplay} autoPlay={autoplay}
playsInline playsInline
crossOrigin="anonymous" crossOrigin="anonymous"
@@ -159,7 +159,7 @@ export const OmniView = ({
label={subtitle.label ?? subtitle.language ?? subtitle.id} label={subtitle.label ?? subtitle.language ?? subtitle.id}
/> />
))} ))}
</Tech> </HlsJsVideo>
)} )}
<SubtitleOverlay <SubtitleOverlay
video={ref} video={ref}