mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-16 02:44:51 +00:00
feat(state): add state for videos/audios/sub/rend
This commit is contained in:
@@ -60,6 +60,14 @@ export function usePlayerState<Key extends keyof OmniPlayerState>(
|
||||
case "source":
|
||||
em.addSourceListener(setState);
|
||||
return () => em.removeSourceListener(setState);
|
||||
case "videos":
|
||||
case "audios":
|
||||
case "subtitles":
|
||||
em.addTracksListener(key, setState);
|
||||
return () => em.removeTracksListener(key, setState);
|
||||
case "renditions":
|
||||
em.addRenditionsListener(setState);
|
||||
return () => em.removeRenditionsListener(setState);
|
||||
}
|
||||
}, [player, key]);
|
||||
|
||||
|
||||
+56
-12
@@ -202,6 +202,32 @@ export const stateMapper = {
|
||||
if (r.remotePlaybackState === "connected") return "connected";
|
||||
return r.remotePlaybackAvailability;
|
||||
}),
|
||||
...createMapper("audios", selectAudioTrack, (audio) => {
|
||||
if (!audio) return [];
|
||||
return audio.audioTrackList.map((track, i) => ({
|
||||
id: track.id ?? i.toString(),
|
||||
label: track.label,
|
||||
language: track.language,
|
||||
selected: track.enabled,
|
||||
}));
|
||||
}),
|
||||
...createMapper("renditions", selectQuality, (quality) => {
|
||||
if (!quality) return [];
|
||||
const active = quality.activeVideoRendition;
|
||||
return quality.videoRenditionList.map((rendition, i) => ({
|
||||
id: rendition.id ?? i.toString(),
|
||||
width: rendition.width ?? 0,
|
||||
height: rendition.height ?? 0,
|
||||
bitrate: rendition.bitrate ?? 0,
|
||||
selected:
|
||||
rendition.selected ||
|
||||
(active != null &&
|
||||
rendition.id === active.id &&
|
||||
rendition.width === active.width &&
|
||||
rendition.height === active.height &&
|
||||
rendition.bitrate === active.bitrate),
|
||||
}));
|
||||
}),
|
||||
};
|
||||
|
||||
// biome-ignore-start lint/correctness/useHookAtTopLevel: key must be const
|
||||
@@ -216,18 +242,36 @@ export function usePlayerState<Key extends keyof OmniPlayerState>(
|
||||
key: Key,
|
||||
_refresh?: number,
|
||||
): OmniPlayerState[Key] {
|
||||
if (key === "source") {
|
||||
const player = usePlayer() as WebOmniPlayer;
|
||||
const source = useSyncExternalStore(
|
||||
player.subscribeSource,
|
||||
() => player.source,
|
||||
() => player.source,
|
||||
);
|
||||
return source as OmniPlayerState[Key];
|
||||
switch (key as keyof OmniPlayerState) {
|
||||
case "source": {
|
||||
const player = usePlayer() as WebOmniPlayer;
|
||||
const source = useSyncExternalStore(
|
||||
player.subscribeSource,
|
||||
() => player.source,
|
||||
() => player.source,
|
||||
);
|
||||
return source as OmniPlayerState[Key];
|
||||
}
|
||||
case "videos": {
|
||||
const player = usePlayer();
|
||||
return player.videos as OmniPlayerState[Key];
|
||||
}
|
||||
case "subtitles": {
|
||||
const player = usePlayer() as WebOmniPlayer;
|
||||
useStoreSelector(selectTextTrack);
|
||||
useSyncExternalStore(
|
||||
player.subscribeOverlaySubtitle,
|
||||
player.getOverlaySubtitle,
|
||||
() => null,
|
||||
);
|
||||
return player.subtitles as OmniPlayerState[Key];
|
||||
}
|
||||
default: {
|
||||
const config = stateMapper[key];
|
||||
const ret = useStoreSelector(config?.selector as Selector<any, any>);
|
||||
if (!config) throw new Error(`No mapper for ${key}`);
|
||||
return config.mapper(ret) as OmniPlayerState[Key];
|
||||
}
|
||||
}
|
||||
const config = stateMapper[key as keyof Omit<OmniPlayerState, "source">];
|
||||
const ret = useStoreSelector(config?.selector as Selector<any, any>);
|
||||
if (!config) throw new Error(`No mapper for ${key}`);
|
||||
return config.mapper(ret) as OmniPlayerState[Key];
|
||||
}
|
||||
// biome-ignore-end lint/correctness/useHookAtTopLevel: key must be const
|
||||
|
||||
+3
-34
@@ -1,4 +1,3 @@
|
||||
import type { MediaVideoRendition } from "@videojs/core";
|
||||
import type { VideoPlayerStore } from "@videojs/core/dom";
|
||||
import {
|
||||
selectAudioTrack,
|
||||
@@ -76,7 +75,6 @@ export class WebOmniPlayer implements OmniPlayer {
|
||||
return () => this.sourceListeners.delete(callback);
|
||||
};
|
||||
|
||||
|
||||
get source(): Source | undefined {
|
||||
return this._source;
|
||||
}
|
||||
@@ -203,14 +201,7 @@ export class WebOmniPlayer implements OmniPlayer {
|
||||
}
|
||||
|
||||
get audios(): Track[] {
|
||||
const audio = selectAudioTrack(this._store.state);
|
||||
if (!audio) return [];
|
||||
return audio.audioTrackList.map((track, i) => ({
|
||||
id: track.id ?? i.toString(),
|
||||
label: track.label,
|
||||
language: track.language,
|
||||
selected: track.enabled,
|
||||
}));
|
||||
return stateMapper.audios.mapper(selectAudioTrack(this._store.state));
|
||||
}
|
||||
|
||||
selectAudio(audio: Track): void {
|
||||
@@ -266,30 +257,8 @@ export class WebOmniPlayer implements OmniPlayer {
|
||||
|
||||
getOverlaySubtitle = (): Subtitle | null => this.overlaySubtitle;
|
||||
|
||||
get rendition(): Rendition[] {
|
||||
function isSameRendition(
|
||||
a: MediaVideoRendition,
|
||||
b: MediaVideoRendition | null,
|
||||
): boolean {
|
||||
return (
|
||||
b != null &&
|
||||
a.id === b.id &&
|
||||
a.width === b.width &&
|
||||
a.height === b.height &&
|
||||
a.bitrate === b.bitrate
|
||||
);
|
||||
}
|
||||
|
||||
const quality = selectQuality(this._store.state);
|
||||
if (!quality) return [];
|
||||
const active = quality.activeVideoRendition;
|
||||
return quality.videoRenditionList.map((rendition, i) => ({
|
||||
id: rendition.id ?? i.toString(),
|
||||
width: rendition.width ?? 0,
|
||||
height: rendition.height ?? 0,
|
||||
bitrate: rendition.bitrate ?? 0,
|
||||
selected: rendition.selected || isSameRendition(rendition, active),
|
||||
}));
|
||||
get renditions(): Rendition[] {
|
||||
return stateMapper.renditions.mapper(selectQuality(this._store.state));
|
||||
}
|
||||
|
||||
selectRendition(rendition?: Rendition): void {
|
||||
|
||||
@@ -6,14 +6,24 @@ import type {
|
||||
OmniPlayer as OmniPlayerT,
|
||||
PlayerBackend,
|
||||
PlayerStatus,
|
||||
Rendition,
|
||||
Track,
|
||||
} from "../types/player";
|
||||
import type { CastOptions, Source } from "../types/source";
|
||||
|
||||
export type NumberProperty = Exclude<
|
||||
keyof OmniPlayerState,
|
||||
"status" | "isPlaying" | "muted" | "source"
|
||||
| "status"
|
||||
| "isPlaying"
|
||||
| "muted"
|
||||
| "source"
|
||||
| "videos"
|
||||
| "audios"
|
||||
| "subtitles"
|
||||
| "renditions"
|
||||
>;
|
||||
export type BoolProperty = "isPlaying" | "muted" | "isAutoQuality";
|
||||
export type TrackProperty = "videos" | "audios" | "subtitles";
|
||||
|
||||
export interface OmniEventMap extends HybridObject<{ android: "kotlin" }> {
|
||||
addStateListener(key: NumberProperty, cb: (value: number) => void): void;
|
||||
@@ -32,6 +42,12 @@ export interface OmniEventMap extends HybridObject<{ android: "kotlin" }> {
|
||||
addSourceListener(cb: (value?: Source) => void): void;
|
||||
removeSourceListener(cb: (value?: Source) => void): void;
|
||||
|
||||
addTracksListener(key: TrackProperty, cb: (value: Track[]) => void): void;
|
||||
removeTracksListener(key: TrackProperty, cb: (value: Track[]) => void): void;
|
||||
|
||||
addRenditionsListener(cb: (value: Rendition[]) => void): void;
|
||||
removeRenditionsListener(cb: (value: Rendition[]) => void): void;
|
||||
|
||||
addOnEndListener(cb: OmniEvents["end"]): void;
|
||||
removeOnEndListener(cb: OmniEvents["end"]): void;
|
||||
|
||||
|
||||
+5
-4
@@ -15,13 +15,9 @@ export interface OmniPlayer extends OmniPlayerState {
|
||||
readonly hasPrev: boolean;
|
||||
readonly hasNext: boolean;
|
||||
|
||||
readonly videos: Track[];
|
||||
selectVideo(video: Track): void;
|
||||
readonly audios: Track[];
|
||||
selectAudio(audio: Track): void;
|
||||
readonly subtitles: Track[];
|
||||
selectSubtitle(subtitle?: Track): void;
|
||||
readonly rendition: Rendition[];
|
||||
selectRendition(rendition?: Rendition): void;
|
||||
}
|
||||
|
||||
@@ -38,6 +34,11 @@ export interface OmniPlayerState {
|
||||
muted: boolean;
|
||||
readonly isAutoQuality: boolean;
|
||||
readonly castStatus: CastStatus;
|
||||
|
||||
readonly videos: Track[];
|
||||
readonly audios: Track[];
|
||||
readonly subtitles: Track[];
|
||||
readonly renditions: Rendition[];
|
||||
}
|
||||
|
||||
export type PlayerStatus = "idle" | "loading" | "readyToPlay" | "error";
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ const SubtitleOverlay = ({
|
||||
// jassub uses this as libass' storage size so subtitles stay sharp
|
||||
// even while a low-bitrate rendition is playing — otherwise the
|
||||
// raster tracks the currently-decoded (possibly tiny) frame size.
|
||||
const renditions = player.rendition;
|
||||
const renditions = player.renditions;
|
||||
const videoWidth = Math.max(0, ...renditions.map((r) => r.width));
|
||||
const videoHeight = Math.max(0, ...renditions.map((r) => r.height));
|
||||
import("jassub")
|
||||
|
||||
Reference in New Issue
Block a user