Compare commits

..
3 Commits
Author SHA1 Message Date
zoriya 8656fb8068 fix(cast): add unsupported state 2026-07-16 23:28:36 +02:00
zoriya 31bd0adcef feat(cast): implement cast on web 2026-07-16 22:59:07 +02:00
zoriya 0d8b33aee3 fix(event): fix stale callbacks 2026-07-16 13:58:32 +02:00
5 changed files with 32 additions and 4 deletions
+3
View File
@@ -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
View File
@@ -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>(
+1
View File
@@ -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
View File
@@ -7,6 +7,7 @@ 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,
@@ -51,6 +52,14 @@ 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();
}
_source: Source | null = null; _source: Source | null = null;
private _showNotification = false; private _showNotification = false;
+10
View File
@@ -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;