Skip to content

Commit 6fc387a

Browse files
committed
refactor(web): extract media player selectors to module scope
Made-with: Cursor
1 parent 7bed650 commit 6fc387a

1 file changed

Lines changed: 131 additions & 103 deletions

File tree

apps/web/app/s/[videoId]/_components/video/media-player.tsx

Lines changed: 131 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ import {
4040
} from "lucide-react";
4141
import {
4242
MediaActionTypes,
43+
MediaContext,
4344
MediaProvider,
45+
type MediaState,
4446
timeUtils,
4547
useMediaDispatch,
4648
useMediaFullscreenRef,
4749
useMediaRef,
48-
useMediaSelector,
4950
} from "media-chrome/react/media-store";
5051
import * as React from "react";
5152
import { forwardRef, useEffect } from "react";
@@ -77,6 +78,73 @@ const SEEK_TOOLTIP_Y = "--seek-tooltip-y";
7778
const SPRITE_CONTAINER_WIDTH = 224;
7879
const SPRITE_CONTAINER_HEIGHT = 128;
7980

81+
const DEFAULT_SEEKABLE: [number, number] = [0, 0];
82+
const DEFAULT_BUFFERED: (readonly [number, number])[] = [];
83+
const DEFAULT_CUES: readonly unknown[] = [];
84+
const DEFAULT_SUBTITLES_LIST: readonly unknown[] = [];
85+
const DEFAULT_SUBTITLES_SHOWING: readonly unknown[] = [];
86+
const DEFAULT_RENDITION_LIST: readonly unknown[] = [];
87+
88+
type S = Partial<MediaState>;
89+
const selectPaused = (s: S) => s.mediaPaused ?? true;
90+
const selectFullscreen = (s: S) => s.mediaIsFullscreen ?? false;
91+
const selectLoading = (s: S) => s.mediaLoading ?? false;
92+
const selectHasPlayed = (s: S) => s.mediaHasPlayed ?? false;
93+
const selectError = (s: S) => s.mediaError;
94+
const selectVolume = (s: S) => s.mediaVolume ?? 1;
95+
const selectMuted = (s: S) => s.mediaMuted ?? false;
96+
const selectVolumeLevel = (s: S) => s.mediaVolumeLevel ?? "high";
97+
const selectCurrentTime = (s: S) => s.mediaCurrentTime ?? 0;
98+
const selectDuration = (s: S) => s.mediaDuration ?? 0;
99+
const selectSeekable = (s: S) => s.mediaSeekable ?? DEFAULT_SEEKABLE;
100+
const selectBuffered = (s: S) => s.mediaBuffered ?? DEFAULT_BUFFERED;
101+
const selectEnded = (s: S) => s.mediaEnded ?? false;
102+
const selectChaptersCues = (s: S) => s.mediaChaptersCues ?? DEFAULT_CUES;
103+
const selectPreviewTime = (s: S) => s.mediaPreviewTime;
104+
const selectPreviewImage = (s: S) => s.mediaPreviewImage;
105+
const selectPreviewCoords = (s: S) => s.mediaPreviewCoords;
106+
const selectPlaybackRate = (s: S) => s.mediaPlaybackRate ?? 1;
107+
const selectPip = (s: S) => s.mediaIsPip ?? false;
108+
const selectSubtitlesList = (s: S) =>
109+
s.mediaSubtitlesList ?? DEFAULT_SUBTITLES_LIST;
110+
const selectSubtitlesShowing = (s: S) =>
111+
s.mediaSubtitlesShowing ?? DEFAULT_SUBTITLES_SHOWING;
112+
const selectRenditionList = (s: S) =>
113+
s.mediaRenditionList ?? DEFAULT_RENDITION_LIST;
114+
const selectRenditionSelected = (s: S) => s.mediaRenditionSelected;
115+
116+
const EMPTY_MEDIA_STATE = Object.freeze({}) as Partial<MediaState>;
117+
118+
const serverSnapshotCache = new Map<
119+
(state: Partial<MediaState>) => unknown,
120+
unknown
121+
>();
122+
123+
function useMediaSelector<T>(selector: (state: Partial<MediaState>) => T): T {
124+
const store = React.useContext(MediaContext);
125+
126+
const subscribe = React.useCallback(
127+
(cb: () => void) => {
128+
if (!store?.subscribe) return () => {};
129+
return store.subscribe(cb);
130+
},
131+
[store],
132+
);
133+
134+
const getSnapshot = React.useCallback(
135+
() => selector(store?.getState?.() ?? EMPTY_MEDIA_STATE),
136+
[store, selector],
137+
);
138+
139+
if (!serverSnapshotCache.has(selector)) {
140+
serverSnapshotCache.set(selector, selector(EMPTY_MEDIA_STATE));
141+
}
142+
const cached = serverSnapshotCache.get(selector) as T;
143+
const getServerSnapshot = React.useCallback(() => cached, [cached]);
144+
145+
return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
146+
}
147+
80148
type Direction = "ltr" | "rtl";
81149

82150
const DirectionContext = React.createContext<Direction | undefined>(undefined);
@@ -287,10 +355,8 @@ function MediaPlayerRootImpl(props: MediaPlayerRootProps) {
287355
const lastMouseMoveRef = React.useRef<number>(Date.now());
288356
const volumeIndicatorTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
289357

290-
const mediaPaused = useMediaSelector((state) => state.mediaPaused ?? true);
291-
const isFullscreen = useMediaSelector(
292-
(state) => state.mediaIsFullscreen ?? false,
293-
);
358+
const mediaPaused = useMediaSelector(selectPaused);
359+
const isFullscreen = useMediaSelector(selectFullscreen);
294360

295361
const [mounted, setMounted] = React.useState(false);
296362
React.useLayoutEffect(() => setMounted(true), []);
@@ -901,9 +967,7 @@ function MediaPlayerControls(props: MediaPlayerControlsProps) {
901967
} = props;
902968

903969
const context = useMediaPlayerContext("MediaPlayerControls");
904-
const isFullscreen = useMediaSelector(
905-
(state) => state.mediaIsFullscreen ?? false,
906-
);
970+
const isFullscreen = useMediaSelector(selectFullscreen);
907971
const controlsVisible = useStoreSelector((state) => state.controlsVisible);
908972
// Call the callback whenever controlsVisible changes
909973
useEffect(() => {
@@ -946,9 +1010,9 @@ function MediaPlayerLoading(props: MediaPlayerLoadingProps) {
9461010
...loadingProps
9471011
} = props;
9481012

949-
const isLoading = useMediaSelector((state) => state.mediaLoading ?? false);
950-
const isPaused = useMediaSelector((state) => state.mediaPaused ?? true);
951-
const hasPlayed = useMediaSelector((state) => state.mediaHasPlayed ?? false);
1013+
const isLoading = useMediaSelector(selectLoading);
1014+
const isPaused = useMediaSelector(selectPaused);
1015+
const hasPlayed = useMediaSelector(selectHasPlayed);
9521016

9531017
const shouldShowLoading = isLoading && !isPaused;
9541018
const shouldUseDelay = hasPlayed && shouldShowLoading;
@@ -1027,10 +1091,8 @@ function MediaPlayerError(props: MediaPlayerErrorProps) {
10271091
} = props;
10281092

10291093
const context = useMediaPlayerContext("MediaPlayerError");
1030-
const isFullscreen = useMediaSelector(
1031-
(state) => state.mediaIsFullscreen ?? false,
1032-
);
1033-
const mediaError = useMediaSelector((state) => state.mediaError);
1094+
const isFullscreen = useMediaSelector(selectFullscreen);
1095+
const mediaError = useMediaSelector(selectError);
10341096

10351097
const error = errorProp ?? mediaError;
10361098

@@ -1183,11 +1245,9 @@ interface MediaPlayerVolumeIndicatorProps extends React.ComponentProps<"div"> {
11831245
function MediaPlayerVolumeIndicator(props: MediaPlayerVolumeIndicatorProps) {
11841246
const { asChild, className, ...indicatorProps } = props;
11851247

1186-
const mediaVolume = useMediaSelector((state) => state.mediaVolume ?? 1);
1187-
const mediaMuted = useMediaSelector((state) => state.mediaMuted ?? false);
1188-
const mediaVolumeLevel = useMediaSelector(
1189-
(state) => state.mediaVolumeLevel ?? "high",
1190-
);
1248+
const mediaVolume = useMediaSelector(selectVolume);
1249+
const mediaMuted = useMediaSelector(selectMuted);
1250+
const mediaVolumeLevel = useMediaSelector(selectVolumeLevel);
11911251
const volumeIndicatorVisible = useStoreSelector(
11921252
(state) => state.volumeIndicatorVisible,
11931253
);
@@ -1255,9 +1315,7 @@ interface MediaPlayerControlsOverlayProps extends React.ComponentProps<"div"> {
12551315
function MediaPlayerControlsOverlay(props: MediaPlayerControlsOverlayProps) {
12561316
const { asChild, className, ...overlayProps } = props;
12571317

1258-
const isFullscreen = useMediaSelector(
1259-
(state) => state.mediaIsFullscreen ?? false,
1260-
);
1318+
const isFullscreen = useMediaSelector(selectFullscreen);
12611319
const controlsVisible = useStoreSelector((state) => state.controlsVisible);
12621320

12631321
const OverlayPrimitive = asChild ? Slot : "div";
@@ -1283,7 +1341,7 @@ function MediaPlayerPlay(props: MediaPlayerPlayProps) {
12831341

12841342
const context = useMediaPlayerContext("MediaPlayerPlay");
12851343
const dispatch = useMediaDispatch();
1286-
const mediaPaused = useMediaSelector((state) => state.mediaPaused ?? true);
1344+
const mediaPaused = useMediaSelector(selectPaused);
12871345

12881346
const isDisabled = disabled || context.disabled;
12891347

@@ -1348,9 +1406,7 @@ function MediaPlayerSeekBackward(props: MediaPlayerSeekBackwardProps) {
13481406

13491407
const context = useMediaPlayerContext("MediaPlayerSeekBackward");
13501408
const dispatch = useMediaDispatch();
1351-
const mediaCurrentTime = useMediaSelector(
1352-
(state) => state.mediaCurrentTime ?? 0,
1353-
);
1409+
const mediaCurrentTime = useMediaSelector(selectCurrentTime);
13541410

13551411
const isDisabled = disabled || context.disabled;
13561412

@@ -1409,12 +1465,8 @@ function MediaPlayerSeekForward(props: MediaPlayerSeekForwardProps) {
14091465

14101466
const context = useMediaPlayerContext("MediaPlayerSeekForward");
14111467
const dispatch = useMediaDispatch();
1412-
const mediaCurrentTime = useMediaSelector(
1413-
(state) => state.mediaCurrentTime ?? 0,
1414-
);
1415-
const [, seekableEnd] = useMediaSelector(
1416-
(state) => state.mediaSeekable ?? [0, 0],
1417-
);
1468+
const mediaCurrentTime = useMediaSelector(selectCurrentTime);
1469+
const [, seekableEnd] = useMediaSelector(selectSeekable);
14181470
const isDisabled = disabled || context.disabled;
14191471

14201472
const onSeekForward = React.useCallback(
@@ -1498,26 +1550,16 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
14981550
const context = useMediaPlayerContext(SEEK_NAME);
14991551
const store = useStoreContext(SEEK_NAME);
15001552
const dispatch = useMediaDispatch();
1501-
const mediaCurrentTime = useMediaSelector(
1502-
(state) => state.mediaCurrentTime ?? 0,
1503-
);
1504-
const mediaDuration = useMediaSelector((state) => state.mediaDuration ?? 0);
1505-
const [seekableStart = 0, seekableEnd = 0] = useMediaSelector(
1506-
(state) => state.mediaSeekable ?? [0, 0],
1507-
);
1508-
const mediaBuffered = useMediaSelector((state) => state.mediaBuffered ?? []);
1509-
const mediaEnded = useMediaSelector((state) => state.mediaEnded ?? false);
1553+
const mediaCurrentTime = useMediaSelector(selectCurrentTime);
1554+
const mediaDuration = useMediaSelector(selectDuration);
1555+
const [seekableStart = 0, seekableEnd = 0] = useMediaSelector(selectSeekable);
1556+
const mediaBuffered = useMediaSelector(selectBuffered);
1557+
const mediaEnded = useMediaSelector(selectEnded);
15101558

1511-
const chapterCues = useMediaSelector(
1512-
(state) => state.mediaChaptersCues ?? [],
1513-
);
1514-
const mediaPreviewTime = useMediaSelector((state) => state.mediaPreviewTime);
1515-
const mediaPreviewImage = useMediaSelector(
1516-
(state) => state.mediaPreviewImage,
1517-
);
1518-
const mediaPreviewCoords = useMediaSelector(
1519-
(state) => state.mediaPreviewCoords,
1520-
);
1559+
const chapterCues = useMediaSelector(selectChaptersCues);
1560+
const mediaPreviewTime = useMediaSelector(selectPreviewTime);
1561+
const mediaPreviewImage = useMediaSelector(selectPreviewImage);
1562+
const mediaPreviewCoords = useMediaSelector(selectPreviewCoords);
15211563

15221564
const seekRef = React.useRef<HTMLDivElement>(null);
15231565
const tooltipRef = React.useRef<HTMLDivElement>(null);
@@ -1551,13 +1593,17 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
15511593

15521594
const timeCache = React.useRef<Map<number, string>>(new Map());
15531595
const resolvedDuration = React.useMemo(() => {
1554-
const candidates = [
1555-
mediaDuration,
1556-
seekableEnd,
1557-
fallbackDuration ?? 0,
1558-
].filter((duration) => Number.isFinite(duration) && duration > 0);
1559-
1560-
return candidates.length > 0 ? Math.max(...candidates) : 0;
1596+
const mediaValues = [mediaDuration, seekableEnd].filter(
1597+
(d) => Number.isFinite(d) && d > 0,
1598+
);
1599+
if (mediaValues.length > 0) return Math.max(...mediaValues);
1600+
if (
1601+
fallbackDuration != null &&
1602+
Number.isFinite(fallbackDuration) &&
1603+
fallbackDuration > 0
1604+
)
1605+
return fallbackDuration;
1606+
return 0;
15611607
}, [fallbackDuration, mediaDuration, seekableEnd]);
15621608
const lastKnownDurationRef = React.useRef(resolvedDuration);
15631609

@@ -2286,11 +2332,9 @@ function MediaPlayerVolume(props: MediaPlayerVolumeProps) {
22862332
const context = useMediaPlayerContext(VOLUME_NAME);
22872333
const store = useStoreContext(VOLUME_NAME);
22882334
const dispatch = useMediaDispatch();
2289-
const mediaVolume = useMediaSelector((state) => state.mediaVolume ?? 1);
2290-
const mediaMuted = useMediaSelector((state) => state.mediaMuted ?? false);
2291-
const mediaVolumeLevel = useMediaSelector(
2292-
(state) => state.mediaVolumeLevel ?? "high",
2293-
);
2335+
const mediaVolume = useMediaSelector(selectVolume);
2336+
const mediaMuted = useMediaSelector(selectMuted);
2337+
const mediaVolumeLevel = useMediaSelector(selectVolumeLevel);
22942338

22952339
const sliderId = React.useId();
22962340
const volumeTriggerId = React.useId();
@@ -2445,21 +2489,21 @@ function MediaPlayerTime(props: MediaPlayerTimeProps) {
24452489
} = props;
24462490

24472491
const context = useMediaPlayerContext("MediaPlayerTime");
2448-
const mediaCurrentTime = useMediaSelector(
2449-
(state) => state.mediaCurrentTime ?? 0,
2450-
);
2451-
const mediaDuration = useMediaSelector((state) => state.mediaDuration ?? 0);
2452-
const [, seekableEnd = 0] = useMediaSelector(
2453-
(state) => state.mediaSeekable ?? [0, 0],
2454-
);
2492+
const mediaCurrentTime = useMediaSelector(selectCurrentTime);
2493+
const mediaDuration = useMediaSelector(selectDuration);
2494+
const [, seekableEnd = 0] = useMediaSelector(selectSeekable);
24552495
const resolvedDuration = React.useMemo(() => {
2456-
const candidates = [
2457-
mediaDuration,
2458-
seekableEnd,
2459-
fallbackDuration ?? 0,
2460-
].filter((duration) => Number.isFinite(duration) && duration > 0);
2461-
2462-
return candidates.length > 0 ? Math.max(...candidates) : 0;
2496+
const mediaValues = [mediaDuration, seekableEnd].filter(
2497+
(d) => Number.isFinite(d) && d > 0,
2498+
);
2499+
if (mediaValues.length > 0) return Math.max(...mediaValues);
2500+
if (
2501+
fallbackDuration != null &&
2502+
Number.isFinite(fallbackDuration) &&
2503+
fallbackDuration > 0
2504+
)
2505+
return fallbackDuration;
2506+
return 0;
24632507
}, [fallbackDuration, mediaDuration, seekableEnd]);
24642508
const lastKnownDurationRef = React.useRef(resolvedDuration);
24652509

@@ -2564,9 +2608,7 @@ function MediaPlayerPlaybackSpeed(props: MediaPlayerPlaybackSpeedProps) {
25642608
const context = useMediaPlayerContext(PLAYBACK_SPEED_NAME);
25652609
const store = useStoreContext(PLAYBACK_SPEED_NAME);
25662610
const dispatch = useMediaDispatch();
2567-
const mediaPlaybackRate = useMediaSelector(
2568-
(state) => state.mediaPlaybackRate ?? 1,
2569-
);
2611+
const mediaPlaybackRate = useMediaSelector(selectPlaybackRate);
25702612

25712613
const isDisabled = disabled || context.disabled;
25722614

@@ -2716,9 +2758,7 @@ function MediaPlayerFullscreen(props: MediaPlayerFullscreenProps) {
27162758

27172759
const context = useMediaPlayerContext("MediaPlayerFullscreen");
27182760
const dispatch = useMediaDispatch();
2719-
const isFullscreen = useMediaSelector(
2720-
(state) => state.mediaIsFullscreen ?? false,
2721-
);
2761+
const isFullscreen = useMediaSelector(selectFullscreen);
27222762

27232763
const isDisabled = disabled || context.disabled;
27242764

@@ -2768,9 +2808,7 @@ function MediaPlayerPiP(props: MediaPlayerPiPProps) {
27682808

27692809
const context = useMediaPlayerContext("MediaPlayerPiP");
27702810
const dispatch = useMediaDispatch();
2771-
const isPictureInPicture = useMediaSelector(
2772-
(state) => state.mediaIsPip ?? false,
2773-
);
2811+
const isPictureInPicture = useMediaSelector(selectPip);
27742812

27752813
const isDisabled = disabled || context.disabled;
27762814

@@ -2971,7 +3009,7 @@ function EnhancedAudioSync({
29713009
enhancedAudioEnabled,
29723010
enhancedAudioMuted,
29733011
}: EnhancedAudioSyncProps) {
2974-
const mediaVolume = useMediaSelector((state) => state.mediaVolume ?? 1);
3012+
const mediaVolume = useMediaSelector(selectVolume);
29753013
const wasEnhancedRef = React.useRef(false);
29763014

29773015
const syncEnhancedAudio = React.useCallback(() => {
@@ -3166,21 +3204,11 @@ function MediaPlayerSettings(props: MediaPlayerSettingsProps) {
31663204
const store = useStoreContext(SETTINGS_NAME);
31673205
const dispatch = useMediaDispatch();
31683206

3169-
const mediaPlaybackRate = useMediaSelector(
3170-
(state) => state.mediaPlaybackRate ?? 1,
3171-
);
3172-
const mediaSubtitlesList = useMediaSelector(
3173-
(state) => state.mediaSubtitlesList ?? [],
3174-
);
3175-
const mediaSubtitlesShowing = useMediaSelector(
3176-
(state) => state.mediaSubtitlesShowing ?? [],
3177-
);
3178-
const mediaRenditionList = useMediaSelector(
3179-
(state) => state.mediaRenditionList ?? [],
3180-
);
3181-
const selectedRenditionId = useMediaSelector(
3182-
(state) => state.mediaRenditionSelected,
3183-
);
3207+
const mediaPlaybackRate = useMediaSelector(selectPlaybackRate);
3208+
const mediaSubtitlesList = useMediaSelector(selectSubtitlesList);
3209+
const mediaSubtitlesShowing = useMediaSelector(selectSubtitlesShowing);
3210+
const mediaRenditionList = useMediaSelector(selectRenditionList);
3211+
const selectedRenditionId = useMediaSelector(selectRenditionSelected);
31843212

31853213
const isDisabled = disabled || context.disabled;
31863214
const isSubtitlesActive = mediaSubtitlesShowing.length > 0;

0 commit comments

Comments
 (0)