Qt视频直播软件--项目实战(Day7)

第七天项目日记

1、今日总结

完成有关弹幕的相关内容的开发
用户发送弹幕之后,开播的人显示弹幕,观看直播的人也显示弹幕
自己发的弹幕用绿色的框框起来
别人发的弹幕直接显示

2、设计思路

参考连接.
对于弹幕的实现,这里使用了定时器,弹幕的队列用vector显示,然后vector中存入Qlabel类,发送或者接收到弹幕之后就给vector中添加对应的label
对于弹幕的移动用QTimer 每20ms移动一次移动到边界之后自动消失
弹幕的长度根据字符串来自适应,弹幕移动到边界之后自动消失并且清除缓存

弹幕的发送流程,对于加入直播间的客户端,服务器端进行记录加入的房间号,对于开播的直播间来说,记录开播的房间号。

发送弹幕之后,客户端发送消息给服务器,服务器转发弹幕消息,转发给对于房间号开播的客户端以及正在观看该直播间的用户。
客户端收到消息之后,随机现在弹幕的位置在视频界面。
(后面有时间会整理一个流程图出来)

3、代码说明

服务器

服务器主要是对消息的接收和发送的修改

首先是加入直播间和离开直播间
在这里插入图片描述
离开直播间只需要清空自定义tcpsocket的类的加入直播间的房间名称即可
在这里插入图片描述
然后是弹幕消息的格式

在这里插入图片描述

主要修改以上四个文件

message.h

在这里插入图片描述
增加了三种消息

myjson.h

消息的打包和解析

#ifndef MYJSON_H
#define MYJSON_H

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QDebug>
#include <QJsonArray>
#include <QByteArray>

class MyJson
{
public:
    MyJson();
    //解析用户名和密码
    QString name_pswd_info(QString message,QString &pswd,QString &name);
    //打包直播刷新消息
    QString  pack_live_flush(QString action,QStringList name_list);
    //解析加入直播json
    QString join_info(QString message,QString &room_name);
    //解析弹幕消息
    QString barrage_info(QString message,QString &from, QString &room_name, QString &desc);
    //打包返回的弹幕消息
    QString pack_ret_barrage_info(QString from, QString desc);
};

#endif // MYJSON_H

myjson.cpp
#include "myjson.h"

MyJson::MyJson()
{

}

QString MyJson::name_pswd_info(QString message, QString &pswd, QString &name)
{
    QByteArray bytes = message.toUtf8();
    QJsonParseError jsonError;
    QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
    if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {  // 解析未发生错误
        if (doucment.isObject()){
            QJsonObject object = doucment.object();  // 转化为对象
            if (object.contains("Name")) {
                QJsonValue value = object.value("Name");
                if (value.isString()) {
                    name = value.toString();
                    qDebug() << "Name : " << name;
                }
            }else{
                return "json_error_no_name";
            }
            if (object.contains("password")) {
                QJsonValue value = object.value("password");
                if (value.isString()) {
                    pswd = value.toString();
                    qDebug() << "pswd : " << pswd;
                }
            }else{
                return "json_error_no_pswd";
            }
        }else{
            return "json is no object";
        }
    }else{
        return "json error";
    }
    return "success";
}

QString MyJson::pack_live_flush(QString action, QStringList name_list)
{
    QJsonObject json;
    json.insert("action",action);
    QJsonArray json_array;
    for (int i = 0; i < name_list.size(); ++i){
        json_array.insert(i,name_list.at(i));
    }
    json.insert("name",json_array);
    QJsonDocument document;
    document.setObject(json);
    QByteArray byteArray = document.toJson(QJsonDocument::Compact);
    QString strJson(byteArray);
    return strJson;
}

QString MyJson::join_info(QString message, QString &room_name)
{
    QByteArray bytes = message.toUtf8();
    QJsonParseError jsonError;
    QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
    if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {  // 解析未发生错误
        if (doucment.isObject()){
            QJsonObject object = doucment.object();  // 转化为对象
            if (object.contains("join")) {
                QJsonValue value = object.value("join");
                if (value.isString()) {
                    room_name = value.toString();
                    qDebug() << "join : " << room_name;
                }
            }else{
                return "json_error_no_join";
            }
        }else{
            return "json is no object";
        }
    }else{
        return "json error";
    }
    return "success";
}

QString MyJson::barrage_info(QString message, QString &from, QString &room_name, QString &desc)
{
    QByteArray bytes = message.toUtf8();
    QJsonParseError jsonError;
    QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
    if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {  // 解析未发生错误
        if (doucment.isObject()){
            QJsonObject object = doucment.object();  // 转化为对象
            if (object.contains("from")) {
                QJsonValue value = object.value("from");
                if (value.isString()) {
                    from = value.toString();
                }
            }else{
                return "json_error_no_from";
            }
            if (object.contains("room_name")) {
                QJsonValue value = object.value("room_name");
                if (value.isString()) {
                    room_name = value.toString();
//                    qDebug() << "room_name : " << room_name;
                }
            }else{
                return "json_error_no_room_name";
            }
            if (object.contains("desc")) {
                QJsonValue value = object.value("desc");
                qDebug()<<"?????";
                if (value.isString()) {
                    desc = value.toString().toUtf8();
                    qDebug() << "desc : " << desc;
                }
            }else{
                return "json_error_no_desc";
            }
        }else{
            return "json is no object";
        }
    }else{
        return "json error";
    }
    return "success";
}

QString MyJson::pack_ret_barrage_info(QString from, QString desc)
{
    QJsonObject json;
    json.insert("from",from);
    json.insert("desc",desc);
    QJsonDocument document;
    document.setObject(json);
    QByteArray byteArray = document.toJson(QJsonDocument::Compact);
    QString strJson(byteArray);
    return strJson;
}

tcpsocket.h

在这里插入图片描述

tcpsocket.cpp

对于新增消息的处理
在这里插入图片描述
由于中文弹幕编码不正常显示问号,所以把接收函数进行了修改
在这里插入图片描述

tcpserver.cpp

只需要转发弹幕消息
在这里插入图片描述

客户端

在这里插入图片描述
客户端均有改动

消息打包以及处理

clientjson.h
#ifndef CLIENTJSON_H
#define CLIENTJSON_H


#include "clientjson.h"

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonParseError>
#include <QDebug>
#include <QByteArray>

typedef struct live_flush{
    QString action;
    QStringList name;
}Live_Flush_S;

typedef struct rcv_barrage{
    QString from;
    QString desc;
}Rcv_Barrage_S;

class ClientJson
{
public:
    ClientJson();
    //打包账号密码
    QString pack_name_pswd(QString name,QString pswd);
    //解析直播间刷新消息
    QString live_flush_info(QString message_buf,Live_Flush_S &live_msg);
    //打包加入直播间的内容
    QString pack_join_room(QString room_name);
    //打包弹幕内容
    QString pack_barrage(QString name,QString room_name,QString message);
    //解析收到的弹幕消息
    QString rcv_barrage(QString message_buf,Rcv_Barrage_S &barrage_msg);
};

#endif // CLIENTJSON_H

clientjson.cpp
#include "clientjson.h"

ClientJson::ClientJson()
{

}

QString ClientJson::pack_name_pswd(QString name, QString pswd)
{
    QJsonObject json;
    json.insert("Name",name);
    json.insert("password",pswd);
    QJsonDocument document;
    document.setObject(json);
    QByteArray byteArray = document.toJson(QJsonDocument::Compact);
    QString strJson(byteArray);
    return strJson;
}

QString ClientJson::live_flush_info(QString message_buf, Live_Flush_S &live_msg)
{
    QByteArray bytes = message_buf.toUtf8();
    QJsonParseError jsonError;
    QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
    if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {  // 解析未发生错误
        if (doucment.isObject()){
            QJsonObject object = doucment.object();  // 转化为对象
            if (object.contains("action")) {
                QJsonValue value = object.value("action");
                if (value.isString()) {
                    live_msg.action = value.toString();
                    qDebug() << "action : " << live_msg.action;
                }
            }else{
                return "json_error_no_name";
            }
            if (object.contains("name")) {
                QJsonValue value = object.value("name");
                if (value.isArray()) {
                    QJsonArray array = value.toArray();
                    int size = array.size();
                    for (int i = 0; i < size; ++i) {
                        QJsonValue value = array.at(i);
                        if (value.isString()) {
                            live_msg.name<<value.toString();
                        }
                    }
                    qDebug() << "name : " << live_msg.name;
                }else{
                    return "name_json_error";
                }
            }else{
                return "json_error_no_action";
            }
        }else{
            return "json is no object";
        }
    }else{
        return "json error";
    }
    return "success";
}

QString ClientJson::pack_join_room(QString room_name)
{
    QJsonObject json;
    json.insert("join",room_name);
    QJsonDocument document;
    document.setObject(json);
    QByteArray byteArray = document.toJson(QJsonDocument::Compact);
    QString strJson(byteArray);
    return strJson;
}

QString ClientJson::pack_barrage(QString name, QString room_name, QString message)
{
//    {"from":"123",room_name:"121","desc":"hahaha"}
    QJsonObject json;
    json.insert("from",name);
    json.insert("room_name",room_name);
    json.insert("desc",message);
    QJsonDocument document;
    document.setObject(json);
    QByteArray byteArray = document.toJson(QJsonDocument::Compact);
    QString strJson(byteArray);
    return strJson;
}

QString ClientJson::rcv_barrage(QString message_buf, Rcv_Barrage_S &barrage_msg)
{
    QByteArray bytes = message_buf.toUtf8();
    QJsonParseError jsonError;
    QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
    if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) {  // 解析未发生错误
        if (doucment.isObject()){
            QJsonObject object = doucment.object();  // 转化为对象
            if (object.contains("from")) {
                QJsonValue value = object.value("from");
                if (value.isString()) {
                    barrage_msg.from = value.toString();
                }
            }else{
                return "json_error_no_from";
            }
            if (object.contains("desc")) {
                QJsonValue value = object.value("desc");
                if (value.isString()) {
                    barrage_msg.desc = value.toString();
                }
            }else{
                return "json_error_no_desc";
            }
        }else{
            return "json is no object";
        }
    }else{
        return "json error";
    }
    return "success";
}


room.h

直播间弹幕的实现和观看直播间弹幕的实现方式一样,都是利用了定时器
每20ms弹幕的label向右移动一个单位
提供了增加弹幕的接口,这样可以在widget种实现弹幕的增加

#ifndef LIVEROOM_H
#define LIVEROOM_H

#include <QWidget>
#include <QDebug>
#include <QCloseEvent>
#include <QPoint>
#include <QLabel>
#include <QTimer>

namespace Ui {
class LiveRoom;
}

class LiveRoom : public QWidget
{
    Q_OBJECT

public:
    explicit LiveRoom(QWidget *parent = nullptr);
    void add_barrage(QString from,QString desc);
    ~LiveRoom();

signals:
    void sendlivemsg(QString);

public slots:
    void barrage_move();

private slots:
    void on_pushexitlive_clicked();

protected:
    virtual void closeEvent(QCloseEvent *ev);

private:
    Ui::LiveRoom *ui;
    QVector<QLabel*> v;//容器用来存放QLabel
    QPoint movep;//移动的位移
    QTimer *timer;
};

#endif // LIVEROOM_H
room.cpp

注意label的一个函数,adjustSize 自适应text长度

#include "room.h"
#include "ui_room.h"

Room::Room(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Room)
{
    ui->setupUi(this);
    movep.setX(1);
    movep.setY(0);
    timer = new QTimer;
    ui->labelvideo->setStyleSheet("QLabel{border:2px solid rgb(0, 255, 0);}");
    this->setAttribute(Qt::WA_DeleteOnClose);
    connect(timer,SIGNAL(timeout()),this,SLOT(barrage_move()));
    //点击enter时也可以发送弹幕
    connect(ui->lineBarrage,SIGNAL(returnPressed()),this,SLOT(on_pushBarrage_clicked()));
    timer->start(20);
}

QString Room::getroom_name()
{
    return room_name;
}

void Room::setroom_name(QString name)
{
    room_name = name;
}

void Room::add_barrage(QString from, QString desc)
{
    QLabel *l = new QLabel(this);
    l->setText(desc);
    int y =30 +rand()%370;//随机值y在30-400之间
    //设置label的坐标、长宽
    l->adjustSize();
    int length = l->width();
    l->setGeometry(20,y,length+10,20);
    l->show();
    v.push_back(l);//把label存到容器中
}

Room::~Room()
{
    qDebug()<<"quit";
    delete ui;
}

void Room::barrage_move()
{
    QVector<QLabel*>::iterator it = v.begin();
    for(;it!=v.end();){
        int len = (*it)->width();
        //向右移动
        QPoint p = (*it)->pos() + movep;
        (*it)->move(p);
        //移动到400的时候删除创建的label
        if(p.x() == 620 - len){
            (*it)->clear();
            (*it)->setStyleSheet("");
            v.erase(it);
        }else{
            it++;
        }
    }
}

void Room::on_pushexit_clicked()
{
    this->close();
}

void Room::closeEvent(QCloseEvent *ev)
{
    Q_UNUSED(ev);
    while(v.size()!=0){
        delete v.last();
        v.pop_back();
    }
    emit roommsg("close");
}

void Room::on_pushBarrage_clicked()
{
    QString str = ui->lineBarrage->text();
    if(!str.isEmpty()){
        //给widget发信号,发弹幕
        emit barrage_send(str);
        //必须将this传进去,不然就会在新的窗口中显示
        QLabel *l = new QLabel(this);
        l->setText(str);
        int y =30 +rand()%370;//随机值y在30-400之间
        //设置label的坐标、长宽
        l->adjustSize();
        int length = l->width();
        l->setGeometry(20,y,length+10,20);
        l->setStyleSheet("QLabel{border:2px solid rgb(0, 255, 0);}");
        l->show();
        v.push_back(l);//把label存到容器中
    }
    ui->lineBarrage->clear();//清空输入框
}

liveroom.h
#ifndef LIVEROOM_H
#define LIVEROOM_H

#include <QWidget>
#include <QDebug>
#include <QCloseEvent>
#include <QPoint>
#include <QLabel>
#include <QTimer>

namespace Ui {
class LiveRoom;
}

class LiveRoom : public QWidget
{
    Q_OBJECT

public:
    explicit LiveRoom(QWidget *parent = nullptr);
    void add_barrage(QString from,QString desc);
    ~LiveRoom();

signals:
    void sendlivemsg(QString);

public slots:
    void barrage_move();

private slots:
    void on_pushexitlive_clicked();

protected:
    virtual void closeEvent(QCloseEvent *ev);

private:
    Ui::LiveRoom *ui;
    QVector<QLabel*> v;//容器用来存放QLabel
    QPoint movep;//移动的位移
    QTimer *timer;
};

#endif // LIVEROOM_H

liveroom.cpp
#include "liveroom.h"
#include "ui_liveroom.h"

LiveRoom::LiveRoom(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::LiveRoom)
{
    ui->setupUi(this);
    movep.setX(1);
    movep.setY(0);
    timer = new QTimer;
    this->setAttribute(Qt::WA_DeleteOnClose);
    ui->labelvideo->setStyleSheet("QLabel{border:2px solid rgb(0, 255, 0);}");
    connect(timer,SIGNAL(timeout()),this,SLOT(barrage_move()));
    timer->start(20);
}

void LiveRoom::add_barrage(QString from, QString desc)
{
    QLabel *l = new QLabel(this);
    l->setText(desc);
    int y =50 +rand()%550;//随机值y在50-600之间
    //设置label的坐标、长宽
    l->adjustSize();
    int length = l->width();
    l->setGeometry(50,y,length+10,20);
    l->show();
    v.push_back(l);//把label存到容器中
}

LiveRoom::~LiveRoom()
{
    qDebug()<<"liveroom quit";
    delete ui;
}

void LiveRoom::barrage_move()
{
    QVector<QLabel*>::iterator it = v.begin();
    for(;it!=v.end();){
        int len = (*it)->width();
        //向右移动
        QPoint p = (*it)->pos() + movep;
        (*it)->move(p);
        //移动到400的时候删除创建的label
        if(p.x() == 750 - len){
            (*it)->clear();
            (*it)->setStyleSheet("");
            v.erase(it);
        }else{
            it++;
        }
    }
}

void LiveRoom::on_pushexitlive_clicked()
{
    emit sendlivemsg("close");
    this->close();
}

void LiveRoom::closeEvent(QCloseEvent *ev)
{
    Q_UNUSED(ev);
    emit sendlivemsg("close");
}

main.cpp

用来获取随机数

#include "widget.h"
#include <QApplication>
#include <time.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    srand(time(0));
    w.show();

    return a.exec();
}
widget.cpp

增加弹幕的发送和接收的处理函数
在这里插入图片描述
加入房间时绑定信号,用户发送弹幕之后会发送信号给widget,widget调用线程的发送给服务器发送弹幕。
在这里插入图片描述
发送弹幕给服务器
在这里插入图片描述

tcpthread.cpp

对于服务器接收到的弹幕消息
tcp线程接收到解析之后直接发消息给widget,然后widget调用界面的弹幕添加函数即可
在这里插入图片描述
由于编码的问题,对线程种的发送和接收的编码也进行了统一,统一成了utf8

void TcpThread::onReadMsg()
{
    if(m_TcpSocket->bytesAvailable() <= 0)
    {
        //  判定连接失败
        m_TcpSocket->disconnectFromHost();
    }
    while (m_TcpSocket->bytesAvailable() > 0)
    {
        // 接收数据
        QByteArray tmpQBA;
        tmpQBA.clear();
        tmpQBA= m_TcpSocket->readAll();
        QString str_tcp_receive = QString::fromUtf8(tmpQBA);
        qDebug()<<"recv "<<str_tcp_receive;
        parse_msg(str_tcp_receive);
        qDebug()<<"read "<<str_tcp_receive;
//        msleep(100);
    }
}

void TcpThread::onSendTcp(QString str_info)
{

    if((!m_TcpSocket)||m_TcpSocket->state()!=QAbstractSocket::ConnectedState)
        return;
    m_iSendData = m_TcpSocket->write(str_info.toUtf8(), str_info.toUtf8().length()+1);
    m_TcpSocket->flush();

    if (m_iSendData < 0)
    {
        m_TcpSocket->disconnectFromHost();
        return;
    }
    msleep(100);
}

然后widget接收到弹幕消息之后进行显示
先添加信号和槽
在这里插入图片描述
然后添加弹幕即可
在这里插入图片描述

4、项目源码

在这里插入图片描述
第一个是服务器剩下的都是客户端
客户端都一样
是为了测试,所以用qt打开多个项目

项目源码.

5、效果展示

测试时利用三个客户端和一个服务器进行测试

测试一个开播两个加入
请添加图片描述

测试发送弹幕(中文和英文)以及弹幕到右边之后自动消失
自己发的弹幕有框,别人的弹幕没有框

请添加图片描述

直播间之后用户列表没显示,是因为还没做

测试完成

6、总结

tcp write和read过程中,写和读的编码要保持一致,并且在传输过程中的长度也要是转换字符格式之后的长度

还有列表私聊、送礼物、聊天框、视频直播几个功能没有写。

这一秒不放弃,下一秒就有希望!!!!!!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
QT中编译arm-linux-gnueabihf项目需要进行以下几个步骤: 1. 安装交叉编译工具链 在Linux系统中使用以下命令安装交叉编译工具链: ``` sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf ``` 2. 在QT中设置交叉编译工具链 打开QT Creator,在菜单栏中选择“工具”->“选项”->“设备”->“添加”,然后选择“通用 Linux 设备”并点击“下一步”。 在“设备类型”下拉列表中选择“通用 Linux 设备”,在“设备名称”中输入你的设备名称,比如“Raspberry Pi”。 在“SSH”选项卡中输入你的设备IP地址和用户名,并选择“密码”或“密钥”进行身份验证。 在“工具链”选项卡中选择“添加”,然后选择“GCC”并点击“下一步”。 在“工具链名称”中输入你的工具链名称,比如“arm-linux-gnueabihf-gcc”,在“工具链路径”中输入你的交叉编译工具链路径,比如“/usr/bin/arm-linux-gnueabihf-gcc”。 3. 配置QT项目 在QT Creator中打开你的项目,然后在左侧窗口中选择“项目”->“构建设置”->“构建环境”。 在“构建工具”下拉列表中选择“Desktop Qt <版本> <编译器>”,在“设备”下拉列表中选择你的设备名称,比如“Raspberry Pi”。 在“构建步骤”选项卡中选择“自定义步骤”并添加以下命令: ``` make -j4 scp -r <本地路径> <远程路径> ``` 其中“-j4”表示使用4个线程进行编译,“<本地路径>”为你的本地项目路径,“<远程路径>”为你的设备上的路径。 4. 编译和部署项目 在QT Creator中点击“构建”按钮进行编译,然后点击“部署”按钮将项目部署到你的设备上。 注意:在编译和部署过程中可能会出现一些问题,需要根据具体情况进行解决。同时,还需要保证你的设备已经正确连接到网络并且已经安装了必要的库和依赖项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值