获取抖音弹幕信息,将其集成在uniapp项目中。使用WebSocket实现弹幕信息实时更新。并且减少服务器多次请求压力

这是一个网上开源的代码。第一次接触这个项目还是有点懵的,boss丢这个项目给后端研究,结果后端将这个项目让我前端来实现。

谁叫我们没有拒绝的能力呢所以开启了一段坎坷之路将这个项目集成到uniapp项目上。首先讲一下这个抖音收集信息弹幕的实现 思路吧。

1、计算抖音弹幕的`wss`链接

进入抖音直播间,打开浏览器的网络请求面板,可看到有一个`ws`链接,其则为抖音直播间实时弹幕通信链接观察ws链接携带参数`signature`,

其需通过`roomId`与`uniqueId`计算得出,通过断点跟随,执行一些逆向工程,即可知道其大致的计算原理

出`signature`参数后,再将其与前面的`roomId`与`uniqueId`整合,即可得到完整的`wss`链接
2、解析弹幕数据=》
成功建立链接后,会发现接收到的数据为二进制串,其运用技术为`protobuf`协议传输,要解析,需对应的`proto`文件,
相应的`proto`文件可通过进入直播间,通过一些逆向工程,模仿其背后解析的对象结构,整合出相应的`proto`文件。
3、有了解析文件后,即可使用其解析弹幕数据。将`ws`获取到的一帧数据解析为`PushFrame`,其中的`payload`依旧为一段二进制数据,且经过了`gzip`压缩,对其进行解压后,解析为`Response`,
其中的`MessagesList`即为对应的消息数据,结构为`Messages`类型
本次分享,主要是为了各位大神有过相关经验解决个问题。在h5端可以发送websocket。在手机模拟运行和小程序,显示连接成功了,但是数据发送不成功。
话不多说直接上代码。
复制代码
/**
 * 封装WebSocket客户端
 */
import "@/static/js/model.js"//propo文件
import pako from 'pako';
export class Client {
    constructor(wsUrl) {// if (typeof WebSocket !== "undefined") {
        this.socket = uni.connectSocket({
            url: wsUrl,
            success: (res) => {
                console.log('连接成功')
            },
            fail: (res) => {
                console.log('失败了')
            }
        })
        console.log(this.socket, "8785555")
        if (this.socket) {
            this.socket.binaryType = "arraybuffer";
            this.socket = this.socket._webSocket//在这里看了uniapp端返回的需要数据放在_websocket对象里才做这部操作
        }
        // }
    }
    onError(cb) {
        this.socket?.addEventListener("error", cb);
    }
    onMessage(cb) {
        this.socket?.addEventListener("message", cb);
    }
    onOpen(cb) {
        this.socket?.addEventListener("open", cb);
    }
    onClose(cb) {
        this.socket?.addEventListener("close", cb);
    }
    /**
     * 向服务端发送消息
     * @param data 数据
     */
    send(data) {
        // console.log(data, "向后端发送消息")
        this.socket?.send(data);
    }
    ready() {
        return this.socket?.readyState;
    }
}

/**
 * 抖音弹幕客户端
 */


export class DyClient {
    constructor() {
        this.client = undefined;
        this.wsUrl = "";
        this.pingStarted = false;
        this.pingTimer = undefined;

        this.heartbeatDuration = 0;

        this.onOpen = undefined;
        this.onClose = undefined;
        this.onError = undefined;
        this.onMessage = undefined;

        this.accept = undefined;
        this.onOff = undefined;
    }

     init(wsUrl) {
        this.wsUrl = wsUrl; 
        console.log('进行链接')
        this.client =  new Client(wsUrl);
        console.log(this.client, '对象')
        this.client.onOpen(this.onOpen ? this.onOpen : () => {
            console.log('连接了')
            this.pingStarted = true;
            this.ping();
            this.onOff && this.onOff(true);
        });
        this.client.onClose(this.onClose ? this.onClose : (e) => {
            if (this.pingStarted) {
                console.log("socket close ......", e.type);
                this.pingStarted = false;
            }
            this.onOff && this.onOff(false);
        });
        this.client.onError(this.onError ? this.onError : (e) => {
            this.pingStarted = false;
            console.error("[socket error]: ", e);
            this.onOff && this.onOff(false);
        });
//对返回的数据进行解析
this.client.onMessage(this.onMessage ? this.onMessage : (e) => { if (this.client) { const frame = proto.PushFrame.deserializeBinary(e.data); let headers = frame.getHeaderslistMap().map_; let gzipFlag = false; for (const t of Object.values(headers)) { if ("compress_type" === t.key && "gzip" === t?.value) { gzipFlag = true; } } let payload = gzipFlag ? pako.inflate(frame.getPayload()) : frame.getPayload_asU8(); const res = proto.Response.deserializeBinary(payload); if (res.getNeedack()) { let ext = res.getInternalext(); const sf = new proto.PushFrame(); sf.setPayloadtype("ack"); sf.setPayload((function(e) { const t = []; for (const i of e) { const e = i.charCodeAt(0); if (e < 128) t.push(e); else if (e < 2048) { t.push(192 + (e >> 6)); t.push(128 + (63 & e)); } else if (e < 65536) { t.push(224 + (e >> 12)); t.push(128 + (e >> 6 & 63)); t.push(128 + (63 & e)); } } return Uint8Array.from(t); })(ext)); sf.setLogid(frame.getLogid()); this.client.send(sf.serializeBinary()); } res.getMessagesList().forEach((message) => { this.accept && this.accept(message); }); } }); } ping() { const t = Math.max(10000, Number(this.heartbeatDuration)); if (this.client && this.client.ready() === WebSocket.OPEN) { const frame = new proto.PushFrame(); frame.setPayloadtype("hb"); this.client.send(frame.serializeBinary()); } this.pingTimer = setTimeout(() => { if (this.pingStarted) this.ping(); }, t); } }
复制代码

这张图片是获取ws链接的签名的。有相关一样源码的朋友可以直接这么改。这个项目不是个人的所以不好分享出来给大家,所以在本次分享只分享个人心得

复制代码
import {getSign} from "../../static/js/signature.js"//
import { Client, DyClient, handleMessage } from "@/utils/websocket.js"; // 根据实际路径调整
在这里我只模拟数据,拿到roomid和uniquerid在getSign这个方法拿到签名然后可以去进行ws链接得到数据然后进行相关解析得到弹幕。

let sign = getSign(7265492128854231848, 7265502860011144764)['X-MS-STUB'];//签名
let roomId='7265492128854231848'
let uniqueId='7265502860011144764'
let now = Date.now();

//ws链接
let wsUrl =
`wss://webcast3-ws-web-hl.douyin.com/
webcast/im/push/v2/?app_name=douyin_web&
version_code=180800&webcast_sdk_version=1.3.0&
update_version_code=1.3.0&
compress=gzip&internal_ext=internal_src:dim|wss_push_room_id:${roomId}|wss_push_did:
${uniqueId}|fetch_time:${now}|seq:1|wss_info:0-${now}-0-0&
cursor=t-${now}_r-1_d-1_u-1_h-1&
host=https://live.douyin.com&aid=6383&live_id=1&did_rule=3&debug=false&
maxCacheMessageNumber=20&endpoint=live_pc&support_wrds=1&
im_path=/webcast/im/fetch/&user_unique_id=${uniqueId}&
device_platform=web&cookie_enabled=true&
screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&
browser_name=Mozilla&browser_version=5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/111.0.0.0%20Safari/537.36%20Edg/111.0.1661.62&
browser_online=true&tz_name=Asia/Shanghai&
identity=audience&room_id=${roomId}&heartbeatDuration=0&signature=${sign}`;

 

//创建websocket实例
this.dyClient = new DyClient();
this.dyClient.init(wsUrl);//调用init方法
this.dyClient.accept = (message) => {//数据解析
if (message) {
let m = handleMessage(message);
console.log(m,"2222");

// handleChat(m);
// renewPos();
// relayMess(m);
}
};

整个过程过程差不多是这样,有问题的友友可以评论。在次也问一下有相关经验的大佬询问一下在手机模拟的情况下显示连接websocket成功,进行这一步

this.client = new Client(wsUrl);
console.log(this.client, '对象')对象为空。

复制代码
posted @   圣迭戈  阅读(3934)  评论(5编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
点击右上角即可分享
微信分享提示