diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index c435439..f460489 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -3959,6 +3959,7 @@ const LiveSourceConfig = ({ const { isLoading, withLoading } = useLoadingState(); const [liveSources, setLiveSources] = useState([]); const [showAddForm, setShowAddForm] = useState(false); + const [editingLiveSource, setEditingLiveSource] = useState(null); const [orderChanged, setOrderChanged] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [newLiveSource, setNewLiveSource] = useState({ @@ -4087,6 +4088,27 @@ const LiveSourceConfig = ({ }); }; + const handleEditLiveSource = () => { + if (!editingLiveSource || !editingLiveSource.name || !editingLiveSource.url) return; + withLoading('editLiveSource', async () => { + await callLiveSourceApi({ + action: 'edit', + key: editingLiveSource.key, + name: editingLiveSource.name, + url: editingLiveSource.url, + ua: editingLiveSource.ua, + epg: editingLiveSource.epg, + }); + setEditingLiveSource(null); + }).catch(() => { + console.error('操作失败', 'edit', editingLiveSource); + }); + }; + + const handleCancelEdit = () => { + setEditingLiveSource(null); + }; + const handleDragEnd = (event: any) => { const { active, over } = event; if (!over || active.id === over.id) return; @@ -4180,13 +4202,22 @@ const LiveSourceConfig = ({ {!liveSource.disabled ? '禁用' : '启用'} {liveSource.from !== 'config' && ( - + <> + + + )} @@ -4290,6 +4321,103 @@ const LiveSourceConfig = ({ )} + {/* 编辑直播源表单 */} + {editingLiveSource && ( +
+
+
+ 编辑直播源: {editingLiveSource.name} +
+ +
+
+
+ + + setEditingLiveSource((prev) => prev ? ({ ...prev, name: e.target.value }) : null) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100' + /> +
+
+ + +
+
+ + + setEditingLiveSource((prev) => prev ? ({ ...prev, url: e.target.value }) : null) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100' + /> +
+
+ + + setEditingLiveSource((prev) => prev ? ({ ...prev, epg: e.target.value }) : null) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100' + /> +
+
+ + + setEditingLiveSource((prev) => prev ? ({ ...prev, ua: e.target.value }) : null) + } + className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100' + /> +
+
+
+ + +
+
+ )} + {/* 直播源表格 */}
diff --git a/src/app/api/admin/live/route.ts b/src/app/api/admin/live/route.ts index 23bb054..b173205 100644 --- a/src/app/api/admin/live/route.ts +++ b/src/app/api/admin/live/route.ts @@ -102,6 +102,34 @@ export async function POST(request: NextRequest) { disableSource.disabled = true; break; + case 'edit': + // 编辑直播源 + const editSource = config.LiveConfig.find((l) => l.key === key); + if (!editSource) { + return NextResponse.json({ error: '直播源不存在' }, { status: 404 }); + } + + // 配置文件中的直播源不允许编辑 + if (editSource.from === 'config') { + return NextResponse.json({ error: '不能编辑配置文件中的直播源' }, { status: 400 }); + } + + // 更新字段(除了 key 和 from) + editSource.name = name as string; + editSource.url = url as string; + editSource.ua = ua || ''; + editSource.epg = epg || ''; + + // 刷新频道数 + try { + const nums = await refreshLiveChannels(editSource); + editSource.channelNumber = nums; + } catch (error) { + console.error('刷新直播源失败:', error); + editSource.channelNumber = 0; + } + break; + case 'sort': // 排序直播源 const { order } = body; diff --git a/src/app/api/live/precheck/route.ts b/src/app/api/live/precheck/route.ts index 0334f13..ff0dafa 100644 --- a/src/app/api/live/precheck/route.ts +++ b/src/app/api/live/precheck/route.ts @@ -31,7 +31,7 @@ export async function GET(request: NextRequest) { }); if (!response.ok) { - return NextResponse.json({ error: 'Failed to fetch' }, { status: 500 }); + return NextResponse.json({ error: 'Failed to fetch', message: response.statusText }, { status: 500 }); } const contentType = response.headers.get('Content-Type'); @@ -43,6 +43,6 @@ export async function GET(request: NextRequest) { } return NextResponse.json({ success: true, type: 'm3u8' }, { status: 200 }); } catch (error) { - return NextResponse.json({ error: 'Failed to fetch' }, { status: 500 }); + return NextResponse.json({ error: 'Failed to fetch', message: error }, { status: 500 }); } } \ No newline at end of file diff --git a/src/app/api/proxy/m3u8/route.ts b/src/app/api/proxy/m3u8/route.ts index edd746d..0154fdb 100644 --- a/src/app/api/proxy/m3u8/route.ts +++ b/src/app/api/proxy/m3u8/route.ts @@ -39,8 +39,9 @@ export async function GET(request: Request) { return NextResponse.json({ error: 'Failed to fetch m3u8' }, { status: 500 }); } + const contentType = response.headers.get('Content-Type') || ''; // rewrite m3u8 - if (response.headers.get('Content-Type')?.includes('application/vnd.apple.mpegurl')) { + if (contentType.toLowerCase().includes('mpegurl')) { // 获取最终的响应URL(处理重定向后的URL) const finalUrl = response.url; const m3u8Content = await response.text(); @@ -52,7 +53,7 @@ export async function GET(request: Request) { const modifiedContent = rewriteM3U8Content(m3u8Content, baseUrl, request, allowCORS); const headers = new Headers(); - headers.set('Content-Type', 'application/vnd.apple.mpegurl'); + headers.set('Content-Type', contentType); headers.set('Access-Control-Allow-Origin', '*'); headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); headers.set('Access-Control-Allow-Headers', 'Content-Type, Range, Origin, Accept');