mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-16 02:44:51 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5921c2da59 | ||
|
|
ee4b1a3e32 | ||
|
|
8656fb8068 | ||
|
|
31bd0adcef | ||
|
|
0d8b33aee3 |
@@ -52,6 +52,9 @@ export function usePlayerState<Key extends keyof OmniPlayerState>(
|
|||||||
case "status":
|
case "status":
|
||||||
em.addPlayerStatusListener(setState);
|
em.addPlayerStatusListener(setState);
|
||||||
return () => em.removePlayerStatusListener(setState);
|
return () => em.removePlayerStatusListener(setState);
|
||||||
|
case "castStatus":
|
||||||
|
// TODO: implement this
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [player, key]);
|
}, [player, key]);
|
||||||
|
|
||||||
|
|||||||
+9
-4
@@ -6,6 +6,7 @@ import {
|
|||||||
selectPlayback,
|
selectPlayback,
|
||||||
selectPlaybackRate,
|
selectPlaybackRate,
|
||||||
selectQuality,
|
selectQuality,
|
||||||
|
selectRemotePlayback,
|
||||||
selectTextTrack,
|
selectTextTrack,
|
||||||
selectTime,
|
selectTime,
|
||||||
selectVolume,
|
selectVolume,
|
||||||
@@ -108,18 +109,16 @@ export const useEvent = <Event extends keyof OmniEvents>(
|
|||||||
prevRef.current = value;
|
prevRef.current = value;
|
||||||
}, [value, config]);
|
}, [value, config]);
|
||||||
|
|
||||||
// prev/next are triggered by the app (or the media session) rather than the
|
|
||||||
// media element
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// don't use callbackRef.current directly to keep future callback updates
|
||||||
|
const cb = () => (callbackRef.current as () => void)();
|
||||||
if (event === "prev") {
|
if (event === "prev") {
|
||||||
const cb = callbackRef.current as () => void;
|
|
||||||
player.onPrev.add(cb);
|
player.onPrev.add(cb);
|
||||||
return () => {
|
return () => {
|
||||||
player.onPrev.delete(cb);
|
player.onPrev.delete(cb);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (event === "next") {
|
if (event === "next") {
|
||||||
const cb = callbackRef.current as () => void;
|
|
||||||
player.onNext.add(cb);
|
player.onNext.add(cb);
|
||||||
return () => {
|
return () => {
|
||||||
player.onNext.delete(cb);
|
player.onNext.delete(cb);
|
||||||
@@ -179,6 +178,12 @@ export const stateMapper = {
|
|||||||
if (!q) return true;
|
if (!q) return true;
|
||||||
return !q.videoRenditionList.some((r) => r.selected);
|
return !q.videoRenditionList.some((r) => r.selected);
|
||||||
}),
|
}),
|
||||||
|
...createMapper("castStatus", selectRemotePlayback, (r) => {
|
||||||
|
if (!r) return "unavailable";
|
||||||
|
if (r.remotePlaybackState === "connecting") return "connecting";
|
||||||
|
if (r.remotePlaybackState === "connected") return "connected";
|
||||||
|
return r.remotePlaybackAvailability
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function usePlayerState<Key extends keyof OmniPlayerState>(
|
export function usePlayerState<Key extends keyof OmniPlayerState>(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export { useEvent, usePlayerState } from "./events";
|
|||||||
export { OmniProvider, usePlayer } from "./provider";
|
export { OmniProvider, usePlayer } from "./provider";
|
||||||
export type { OmniEvents } from "./types/events";
|
export type { OmniEvents } from "./types/events";
|
||||||
export type {
|
export type {
|
||||||
|
CastStatus,
|
||||||
OmniPlayer,
|
OmniPlayer,
|
||||||
OmniPlayerState,
|
OmniPlayerState,
|
||||||
PlayerStatus,
|
PlayerStatus,
|
||||||
@@ -9,6 +10,7 @@ export type {
|
|||||||
Track,
|
Track,
|
||||||
} from "./types/player";
|
} from "./types/player";
|
||||||
export type {
|
export type {
|
||||||
|
CastOptions,
|
||||||
Metadata,
|
Metadata,
|
||||||
MixAudioMode,
|
MixAudioMode,
|
||||||
Source,
|
Source,
|
||||||
|
|||||||
+11
-1
@@ -7,12 +7,13 @@ import {
|
|||||||
} from "@videojs/react";
|
} from "@videojs/react";
|
||||||
import { stateMapper } from "./events.web";
|
import { stateMapper } from "./events.web";
|
||||||
import type {
|
import type {
|
||||||
|
CastStatus,
|
||||||
OmniPlayer,
|
OmniPlayer,
|
||||||
PlayerStatus,
|
PlayerStatus,
|
||||||
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";
|
||||||
|
|
||||||
@@ -51,6 +52,15 @@ export class WebOmniPlayer implements OmniPlayer {
|
|||||||
this._store = store;
|
this._store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get castStatus(): CastStatus {
|
||||||
|
return stateMapper.castStatus.mapper(this._store.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCastStatus(): void {
|
||||||
|
this._store.state.toggleRemotePlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
castOptions: CastOptions | null = null;
|
||||||
_source: Source | null = null;
|
_source: Source | null = null;
|
||||||
private _showNotification = false;
|
private _showNotification = false;
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -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
@@ -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]);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export interface OmniPlayer extends OmniPlayerState {
|
|||||||
pause(): void;
|
pause(): void;
|
||||||
seekBy(offset: number): void;
|
seekBy(offset: number): void;
|
||||||
|
|
||||||
|
toggleCastStatus(): void;
|
||||||
|
|
||||||
// trigger the prev/next event manually, the user has to implement the event
|
// trigger the prev/next event manually, the user has to implement the event
|
||||||
playPrev(): void;
|
playPrev(): void;
|
||||||
playNext(): void;
|
playNext(): void;
|
||||||
@@ -32,10 +34,18 @@ export interface OmniPlayerState {
|
|||||||
volume: number;
|
volume: number;
|
||||||
muted: boolean;
|
muted: boolean;
|
||||||
readonly isAutoQuality: boolean;
|
readonly isAutoQuality: boolean;
|
||||||
|
readonly castStatus: CastStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlayerStatus = "idle" | "loading" | "readyToPlay" | "error";
|
export type PlayerStatus = "idle" | "loading" | "readyToPlay" | "error";
|
||||||
|
|
||||||
|
export type CastStatus =
|
||||||
|
| "connecting"
|
||||||
|
| "connected"
|
||||||
|
| "available"
|
||||||
|
| "unavailable"
|
||||||
|
| "unsupported";
|
||||||
|
|
||||||
export interface Track {
|
export interface Track {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly label?: string;
|
readonly label?: string;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+18
-12
@@ -116,20 +116,26 @@ export const OmniView = ({
|
|||||||
|
|
||||||
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 (
|
||||||
@@ -141,7 +147,7 @@ export const OmniView = ({
|
|||||||
<Tech
|
<Tech
|
||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user