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

基于libwebsockets库实现语音聊天

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(){
				    // 读取的ArrayBuffer
					const arrayBuffer = this.result;
					// 解码ArrayBuffer数据
					audioContext.decodeAudioData(arrayBuffer, function(audioData){
						// 创建音频缓冲区源节点
						const source = audioContext.createBufferSource();
						source.buffer = audioData;
						source.connect(audioContext.destination);
						source.start(); // 播放音频
					});
				};
            }
			
			// 读取Blob为ArrayBuffer
			reader.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 100

struct 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 
", 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', ''};
                    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;
}
相关推荐
IT、木易4 分钟前
JavaScript如何判断一个变量是否为数组的多种方法及原理,除Array.isArray()外还有哪些方式?
开发语言·前端·javascript
几度泥的菜花27 分钟前
使用 Promise 和 .then() 解决同异步问题
前端·javascript
CryptoRzz27 分钟前
对接股票金融数据源API
网络·python·websocket·网络协议·金融
沉默的煎蛋33 分钟前
深入理解 TCP 三次握手与四次挥手
java·网络·数据结构·网络协议·tcp/ip
TSINGSEE34 分钟前
EasyRTC嵌入式音视频通话SDK:微信生态支持、轻量化架构与跨平台兼容性(Linix/Windows/ARM/Android/iOS/LiteOS)
arm开发·网络协议·微信·架构·音视频·webrtc·智能硬件
云说智树1 小时前
工业物联网的“边缘革命”:研华IoT Edge 设备联网与边缘计算的突破与实践
前端·物联网·edge
*goliter *1 小时前
css重点知识汇总(一)
前端·css
赵啸林1 小时前
npm 安装 pnpm 的详细步骤及注意事项
前端·npm·node.js
喆星时瑜1 小时前
npm 命令使用文档
前端·npm·node.js
网硕互联的小客服1 小时前
如何启用 HTTPS 并配置免费的 SSL 证书
网络协议·https·ssl