@bililive-tools/douyin-recorder
Version:
@bililive-tools douyin recorder implemention
115 lines (114 loc) • 4.12 kB
JavaScript
import axios from "axios";
import { assert } from "./utils.js";
const requester = axios.create({
timeout: 10e3,
// axios 会自动读取环境变量中的 http_proxy 和 https_proxy 并应用,这会让请求发往代理的 host。
// 所以这里需要主动禁用代理功能。
proxy: false,
});
let cookieCache;
export const getCookie = async () => {
const now = new Date().getTime();
// 缓存24小时
if (cookieCache?.startTimestamp && now - cookieCache.startTimestamp < 24 * 60 * 60 * 1000) {
return cookieCache.cookies;
}
const res = await requester.get("https://live.douyin.com/");
if (!res.headers["set-cookie"]) {
throw new Error("No cookie in response");
}
const cookies = (res.headers["set-cookie"] ?? [])
.map((cookie) => {
return cookie.split(";")[0];
})
.join("; ");
cookieCache = {
startTimestamp: now,
cookies,
};
return cookies;
};
export async function getRoomInfo(webRoomId, retryOnSpecialCode = true) {
// 抖音的 'webcast/room/web/enter' api 会需要 ttwid 的 cookie,这个 cookie 是由这个请求的响应头设置的,
// 所以在这里请求一次自动设置。
const cookies = await getCookie();
const res = await requester.get("https://live.douyin.com/webcast/room/web/enter/", {
params: {
aid: 6383,
live_id: 1,
device_platform: "web",
language: "zh-CN",
enter_from: "web_live",
cookie_enabled: "true",
screen_width: 1920,
screen_height: 1080,
browser_language: "zh-CN",
browser_platform: "MacIntel",
browser_name: "Chrome",
browser_version: "108.0.0.0",
web_rid: webRoomId,
// enter_source:,
"Room-Enter-User-Login-Ab": 0,
is_need_double_stream: "false",
},
headers: {
cookie: cookies,
},
});
// console.log(JSON.stringify(res.data, null, 2));
// 无 cookie 时 code 为 10037
if (res.data.status_code === 10037 && retryOnSpecialCode) {
// resp 自动设置 cookie
// const cookieRes = await requester.get("https://live.douyin.com/favicon.ico");
// const cookies = cookieRes.headers["set-cookie"]
// .map((cookie) => {
// return cookie.split(";")[0];
// })
// .join("; ");
// console.log("cookies", cookies);
return getRoomInfo(webRoomId, false);
}
assert(res.data.status_code === 0, `Unexpected resp, code ${res.data.status_code}, msg ${res.data.data}, id ${webRoomId}`);
const data = res.data.data;
const room = data.data[0];
assert(room, `No room data, id ${webRoomId}`);
if (room?.stream_url == null) {
return {
living: false,
roomId: webRoomId,
owner: data.user.nickname,
title: room?.title ?? data.user.nickname,
streams: [],
sources: [],
avatar: data.user?.avatar_thumb?.url_list?.[0],
cover: room.cover?.url_list?.[0],
liveId: room.id_str,
};
}
const { options: { qualities }, stream_data, } = room.stream_url.live_core_sdk_data.pull_data;
const streamData = JSON.parse(stream_data).data;
const streams = qualities.map((info) => ({
desc: info.name,
key: info.sdk_key,
bitRate: info.v_bit_rate,
}));
// 看起来抖音是自动切换 cdn 的,所以这里固定返回一个默认的 source。
const sources = [
{
name: "自动",
streamMap: streamData,
},
];
return {
living: data.room_status === 0,
// 接口里不会再返回 web room id,只能直接用入参原路返回了。
roomId: webRoomId,
owner: data.user.nickname,
title: room.title,
streams,
sources,
avatar: data.user.avatar_thumb.url_list[0],
cover: room.cover?.url_list?.[0],
liveId: room.id_str,
};
}