@bililive-tools/huya-recorder
Version:
bililive-tools huya recorder implemention
168 lines (167 loc) • 5.98 kB
JavaScript
import { sortBy } from "lodash-es";
import { HuYaQualities } from "@bililive-tools/manager";
import { getRoomInfo as getRoomInfoByWeb } from "./huya_api.js";
import { getRoomInfo as getRoomInfoByMobile } from "./huya_mobile_api.js";
import { getCdnTokenInfoEx } from "./huya_wup_api.js";
import { assert } from "./utils.js";
export async function getInfo(channelId) {
const info = await getRoomInfoByWeb(channelId);
const recordStartTime = new Date();
return {
living: info.living,
owner: info.owner,
title: info.title,
avatar: info.avatar,
cover: info.cover,
roomId: info.roomId,
liveStartTime: info.startTime,
liveId: info.liveId,
recordStartTime: recordStartTime,
};
}
async function getRoomInfo(channelId, options) {
if (options.api == "auto") {
const info = await getRoomInfoByWeb(channelId, {
formatPriorities: options.formatPriorities,
quality: options.quality,
});
if (info.gid == 1663 || info.gid == 1) {
// 1663=星秀区,1=英雄联盟区
// return getRoomInfoByMobile(channelId, options.formatPriorities);
return getRoomInfo(channelId, {
api: "wup",
formatPriorities: options.formatPriorities,
quality: options.quality,
});
}
return info;
}
else if (options.api == "mp") {
return getRoomInfoByMobile(channelId, options.formatPriorities);
}
else if (options.api == "web") {
return getRoomInfoByWeb(channelId, {
formatPriorities: options.formatPriorities,
quality: options.quality,
});
}
else if (options.api == "wup") {
// 参数与web一致,之后anticode有额外处理
const info = await getRoomInfoByWeb(channelId, {
formatPriorities: options.formatPriorities,
quality: options.quality,
});
return { ...info, api: "wup" };
}
assert(false, "Invalid api");
}
export async function getStream(opts) {
const info = await getRoomInfo(opts.channelId, {
api: opts.api ?? "auto",
formatPriorities: opts.formatPriorities ?? ["flv", "hls"],
quality: opts.quality,
});
if (!info.living) {
throw new Error("It must be called getStream when living");
}
if (info.streams.length === 0) {
throw new Error(`No stream found in huya ${opts.channelId} room`);
}
const qn = (HuYaQualities.includes(opts.quality) ? opts.quality : 0);
let expectStream = info.streams.find((stream) => stream.bitRate === qn);
if (!expectStream && opts.strictQuality) {
throw new Error("Can not get expect quality because of strictQuality");
}
if (!expectStream) {
expectStream = info.streams[0];
}
if (!expectStream) {
throw new Error("未找到对应的流");
}
let expectSource = null;
const sourcesWithPriority = sortAndFilterSourcesByPriority(info.sources, opts.sourcePriorities);
if (sourcesWithPriority.length > 0) {
expectSource = sourcesWithPriority[0];
}
else {
expectSource = info.sources.find((source) => source.name === "TX") ?? null;
if (!expectSource) {
expectSource = info.sources[0];
}
if (expectSource.name === "TX") {
expectSource.url = expectSource.url
.replace("&ctype=tars_mp", "&ctype=huya_webh5")
.replace("&fs=bhct", "&fs=bgct");
}
}
let url = expectSource.url;
let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36";
if (info.api === "wup") {
try {
const res = await getCdnTokenInfoEx(expectSource.streamName);
let urlObj = new URL(url);
urlObj.search = `?${res.sFlvToken}`;
let newUrl = urlObj.toString();
ua = res.ua;
if (!newUrl.includes("codec=")) {
newUrl += "&codec=264";
}
url = newUrl;
}
catch (e) {
console.warn("Get stream url by WUP failed, fallback to original url", e);
}
}
// MP协议下原画不需要添加ratio参数
if (expectStream.bitRate && expectStream.bitRate !== -1 && !url.includes("ratio=")) {
url = url + "&ratio=" + expectStream.bitRate;
}
return {
...info,
currentStream: {
ua: ua,
name: expectStream.desc,
source: expectSource.name,
uid: expectSource.presenterUid,
subChannelId: expectSource.subChannelId,
channelId: expectSource.channelId,
url,
},
};
}
// /**
// * 按提供的流优先级去给流列表排序,并过滤掉不在优先级配置中的流
// */
// function sortAndFilterStreamsByPriority(
// streams: StreamProfile[],
// streamPriorities: Recorder["streamPriorities"],
// ): (StreamProfile & {
// priority: number;
// })[] {
// if (streamPriorities.length === 0) return [];
// return sortBy(
// // 分配优先级属性,数字越大优先级越高
// streams
// .map((stream) => ({
// ...stream,
// priority: streamPriorities.toReversed().indexOf(stream.desc),
// }))
// .filter(({ priority }) => priority !== -1),
// "priority",
// );
// }
/**
* 按提供的源优先级去给源列表排序,并过滤掉不在优先级配置中的源
*/
function sortAndFilterSourcesByPriority(sources, sourcePriorities) {
if (sourcePriorities.length === 0)
return [];
return sortBy(
// 分配优先级属性,数字越大优先级越高
sources
.map((source) => ({
...source,
priority: sourcePriorities.toReversed().indexOf(source.name),
}))
.filter(({ priority }) => priority !== -1), "priority");
}