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);//解析加入直播jsonQString 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_Hmyjson.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_Hclientjson.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種實現彈幕的增加
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的時候刪除創建的labelif(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_OBJECTpublic: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;//容器用來存放QLabelQPoint movep;//移動的位移QTimer *timer; };#endif // LIVEROOM_Hliveroom.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的時候刪除創建的labelif(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
然后widget接收到彈幕消息之后進行顯示
先添加信號和槽
然后添加彈幕即可
4、項目源碼
第一個是服務器剩下的都是客戶端
客戶端都一樣
是為了測試,所以用qt打開多個項目
項目源碼.
5、效果展示
測試時利用三個客戶端和一個服務器進行測試
測試一個開播兩個加入
測試發送彈幕(中文和英文)以及彈幕到右邊之后自動消失
自己發的彈幕有框,別人的彈幕沒有框
直播間之后用戶列表沒顯示,是因為還沒做
測試完成
6、總結
tcp write和read過程中,寫和讀的編碼要保持一致,并且在傳輸過程中的長度也要是轉換字符格式之后的長度
還有列表私聊、送禮物、聊天框、視頻直播幾個功能沒有寫。
這一秒不放棄,下一秒就有希望!!!!!!
總結
以上是生活随笔為你收集整理的Qt视频直播软件--项目实战(Day7)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 失败的数据恢复经历
- 下一篇: 流形学习t-SNE,LLE,Isomap