【Qt聊天室客户端】消息功能--发布程序

1. 获取文件内容

主要目标是实现获取内容二进制数据的接口,主要是为后面的消息功能提供服务

具体实现

客户端发送请求

服务端处理请求,同时支持三种数据类型

客户端处理服务端的响应

2. 发送图片消息

客户端与服务端的通信约定

客户端从服务器中获取图片消息的时候的,仅返回消息中的文件ID,而不是直接包含文件内容。如果需要文件的实际内容则需要客户端进行二次请求来获取相应的数据

设计目的为了减少初始消息传输体积,从而提高传输效率。客户端和服务端通信,直接传输问价的时候,可能会影响性能,通过文件ID二次获取内容,确保消息的基本信息与文件数据分开传输,从而避免占用过多带宽

服务器和服务器之间,直接附带文件内容,而不会单独的通过文件ID请求。因为服务器的网络环境是相对稳定的,传输文件不会造成较大的性能问题

客户端界面发送图片消息实现

整体流程首先是初始化显示图片的控件,然后配置其样式。然后异步加载图片,如果图片数据没有加载那么就异步获取图片内容;如果获取了图片数据,那么就出发updateUI进行页面更新。最后根据父组件的大小在页面上进行绘制

MessageImageLabel::MessageImageLabel(const QString &fileId, const QByteArray &content, bool isLeft):fileId(fileId),content(content),isLeft(isLeft)
{this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);imageBtn = new QPushButton(this);imageBtn->setStyleSheet("QPushButton { border: none; }");if(content.isEmpty()){DataCenter* dataCenter = DataCenter::genInstance();connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageImageLabel::updateUI);dataCenter->getSingleFileAsync(fileId);}
}void MessageImageLabel::updateUI(const QString &fileId, const QByteArray &content)
{//不是当前FileIDif(this->fileId != fileId){return;}this->content = content;this->update();
}void MessageImageLabel::paintEvent(QPaintEvent *event)
{(void)event;//1.根据父控件计算图片最大宽度QObject* object = this->parent();if(!object->isWidgetType()){return;}QWidget* parent = dynamic_cast<QWidget*>(object);int width = parent->width()*0.6;//最大宽度设置为父控件的0.6倍//2.加载图片数据QImage image;if(content.isEmpty()){//图片数据为空的时候,加载默认图片QByteArray tmpContent = loadFileToByteArray(":/resource/image/image.png");image.loadFromData(tmpContent);}else{image.loadFromData(content);}//3.根据父控件宽度缩放照片int height = 0;if(image.width() > width){height = static_cast<int>(((double)image.height() / image.width()) * width);}else{width = image.width();height = image.height();}//4.将QImage转换为QPixmapQPixmap pixmap = QPixmap::fromImage(image);imageBtn->setIconSize(QSize(width,height));imageBtn->setIcon(QIcon(pixmap));//5.动态调整父组件高度parent->setFixedHeight(height + 50);//6.根据消息类型调整按钮位置if(isLeft){//左侧消息靠左显示imageBtn->setGeometry(10,0,width,height);}else{//右侧消息靠右显示int leftPos = this->width() - width -10;imageBtn->setGeometry(leftPos,0,width,height);}}

websocket推送图片消息实现

总体逻辑仍然通过按钮触发信号,然后通过服务器槽函数进行处理,向客户端推送图片消息,最后客户端对接收到的响应进行处理即可(处理响应已经在获取文件内容进行了统一处理)

3. 发送文件消息

具体实现

 点击图片按钮触发该处点击逻辑

通过客户端发送请求到服务端

在消息显示区中,将文件信息显示上去

4. 语音消息

4.1 录制音频

 具体实现

实现鼠标按下录制,松开完成录制的功能

发送语音逻辑

4.2 播放音频

具体实现

点击语音消息的时候触发该处逻辑

更新UI

4.3 语音转文字

 具体实现

补充:音频代码

#ifndef SOUNDRECORDER_H
#define SOUNDRECORDER_H#include <QObject>
#include <QStandardPaths>
#include <QFile>
#include <QAudioSource>
#include <QAudioSink>
#include <QMediaDevices>class SoundRecorder : public QObject
{Q_OBJECT
public:const QString RECORD_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpRecord.pcm";const QString PLAY_PATH = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sound/tmpPlay.pcm";public:static SoundRecorder* getInstance();//// 录制语音语音/// 开始录制void startRecord();// 停止录制void stopRecord();private:static SoundRecorder* instance;explicit SoundRecorder(QObject *parent = nullptr);QFile soundFile;QAudioSource* audioSource;//// 播放语音/
public:// 开始播放void startPlay(const QByteArray& content);// 停止播放void stopPlay();private:QAudioSink *audioSink;QMediaDevices *outputDevices;QAudioDevice outputDevice;QFile inputFile;signals:// 录制完毕后发送这个信号void soundRecordDone(const QString& path);// 播放完毕发送这个信号void soundPlayDone();};#endif // SOUNDRECORDER_H
#include "soundrecorder.h"
#include <QDir>
#include <QMediaDevices>#include "model/data.h"
#include "toast.h"/
/// 单例模式
/
SoundRecorder* SoundRecorder::instance = nullptr;SoundRecorder *SoundRecorder::getInstance()
{if (instance == nullptr) {instance = new SoundRecorder();}return instance;
}// 播放参考 https://www.cnblogs.com/tony-yang-flutter/p/16477212.html
// 录制参考 https://doc.qt.io/qt-6/qaudiosource.html
SoundRecorder::SoundRecorder(QObject *parent): QObject{parent} {// 1. 创建目录QDir soundRootPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));soundRootPath.mkdir("sound");// 2. 初始化录制模块soundFile.setFileName(RECORD_PATH);QAudioFormat inputFormat;inputFormat.setSampleRate(16000);inputFormat.setChannelCount(1);inputFormat.setSampleFormat(QAudioFormat::Int16);QAudioDevice info = QMediaDevices::defaultAudioInput();if (!info.isFormatSupported(inputFormat)) {LOG() << "录制设备, 格式不支持!";return;}audioSource = new QAudioSource(inputFormat, this);connect(audioSource, &QAudioSource::stateChanged, this, [=](QtAudio::State state) {if (state == QtAudio::StoppedState) {// 录制完毕if (audioSource->error() != QAudio::NoError) {LOG() << audioSource->error();}}});// 3. 初始化播放模块outputDevices = new QMediaDevices(this);outputDevice = outputDevices->defaultAudioOutput();QAudioFormat outputFormat;outputFormat.setSampleRate(16000);outputFormat.setChannelCount(1);outputFormat.setSampleFormat(QAudioFormat::Int16);if (!outputDevice.isFormatSupported(outputFormat)) {LOG() << "播放设备, 格式不支持";return;}audioSink = new QAudioSink(outputDevice, outputFormat);connect(audioSink, &QAudioSink::stateChanged, this, [=](QtAudio::State state) {if (state == QtAudio::IdleState) {LOG() << "IdleState";this->stopPlay();emit this->soundPlayDone();} else if (state == QAudio::ActiveState) {LOG() << "ActiveState";} else if (state == QAudio::StoppedState) {LOG() << "StoppedState";if (audioSink->error() != QtAudio::NoError) {LOG() << audioSink->error();}}});
}void SoundRecorder::startRecord() {soundFile.open( QIODevice::WriteOnly | QIODevice::Truncate );audioSource->start(&soundFile);
}void SoundRecorder::stopRecord() {audioSource->stop();soundFile.close();emit this->soundRecordDone(RECORD_PATH);
}void SoundRecorder::startPlay(const QByteArray& content) {if (content.isEmpty()) {Toast::showMessage("数据加载中, 请稍后播放");return;}// 1. 把数据写入到临时文件model::writeByteArrayToFile(PLAY_PATH, content);// 2. 播放语音inputFile.setFileName(PLAY_PATH);inputFile.open(QIODevice::ReadOnly);audioSink->start(&inputFile);
}void SoundRecorder::stopPlay() {audioSink->stop();inputFile.close();
}

5. 历史消息调整

补充之前历史消息的遗漏问题,历史消息可以显示文本消息和语音消息,其中点击文件消息可以出触发保存操作;点击语音消息可以触发播放操作 

 具体实现

调用地方在历史消息显示窗口,其中通过判断不同消息类型进行创建

图片历史消息

  • 初始化的时候,如果图片内容存在就直接显示;如果图片为空,那么就通过DataCenter请求图片数据
  • 图片数据从网络中加载完成后,通过更新界面的方法显示到界面上,同时根据窗口大小进行调整

文件历史消息

基本逻辑与图片消息相同,只是多了一个重写鼠标点击操作,点击触发另存为操作

语音历史消息

逻辑和文件消息相同,点击语音消息可以触发播放操作

 

6. 发布程序

借助Qt下的windeployqt.ext实现程序自动获得依赖文件

 release版本中添加外部依赖库

创建好的文件结构

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

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

相关文章

【MySQL 保姆级教学】事务的自动提交和手动提交(重点)--上(13)

目录 1. 什么是事务&#xff1f;2. 事务的版本支持3. 事务提交的方式3.1 事务提交方式的分类3.2 演示的准备的工作3.2.1 创建表3.2.2 MySQL的服务端和客户端3.2.3 调低事务的隔离级别 4. 手动提交4.1 手动提交的命令说明4.2 示例一4.3 示例二4.4 示例三4.5 示例四 5. 自动提交5…

Mybatis配置文件的增删改查功能

Mybatis配置文件的增删改查功能 查询—条件查询 //resources里面的org.example.mapper中的BrandMapper.xml文件 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://…

Android 项目依赖库无法找到的解决方案

目录 错误信息解析 解决方案 1. 检查依赖版本 2. 检查 Maven 仓库配置 3. 强制刷新 Gradle 缓存 4. 检查网络连接 5. 手动下载依赖 总结 相关推荐 最近&#xff0c;我在编译一个 Android 老项目时遇到了一个问题&#xff0c;错误信息显示无法找到 com.gyf.immersionba…

群控系统服务端开发模式-应用开发-前端登录接口开发

一、修改验证方法 1、修改验证器 loginRules: {username: [{required: true, trigger: blur, validator: validateUsername}],password: [{required: true, trigger: blur, validator: validatePassword}],captcha_code: [{required: true, trigger: blur, validator: validat…

java基础入门学习09-迭代器

文章目录 一、引言二、迭代器2.1 迭代器对象的创建2.2 迭代器的使用 一、引言 迭代器是设计模式的一种&#xff0c;迭代器模式提供方法来访问容器中的的元素&#xff0c;这听起来跟c语言中指针十分相似&#xff0c;其实数组访问中的指针本质上就是迭代器的一种。Iterrator对象…

深度解析:Android APP集成与拉起微信小程序开发全攻略

目录 一、背景以及功能介绍 二、Android开发示例 2.1 下载 SDK 2.2 调用接口 2.3 获取小程序原始Id 2.4 报错提示&#xff1a;bad_param 2.4.1 错误日志 2.4.2 解决方案 相关推荐 一、背景以及功能介绍 需求&#xff1a;产品经理需要APP跳转到公司的小程序(最好指定页…

Python学习26天

集合 # 定义集合 num {1, 2, 3, 4, 5} print(f"num&#xff1a;{num}\nnum数据类型为&#xff1a;{type(num)}") # 求集合中元素个数 print(f"num中元素个数为&#xff1a;{len(num)}") # 增加集合中的元素 num.add(6) print(num) # {1,2,3,4,5,6} # 删除…

python爬虫(二)爬取国家博物馆的信息

import requests from bs4 import BeautifulSoup# 起始网址 url https://www.chnmuseum.cn/zx/xingnew/index_1.shtml # 用于存储所有数据 all_data [] page 1 global_index 1 # 定义全局序号变量并初始化为1 while True:html_url requests.get(url).textif requests.get…

Android 单元测试环境配置问题 Execution failed for task ‘:mergeDebugJavaResource‘.

背景和挑战 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;AI在各行各业的应用前景被普遍看好。无论是在医疗、金融、教育&#xff0c;还是在软件开发领域&#xff0c;AI都展示出了巨大的潜力。然而&#xff0c;尽管AI能够在许多方面提供支持和提升效率&a…

无人机应用场景:石油管道巡检技术详解

无人机在石油管道巡检中的应用&#xff0c;以其高效、便捷、灵活的特点&#xff0c;为石油管道的安全管理提供了有力支持。以下是对无人机在石油管道巡检技术方面的详细解析&#xff1a; 一、无人机巡检技术的概述 无人机巡检技术是指利用无人机搭载各种传感器和检测设备&…

51c嵌入式~单片机合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12362395 一、不同的电平信号的MCU怎么通信&#xff1f; 下面这个“电平转换”电路&#xff0c;理解后令人心情愉快。电路设计其实也可以很有趣。 先说一说这个电路的用途&#xff1a;当两个MCU在不同的工作电压下工作&…

嵌入式硬件实战基础篇(一)-STM32+DAC0832 可调信号发生器-产生方波-三角波-正弦波

引言&#xff1a;本内容主要用作于学习巩固嵌入式硬件内容知识&#xff0c;用于想提升下述能力&#xff0c;针对学习STM32与DAC0832产生波形以及波形转换&#xff0c;对于硬件的降压和对于前面硬件篇的实际运用&#xff0c;针对仿真的使用&#xff0c;具体如下&#xff1a; 设…

Qt主线程把数据发给子线程,主线程会阻塞吗

演示&#xff1a; #include <QCoreApplication> #include <QThread> #include <QObject> #include <QDebug>// 子线程类 class Worker : public QObject {Q_OBJECT public slots:void processData(int data) {qDebug() << "Processing dat…

C++内存池实现

1.内存池概念 内存池就和其他的池数据&#xff08;如线程池&#xff09;结构类似&#xff0c;由程序维护一个“池”结构来管理程序使用的内存&#xff0c;然后根据需要从内存池中申请使用内存或者向内存池中释放内存&#xff0c;来达到高效管理内存的目的。 在一般的内存管理的…

STM32设计学生宿舍监测控制系统

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 随着科技的飞速发展和智能化时代的到来&#xff0c;学生宿舍的安全、舒适…

企业如何提高招聘能力?

企业如何提高招聘能力&#xff1f; 许多企业在进行招聘工作时&#xff0c;常常会遇到各种问题和挑战。尽管付出了大量的时间和精力&#xff0c;但结果却并不总是如人意。例如&#xff0c;企业可能会经历一次又一次的面试&#xff0c;却仍然找不到一个能够适应岗位要求的合适人…

大模型在蓝鲸运维体系应用——蓝鲸运维开发智能助手

本文来自腾讯蓝鲸智云社区用户: CanWay 背景 1、运维转型背景 蓝鲸平台从诞生之初&#xff0c;就一直在不遗余力地推动运维转型&#xff0c;让运维团队可以通过一体化PaaS平台&#xff0c;快速编写脚本&#xff0c;编排流程&#xff0c;开发运维工具&#xff0c;从被动地提供…

3588 yolov8 onnx 量化转 rknn 并运行

本教程重点不在如何训练模型&#xff0c;重点是全流程链路&#xff0c;想学训练的可以网上找教程 环境 python 3.10.xrknn-toolkit2-2.2.0ultralytics_yolov8rknn 驱动版本2.2 模型训练 yolov8仓库地址&#xff1a;https://github.com/airockchip/ultralytics_yolov8.git下载…

Vue 组件通信及进阶语法

文章目录 一、scoped 样式冲突二、data 是一个函数三、组件通信1. 父子通信1.1 props 校验1.2 props 比较 data 2. 非父子通信2.1 event bus2.2 provide-inject 四、进阶语法1. v-model 详解2. sync 修饰符3. ref 和 $refs4. $nextTick 一、scoped 样式冲突 注意点&#xff1a;…

LeetCode105.从前序与中序遍历构造二叉树

题目要求 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 提示: 1 < preorder.length < 3000inorder.length preorder.length-3000 < pr…