增加测速超时设置
This commit is contained in:
@@ -140,6 +140,32 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
// 集数过滤设置弹窗状态
|
// 集数过滤设置弹窗状态
|
||||||
const [showFilterSettings, setShowFilterSettings] = useState<boolean>(false);
|
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(
|
const isEpisodeFiltered = useCallback(
|
||||||
(episodeNumber: number): boolean => {
|
(episodeNumber: number): boolean => {
|
||||||
@@ -205,7 +231,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
setAttemptedSources((prev) => new Set(prev).add(sourceKey));
|
setAttemptedSources((prev) => new Set(prev).add(sourceKey));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const info = await getVideoResolutionFromM3u8(episodeUrl);
|
const info = await getVideoResolutionFromM3u8(episodeUrl, speedTestTimeout);
|
||||||
setVideoInfoMap((prev) => new Map(prev).set(sourceKey, info));
|
setVideoInfoMap((prev) => new Map(prev).set(sourceKey, info));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 失败时保存错误状态
|
// 失败时保存错误状态
|
||||||
@@ -219,7 +245,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [speedTestTimeout]);
|
||||||
|
|
||||||
// 当有预计算结果时,先合并到videoInfoMap中
|
// 当有预计算结果时,先合并到videoInfoMap中
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -252,21 +278,6 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
}
|
}
|
||||||
}, [precomputedVideoInfo]);
|
}, [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 依赖避免循环触发
|
// 当切换到换源tab并且有源数据时,异步获取视频信息 - 移除 attemptedSources 依赖避免循环触发
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchVideoInfosInBatches = async () => {
|
const fetchVideoInfosInBatches = async () => {
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export const UserMenu: React.FC = () => {
|
|||||||
const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true);
|
const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true);
|
||||||
const [doubanProxyUrl, setDoubanProxyUrl] = useState('');
|
const [doubanProxyUrl, setDoubanProxyUrl] = useState('');
|
||||||
const [enableOptimization, setEnableOptimization] = useState(true);
|
const [enableOptimization, setEnableOptimization] = useState(true);
|
||||||
|
const [speedTestTimeout, setSpeedTestTimeout] = useState(4000); // 测速超时时间(毫秒)
|
||||||
const [fluidSearch, setFluidSearch] = useState(true);
|
const [fluidSearch, setFluidSearch] = useState(true);
|
||||||
const [liveDirectConnect, setLiveDirectConnect] = useState(false);
|
const [liveDirectConnect, setLiveDirectConnect] = useState(false);
|
||||||
const [tmdbBackdropDisabled, setTmdbBackdropDisabled] = useState(false);
|
const [tmdbBackdropDisabled, setTmdbBackdropDisabled] = useState(false);
|
||||||
@@ -344,6 +345,11 @@ export const UserMenu: React.FC = () => {
|
|||||||
setEnableOptimization(JSON.parse(savedEnableOptimization));
|
setEnableOptimization(JSON.parse(savedEnableOptimization));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const savedSpeedTestTimeout = localStorage.getItem('speedTestTimeout');
|
||||||
|
if (savedSpeedTestTimeout !== null) {
|
||||||
|
setSpeedTestTimeout(Number(savedSpeedTestTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
const savedFluidSearch = localStorage.getItem('fluidSearch');
|
const savedFluidSearch = localStorage.getItem('fluidSearch');
|
||||||
const defaultFluidSearch =
|
const defaultFluidSearch =
|
||||||
(window as any).RUNTIME_CONFIG?.FLUID_SEARCH !== false;
|
(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) => {
|
const handleFluidSearchToggle = (value: boolean) => {
|
||||||
setFluidSearch(value);
|
setFluidSearch(value);
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -1439,6 +1452,63 @@ export const UserMenu: React.FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</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 className='flex items-center justify-between'>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+6
-3
@@ -138,7 +138,10 @@ export function processVideoUrl(originalUrl: string): string {
|
|||||||
* @param m3u8Url m3u8播放列表的URL
|
* @param m3u8Url m3u8播放列表的URL
|
||||||
* @returns Promise<{quality: string, loadSpeed: string, pingTime: number, bitrate: string}> 视频质量等级和网络信息
|
* @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等
|
quality: string; // 如720p、1080p等
|
||||||
loadSpeed: string; // 自动转换为KB/s或MB/s
|
loadSpeed: string; // 自动转换为KB/s或MB/s
|
||||||
pingTime: number; // 网络延迟(毫秒)
|
pingTime: number; // 网络延迟(毫秒)
|
||||||
@@ -167,12 +170,12 @@ export async function getVideoResolutionFromM3u8(m3u8Url: string): Promise<{
|
|||||||
// 固定使用hls.js加载
|
// 固定使用hls.js加载
|
||||||
const hls = new Hls();
|
const hls = new Hls();
|
||||||
|
|
||||||
// 设置超时处理
|
// 设置超时处理 - 使用传入的超时时间
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
hls.destroy();
|
hls.destroy();
|
||||||
video.remove();
|
video.remove();
|
||||||
reject(new Error('Timeout loading video metadata'));
|
reject(new Error('Timeout loading video metadata'));
|
||||||
}, 4000);
|
}, timeoutMs);
|
||||||
|
|
||||||
video.onerror = () => {
|
video.onerror = () => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
|||||||
Reference in New Issue
Block a user