douyinweblive
This commit is contained in:
@@ -10831,6 +10831,7 @@ const WebLiveConfig = ({
|
|||||||
>
|
>
|
||||||
<option value='huya'>虎牙</option>
|
<option value='huya'>虎牙</option>
|
||||||
<option value='bilibili'>哔哩哔哩</option>
|
<option value='bilibili'>哔哩哔哩</option>
|
||||||
|
<option value='douyin'>抖音</option>
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
@@ -10881,6 +10882,7 @@ const WebLiveConfig = ({
|
|||||||
>
|
>
|
||||||
<option value='huya'>虎牙</option>
|
<option value='huya'>虎牙</option>
|
||||||
<option value='bilibili'>哔哩哔哩</option>
|
<option value='bilibili'>哔哩哔哩</option>
|
||||||
|
<option value='douyin'>抖音</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -10922,9 +10924,9 @@ const WebLiveConfig = ({
|
|||||||
<tr key={source.key} className='hover:bg-gray-50 dark:hover:bg-gray-800'>
|
<tr key={source.key} className='hover:bg-gray-50 dark:hover:bg-gray-800'>
|
||||||
<td className='px-3 sm:px-6 py-4 text-sm text-gray-900 dark:text-gray-100'>
|
<td className='px-3 sm:px-6 py-4 text-sm text-gray-900 dark:text-gray-100'>
|
||||||
<div>{source.name}</div>
|
<div>{source.name}</div>
|
||||||
<div className='sm:hidden text-xs text-gray-500 dark:text-gray-400 mt-1'>{source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform} · {source.roomId}</div>
|
<div className='sm:hidden text-xs text-gray-500 dark:text-gray-400 mt-1'>{source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform === 'douyin' ? '抖音' : source.platform} · {source.roomId}</div>
|
||||||
</td>
|
</td>
|
||||||
<td className='hidden sm:table-cell px-6 py-4 text-sm text-gray-900 dark:text-gray-100'>{source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform}</td>
|
<td className='hidden sm:table-cell px-6 py-4 text-sm text-gray-900 dark:text-gray-100'>{source.platform === 'huya' ? '虎牙' : source.platform === 'bilibili' ? '哔哩哔哩' : source.platform === 'douyin' ? '抖音' : source.platform}</td>
|
||||||
<td className='hidden sm:table-cell px-6 py-4 text-sm text-gray-900 dark:text-gray-100'>{source.roomId}</td>
|
<td className='hidden sm:table-cell px-6 py-4 text-sm text-gray-900 dark:text-gray-100'>{source.roomId}</td>
|
||||||
<td className='px-3 sm:px-6 py-4 whitespace-nowrap'>
|
<td className='px-3 sm:px-6 py-4 whitespace-nowrap'>
|
||||||
<span className={`px-2 py-1 text-xs rounded-full whitespace-nowrap ${!source.disabled ? 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300' : 'bg-red-100 dark:bg-red-900/20 text-red-800 dark:text-red-300'}`}>
|
<span className={`px-2 py-1 text-xs rounded-full whitespace-nowrap ${!source.disabled ? 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300' : 'bg-red-100 dark:bg-red-900/20 text-red-800 dark:text-red-300'}`}>
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export async function GET(request: NextRequest) {
|
|||||||
let referer = 'https://www.huya.com/';
|
let referer = 'https://www.huya.com/';
|
||||||
if (url.includes('bilivideo.com') || url.includes('bilibili.com')) {
|
if (url.includes('bilivideo.com') || url.includes('bilibili.com')) {
|
||||||
referer = 'https://live.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, {
|
const streamRes = await fetch(url, {
|
||||||
|
|||||||
@@ -97,6 +97,75 @@ async function getBilibiliStream(roomId: string) {
|
|||||||
return m3u8Url;
|
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) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url);
|
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 });
|
return NextResponse.json({ error: '不支持的平台' }, { status: 400 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ export default function WebLivePage() {
|
|||||||
if (source.platform === 'bilibili') {
|
if (source.platform === 'bilibili') {
|
||||||
return `https://live.bilibili.com/${source.roomId}`;
|
return `https://live.bilibili.com/${source.roomId}`;
|
||||||
}
|
}
|
||||||
|
if (source.platform === 'douyin') {
|
||||||
|
return `https://live.douyin.com/${source.roomId}`;
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -502,7 +505,7 @@ export default function WebLivePage() {
|
|||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<span className='text-xs text-green-700 dark:text-green-300'>筛选平台:</span>
|
<span className='text-xs text-green-700 dark:text-green-300'>筛选平台:</span>
|
||||||
<span className='text-sm font-medium text-green-800 dark:text-green-200'>
|
<span className='text-sm font-medium text-green-800 dark:text-green-200'>
|
||||||
{selectedPlatform === 'huya' ? '虎牙' : selectedPlatform === 'bilibili' ? '哔哩哔哩' : selectedPlatform}
|
{selectedPlatform === 'huya' ? '虎牙' : selectedPlatform === 'bilibili' ? '哔哩哔哩' : selectedPlatform === 'douyin' ? '抖音' : selectedPlatform}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -562,13 +565,15 @@ export default function WebLivePage() {
|
|||||||
<img src='https://hd.huya.com/favicon.ico' alt='虎牙' className='w-8 h-8' />
|
<img src='https://hd.huya.com/favicon.ico' alt='虎牙' className='w-8 h-8' />
|
||||||
) : platform === 'bilibili' ? (
|
) : platform === 'bilibili' ? (
|
||||||
<img src='https://www.bilibili.com/favicon.ico' alt='哔哩哔哩' className='w-8 h-8' />
|
<img src='https://www.bilibili.com/favicon.ico' alt='哔哩哔哩' className='w-8 h-8' />
|
||||||
|
) : platform === 'douyin' ? (
|
||||||
|
<img src='https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin_web/public/favicon.ico' alt='抖音' className='w-8 h-8' />
|
||||||
) : (
|
) : (
|
||||||
<Radio className='w-6 h-6 text-gray-500' />
|
<Radio className='w-6 h-6 text-gray-500' />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1 min-w-0 text-left'>
|
<div className='flex-1 min-w-0 text-left'>
|
||||||
<div className='text-sm font-medium text-gray-900 dark:text-gray-100 truncate'>
|
<div className='text-sm font-medium text-gray-900 dark:text-gray-100 truncate'>
|
||||||
{platform === 'huya' ? '虎牙' : platform === 'bilibili' ? '哔哩哔哩' : platform}
|
{platform === 'huya' ? '虎牙' : platform === 'bilibili' ? '哔哩哔哩' : platform === 'douyin' ? '抖音' : platform}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
<div className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||||
{sources.filter(s => s.platform === platform).length} 个房间
|
{sources.filter(s => s.platform === platform).length} 个房间
|
||||||
|
|||||||
Reference in New Issue
Block a user