diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 823f67de..11bfd53b 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-native/no-inline-styles */ import { type BaseItemDto, type MediaSourceInfo, @@ -48,32 +49,13 @@ import { storage } from "@/utils/mmkv"; import generateDeviceProfile from "@/utils/profiles/native"; import { msToTicks, ticksToSeconds } from "@/utils/time"; -/* ---------- helpers ---------- */ - const downloadProvider = !Platform.isTV ? require("@/providers/DownloadProvider") : { useDownload: () => null }; const IGNORE_SAFE_AREAS_KEY = "video_player_ignore_safe_areas"; -/* ---------- performance monitor ---------- */ - -const usePerformanceMonitoring = (name: string) => { - useEffect(() => { - const start = performance.now(); - return () => { - const end = performance.now(); - const ms = end - start; - if (ms > 16.67) { - // >1 frame at 60 fps - console.warn(`[Perf] ${name} render took ${ms.toFixed(1)} ms`); - } - }; - }); -}; - -/* ---------- reducer ---------- */ - +/* Playback state reducer to consolidate related state */ interface VideoState { isPlaying: boolean; isMuted: boolean; @@ -95,6 +77,7 @@ const videoReducer = (state: VideoState, action: VideoAction): VideoState => { case "BUFFERING_CHANGED": return { ...state, isBuffering: action.value }; case "VIDEO_LOADED": + // Mark video as loaded and buffering false here return { ...state, isVideoLoaded: true, isBuffering: false }; case "MUTED_CHANGED": return { ...state, isMuted: action.value }; @@ -113,23 +96,16 @@ const initialVideoState: VideoState = { isPipStarted: false, }; -/* ---------- main component ---------- */ - export default function DirectPlayerPage() { - usePerformanceMonitoring("DirectPlayerPage"); - - /* ---------- refs & atoms ---------- */ const videoRef = useRef(null); const user = useAtomValue(userAtom); const api = useAtomValue(apiAtom); - const navigation = useNavigation(); const { t } = useTranslation(); - /* ---------- consolidated playback state ---------- */ + /* Consolidated video playback state */ const [videoState, dispatch] = useReducer(videoReducer, initialVideoState); - /* ---------- misc UI state ---------- */ const [showControls, _setShowControls] = useState(true); const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(() => { return storage.getBoolean(IGNORE_SAFE_AREAS_KEY) ?? false; @@ -147,7 +123,6 @@ export default function DirectPlayerPage() { ? null : require("react-native-volume-manager"); - /* ---------- URL params ---------- */ const { itemId, audioIndex: audioIndexStr, @@ -173,7 +148,6 @@ export default function DirectPlayerPage() { ? parseInt(bitrateValueStr, 10) : BITRATES[0].value; - /* ---------- stable callbacks ---------- */ const setShowControls = useCallback( (show: boolean) => { _setShowControls(show); @@ -186,9 +160,7 @@ export default function DirectPlayerPage() { storage.set(IGNORE_SAFE_AREAS_KEY, ignoreSafeAreas); }, [ignoreSafeAreas]); - /* ---------- data fetching ---------- */ - - /* item */ + /* Fetch the item info */ const [item, setItem] = useState(null); const [itemStatus, setItemStatus] = useState({ isLoading: true, @@ -236,7 +208,7 @@ export default function DirectPlayerPage() { return () => controller.abort(); }, [itemId, offline, api, user?.Id, getDownloadedItem]); - /* stream */ + /* Fetch stream info */ interface Stream { mediaSource: MediaSourceInfo; sessionId: string; @@ -306,10 +278,9 @@ export default function DirectPlayerPage() { subtitleIndex, ]); - /* ---------- playback API reporting ---------- */ - const revalidateProgressCache = useInvalidatePlaybackProgressCache(); + /* Memoized playback state info for reporting */ const currentPlayStateInfo = useMemo(() => { if (!stream) return null; return { @@ -337,6 +308,7 @@ export default function DirectPlayerPage() { stream, ]); + /* Playback progress reporting */ const reportPlaybackProgress = useCallback(async () => { if (!api || offline || !stream || !currentPlayStateInfo) return; await getPlaystateApi(api).reportPlaybackProgress({ @@ -344,6 +316,7 @@ export default function DirectPlayerPage() { }); }, [api, offline, stream, currentPlayStateInfo]); + /* Report playback stopped */ const reportPlaybackStopped = useCallback(async () => { if (offline || !stream) return; await getPlaystateApi(api!).onPlaybackStopped({ @@ -363,8 +336,7 @@ export default function DirectPlayerPage() { revalidateProgressCache, ]); - /* ---------- UI / player actions ---------- */ - + /* Toggle play/pause */ const togglePlay = useCallback(async () => { lightHapticFeedback(); const playing = videoState.isPlaying; @@ -375,9 +347,11 @@ export default function DirectPlayerPage() { reportPlaybackProgress(); } else { await videoRef.current?.play(); - await getPlaystateApi(api!).reportPlaybackStart({ - playbackStartInfo: currentPlayStateInfo as PlaybackStartInfo, - }); + if (currentPlayStateInfo) { + await getPlaystateApi(api!).reportPlaybackStart({ + playbackStartInfo: currentPlayStateInfo as PlaybackStartInfo, + }); + } } }, [ videoState.isPlaying, @@ -387,8 +361,7 @@ export default function DirectPlayerPage() { currentPlayStateInfo, ]); - /* ---------- React Navigation cleanup ---------- */ - + /* Stop playback and clean up */ const stop = useCallback(() => { reportPlaybackStopped(); setIsPlaybackStopped(true); @@ -400,18 +373,15 @@ export default function DirectPlayerPage() { return unsubscribe; }, [navigation, stop]); - /* ---------- VLC init options ---------- */ - + /* VLC init options optimized for performance */ const optimizedInitOptions = useMemo(() => { const opts = [`--sub-text-scale=${settings.subtitleSize}`]; - - // reduce buffering memory + // Reduce buffering memory usage opts.push("--network-caching=300", "--file-caching=300"); - if (Platform.OS === "android") opts.push("--aout=opensles"); if (Platform.OS === "ios") opts.push("--ios-hw-decoding"); - // pre-select tracks + // Pre-selection of audio & subtitle tracks handled here const notTranscoding = !stream?.mediaSource.TranscodingUrl; const allAudio = stream?.mediaSource.MediaStreams?.filter((s) => s.Type === "Audio") ?? []; @@ -444,26 +414,20 @@ export default function DirectPlayerPage() { return opts; }, [settings.subtitleSize, stream?.mediaSource, subtitleIndex, audioIndex]); - /* ---------- picture-in-picture ---------- */ - + /* On Picture-In-Picture started or stopped */ const onPipStarted = useCallback((e: PipStartedPayload) => { dispatch({ type: "PIP_CHANGED", value: e.nativeEvent.pipStarted }); }, []); - /* ---------- progress ---------- */ - + /* Progress event handler */ const onProgress = useCallback( (data: ProgressUpdatePayload) => { if (isSeeking.get() || isPlaybackStopped) return; - if (videoState.isBuffering) dispatch({ type: "BUFFERING_CHANGED", value: false }); - const { currentTime } = data.nativeEvent; progress.set(currentTime); - router.setParams({ playbackPosition: msToTicks(currentTime).toString() }); - if (!offline) reportPlaybackProgress(); }, [ @@ -476,12 +440,10 @@ export default function DirectPlayerPage() { ], ); - /* ---------- playback state listener ---------- */ - + /* Playback state changes */ const onPlaybackStateChanged = useCallback( async (e: PlaybackStatePayload) => { const { state, isBuffering, isPlaying } = e.nativeEvent; - switch (state) { case "Playing": dispatch({ type: "PLAYING_CHANGED", value: true }); @@ -494,7 +456,6 @@ export default function DirectPlayerPage() { reportPlaybackProgress(); break; default: - // fallback dispatch({ type: "BUFFERING_CHANGED", value: !!isBuffering }); dispatch({ type: "PLAYING_CHANGED", value: !!isPlaying }); } @@ -502,85 +463,18 @@ export default function DirectPlayerPage() { [reportPlaybackProgress], ); - /* ---------- web socket / remote ---------- */ - - /* volume handlers */ - const [previousVolume, setPreviousVolume] = useState(null); - - const volumeUpCb = useCallback(async () => { - if (Platform.isTV) return; - const { volume } = await VolumeManager.getVolume(); - await VolumeManager.setVolume(Math.min(volume + 0.1, 1)); - }, []); - - const volumeDownCb = useCallback(async () => { - if (Platform.isTV) return; - const { volume } = await VolumeManager.getVolume(); - await VolumeManager.setVolume(Math.max(volume - 0.1, 0)); - }, []); - - const setVolumeCb = useCallback(async (v: number) => { - if (Platform.isTV) return; - await VolumeManager.setVolume(Math.max(0, Math.min(v, 100)) / 100); - }, []); - - const toggleMuteCb = useCallback(async () => { - if (Platform.isTV) return; - const { volume } = await VolumeManager.getVolume(); - const percent = volume * 100; - if (percent > 0) { - setPreviousVolume(percent); - await VolumeManager.setVolume(0); - dispatch({ type: "MUTED_CHANGED", value: true }); - } else { - const restore = previousVolume || 50; - await VolumeManager.setVolume(restore / 100); - setPreviousVolume(null); - dispatch({ type: "MUTED_CHANGED", value: false }); - } - }, [previousVolume]); - - useWebSocket({ - isPlaying: videoState.isPlaying, - togglePlay, - stopPlayback: stop, - offline, - toggleMute: toggleMuteCb, - volumeUp: volumeUpCb, - volumeDown: volumeDownCb, - setVolume: setVolumeCb, - }); - - /* ---------- start position ---------- */ - - const startPosition = useMemo( - () => (offline ? 0 : ticksToSeconds(getInitialPlaybackTicks())), - [offline, getInitialPlaybackTicks], - ); - - /* ---------- subtitle & audio helpers ---------- */ - - const _allAudio = - stream?.mediaSource.MediaStreams?.filter((a) => a.Type === "Audio") ?? []; - const allSubs = - stream?.mediaSource.MediaStreams?.filter( - (s) => s.Type === "Subtitle", - )?.sort((a, b) => Number(a.IsExternal) - Number(b.IsExternal)) ?? []; - - const externalSubtitles = allSubs - .filter((s) => s.DeliveryMethod === "External") - .map((s) => ({ - name: s.DisplayTitle, - DeliveryUrl: api?.basePath + s.DeliveryUrl, - })); - - /* ---------- player helpers (memoised safe wrappers) ---------- */ + /* Safe wrapper for player methods that skips calls if video not loaded */ const safeMethod = ( fn: ((...args: T) => any) | undefined, name: string, ) => async (...args: T) => { + // New safeguard: skip calling if video not loaded yet + if (!videoState.isVideoLoaded) { + writeToLog("WARN", `${name} skipped - video not loaded yet`); + return; + } if (!fn) { writeToLog("ERROR", `${name} fn missing`, { isVideoLoaded: videoState.isVideoLoaded, @@ -638,19 +532,56 @@ export default function DirectPlayerPage() { [videoRef], ); - /* ---------- memory / cache cleanup ---------- */ - useEffect(() => { - const interval = setInterval(() => { - if (!videoState.isPlaying) videoRef.current?.clearCache?.(); - }, 60000); // every minute - return () => { - clearInterval(interval); - videoRef.current?.dispose?.(); - }; - }, [videoState.isPlaying]); + /* Volume handlers */ + const [previousVolume, setPreviousVolume] = useState(null); + const volumeUpCb = useCallback(async () => { + if (Platform.isTV) return; + const { volume } = await VolumeManager.getVolume(); + await VolumeManager.setVolume(Math.min(volume + 0.1, 1)); + }, []); + const volumeDownCb = useCallback(async () => { + if (Platform.isTV) return; + const { volume } = await VolumeManager.getVolume(); + await VolumeManager.setVolume(Math.max(volume - 0.1, 0)); + }, []); + const setVolumeCb = useCallback(async (v: number) => { + if (Platform.isTV) return; + await VolumeManager.setVolume(Math.max(0, Math.min(v, 100)) / 100); + }, []); + const toggleMuteCb = useCallback(async () => { + if (Platform.isTV) return; + const { volume } = await VolumeManager.getVolume(); + const percent = volume * 100; + if (percent > 0) { + setPreviousVolume(percent); + await VolumeManager.setVolume(0); + dispatch({ type: "MUTED_CHANGED", value: true }); + } else { + const restore = previousVolume || 50; + await VolumeManager.setVolume(restore / 100); + setPreviousVolume(null); + dispatch({ type: "MUTED_CHANGED", value: false }); + } + }, [previousVolume]); - /* ---------- render guard ---------- */ + useWebSocket({ + isPlaying: videoState.isPlaying, + togglePlay, + stopPlayback: stop, + offline, + toggleMute: toggleMuteCb, + volumeUp: volumeUpCb, + volumeDown: volumeDownCb, + setVolume: setVolumeCb, + }); + /* Calculate start position in seconds */ + const startPosition = useMemo( + () => (offline ? 0 : ticksToSeconds(getInitialPlaybackTicks())), + [offline, getInitialPlaybackTicks], + ); + + /* Conditionally render based on loading and error state */ if (itemStatus.isError || streamStatus.isError) { return ( @@ -658,7 +589,6 @@ export default function DirectPlayerPage() { ); } - if (itemStatus.isLoading || streamStatus.isLoading || !item || !stream) { return ( @@ -667,7 +597,16 @@ export default function DirectPlayerPage() { ); } - /* ---------- render ---------- */ + const allSubs = + stream?.mediaSource.MediaStreams?.filter((s) => s.Type === "Subtitle") || + []; + const externalSubtitles = allSubs + .filter((s) => s.DeliveryMethod === "External") + .map((s) => ({ + name: s.DisplayTitle, + DeliveryUrl: api?.basePath + s.DeliveryUrl, + })); + return ( dispatch({ type: "VIDEO_LOADED" })} onVideoError={(e) => { console.error("Video Error:", e.nativeEvent); @@ -726,12 +666,17 @@ export default function DirectPlayerPage() { pause={pause} seek={seek} enableTrickplay - getAudioTracks={getAudioTracks} - getSubtitleTracks={getSubtitleTracks} + // Pass undefined for player methods until the video is loaded to avoid crashes + getAudioTracks={videoState.isVideoLoaded ? getAudioTracks : undefined} + getSubtitleTracks={ + videoState.isVideoLoaded ? getSubtitleTracks : undefined + } offline={offline} - setSubtitleTrack={setSubtitleTrack} - setSubtitleURL={setSubtitleURL} - setAudioTrack={setAudioTrack} + setSubtitleTrack={ + videoState.isVideoLoaded ? setSubtitleTrack : undefined + } + setSubtitleURL={videoState.isVideoLoaded ? setSubtitleURL : undefined} + setAudioTrack={videoState.isVideoLoaded ? setAudioTrack : undefined} isVlc /> )} diff --git a/biome.json b/biome.json index 5dc6c54a..de0bc7aa 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json", + "$schema": "https://biomejs.dev/schemas/2.1.4/schema.json", "files": { "includes": [ "**/*", diff --git a/bun.lock b/bun.lock index c3d3d27f..e6ebe042 100644 --- a/bun.lock +++ b/bun.lock @@ -85,7 +85,7 @@ }, "devDependencies": { "@babel/core": "^7.20.0", - "@biomejs/biome": "^2.1.3", + "@biomejs/biome": "^2.1.4", "@react-native-community/cli": "^19", "@react-native-tvos/config-tv": "^0.1.1", "@types/jest": "^29.5.12", @@ -94,7 +94,7 @@ "@types/react-test-renderer": "^19.0.0", "cross-env": "^10.0.0", "husky": "^9.1.7", - "lint-staged": "^16.1.2", + "lint-staged": "^16.1.5", "postinstall-postinstall": "^2.1.0", "react-test-renderer": "19.1.1", "typescript": "~5.8.3", @@ -297,23 +297,23 @@ "@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], - "@biomejs/biome": ["@biomejs/biome@2.1.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.3", "@biomejs/cli-darwin-x64": "2.1.3", "@biomejs/cli-linux-arm64": "2.1.3", "@biomejs/cli-linux-arm64-musl": "2.1.3", "@biomejs/cli-linux-x64": "2.1.3", "@biomejs/cli-linux-x64-musl": "2.1.3", "@biomejs/cli-win32-arm64": "2.1.3", "@biomejs/cli-win32-x64": "2.1.3" }, "bin": { "biome": "bin/biome" } }, "sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w=="], + "@biomejs/biome": ["@biomejs/biome@2.1.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.4", "@biomejs/cli-darwin-x64": "2.1.4", "@biomejs/cli-linux-arm64": "2.1.4", "@biomejs/cli-linux-arm64-musl": "2.1.4", "@biomejs/cli-linux-x64": "2.1.4", "@biomejs/cli-linux-x64-musl": "2.1.4", "@biomejs/cli-win32-arm64": "2.1.4", "@biomejs/cli-win32-x64": "2.1.4" }, "bin": { "biome": "bin/biome" } }, "sha512-QWlrqyxsU0FCebuMnkvBIkxvPqH89afiJzjMl+z67ybutse590jgeaFdDurE9XYtzpjRGTI1tlUZPGWmbKsElA=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sCrNENE74I9MV090Wq/9Dg7EhPudx3+5OiSoQOkIe3DLPzFARuL1dOwCWhKCpA3I5RHmbrsbNSRfZwCabwd8Qg=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-gOEICJbTCy6iruBywBDcG4X5rHMbqCPs3clh3UQ+hRKlgvJTk4NHWQAyHOXvaLe+AxD1/TNX1jbZeffBJzcrOw=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-juhEkdkKR4nbUi5k/KRp1ocGPNWLgFRD4NrHZSveYrD6i98pyvuzmS9yFYgOZa5JhaVqo0HPnci0+YuzSwT2fw=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-nYr7H0CyAJPaLupFE2cH16KZmRC5Z9PEftiA2vWxk+CsFkPZQ6dBRdcC6RuS+zJlPc/JOd8xw3uCCt9Pv41WvQ=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Eoy9ycbhpJVYuR+LskV9s3uyaIkp89+qqgqhGQsWnp/I02Uqg2fXFblHJOpGZR8AxdB9ADy87oFVxn9MpFKUrw=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lvwvb2SQQHctHUKvBKptR6PLFCM7JfRjpCCrDaTmvB7EeZ5/dQJPhTYBf36BE/B4CRWR2ZiBLRYhK7hhXBCZAg=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.1.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-3WRYte7orvyi6TRfIZkDN9Jzoogbv+gSvR+b9VOXUg1We1XrjBg6WljADeVEaKTvOcpVdH0a90TwyOQ6ue4fGw=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-tBc+W7anBPSFXGAoQW+f/+svkpt8/uXfRwDzN1DvnatkRMt16KIYpEi/iw8u9GahJlFv98kgHcIrSsZHZTR0sw=="], "@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@0.9.2", "", { "dependencies": { "color": "^4.2.3" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-IZZKllcaqCGsKIgeXmYFGU95IXxbBpXtwKws4Lg2GJw/qqAYYsPFEl0JBvnymSD7G1zkHYEilg5UHuTd0NmX7A=="], @@ -1335,9 +1335,9 @@ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - "lint-staged": ["lint-staged@16.1.2", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0", "debug": "^4.4.1", "lilconfig": "^3.1.3", "listr2": "^8.3.3", "micromatch": "^4.0.8", "nano-spawn": "^1.0.2", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.0" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q=="], + "lint-staged": ["lint-staged@16.1.5", "", { "dependencies": { "chalk": "^5.5.0", "commander": "^14.0.0", "debug": "^4.4.1", "lilconfig": "^3.1.3", "listr2": "^9.0.1", "micromatch": "^4.0.8", "nano-spawn": "^1.0.2", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-uAeQQwByI6dfV7wpt/gVqg+jAPaSp8WwOA8kKC/dv1qw14oGpnpAisY65ibGHUGDUv0rYaZ8CAJZ/1U8hUvC2A=="], - "listr2": ["listr2@8.3.3", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ=="], + "listr2": ["listr2@9.0.1", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], @@ -1987,7 +1987,7 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="], + "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -2081,6 +2081,8 @@ "@react-native-community/cli-doctor/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "@react-native-community/cli-doctor/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="], + "@react-native-community/cli-server-api/pretty-format": ["pretty-format@26.6.2", "", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], "@react-native-community/cli-tools/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], @@ -2167,7 +2169,7 @@ "lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "lint-staged/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "lint-staged/chalk": ["chalk@5.5.0", "", {}, "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg=="], "lint-staged/commander": ["commander@14.0.0", "", {}, "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA=="], @@ -2201,6 +2203,8 @@ "postcss-css-variables/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + "postcss-load-config/yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="], + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], diff --git a/package.json b/package.json index 1829d8c0..9a27aa07 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ }, "devDependencies": { "@babel/core": "^7.20.0", - "@biomejs/biome": "^2.1.3", + "@biomejs/biome": "^2.1.4", "@react-native-community/cli": "^19", "@react-native-tvos/config-tv": "^0.1.1", "@types/jest": "^29.5.12", @@ -109,7 +109,7 @@ "@types/react-test-renderer": "^19.0.0", "cross-env": "^10.0.0", "husky": "^9.1.7", - "lint-staged": "^16.1.2", + "lint-staged": "^16.1.5", "postinstall-postinstall": "^2.1.0", "react-test-renderer": "19.1.1", "typescript": "~5.8.3"