网络直播增加bilibili

This commit is contained in:
mtvpls
2026-01-20 20:17:06 +08:00
parent 7ef9737cb6
commit 30a89d878d
4 changed files with 205 additions and 11 deletions

View File

@@ -1,5 +1,41 @@
import { NextRequest, NextResponse } from 'next/server';
function getBaseUrl(url: string): string {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/');
pathParts.pop();
return `${urlObj.protocol}//${urlObj.host}${pathParts.join('/')}`;
}
function processM3u8Content(content: string, baseUrl: string): string {
const lines = content.split('\n');
const processedLines = lines.map(line => {
const trimmedLine = line.trim();
// 跳过注释行和空行
if (trimmedLine.startsWith('#') || trimmedLine === '') {
return line;
}
// 如果已经是完整URL不处理
if (trimmedLine.startsWith('http://') || trimmedLine.startsWith('https://')) {
return line;
}
// 处理相对路径
if (trimmedLine.startsWith('/')) {
// 绝对路径(相对于域名)
const urlObj = new URL(baseUrl);
return `${urlObj.protocol}//${urlObj.host}${trimmedLine}`;
} else {
// 相对路径
return `${baseUrl}/${trimmedLine}`;
}
});
return processedLines.join('\n');
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
@@ -9,10 +45,16 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: '缺少URL参数' }, { status: 400 });
}
// 根据URL判断Referer
let referer = 'https://www.huya.com/';
if (url.includes('bilivideo.com') || url.includes('bilibili.com')) {
referer = 'https://live.bilibili.com/';
}
const streamRes = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.huya.com/'
'Referer': referer
}
});
@@ -20,9 +62,32 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: '无法获取直播流' }, { status: 404 });
}
const contentType = streamRes.headers.get('Content-Type') || '';
// 检测是否为m3u8文件
const isM3u8 = url.endsWith('.m3u8') ||
contentType.includes('application/vnd.apple.mpegurl') ||
contentType.includes('application/x-mpegURL');
if (isM3u8) {
// 读取m3u8内容
const content = await streamRes.text();
const baseUrl = getBaseUrl(url);
const processedContent = processM3u8Content(content, baseUrl);
return new NextResponse(processedContent, {
headers: {
'Content-Type': 'application/vnd.apple.mpegurl',
'Cache-Control': 'no-cache',
'Access-Control-Allow-Origin': '*'
}
});
}
// 非m3u8文件直接返回流
return new NextResponse(streamRes.body, {
headers: {
'Content-Type': streamRes.headers.get('Content-Type') || 'application/octet-stream',
'Content-Type': contentType || 'application/octet-stream',
'Cache-Control': 'no-cache',
'Access-Control-Allow-Origin': '*'
}

View File

@@ -24,6 +24,79 @@ function getAntiCode(oldAntiCode: string, streamName: string): string {
return `wsSecret=${wsSecretMd5}&wsTime=${wsTime}&seqid=${seqId}&ctype=${urlQuery.get('ctype')}&ver=1&fs=${urlQuery.get('fs')}&uuid=${initUuid}&u=${uid}&t=${paramsT}&sv=${sdkVersion}&sdk_sid=${sdkSid}&codec=264`;
}
async function getBilibiliStream(roomId: string) {
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Referer': 'https://live.bilibili.com/'
};
// 获取房间初始化信息
const roomInitRes = await fetch(`https://api.live.bilibili.com/room/v1/Room/room_init?id=${roomId}`, {
headers
});
const roomInitData = await roomInitRes.json();
if (roomInitData.code !== 0) {
throw new Error(roomInitData.message || '获取房间信息失败');
}
const roomData = roomInitData.data;
const realRoomId = roomData.room_id;
const liveStatus = roomData.live_status; // 0=未开播, 1=直播中, 2=轮播
if (liveStatus !== 1) {
throw new Error('直播未开启');
}
// 获取播放地址 (原画质量 qn=10000)
const playInfoRes = await fetch(
`https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?room_id=${realRoomId}&protocol=0,1&format=0,1,2&codec=0,1&qn=10000&platform=web&ptype=8`,
{ headers }
);
const playInfoData = await playInfoRes.json();
if (playInfoData.code !== 0) {
throw new Error(playInfoData.message || '获取播放信息失败');
}
const playurl = playInfoData.data?.playurl_info?.playurl;
if (!playurl) {
throw new Error('未找到播放地址');
}
// 提取m3u8地址
let m3u8Url = '';
const streamList = playurl.stream || [];
for (const stream of streamList) {
const formatList = stream.format || [];
for (const fmt of formatList) {
if (fmt.format_name === 'ts') {
const codecList = fmt.codec || [];
for (const codec of codecList) {
const urlInfoList = codec.url_info || [];
const baseUrl = codec.base_url || '';
if (urlInfoList.length > 0 && baseUrl) {
const host = urlInfoList[0].host || '';
const extra = urlInfoList[0].extra || '';
m3u8Url = `${host}${baseUrl}${extra}`;
break;
}
}
if (m3u8Url) break;
}
}
if (m3u8Url) break;
}
if (!m3u8Url) {
throw new Error('未找到m3u8地址');
}
return m3u8Url;
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
@@ -65,6 +138,16 @@ export async function GET(request: NextRequest) {
});
}
if (platform === 'bilibili') {
const streamUrl = await getBilibiliStream(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(