mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
fix issue 210 slow source search reliability
This commit is contained in:
+62
-30
@@ -5,6 +5,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { settingsStore } from '@/lib/store/settings-store';
|
||||
import { probeLatencyTargets } from '@/lib/utils/latency';
|
||||
|
||||
interface LatencyState {
|
||||
[sourceId: string]: number;
|
||||
@@ -23,17 +24,18 @@ export function useLatencyPing({
|
||||
}: UseLatencyPingOptions) {
|
||||
const [latencies, setLatencies] = useState<LatencyState>({});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const intervalRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const roundInFlightRef = useRef(false);
|
||||
const mountedRef = useRef(true);
|
||||
|
||||
// Check if real-time latency is enabled in settings
|
||||
const [realtimeEnabled, setRealtimeEnabled] = useState(false);
|
||||
|
||||
// Stabilize sourceUrls to prevent unnecessary effect re-runs if parent passes new array
|
||||
const stableSourceUrls = useMemo(() => sourceUrls, [
|
||||
// Create a unique key for the sources array
|
||||
sourceUrls.map(s => `${s.id}|${s.baseUrl}`).join(',')
|
||||
]);
|
||||
const stableSourceUrls = useMemo(
|
||||
() => sourceUrls.map((source) => ({ ...source })),
|
||||
[sourceUrls],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const settings = settingsStore.getSettings();
|
||||
@@ -50,47 +52,64 @@ export function useLatencyPing({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const pingSource = useCallback(async (sourceId: string, baseUrl: string): Promise<number | null> => {
|
||||
const pingSource = useCallback(async (baseUrl: string): Promise<number | null> => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 12000);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ping', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: baseUrl }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data.latency || null;
|
||||
return data.success && typeof data.latency === 'number'
|
||||
? data.latency
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const pingAllSources = useCallback(async () => {
|
||||
if (!mountedRef.current || stableSourceUrls.length === 0) return;
|
||||
if (
|
||||
!mountedRef.current ||
|
||||
stableSourceUrls.length === 0 ||
|
||||
roundInFlightRef.current
|
||||
) return;
|
||||
|
||||
roundInFlightRef.current = true;
|
||||
setIsLoading(true);
|
||||
|
||||
const results = await Promise.all(
|
||||
stableSourceUrls.map(async ({ id, baseUrl }) => {
|
||||
const latency = await pingSource(id, baseUrl);
|
||||
return { id, latency };
|
||||
})
|
||||
);
|
||||
try {
|
||||
const results = await probeLatencyTargets(
|
||||
stableSourceUrls,
|
||||
({ baseUrl }) => pingSource(baseUrl),
|
||||
);
|
||||
|
||||
if (mountedRef.current) {
|
||||
setLatencies(prev => {
|
||||
const newState = { ...prev };
|
||||
results.forEach(({ id, latency }) => {
|
||||
if (latency !== null) {
|
||||
newState[id] = latency;
|
||||
}
|
||||
if (mountedRef.current) {
|
||||
setLatencies(prev => {
|
||||
const newState = { ...prev };
|
||||
results.forEach(({ id, latency }) => {
|
||||
if (latency !== null) {
|
||||
newState[id] = latency;
|
||||
}
|
||||
});
|
||||
return newState;
|
||||
});
|
||||
return newState;
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
} finally {
|
||||
roundInFlightRef.current = false;
|
||||
if (mountedRef.current) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}, [stableSourceUrls, pingSource]);
|
||||
|
||||
@@ -101,17 +120,30 @@ export function useLatencyPing({
|
||||
const shouldPoll = enabled && realtimeEnabled && stableSourceUrls.length > 0;
|
||||
|
||||
if (shouldPoll) {
|
||||
// Initial ping
|
||||
pingAllSources();
|
||||
let cancelled = false;
|
||||
const poll = async () => {
|
||||
await pingAllSources();
|
||||
if (!cancelled && mountedRef.current) {
|
||||
intervalRef.current = setTimeout(poll, intervalMs);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up interval
|
||||
intervalRef.current = setInterval(pingAllSources, intervalMs);
|
||||
void poll();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
mountedRef.current = false;
|
||||
if (intervalRef.current) {
|
||||
clearTimeout(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
clearTimeout(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
@@ -120,7 +152,7 @@ export function useLatencyPing({
|
||||
const refreshLatency = useCallback((sourceId: string) => {
|
||||
const source = stableSourceUrls.find(s => s.id === sourceId);
|
||||
if (source) {
|
||||
pingSource(sourceId, source.baseUrl).then(latency => {
|
||||
pingSource(source.baseUrl).then(latency => {
|
||||
if (latency !== null && mountedRef.current) {
|
||||
setLatencies(prev => ({ ...prev, [sourceId]: latency }));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { SOURCE_IDS } from '@/lib/utils/source-names';
|
||||
import { sortVideos } from '@/lib/utils/sort';
|
||||
import { binaryInsertVideos } from '@/lib/utils/sorted-insert';
|
||||
import { processSearchStream } from '@/lib/utils/search-stream';
|
||||
import type { SortOption } from '@/lib/store/settings-store';
|
||||
import { settingsStore } from '@/lib/store/settings-store';
|
||||
import type { Video } from '@/lib/types';
|
||||
import { useSearchState } from './useSearchState';
|
||||
|
||||
type SearchState = ReturnType<typeof useSearchState>;
|
||||
type SearchSourceConfig = { id: string; baseUrl?: string };
|
||||
|
||||
interface UseSearchActionProps {
|
||||
state: SearchState;
|
||||
@@ -77,7 +76,10 @@ export function useSearchAction({ state, onCacheUpdate, onUrlUpdate }: UseSearch
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) throw new Error('No response stream');
|
||||
|
||||
const sourcesMap = new Map<string, { count: number; name: string }>();
|
||||
const sourceConfigs = new Map<string, SearchSourceConfig>(
|
||||
targetSources.map((source: SearchSourceConfig) => [source.id, source])
|
||||
);
|
||||
const sourcesMap = new Map<string, { count: number; name: string; baseUrl?: string }>();
|
||||
|
||||
await processSearchStream({
|
||||
reader,
|
||||
@@ -95,6 +97,7 @@ export function useSearchAction({ state, onCacheUpdate, onUrlUpdate }: UseSearch
|
||||
sourcesMap.set(sourceId, {
|
||||
count: newVideos.length,
|
||||
name: newVideos[0]?.sourceName || sourceId,
|
||||
baseUrl: sourceConfigs.get(sourceId)?.baseUrl,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -113,6 +116,7 @@ export function useSearchAction({ state, onCacheUpdate, onUrlUpdate }: UseSearch
|
||||
id: id,
|
||||
name: info.name,
|
||||
count: info.count,
|
||||
...(info.baseUrl ? { baseUrl: info.baseUrl } : {}),
|
||||
}));
|
||||
setAvailableSources(sources);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user