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

基于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 
", 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/65151.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git推送本地仓库到远程(Gitee)

目录 一、注册创建库 二、创建仓库 三、推送本地仓库到远程 1.修改本地仓库用户名和邮箱 2.本地库关联远程仓库 3.拉取远程仓库的文件 4.推送本地库的文件 5.查看远程仓库 四、远程分支查看 1.查看远程分支 2.修改test.txt文件 一、注册创建库 Gitee官网&#xff1…

GoZero框架接入数据库引擎Gorm 并实战:构建简单的 CRUD 业务API

GoZero 是一个高性能的微服务框架&#xff0c;它基于 Go 语言开发&#xff0c;提供了丰富的工具支持&#xff0c;能够帮助开发者快速构建可扩展、易维护的应用。Gorm 是 Go 语言中常用的 ORM 库&#xff0c;它帮助我们简化数据库操作&#xff0c;使用面向对象的方式进行增删改查…

KNN分类算法 HNUST【数据分析技术】(2025)

1.理论知识 KNN&#xff08;K-Nearest Neighbor&#xff09;算法是机器学习算法中最基础、最简单的算法之一。它既能用于分类&#xff0c;也能用于回归。KNN通过测量不同特征值之间的距离来进行分类。 KNN算法的思想&#xff1a; 对于任意n维输入向量&#xff0c;分别对应于特征…

达梦数据守护搭建

主备库初始化 ./dminit path/dmdata/data db_nameDM01 instance_nameDMSVR01 port_num5236 page_size16 extent_size32 log_size500 case_sensitive1 SYSDBA_PWDDM01SYSDBA ./dminit path/dmdata/data db_nameDM02 instance_nameDMSVR02 port_num5236 page_size16 extent_size3…

探索Flink动态CEP:杭州银行的实战案例

摘要&#xff1a;本文撰写自杭州银行大数据工程师唐占峰、欧阳武林老师。将介绍 Flink 动态 CEP的定义与核心概念、应用场景、并深入探讨其技术实现并介绍使用方式。主要分为以下几个内容&#xff1a; Flink动态CEP简介 Flink动态CEP的应用场景 Flink动态CEP的技术实现 Flin…

谷歌集群数据集:负载均衡云服务测试数据

谷歌集群数据集 以下为你举例说明各文件中一条数据的具体含义,方便你更好地理解这个数据集: 1. machine_events 文件示例 假设其中有这样一条数据:123456789, 101, 0, "platform_abc", 4, 8 含义:表示在时间戳为123456789微秒时,机器ID为101的这台机器发生了…

打造高效租赁小程序让交易更便捷

内容概要 在如今节奏飞快的商业世界里&#xff0c;租赁小程序如同一只聪明的小狐狸&#xff0c;迅速突围而出&#xff0c;成为商家与消费者之间的桥梁。它不仅简化了交易流程&#xff0c;还在某种程度上将传统租赁模式带入了互联网时代。越来越多的企业意识到&#xff0c;这种…

/proc /dev /sys 目录的用途

author: hjjdebug date: 2024年 12月 25日 星期三 16:32:44 CST description: /proc /dev /sys 目录的用途 文章目录 甲: /proc目录:对module 的管理:$cat /proc/devices 乙: /dev 目录创建设备节点命令 mknod 丙: /sys 目录1. /sys/dev2. /sys/devices3. /sys/module4. /sys/c…

来道面试题——CopyOnWriteArrayList

原理 初始化时候&#xff0c;CopyOnWriteArrayList内部维护了一个可变数组&#xff0c;用于存储元素当执行数据变更操作的时候&#xff0c;会先创建一个原数组的副本&#xff0c;在副本上进行写操作&#xff0c;修改副本中的元素。写操作完成之后&#xff0c;把原数组的引用指…

Nginx整合Lua脚本

Nginx-Lua Nginx整合Lua脚本 Lua环境搭建 下载地址 linux环境下 yum install lua安装后验证 lua -vLua脚本执行 lua xxx.luaNginx整合Lua nginx需要添加lua模块 嵌入内容 示例如下 修改nginx.conf如下 location /lua {default_type text/plain;content_by_lua ngx.sa…

【MinIO系列】MinIO Client (mc) 完全指南

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Jmeter录制https请求

jmeter 5.5版本&#xff0c;chrome浏览器 1、首先添加Test Plan-Thread Group-HTTP(S) Test Script Recorder 2、设置HTTP(S) Test Script Recorder界面的Port&#xff08;监听端口&#xff0c;设置浏览器代理时需要与这里保持一致&#xff09;、HTPS Domains&#xff08;录制…

安装Visual studio2022后,没法新建.net 4.5.2的项目

在Visual Studio 2022中无法新建.NET Framework 4.5.2的项目&#xff0c;通常是因为Visual Studio 2022默认不再支持较旧的.NET Framework版本&#xff0c;如4.5或4.5.2。不过&#xff0c;你可以通过一些手动步骤来使Visual Studio 2022支持.NET Framework 4.5.2项目。以下是一…

Nginx配置:如何在一个域名下运行两个网站

在现代的Web应用开发中&#xff0c;网站的数量和复杂性越来越高&#xff0c;多个网站使用同一个域名的情况也越来越常见。通过Nginx配置&#xff0c;我们可以轻松实现两个网站共用一个域名&#xff0c;并根据特定的路径、子域名或其他规则对流量进行分发。本文将详细介绍如何使…

前端最新Vue2+Vue3基础入门到实战项目全套教程,自学前端vue就选黑马程序员,一套全通关!

Vue 快速上手 Vue概念 Vue 是一个用于构建用户界面的渐进式框架 构建用户界面&#xff1a;基于数据渲染出用户看到的页面 渐进式&#xff1a;循序渐进 框架&#xff1a;一套完整的项目解决方案 Vue 的两种使用方式: ① Vue 核心包开发 场景:局部 模块改造 ② Vue 核心包 &am…

基于Spring Boot的高校请假管理系统

一、系统背景与意义 随着高校规模的扩大和学生数量的增加&#xff0c;传统的请假管理方式已经难以满足高校管理的需求。人工请假流程繁琐、耗时长&#xff0c;且容易出现信息错误或遗漏。因此&#xff0c;开发一套基于Spring Boot的高校请假管理系统具有重要意义&#xff0c;它…

Gate.io 平台通证 GT:持续赋能与销毁、财富效应显著

在瞬息万变的加密市场中&#xff0c;每一轮牛熊转换都在加速 CEX 市场的一轮又一轮洗牌&#xff0c;这也使得该赛道的格局始终处于动态的变化。而在本轮牛市中&#xff0c;CEX 赛道也正在从最初的三大领衔变成了多强角逐&#xff0c;而 Gate.io 作为创立 11 余年的老牌交易平台…

mysql怎么返回一个字段逗号分隔后的所有数据的sql

mysql怎么返回一个字段逗号分隔后的所有数据的sql 场景描述5.7版本MySql : 完整sql如下sql解析8.0 版本MySql : 完整sql如下 场景描述 mysql有一张表比如result表,表中有个字段场景id:scene_id,这个id存储的值可以是单个的id也可以是多个id用逗号拼接起来的&#xff0c;现在需…

RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题

最近遇到RK3576 Android14编译OTA包时&#xff0c;出现如下报错log: Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/signapk/SignApk has been…

Spring Boot 中的 @Scheduled 定时任务以及开关控制

Scheduled注解是Spring框架&#xff08;包括Spring Boot&#xff09;中用于实现定时任务的一种方式。以下是对Scheduled注解的详细解析&#xff1a; 一、基本概念 Scheduled注解允许开发者在Spring容器中定义定时任务。通过简单地在一个方法上添加Scheduled注解&#xff0c;S…