From fa46e05c7c8db57e6b3f4736527c2087c8433b36 Mon Sep 17 00:00:00 2001 From: shinya Date: Tue, 26 Aug 2025 14:31:46 +0800 Subject: [PATCH] feat: disable flv live --- package.json | 1 - pnpm-lock.yaml | 37 ++-------- src/app/api/live/precheck/route.ts | 3 + src/app/live/page.tsx | 113 +++++++++++++++++++---------- 4 files changed, 86 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index 6e18f53..e5be487 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "bs58": "^6.0.0", "clsx": "^2.0.0", "crypto-js": "^4.2.0", - "flv.js": "^1.6.2", "framer-motion": "^12.18.1", "he": "^1.2.0", "hls.js": "^1.6.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 915f639..7aa9521 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,6 @@ importers: crypto-js: specifier: ^4.2.0 version: 4.2.0 - flv.js: - specifier: ^1.6.2 - version: 1.6.2 framer-motion: specifier: ^12.18.1 version: 12.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2630,9 +2627,6 @@ packages: es6-object-assign@1.1.0: resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2887,9 +2881,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - flv.js@1.6.2: - resolution: {integrity: sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==} - for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -5133,9 +5124,6 @@ packages: webpack-cli: optional: true - webworkify-webpack@2.1.5: - resolution: {integrity: sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==} - whatwg-encoding@1.0.5: resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} @@ -8210,8 +8198,6 @@ snapshots: es6-object-assign@1.1.0: {} - es6-promise@4.2.8: {} - escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -8234,8 +8220,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -8258,7 +8244,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1(supports-color@9.4.0) @@ -8269,22 +8255,22 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -8295,7 +8281,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -8553,11 +8539,6 @@ snapshots: flatted@3.3.3: {} - flv.js@1.6.2: - dependencies: - es6-promise: 4.2.8 - webworkify-webpack: 2.1.5 - for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -11146,8 +11127,6 @@ snapshots: - esbuild - uglify-js - webworkify-webpack@2.1.5: {} - whatwg-encoding@1.0.5: dependencies: iconv-lite: 0.4.24 diff --git a/src/app/api/live/precheck/route.ts b/src/app/api/live/precheck/route.ts index ff0dafa..ba37532 100644 --- a/src/app/api/live/precheck/route.ts +++ b/src/app/api/live/precheck/route.ts @@ -35,6 +35,9 @@ export async function GET(request: NextRequest) { } const contentType = response.headers.get('Content-Type'); + if (response.body) { + response.body.cancel(); + } if (contentType?.includes('video/mp4')) { return NextResponse.json({ success: true, type: 'mp4' }, { status: 200 }); } diff --git a/src/app/live/page.tsx b/src/app/live/page.tsx index e4cd163..0abc48d 100644 --- a/src/app/live/page.tsx +++ b/src/app/live/page.tsx @@ -16,7 +16,6 @@ import PageLayout from '@/components/PageLayout'; declare global { interface HTMLVideoElement { hls?: any; - flv?: any; } } @@ -68,6 +67,7 @@ function LivePageClient() { // 播放器相关 const [videoUrl, setVideoUrl] = useState(''); const [isVideoLoading, setIsVideoLoading] = useState(false); + const [unsupportedType, setUnsupportedType] = useState(null); // 切换直播源状态 const [isSwitchingSource, setIsSwitchingSource] = useState(false); @@ -350,6 +350,12 @@ function LivePageClient() { // 设置切换状态,锁住频道切换器 setIsSwitchingSource(true); + // 首先销毁当前播放器 + cleanupPlayer(); + + // 重置不支持的类型状态 + setUnsupportedType(null); + // 清空节目单信息 setEpgData(null); @@ -371,6 +377,12 @@ function LivePageClient() { // 如果正在切换直播源,则禁用频道切换 if (isSwitchingSource) return; + // 首先销毁当前播放器 + cleanupPlayer(); + + // 重置不支持的类型状态 + setUnsupportedType(null); + setCurrentChannel(channel); setVideoUrl(channel.url); @@ -404,6 +416,9 @@ function LivePageClient() { // 清理播放器资源的统一函数 const cleanupPlayer = () => { + // 重置不支持的类型状态 + setUnsupportedType(null); + if (artPlayerRef.current) { try { // 先暂停播放 @@ -419,10 +434,22 @@ function LivePageClient() { artPlayerRef.current.video.hls = null; } - // 销毁 FLV 实例 + // 销毁 FLV 实例 - 增强清理逻辑 if (artPlayerRef.current.video && artPlayerRef.current.video.flv) { - artPlayerRef.current.video.flv.destroy(); - artPlayerRef.current.video.flv = null; + try { + // 先停止加载 + if (artPlayerRef.current.video.flv.unload) { + artPlayerRef.current.video.flv.unload(); + } + // 销毁播放器 + artPlayerRef.current.video.flv.destroy(); + // 确保引用被清空 + artPlayerRef.current.video.flv = null; + } catch (flvError) { + console.warn('FLV实例销毁时出错:', flvError); + // 强制清空引用 + artPlayerRef.current.video.flv = null; + } } // 移除所有事件监听器 @@ -607,32 +634,6 @@ function LivePageClient() { }); } - async function flvLoader(video: HTMLVideoElement, url: string) { - try { - const flvjs = await import('flv.js'); - const flv = flvjs.default as any; - - if (!flv.isSupported()) { - console.error('Flv.js 未支持'); - return; - } - - if (video.flv) { - video.flv.destroy(); - } - - const flvPlayer = flv.createPlayer({ - type: 'flv', - url: url, - }); - flvPlayer.attachMediaElement(video); - flvPlayer.load(); - video.flv = flvPlayer; - } catch (error) { - console.error('加载 Flv.js 失败:', error); - } - } - // 播放器初始化 useEffect(() => { const preload = async () => { @@ -666,18 +667,25 @@ function LivePageClient() { type = precheckResult.type; } - const customType = type === 'flv' ? { - flv: flvLoader, - } : type === 'mp4' ? {} : { - m3u8: m3u8Loader, - }; + // 如果不是 m3u8 类型,设置不支持的类型并返回 + if (type !== 'm3u8') { + setUnsupportedType(type); + setIsVideoLoading(false); + return; + } + + // 重置不支持的类型 + setUnsupportedType(null); + + const customType = { m3u8: m3u8Loader }; + const targetUrl = `/api/proxy/m3u8?url=${encodeURIComponent(videoUrl)}&moontv-source=${currentSourceRef.current?.key || ''}`; try { // 创建新的播放器实例 Artplayer.USE_RAF = true; artPlayerRef.current = new Artplayer({ container: artRef.current, - url: `/api/proxy/m3u8?url=${encodeURIComponent(videoUrl)}&moontv-source=${currentSourceRef.current?.key || ''}`, + url: targetUrl, poster: currentChannel.logo, volume: 0.7, isLive: true, // 设置为直播模式 @@ -746,10 +754,9 @@ function LivePageClient() { }); if (artPlayerRef.current?.video) { - const finalUrl = `/api/proxy/m3u8?url=${encodeURIComponent(videoUrl)}`; ensureVideoSource( artPlayerRef.current.video as HTMLVideoElement, - finalUrl + targetUrl ); } @@ -1029,6 +1036,36 @@ function LivePageClient() { className='bg-black w-full h-full rounded-xl overflow-hidden shadow-lg border border-white/0 dark:border-white/30' > + {/* 不支持的直播类型提示 */} + {unsupportedType && ( +
+
+
+
+
⚠️
+
+
+
+
+

+ 暂不支持的直播类型 +

+
+

+ 当前直播源类型:{unsupportedType.toUpperCase()} +

+

+ 目前仅支持 M3U8 格式的直播流 +

+
+

+ 请尝试其他频道 +

+
+
+
+ )} + {/* 视频加载蒙层 */} {isVideoLoading && (