当前位置:首页 » 《我的小黑屋》 » 正文

基于websocket实现本地web语音聊天

13 人参与  2024年09月24日 18:40  分类 : 《我的小黑屋》  评论

点击全文阅读


基于libwebsockets库实现语音聊天

1、关于libwebsocket库自行编译2、client使用html+js 代码3、服务端代码

1、关于libwebsocket库自行编译

2、client使用html+js 代码

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>多人语音聊天</title></head><body>    <h1>多人语音聊天</h1>    服务器地址: <input type="text" id="serverAddress" placeholder="ws://10.114.139.161:12345">    <button id="connectButton">连接</button>    <button id="startButton" disabled>开始</button>    <button id="stopButton" disabled>停止</button>    <audio id="audio" autoplay></audio>    <script>        const audio = document.getElementById('audio');        const serverAddress = document.getElementById('serverAddress');        const connectButton = document.getElementById('connectButton');        const startButton = document.getElementById('startButton');        const stopButton = document.getElementById('stopButton');        let ws = null;        let mediaRecorder;        let audioContext;        let source;let reader;let receivedBlobs = []; // 用于缓存ArrayBuffer片段        connectButton.addEventListener('click', () => {            const url = serverAddress.value;            if (url) {                ws = new WebSocket(url);                ws.onopen = () => {                    console.log('连接到服务器');                    connectButton.disabled = true;                    startButton.disabled = false;                };                ws.onmessage = (event) => {if (event.data instanceof Blob) {receivedBlobs.push(event.data)console.log('Received message length:', event.data.size);} else {const combinedBlob = new Blob(receivedBlobs, { type: 'audio/opus' });playAudio(combinedBlob);receivedBlobs.fill(null);receivedBlobs.length = 0;// 如果接收到的是其他类型的数据,可以在这里处理console.log('Received message:', event.data);}                };                ws.onclose = () => {                    console.log('与服务器断开连接');                    connectButton.disabled = false;                    startButton.disabled = true;                    stopButton.disabled = true;                    if (mediaRecorder) {                        mediaRecorder.stop();                    }                };                ws.onerror = (error) => {                    console.error('WebSocket错误:', error);                };            } else {                console.error('请输入服务器地址');            }        });        startButton.addEventListener('click', () => {            if (!ws || ws.readyState !== WebSocket.OPEN) {                console.error('WebSocket连接未打开');                return;            }            startRecording();            startButton.disabled = true;            stopButton.disabled = false;        });        stopButton.addEventListener('click', () => {            if (mediaRecorder) {                mediaRecorder.stop();            }            startButton.disabled = false;            stopButton.disabled = true;        });        function startRecording() {            navigator.mediaDevices.getUserMedia({ audio: true })                .then(stream => {                    mediaRecorder = new MediaRecorder(stream);                    mediaRecorder.ondataavailable = event => {                        if (event.data.size > 0) {                            ws.send(event.data);                        }                    };                    mediaRecorder.start();                })                .catch(error => {                    console.error('无法获取麦克风权限', error);                });        }        function playAudio(data) {            if (!audioContext) {                audioContext = new (window.AudioContext || webkitAudioContext)();reader = new FileReader();reader.onload = function(){    // 读取的ArrayBufferconst arrayBuffer = this.result;// 解码ArrayBuffer数据audioContext.decodeAudioData(arrayBuffer, function(audioData){// 创建音频缓冲区源节点const source = audioContext.createBufferSource();source.buffer = audioData;source.connect(audioContext.destination);source.start(); // 播放音频});};            }// 读取Blob为ArrayBufferreader.readAsArrayBuffer(data);  }        function mergeArrayBuffers(...arrays) {            let totalLength = arrays.reduce((acc, arrayBuffer) => acc + arrayBuffer.byteLength, 0);            let result = new Uint8Array(totalLength);            let offset = 0;            for (let arrayBuffer of arrays) {                result.set(new Uint8Array(arrayBuffer), offset);                offset += arrayBuffer.byteLength;            }            return result.buffer;        }    </script></body></html>

3、服务端代码

#include "libwebsockets.h"#include <signal.h>#include <iostream>#include <netinet/in.h>#include <sys/socket.h>#include <arpa/inet.h>#include <fstream>static volatile int exit_sig = 0;#define LOCALHOST_LEN 256#define MAX_PAYLOAD_SIZE 10 * 1024#define MAX_CLIENTS 100struct lws *active_clients[MAX_CLIENTS] = {0};int num_active_clients = 0;void sighdl(int sig){    lwsl_notice("%d traped", sig);    exit_sig = 1;}/** * 会话上下文对象,结构根据需要自定义 */struct session_data{    int msg_count;    unsigned char buf[LWS_PRE + MAX_PAYLOAD_SIZE];    int len;    bool bin;    bool fin;};static int protocol_my_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len){    struct session_data *data = (struct session_data *)user;    switch (reason)    {    case LWS_CALLBACK_ESTABLISHED: // 当服务器和客户端完成握手后    {        char ip[LOCALHOST_LEN];        lws_get_peer_simple(wsi, ip, sizeof(ip));        printf("ip: %s \n", ip);        if (num_active_clients < MAX_CLIENTS)        {            active_clients[num_active_clients++] = wsi;        }        std::cout << "ws client connect cur client num: " << num_active_clients << std::endl;        break;    }    case LWS_CALLBACK_RECEIVE: // 当接收到客户端发来的帧以后    {        // 判断是否最后一帧        bool is_last_frame = lws_is_final_fragment(wsi);        bool is_bin = lws_frame_is_binary(wsi);        std::cout << "receive msg size: " << len << " is last: " << is_last_frame << " is_bin: " << is_bin << std::endl;        for (int i = 0; i < num_active_clients; i++)        {            // 跳过发送者自身            if (active_clients[i] != wsi)            {                std::cout << "send client msg size: " << len << std::endl;                lws_write(active_clients[i], (unsigned char *)in, len, LWS_WRITE_BINARY);                if (is_last_frame)                {                    unsigned char end[] = {'e', 'n', 'd', '\0'};                    lws_write(active_clients[i], end, sizeof(end), LWS_WRITE_TEXT);                }            }        }        break;    }    case LWS_CALLBACK_CLOSED: // 当此连接可写时    {        for (int i = 0; i < num_active_clients; i++)        {            if (active_clients[i] == wsi)            {                active_clients[i] = active_clients[num_active_clients - 1];                num_active_clients--;                break;            }        }        std::cout << "ws client close cur client num: " << num_active_clients << std::endl;        break;    }    }    // 回调函数最终要返回0,否则无法创建服务器    return 0;}/** * 支持的WebSocket子协议数组 * 子协议即JavaScript客户端WebSocket(url, protocols)第2参数数组的元素 * 你需要为每种协议提供回调函数 */struct lws_protocols protocols[] = {    {        // 协议名称,协议回调,接收缓冲区大小        "ws",        protocol_my_callback,        sizeof(struct session_data),        MAX_PAYLOAD_SIZE,    },    {        NULL, NULL, 0 // 最后一个元素固定为此格式    }};int main(int agrc, char *argv[]){    // 信号处理函数    signal(SIGTERM, sighdl);    struct lws_context_creation_info ctx_info = {0};    ctx_info.port = 12345;    ctx_info.iface = nullptr;    ctx_info.protocols = protocols;    ctx_info.gid = -1;    ctx_info.uid = -1;    ctx_info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;    struct lws_context *context = lws_create_context(&ctx_info);    while (!exit_sig)    {        lws_service(context, 1000);    }    lws_context_destroy(context);    return 0;}

点击全文阅读


本文链接:http://zhangshiyu.com/post/164143.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1