mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-02 20:18:29 +01:00
Merge origin/develop into refactor-chromecast
Bring 323 commits of develop (incl. the Expo SDK 56 / TV-branch work) into the chromecast refactor. Conflict resolutions: - chapters: take develop's reviewed version (ChapterList/ChapterTicks/ chapters.ts/test) — adds chapterNameAt, markers API, themed Colors. - auto-skip: keep chromecast's unified useSegmentSkipper for the phone player; restore develop's useCreditSkipper/useIntroSkipper (deleted on chromecast) so develop's Controls.tv.tsx compiles. TV->useSegmentSkipper migration left as follow-up. - en.json: union the two player blocks (kept chromecast casting keys + develop's subtitle/playback keys). - TechnicalInfoOverlay/PlatformDropdown: take develop's TV-safe versions (kept chromecast's disabled-prop branch, aliased to avoid shadowing the @expo/ui disabled modifier). - SDK 56 fixes: expo-router Router -> ImperativeRouter in cast components; ChapterTicks markers API in CastPlayerProgressBar. - restore utils/profiles/chromecast* (deleted on chromecast, still used by PlayButton). Typecheck passes; bun.lock regenerated against merged package.json.
This commit is contained in:
@@ -12,10 +12,23 @@ import {
|
||||
} from "react";
|
||||
import { AppState, type AppStateStatus } from "react-native";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
|
||||
import { useRemoteControl } from "@/hooks/useRemoteControl";
|
||||
import { apiAtom, getOrSetDeviceId } from "@/providers/JellyfinProvider";
|
||||
import { useNetworkStatus } from "@/providers/NetworkStatusProvider";
|
||||
|
||||
// Query keys that depend on the set of library items and should be refreshed
|
||||
// when the server reports that the library changed (items added/removed/updated).
|
||||
const LIBRARY_CHANGE_QUERY_KEYS = [
|
||||
["home"],
|
||||
["library-items"],
|
||||
["nextUp-all"],
|
||||
["nextUp"],
|
||||
["resumeItems"],
|
||||
["seasons"],
|
||||
["episodes"],
|
||||
] as const;
|
||||
|
||||
interface WebSocketMessage {
|
||||
MessageType: string;
|
||||
Data: any;
|
||||
@@ -45,10 +58,14 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
|
||||
// Route Jellyfin remote-control messages to the active player.
|
||||
useRemoteControl(lastMessage);
|
||||
const router = useRouter();
|
||||
const queryClient = useNetworkAwareQueryClient();
|
||||
const deviceId = useMemo(() => {
|
||||
return getOrSetDeviceId();
|
||||
}, []);
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
const libraryChangeDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const connectWebSocket = useCallback(() => {
|
||||
if (!deviceId || !api?.accessToken || !isNetworkConnected) {
|
||||
@@ -69,7 +86,6 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
|
||||
const reconnectDelay = 10000;
|
||||
|
||||
newWebSocket.onopen = () => {
|
||||
console.log("WebSocket connection opened");
|
||||
setIsConnected(true);
|
||||
reconnectAttemptsRef.current = 0;
|
||||
keepAliveInterval = setInterval(() => {
|
||||
@@ -115,18 +131,57 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
|
||||
};
|
||||
}, [api, deviceId, isNetworkConnected]);
|
||||
|
||||
const handleLibraryChanged = useCallback(
|
||||
(data: any) => {
|
||||
// Jellyfin sends LibraryChanged when a scan adds/updates/removes items.
|
||||
// Only refresh when something actually changed in the item set.
|
||||
const hasChanges =
|
||||
(data?.ItemsAdded?.length ?? 0) > 0 ||
|
||||
(data?.ItemsRemoved?.length ?? 0) > 0 ||
|
||||
(data?.ItemsUpdated?.length ?? 0) > 0 ||
|
||||
(data?.FoldersAddedTo?.length ?? 0) > 0 ||
|
||||
(data?.FoldersRemovedFrom?.length ?? 0) > 0;
|
||||
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A single scan can emit several LibraryChanged messages in quick
|
||||
// succession, so debounce the invalidation to refetch only once.
|
||||
if (libraryChangeDebounceRef.current) {
|
||||
clearTimeout(libraryChangeDebounceRef.current);
|
||||
}
|
||||
libraryChangeDebounceRef.current = setTimeout(() => {
|
||||
for (const queryKey of LIBRARY_CHANGE_QUERY_KEYS) {
|
||||
queryClient.invalidateQueries({ queryKey: [...queryKey] });
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
[queryClient],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastMessage) {
|
||||
return;
|
||||
}
|
||||
if (lastMessage.MessageType === "Play") {
|
||||
handlePlayCommand(lastMessage.Data);
|
||||
} else if (lastMessage.MessageType === "LibraryChanged") {
|
||||
handleLibraryChanged(lastMessage.Data);
|
||||
}
|
||||
}, [lastMessage, router]);
|
||||
}, [lastMessage, router, handleLibraryChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (libraryChangeDebounceRef.current) {
|
||||
clearTimeout(libraryChangeDebounceRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handlePlayCommand = useCallback(
|
||||
(data: any) => {
|
||||
if (!data || !data.ItemIds || !data.ItemIds.length) {
|
||||
if (!data?.ItemIds?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,7 +209,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
|
||||
}, [connectWebSocket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!deviceId || !api || !api?.accessToken || !isNetworkConnected) {
|
||||
if (!deviceId || !api?.accessToken || !isNetworkConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user