From 54423a1267f9db53f8147a058453ddd74dea2eb1 Mon Sep 17 00:00:00 2001 From: Ahmed Sbai <30757139+sbaiahmed1@users.noreply.github.com> Date: Sun, 16 Mar 2025 17:57:25 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20fixed=20app=20crash=20on=20next=20downlo?= =?UTF-8?q?aded=20item=20&&=20update=20biome=20schema=20v=E2=80=A6=20(#610?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biome.json | 8 +- components/video-player/controls/Controls.tsx | 293 ++++++++++++------ 2 files changed, 203 insertions(+), 98 deletions(-) diff --git a/biome.json b/biome.json index f429e9bc..cfee4e6d 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "organizeImports": { "enabled": true }, @@ -8,6 +8,10 @@ "enabled": true, "rules": { "recommended": true, + "style": { + "useImportType": "off", + "noNonNullAssertion": "off" + }, "correctness": { "useExhaustiveDependencies": "off" }, "suspicious": { "noExplicitAny": "off" @@ -21,7 +25,7 @@ "indentStyle": "space", "indentWidth": 2, "lineEnding": "lf", - "lineWidth": 80, + "lineWidth": 80 }, "javascript": { "formatter": { diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx index 94914307..d0084720 100644 --- a/components/video-player/controls/Controls.tsx +++ b/components/video-player/controls/Controls.tsx @@ -1,43 +1,71 @@ -import { Text } from "@/components/common/Text"; import { Loader } from "@/components/Loader"; +import { Text } from "@/components/common/Text"; import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes"; import { useCreditSkipper } from "@/hooks/useCreditSkipper"; import { useHaptic } from "@/hooks/useHaptic"; import { useIntroSkipper } from "@/hooks/useIntroSkipper"; import { useTrickplay } from "@/hooks/useTrickplay"; -import { TrackInfo, VlcPlayerViewRef } from "@/modules/VlcPlayer.types"; +import type { TrackInfo, VlcPlayerViewRef } from "@/modules/VlcPlayer.types"; import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { apiAtom } from "@/providers/JellyfinProvider"; -import { useSettings, VideoPlayer } from "@/utils/atoms/settings"; +import { VideoPlayer, useSettings } from "@/utils/atoms/settings"; import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings"; import { getItemById } from "@/utils/jellyfin/user-library/getItemById"; import { writeToLog } from "@/utils/log"; -import { formatTimeString, msToTicks, secondsToMs, ticksToMs, ticksToSeconds } from "@/utils/time"; +import { + formatTimeString, + msToTicks, + secondsToMs, + ticksToMs, + ticksToSeconds, +} from "@/utils/time"; import { Ionicons, MaterialIcons } from "@expo/vector-icons"; -import { BaseItemDto, MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client"; +import type { + BaseItemDto, + MediaSourceInfo, +} from "@jellyfin/sdk/lib/generated-client"; import { Image } from "expo-image"; import { useLocalSearchParams, useRouter } from "expo-router"; import { useAtom } from "jotai"; import { debounce } from "lodash"; -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { Platform, TouchableOpacity, useWindowDimensions, View } from "react-native"; +import { + type Dispatch, + type FC, + type MutableRefObject, + type SetStateAction, + useCallback, + useEffect, + useRef, + useState, +} from "react"; +import { + Platform, + TouchableOpacity, + View, + useWindowDimensions, +} from "react-native"; import { Slider } from "react-native-awesome-slider"; -import { runOnJS, SharedValue, useAnimatedReaction, useSharedValue } from "react-native-reanimated"; +import { + type SharedValue, + runOnJS, + useAnimatedReaction, + useSharedValue, +} from "react-native-reanimated"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import AudioSlider from "./AudioSlider"; import BrightnessSlider from "./BrightnessSlider"; -import { ControlProvider } from "./contexts/ControlContext"; -import { VideoProvider } from "./contexts/VideoContext"; -import DropdownView from "./dropdown/DropdownView"; import { EpisodeList } from "./EpisodeList"; import NextEpisodeCountDownButton from "./NextEpisodeCountDownButton"; import SkipButton from "./SkipButton"; -import { useControlsTimeout } from "./useControlsTimeout"; import { VideoTouchOverlay } from "./VideoTouchOverlay"; +import { ControlProvider } from "./contexts/ControlContext"; +import { VideoProvider } from "./contexts/VideoContext"; +import DropdownView from "./dropdown/DropdownView"; +import { useControlsTimeout } from "./useControlsTimeout"; interface Props { item: BaseItemDto; - videoRef: React.MutableRefObject; + videoRef: MutableRefObject; isPlaying: boolean; isSeeking: SharedValue; cacheProgress: SharedValue; @@ -45,7 +73,7 @@ interface Props { isBuffering: boolean; showControls: boolean; ignoreSafeAreas?: boolean; - setIgnoreSafeAreas: React.Dispatch>; + setIgnoreSafeAreas: Dispatch>; enableTrickplay?: boolean; togglePlay: () => void; setShowControls: (shown: boolean) => void; @@ -66,7 +94,7 @@ interface Props { const CONTROLS_TIMEOUT = 4000; -export const Controls: React.FC = ({ +export const Controls: FC = ({ item, seek, startPictureInPicture, @@ -106,13 +134,15 @@ export const Controls: React.FC = ({ const { height: screenHeight, width: screenWidth } = useWindowDimensions(); const { previousItem, nextItem } = useAdjacentItems({ item }); - const { trickPlayUrl, calculateTrickplayUrl, trickplayInfo, prefetchAllTrickplayImages } = useTrickplay( - item, - !offline && enableTrickplay - ); + const { + trickPlayUrl, + calculateTrickplayUrl, + trickplayInfo, + prefetchAllTrickplayImages, + } = useTrickplay(item, !offline && enableTrickplay); const [currentTime, setCurrentTime] = useState(0); - const [remainingTime, setRemainingTime] = useState(Infinity); + const [remainingTime, setRemainingTime] = useState(Number.POSITIVE_INFINITY); const min = useSharedValue(0); const max = useSharedValue(item.RunTimeTicks || 0); @@ -131,7 +161,9 @@ export const Controls: React.FC = ({ progress.value = isVlc ? ticksToMs(item?.UserData?.PlaybackPositionTicks) : item?.UserData?.PlaybackPositionTicks || 0; - max.value = isVlc ? ticksToMs(item.RunTimeTicks || 0) : item.RunTimeTicks || 0; + max.value = isVlc + ? ticksToMs(item.RunTimeTicks || 0) + : item.RunTimeTicks || 0; } }, [item, isVlc]); @@ -141,49 +173,66 @@ export const Controls: React.FC = ({ subtitleIndex: string; }>(); - const { showSkipButton, skipIntro } = useIntroSkipper(offline ? undefined : item.Id, currentTime, seek, play, isVlc); + const { showSkipButton, skipIntro } = useIntroSkipper( + offline ? undefined : item.Id, + currentTime, + seek, + play, + isVlc, + ); const { showSkipCreditButton, skipCredit } = useCreditSkipper( offline ? undefined : item.Id, currentTime, seek, play, - isVlc + isVlc, ); const goToItemCommon = useCallback( (item: BaseItemDto) => { - if (!item || !settings) return; + if (!item || !settings) { + return; + } lightHapticFeedback(); const previousIndexes = { - subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined, - audioIndex: audioIndex ? parseInt(audioIndex) : undefined, + subtitleIndex: subtitleIndex + ? Number.parseInt(subtitleIndex) + : undefined, + audioIndex: audioIndex ? Number.parseInt(audioIndex) : undefined, }; const { mediaSource: newMediaSource, audioIndex: defaultAudioIndex, subtitleIndex: defaultSubtitleIndex, - } = getDefaultPlaySettings(item, settings, previousIndexes, mediaSource ?? undefined); + } = getDefaultPlaySettings( + item, + settings, + previousIndexes, + mediaSource ?? undefined, + ); const queryParams = new URLSearchParams({ itemId: item.Id ?? "", audioIndex: defaultAudioIndex?.toString() ?? "", subtitleIndex: defaultSubtitleIndex?.toString() ?? "", mediaSourceId: newMediaSource?.Id ?? "", - bitrateValue: bitrateValue.toString(), + bitrateValue: bitrateValue?.toString(), }).toString(); // @ts-expect-error router.replace(`player/direct-player?${queryParams}`); }, - [settings, subtitleIndex, audioIndex, mediaSource, bitrateValue, router] + [settings, subtitleIndex, audioIndex, mediaSource, bitrateValue, router], ); const goToPreviousItem = useCallback(() => { - if (!previousItem) return; + if (!previousItem) { + return; + } goToItemCommon(previousItem); }, [previousItem, goToItemCommon]); @@ -198,18 +247,20 @@ export const Controls: React.FC = ({ if (!gotoItem) return; goToItemCommon(gotoItem); }, - [goToItemCommon, api] + [goToItemCommon, api], ); const updateTimes = useCallback( (currentProgress: number, maxValue: number) => { const current = isVlc ? currentProgress : ticksToSeconds(currentProgress); - const remaining = isVlc ? maxValue - currentProgress : ticksToSeconds(maxValue - currentProgress); + const remaining = isVlc + ? maxValue - currentProgress + : ticksToSeconds(maxValue - currentProgress); setCurrentTime(current); setRemainingTime(remaining); }, - [goToNextItem, isVlc] + [goToNextItem, isVlc], ); useAnimatedReaction( @@ -219,11 +270,11 @@ export const Controls: React.FC = ({ isSeeking: isSeeking.value, }), (result) => { - if (result.isSeeking === false) { + if (!result.isSeeking) { runOnJS(updateTimes)(result.progress, result.max); } }, - [updateTimes] + [updateTimes], ); const hideControls = useCallback(() => { @@ -249,7 +300,7 @@ export const Controls: React.FC = ({ }; const handleSliderStart = useCallback(() => { - if (showControls === false) return; + if (!showControls) return; setIsSliding(true); wasPlayingRef.current = isPlaying; @@ -266,9 +317,11 @@ export const Controls: React.FC = ({ setIsSliding(false); seek(Math.max(0, Math.floor(isVlc ? value : ticksToSeconds(value)))); - if (wasPlayingRef.current === true) play(); + if (wasPlayingRef.current) { + play(); + } }, - [isVlc] + [isVlc], ); const [time, setTime] = useState({ hours: 0, minutes: 0, seconds: 0 }); @@ -282,7 +335,7 @@ export const Controls: React.FC = ({ const seconds = progressInSeconds % 60; setTime({ hours, minutes, seconds }); }, 3), - [] + [], ); const handleSkipBackward = useCallback(async () => { @@ -296,7 +349,9 @@ export const Controls: React.FC = ({ ? Math.max(0, curr - secondsToMs(settings.rewindSkipTime)) : Math.max(0, ticksToSeconds(curr) - settings.rewindSkipTime); seek(newTime); - if (wasPlayingRef.current === true) play(); + if (wasPlayingRef.current) { + play(); + } } } catch (error) { writeToLog("ERROR", "Error seeking video backwards", error); @@ -304,7 +359,9 @@ export const Controls: React.FC = ({ }, [settings, isPlaying, isVlc]); const handleSkipForward = useCallback(async () => { - if (!settings?.forwardSkipTime) return; + if (!settings?.forwardSkipTime) { + return; + } wasPlayingRef.current = isPlaying; lightHapticFeedback(); try { @@ -314,7 +371,7 @@ export const Controls: React.FC = ({ ? curr + secondsToMs(settings.forwardSkipTime) : ticksToSeconds(curr) + settings.forwardSkipTime; seek(Math.max(0, newTime)); - if (wasPlayingRef.current === true) play(); + if (wasPlayingRef.current) play(); } } catch (error) { writeToLog("ERROR", "Error seeking video forwards", error); @@ -328,7 +385,9 @@ export const Controls: React.FC = ({ const switchOnEpisodeMode = useCallback(() => { setEpisodeView(true); - if (isPlaying) togglePlay(); + if (isPlaying) { + togglePlay(); + } }, [isPlaying, togglePlay]); const memoizedRenderBubble = useCallback(() => { @@ -360,18 +419,23 @@ export const Controls: React.FC = ({ transform: [{ scale: 1.4 }], borderRadius: 5, }} - className="bg-neutral-800 overflow-hidden" + className='bg-neutral-800 overflow-hidden' > = ({ const onClose = async () => { lightHapticFeedback(); - await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP); + await ScreenOrientation.lockAsync( + ScreenOrientation.OrientationLock.PORTRAIT_UP, + ); router.back(); }; return ( - + {episodeView ? ( - setEpisodeView(false)} goToItem={goToItem} /> + setEpisodeView(false)} + goToItem={goToItem} + /> ) : ( <> = ({ position: "absolute", top: settings?.safeAreaInControlsEnabled ? insets.top : 0, right: settings?.safeAreaInControlsEnabled ? insets.right : 0, - width: settings?.safeAreaInControlsEnabled ? screenWidth - insets.left - insets.right : screenWidth, + width: settings?.safeAreaInControlsEnabled + ? screenWidth - insets.left - insets.right + : screenWidth, opacity: showControls ? 1 : 0, }, ]} pointerEvents={showControls ? "auto" : "none"} - className={`flex flex-row w-full pt-2`} + className={"flex flex-row w-full pt-2"} > {!Platform.isTV && ( - + = ({ )} - - {!Platform.isTV && settings.defaultPlayer == VideoPlayer.VLC_4 && ( - - - - )} + + {!Platform.isTV && + settings.defaultPlayer === VideoPlayer.VLC_4 && ( + + + + )} {item?.Type === "Episode" && !offline && ( { switchOnEpisodeMode(); }} - className="aspect-square flex flex-col rounded-xl items-center justify-center p-2" + className='aspect-square flex flex-col rounded-xl items-center justify-center p-2' > - + )} {previousItem && !offline && ( - + )} {nextItem && !offline && ( - + )} {/* {mediaSource?.TranscodingUrl && ( */} - + {/* )} */} - + @@ -529,9 +610,9 @@ export const Controls: React.FC = ({ }} > = ({ = ({ opacity: showControls ? 1 : 0, }} > - + = ({ bottom: settings?.safeAreaInControlsEnabled ? insets.bottom : 0, }, ]} - className={`flex flex-col px-2`} + className={"flex flex-col px-2"} onTouchStart={handleControlsInteraction} > = ({ pointerEvents={showControls ? "box-none" : "none"} > {item?.Type === "Episode" && ( - + {`${item.SeriesName} - ${item.SeasonName} Episode ${item.IndexNumber}`} )} - {item?.Name} - {item?.Type === "Movie" && {item?.ProductionYear}} - {item?.Type === "Audio" && {item?.Album}} + {item?.Name} + {item?.Type === "Movie" && ( + + {item?.ProductionYear} + + )} + {item?.Type === "Audio" && ( + {item?.Album} + )} - - - + + + - + = ({ minimumValue={min} maximumValue={max} /> - - + + {formatTimeString(currentTime, isVlc ? "ms" : "s")} - + -{formatTimeString(remainingTime, isVlc ? "ms" : "s")}