From c354ec8168bf8b89efd2a0398b47989f0b6716c7 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Tue, 20 Jan 2026 20:31:13 +0800 Subject: [PATCH] douyinweblive --- src/app/admin/page.tsx | 6 +- .../api/web-live/proxy/[filename]/route.ts | 2 + src/app/api/web-live/stream/route.ts | 79 +++++++++++++++++++ src/app/web-live/page.tsx | 9 ++- 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index eeb970f..8f0625f 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -10831,6 +10831,7 @@ const WebLiveConfig = ({ > + +
@@ -10922,9 +10924,9 @@ const WebLiveConfig = ({
{source.name}
-
{source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform} · {source.roomId}
+
{source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform === 'douyin' ? '抖音' : source.platform} · {source.roomId}
- {source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform} + {source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform === 'douyin' ? '抖音' : source.platform} {source.roomId} diff --git a/src/app/api/web-live/proxy/[filename]/route.ts b/src/app/api/web-live/proxy/[filename]/route.ts index 31c76ac..0449e5a 100644 --- a/src/app/api/web-live/proxy/[filename]/route.ts +++ b/src/app/api/web-live/proxy/[filename]/route.ts @@ -49,6 +49,8 @@ export async function GET(request: NextRequest) { let referer = 'https://www.huya.com/'; if (url.includes('bilivideo.com') || url.includes('bilibili.com')) { referer = 'https://live.bilibili.com/'; + } else if (url.includes('douyin.com') || url.includes('douyincdn.com')) { + referer = 'https://live.douyin.com/'; } const streamRes = await fetch(url, { diff --git a/src/app/api/web-live/stream/route.ts b/src/app/api/web-live/stream/route.ts index c202f8e..9a0beb2 100644 --- a/src/app/api/web-live/stream/route.ts +++ b/src/app/api/web-live/stream/route.ts @@ -97,6 +97,75 @@ async function getBilibiliStream(roomId: string) { return m3u8Url; } +async function getDouyinStream(roomId: string) { + const cookies = 'ttwid=1%7C2iDIYVmjzMcpZ20fcaFde0VghXAA3NaNXE_SLR68IyE%7C1761045455%7Cab35197d5cfb21df6cbb2fa7ef1c9262206b062c315b9d04da746d0b37dfbc7d'; + + const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36', + 'Referer': 'https://live.douyin.com/', + 'Cookie': cookies + }; + + // 构建API参数 + const params = new URLSearchParams({ + aid: '6383', + app_name: 'douyin_web', + live_id: '1', + device_platform: 'web', + language: 'zh-CN', + browser_language: 'zh-CN', + browser_platform: 'Win32', + browser_name: 'Chrome', + browser_version: '116.0.0.0', + web_rid: roomId, + msToken: '' + }); + + const apiUrl = `https://live.douyin.com/webcast/room/web/enter/?${params.toString()}`; + + const response = await fetch(apiUrl, { headers }); + const jsonData = await response.json(); + + if (!jsonData.data || !jsonData.data.data) { + throw new Error('获取直播间信息失败'); + } + + const roomData = jsonData.data.data[0]; + const status = roomData.status; + + if (status !== 2) { + throw new Error('直播未开启'); + } + + const streamUrl = roomData.stream_url; + if (!streamUrl) { + throw new Error('未找到流地址'); + } + + // 获取m3u8地址 + const hlsPullUrlMap = streamUrl.hls_pull_url_map; + if (!hlsPullUrlMap) { + throw new Error('未找到m3u8地址'); + } + + // 尝试获取原画质,如果没有则获取第一个可用的 + let m3u8Url = hlsPullUrlMap.ORIGIN || hlsPullUrlMap.FULL_HD1 || hlsPullUrlMap.HD1 || hlsPullUrlMap.SD1 || hlsPullUrlMap.SD2; + + if (!m3u8Url) { + // 如果上述都没有,获取第一个可用的 + const urls = Object.values(hlsPullUrlMap); + if (urls.length > 0) { + m3u8Url = urls[0] as string; + } + } + + if (!m3u8Url) { + throw new Error('未找到可用的m3u8地址'); + } + + return m3u8Url; +} + export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); @@ -148,6 +217,16 @@ export async function GET(request: NextRequest) { }); } + if (platform === 'douyin') { + const streamUrl = await getDouyinStream(roomId); + const proxyUrl = `/api/web-live/proxy/proxy.m3u8?url=${encodeURIComponent(streamUrl)}`; + + return NextResponse.json({ + url: proxyUrl, + originalUrl: streamUrl + }); + } + return NextResponse.json({ error: '不支持的平台' }, { status: 400 }); } catch (error) { return NextResponse.json( diff --git a/src/app/web-live/page.tsx b/src/app/web-live/page.tsx index 84d8a96..699aa54 100644 --- a/src/app/web-live/page.tsx +++ b/src/app/web-live/page.tsx @@ -141,6 +141,9 @@ export default function WebLivePage() { if (source.platform === 'bilibili') { return `https://live.bilibili.com/${source.roomId}`; } + if (source.platform === 'douyin') { + return `https://live.douyin.com/${source.roomId}`; + } return ''; }; @@ -502,7 +505,7 @@ export default function WebLivePage() {
筛选平台: - {selectedPlatform === 'huya' ? '虎牙' : selectedPlatform === 'bilibili' ? '哔哩哔哩' : selectedPlatform} + {selectedPlatform === 'huya' ? '虎牙' : selectedPlatform === 'bilibili' ? '哔哩哔哩' : selectedPlatform === 'douyin' ? '抖音' : selectedPlatform}
- {platform === 'huya' ? '虎牙' : platform === 'bilibili' ? '哔哩哔哩' : platform} + {platform === 'huya' ? '虎牙' : platform === 'bilibili' ? '哔哩哔哩' : platform === 'douyin' ? '抖音' : platform}
{sources.filter(s => s.platform === platform).length} 个房间