增加测速超时设置

This commit is contained in:
mtvpls
2026-01-17 00:28:51 +08:00
parent ce6d02c023
commit 2587479970
3 changed files with 104 additions and 20 deletions

View File

@@ -140,6 +140,32 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
// 集数过滤设置弹窗状态
const [showFilterSettings, setShowFilterSettings] = useState<boolean>(false);
// 读取本地"优选和测速"开关,默认开启
const [optimizationEnabled] = useState<boolean>(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('enableOptimization');
if (saved !== null) {
try {
return JSON.parse(saved);
} catch {
/* ignore */
}
}
}
return true;
});
// 读取测速超时设置默认4秒
const [speedTestTimeout] = useState<number>(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('speedTestTimeout');
if (saved !== null) {
return Number(saved);
}
}
return 4000;
});
// 集数过滤逻辑
const isEpisodeFiltered = useCallback(
(episodeNumber: number): boolean => {
@@ -205,7 +231,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
setAttemptedSources((prev) => new Set(prev).add(sourceKey));
try {
const info = await getVideoResolutionFromM3u8(episodeUrl);
const info = await getVideoResolutionFromM3u8(episodeUrl, speedTestTimeout);
setVideoInfoMap((prev) => new Map(prev).set(sourceKey, info));
} catch (error) {
// 失败时保存错误状态
@@ -219,7 +245,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
})
);
}
}, []);
}, [speedTestTimeout]);
// 当有预计算结果时先合并到videoInfoMap中
useEffect(() => {
@@ -252,21 +278,6 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
}
}, [precomputedVideoInfo]);
// 读取本地"优选和测速"开关,默认开启
const [optimizationEnabled] = useState<boolean>(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('enableOptimization');
if (saved !== null) {
try {
return JSON.parse(saved);
} catch {
/* ignore */
}
}
}
return true;
});
// 当切换到换源tab并且有源数据时异步获取视频信息 - 移除 attemptedSources 依赖避免循环触发
useEffect(() => {
const fetchVideoInfosInBatches = async () => {

View File

@@ -95,6 +95,7 @@ export const UserMenu: React.FC = () => {
const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true);
const [doubanProxyUrl, setDoubanProxyUrl] = useState('');
const [enableOptimization, setEnableOptimization] = useState(true);
const [speedTestTimeout, setSpeedTestTimeout] = useState(4000); // 测速超时时间(毫秒)
const [fluidSearch, setFluidSearch] = useState(true);
const [liveDirectConnect, setLiveDirectConnect] = useState(false);
const [tmdbBackdropDisabled, setTmdbBackdropDisabled] = useState(false);
@@ -344,6 +345,11 @@ export const UserMenu: React.FC = () => {
setEnableOptimization(JSON.parse(savedEnableOptimization));
}
const savedSpeedTestTimeout = localStorage.getItem('speedTestTimeout');
if (savedSpeedTestTimeout !== null) {
setSpeedTestTimeout(Number(savedSpeedTestTimeout));
}
const savedFluidSearch = localStorage.getItem('fluidSearch');
const defaultFluidSearch =
(window as any).RUNTIME_CONFIG?.FLUID_SEARCH !== false;
@@ -642,6 +648,13 @@ export const UserMenu: React.FC = () => {
}
};
const handleSpeedTestTimeoutChange = (value: number) => {
setSpeedTestTimeout(value);
if (typeof window !== 'undefined') {
localStorage.setItem('speedTestTimeout', String(value));
}
};
const handleFluidSearchToggle = (value: boolean) => {
setFluidSearch(value);
if (typeof window !== 'undefined') {
@@ -1439,6 +1452,63 @@ export const UserMenu: React.FC = () => {
</label>
</div>
{/* 测速超时设置 */}
{enableOptimization && (
<div className='ml-4 mt-2 space-y-2'>
<div className='flex items-center justify-between'>
<span className='text-xs text-gray-600 dark:text-gray-400'>
</span>
<span className='text-xs font-medium text-gray-700 dark:text-gray-300'>
{speedTestTimeout / 1000}
</span>
</div>
<div className='flex items-center gap-2'>
<input
type='range'
min='4000'
max='30000'
step='1000'
value={speedTestTimeout}
onChange={(e) => handleSpeedTestTimeoutChange(Number(e.target.value))}
className='flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700'
style={{
background: `linear-gradient(to right, #10b981 0%, #10b981 ${((speedTestTimeout - 4000) / (30000 - 4000)) * 100}%, #e5e7eb ${((speedTestTimeout - 4000) / (30000 - 4000)) * 100}%, #e5e7eb 100%)`
}}
/>
</div>
<div className='flex justify-between text-xs text-gray-500 dark:text-gray-400'>
<button
onClick={() => handleSpeedTestTimeoutChange(4000)}
className={`px-2 py-0.5 rounded ${speedTestTimeout === 4000 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
>
4
</button>
<button
onClick={() => handleSpeedTestTimeoutChange(10000)}
className={`px-2 py-0.5 rounded ${speedTestTimeout === 10000 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
>
10
</button>
<button
onClick={() => handleSpeedTestTimeoutChange(20000)}
className={`px-2 py-0.5 rounded ${speedTestTimeout === 20000 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
>
20
</button>
<button
onClick={() => handleSpeedTestTimeoutChange(30000)}
className={`px-2 py-0.5 rounded ${speedTestTimeout === 30000 ? 'bg-green-500 text-white' : 'hover:bg-gray-200 dark:hover:bg-gray-700'}`}
>
30
</button>
</div>
<p className='text-xs text-gray-500 dark:text-gray-400 italic'>
使4
</p>
</div>
)}
{/* 流式搜索 */}
<div className='flex items-center justify-between'>
<div>

View File

@@ -138,7 +138,10 @@ export function processVideoUrl(originalUrl: string): string {
* @param m3u8Url m3u8播放列表的URL
* @returns Promise<{quality: string, loadSpeed: string, pingTime: number, bitrate: string}> 视频质量等级和网络信息
*/
export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{
export async function getVideoResolutionFromM3u8(
m3u8Url: string,
timeoutMs: number = 4000
): Promise<{
quality: string; // 如720p、1080p等
loadSpeed: string; // 自动转换为KB/s或MB/s
pingTime: number; // 网络延迟(毫秒)
@@ -167,12 +170,12 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{
// 固定使用hls.js加载
const hls = new Hls();
// 设置超时处理
// 设置超时处理 - 使用传入的超时时间
const timeout = setTimeout(() => {
hls.destroy();
video.remove();
reject(new Error('Timeout loading video metadata'));
}, 4000);
}, timeoutMs);
video.onerror = () => {
clearTimeout(timeout);