【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)
{
    //不是当前FileID
    if(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转换为QPixmap
    QPixmap 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版本中添加外部依赖库

创建好的文件结构

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ab (Apache Bench)的使用

Apache Bench&#xff08;ab&#xff09;是一个用于基准测试HTTP Web服务器的命令行工具&#xff0c;广泛用于评估和优化Web服务器的性能。以下是关于Apache Bench的详细介绍&#xff0c;包括其功能、使用方法、常用参数和输出结果解析。 功能 性能测试&#xff1a;通过模拟多…

【HarmonyNext】显示提示文字的方法

【HarmonyNext】显示提示文字的方法 本文介绍在 HarmonyNext 中显示提示文字的两种常见方法&#xff1a;使用自定义弹窗 CustomDialog 和使用 promptAction 的 showToast 方法。 一、使用自定义弹窗 CustomDialog 在 HarmonyNext 中&#xff0c;自定义弹窗是实现复杂提示信…

第三十一天|贪心算法| 56. 合并区间,738.单调递增的数字 , 968.监控二叉树

目录 56. 合并区间 方法1&#xff1a;fff 看方法2&#xff1a;fff优化版 方法3&#xff1a; 738.单调递增的数字 968.监控二叉树&#xff08;贪心二叉树&#xff09; 56. 合并区间 判断重叠区间问题&#xff0c;与452和435是一个套路 方法1&#xff1a;fff 看方法2&am…

【自用】0-1背包问题与完全背包问题的Java实现

引言 背包问题是计算机科学领域的一个经典优化问题&#xff0c;分为多种类型&#xff0c;其中最常见的是0-1背包问题和完全背包问题。这两种问题的核心在于如何在有限的空间内最大化收益&#xff0c;但它们之间存在一些关键的区别&#xff1a;0-1背包问题允许每个物品只能选择…

【Unity】ScriptableObject的应用:利用配方合成新物体

前一篇已经使用ScriptableObject(SO)类配置可放置物体&#xff0c;本篇探索更多的SO类应用场景。 需求分析 将若干指定物体放在工作台上&#xff0c;可以生成新的物体。 成果展示 Scene部分 准备工作台&#xff0c;放在工作台上的物体全部放在指定PlacedObjects空物体下。 …

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

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

TofuAI处理BT1120时序视频要求

时序要求 BT.1120视频用于1920x108030Hz数字视频输入。具体时序必须严格按照说明。BT.1120输入电平为1.8V。 BT1120数字视频采用YCbCr彩色格式输出&#xff0c;串行数据位宽为16bit&#xff0c;亮度在 高8bit&#xff0c;色度在低8bit&#xff0c;亮度和色度在同一个时钟周期输…

C++内存池实现

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

设计模式-参考的雷丰阳老师直播课

一般开发中使用的模式为模版模式策略模式组合&#xff0c;模版用来定义骨架&#xff0c;策略用来实现细节。 模版模式 策略模式 与模版模式特别像&#xff0c;模版模式会定义好步骤定义好框架&#xff0c;策略模式定义小细节 入口类 使用模版模式策略模式开发支付 以上使用…

Vue3打包自动生成版本JSON文件,添加系统版本检查,实现系统自动更新提示

实现该功能一共有三步。废话不多说&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; 第一步&#xff1a;打包时自动生成版本信息的js文件&#xff0c;versionUpdate.js import fs from fs; import path from path; import { ElMessageBox } from element-plus; i…

华为云前台展示公网访问需要购买EIP,EIP流量走向

华为云前台网络&#xff08;VPC,安全组&#xff0c;EIP&#xff09; 1.EIP网段是从哪里划分的&#xff1f; 管理员在后台Service_OM已设置 Service_OM-网络资源-外部网络-创建外部网络基本信息&#xff1a;配置参数&#xff1a;*名称 public*网络类型 LOCAL 不带标签 类似开…

[Mysql基础] 表的操作

一、创建表 1.1 语法 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 说明&#xff1a; field 表示列名 datatype 表示列的类型 character set 字符集&#xff0c;如果没有指定字符集…

ASP.NET MVC宠物商城系统

该系统采用B/S架构&#xff0c;使用C#编程语言进行开发&#xff0c;以ASP.NET MVC框架为基础&#xff0c;以Visual Studio 2019为开发工具&#xff0c;数据库采用SQL Server进行保存数据。系统主要功能包括登录注册、宠物展示、个人中心、我的订单、购物车、用户管理、宠物类别…

ArkTS学习笔记:ArkTS起步

ArkTS是HarmonyOS的主力应用开发语言&#xff0c;基于TypeScript扩展&#xff0c;强化了静态检查和分析&#xff0c;旨在提升程序稳定性和性能。它采用静态类型&#xff0c;禁止运行时改变对象布局&#xff0c;并对UI开发框架能力进行扩展&#xff0c;支持声明式UI描述和自定义…

Tofu AI视频处理模块视频输入配置方法

应用Tofu产品对网络视频进行获取做视频处理时&#xff0c;首先需要配置Tofu产品的硬件连接关系与设备IP地址、视频拉流地址。 步骤1 Tofu设备点对点直连或者通过交换机连接到电脑&#xff0c;电脑IP配置到与Tofu默认IP地址同一个网段。 打开软件 点击右上角系统设置 单击左侧…

stream学习

Stream流 定义 Steam流&#xff0c;用于操作集合或者数组中的数据&#xff0c;大量结合了Lamda表达式的语法风格&#xff0c;代码简洁。 重点&#xff1a; 流只能收集一次 ​ 获取Stream流 Stream流要与数据源建立连接。 1.list ​ 直接调用steam()即可 // list List<Stri…

鸿蒙动画开发06——打断动画

1、前 言 UI界面除了运行动画之外&#xff0c;还承载着与用户进行实时交互的功能。当用户行为根据意图变化发生改变时&#xff0c;UI界面应做到即时响应。 例如用户在应用启动过程中&#xff0c;上滑退出&#xff0c;那么启动动画应该立即过渡到退出动画&#xff0c;而不应该…

运算放大器的学习(一)输入阻抗

输入阻抗 最近需要对运算放大器进行学习&#xff0c;我们后面逐一对其参数进行了解。 首先了解下输入阻抗。 放大电路技术指标测试示意图&#xff1a; 输入电阻&#xff1a; 从放大电路的输入端看进去的等效电阻称为放大电路的输入电阻&#xff0c;如上图&#xff0c;此处考虑…

【测试框架篇】单元测试框架pytest(1):环境安装和配置

一、pytest简介 Pytest是Python的一种单元测试框架&#xff0c;与Python自带的unittest测试框架类似&#xff0c;但是比 unittest框架使用起来更简洁&#xff0c;效率更高。 二、pytest特点 Pytest是一个非常成熟的Python测试框架,主要特点有以下几点&#xff1a; 非常容易…

蓝桥杯竞赛单片机组备赛【经验帖】

本人获奖情况说明 笔者本人曾参加过两次蓝桥杯电子赛&#xff0c;在第十二届蓝桥杯大赛单片机设计与开发组获得省级一等奖和国家级二等奖&#xff0c;在第十五届嵌入式设计开发组获得省级二等奖。如果跟着本帖的流程备赛&#xff0c;只要认真勤奋&#xff0c;拿个省二绝对没问…