完成有关弹幕的相关内容的开发
用户发送弹幕之后,开播的人显示弹幕,观看直播的人也显示弹幕
自己发的弹幕用绿色的框框起来
别人发的弹幕直接显示
参考连接.
对于弹幕的实现,这里使用了定时器,弹幕的队列用vector显示,然后vector中存入Qlabel类,发送或者接收到弹幕之后就给vector中添加对应的label
对于弹幕的移动用QTimer 每20ms移动一次移动到边界之后自动消失
弹幕的长度根据字符串来自适应,弹幕移动到边界之后自动消失并且清除缓存
弹幕的发送流程,对于加入直播间的客户端,服务器端进行记录加入的房间号,对于开播的直播间来说,记录开播的房间号。
发送弹幕之后,客户端发送消息给服务器,服务器转发弹幕消息,转发给对于房间号开播的客户端以及正在观看该直播间的用户。
客户端收到消息之后,随机现在弹幕的位置在视频界面。
(后面有时间会整理一个流程图出来)
服务器主要是对消息的接收和发送的修改
首先是加入直播间和离开直播间
离开直播间只需要清空自定义tcpsocket的类的加入直播间的房间名称即可
然后是弹幕消息的格式
主要修改以上四个文件
消息的打包和解析
#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
#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;
}
对于新增消息的处理
由于中文弹幕编码不正常显示问号,所以把接收函数进行了修改
消息打包以及处理
#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
#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";
}
直播间弹幕的实现和观看直播间弹幕的实现方式一样,都是利用了定时器
每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
注意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();//清空输入框
}
#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
#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");
}
用来获取随机数
#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,widget调用线程的发送给服务器发送弹幕。
发送弹幕给服务器
对于服务器接收到的弹幕消息
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接收到弹幕消息之后进行显示
先添加信号和槽
然后添加弹幕即可
第一个是服务器剩下的都是客户端
客户端都一样
是为了测试,所以用qt打开多个项目
项目源码.
测试时利用三个客户端和一个服务器进行测试
测试一个开播两个加入
测试发送弹幕(中文和英文)以及弹幕到右边之后自动消失
自己发的弹幕有框,别人的弹幕没有框
直播间之后用户列表没显示,是因为还没做
测试完成
tcp write和read过程中,写和读的编码要保持一致,并且在传输过程中的长度也要是转换字符格式之后的长度
还有列表私聊、送礼物、聊天框、视频直播几个功能没有写。
这一秒不放弃,下一秒就有希望!!!!!!