1.效果展示
基于QT的广告机
2.设计思路
广告机基于QT框架进行设计,实现了客户端与服务器的通信。客户端实现广告的播放、管理等功能,此外,还实现了实时播放天气,以及字幕滚动广告等功能。服务器实现了视频广告与字幕广告的上传,并且实现了将品牌方与广告名称,广告内容大小储存进数据库内。
2.1 服务器
需要用到的模块
首先先添加需要的对象
QTcpServer *server; //服务器对象
QTcpSocket *client;//客户端对象
QString filename;//要给客户端的文件名
qint64 fileSize;//文件大小
qint64 sendSize;//发送的文件大小
QFile file;//文件路径
QTimer timer;所需的定时器
QSqlDatabase sql; // 数据库对象
QString filePath;//文件路径
所需的槽函数
void on_send_btn_clicked(); // 发送广告视频和广告字幕
void on_video_btn_clicked();//选择需要发送的广告
void sendData(); //发送广告的文件信息
void on_sqlite_btn_clicked(); //将文件信息记录进数据库
void on_openserver_clicked(); //监听客户端
void on_show_btn_clicked(); //显示数据库的内容
服务器.h文件
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void go();
private slots:
void on_send_btn_clicked();
void on_video_btn_clicked();
void sendData();
void on_sqlite_btn_clicked();
void on_openserver_clicked();
void on_show_btn_clicked();
private:
Ui::Widget *ui;
QTcpServer *server;
QTcpSocket *client;
QString filename;
qint64 fileSize;
qint64 sendSize;
QFile file;
QTimer timer;
QSqlDatabase sql;
QString filePath;
};
服务器.cpp文件
构造函数中 申请对象需要的空间,以及等待客户端连接的信号,和定时器启动 timeout信号触发需要发的文件函数
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("服务器");
//实例化服务器对象
this->server = new QTcpServer(this);
//创建监听队列+绑定IP和port
// this->server->listen(QHostAddress("192.168.12.32"), 8888);
//连接客户端
this->client= nullptr;
// this->client = this->server->nextPendingConnection();
connect(this->server, &QTcpServer::newConnection, [=](){
//连接客户端
this->client = this->server->nextPendingConnection();
QMessageBox::information(this,"连接","连接成功");
});
connect(&timer, &QTimer::timeout, [=](){
timer.stop();
sendData();
});
}
析构中释放UI
Widget::~Widget()
{
delete ui;
}
void Widget::on_video_btn_clicked()
{
filePath=QFileDialog::getOpenFileName(this,"open","./mv","WMV(*wmv)");
QFileInfo info(filePath);
filename = info.fileName();
fileSize=info.size();
sendSize = 0;
file.setFileName(filePath);
bool isok=file.open(QIODevice::ReadOnly);
if(isok==false)
{
qDebug()<<"文件打开失败";
}
}
void Widget::on_send_btn_clicked()
{
QString Data= this->ui->subtitle_edt->text();
QString str = QString("%1#%2#%3").arg(Data).arg(filename).arg(fileSize);
this->ui->subtitle_edt->clear();
qDebug()<<str;
qint64 len =client->write(str.toUtf8().data());
if(len >0)
{
timer.start(20);
}
}
void Widget::sendData()
{
qint64 len=0;
sendSize = 0;
//sendSize = 0;
do
{
char buf[4*1024]={0};
len=file.read(buf,sizeof(buf));
len =client->write(buf,len);
sendSize +=len;
}while(len >0);
if(sendSize==fileSize)
{
qDebug()<<"广告发送完毕";
QMessageBox::information(this,"完毕","文件已发送");
}
}
void Widget::on_sqlite_btn_clicked()
{
//数据库的建立
sql= QSqlDatabase::addDatabase("QSQLITE","sqlite3");
sql.setDatabaseName("./data/user.db");
if(sql.open())
{
qDebug()<<"open success";
}
else
{
qDebug()<<"open error";
}
QString sqldatabase ="create table if not exists brand(ID integer PRIMARY KEY AUTOINCREMENT,""name varchar(30) not null,""video varchar(30) not null,""videosize varchar(30) not null)";
QSqlQuery query(sql);
bool ret = query.exec(sqldatabase);
if(ret == false)
{
qDebug()<<"建表失败";
return;
}
//获取选择文件的大小
QFile file(filePath);
QFileInfo fileInfo(file);
qint64 size = fileInfo.size();
int filesize=static_cast<int>(size);
//插入语句
QString brandname = this->ui->brand_name->text();
QString sqldata;
this->ui->brand_name->clear();
sqldata =QString ("insert into brand values(null,'%1','%2','%3')").arg(brandname).arg(filename).arg(filesize);
ret = query.exec(sqldata);
if(ret == false)
{
QMessageBox::information(this,"数据","插入失败");
}else
{
QMessageBox::information(this,"数据","插入成功");
}
sql.close();
QSqlDatabase::removeDatabase("sqlite3");
}
void Widget::on_openserver_clicked()
{
bool ret = this->server->listen(QHostAddress("192.168.12.9"), 8888);
if(true ==ret)
{
QMessageBox::information(this,"开启","开始监听");
}
}
void Widget::on_show_btn_clicked()
{
sql= QSqlDatabase::addDatabase("QSQLITE","sqlite3");
sql.setDatabaseName("./data/user.db");
if(sql.open())
{
qDebug()<<"open success";
}
else
{
qDebug()<<"open error";
}
QString sqldatabase ="create table if not exists brand(ID integer PRIMARY KEY AUTOINCREMENT,""name varchar(30) not null,""video varchar(30) not null,""videosize varchar(30) not null)";
QSqlQuery query(sql);
bool ret = query.exec(sqldatabase);
if(ret == false)
{
qDebug()<<"建表失败";
return;
}
QString sqldata =QString ("select * from brand");
ret = query.exec(sqldata);
if(ret == false)
{
qDebug()<<"语句执行失败";
}
QSqlRecord result = query.record();
while(query.next()!=false)
{
for(int i=0;i<result.count();i++)
{
qDebug()<<query.value(i).toString();
}
}
sql.close();
QSqlDatabase::removeDatabase("sqlite3");
}
2.2客户端
需要用到的模块
客户端.h文件
客户端使用的布局的形式,所以需要创建的比较多
用到的对象:
QStringList filePaths; //下载下来的广告视频文件路径全部放进这个链表中 ticker *subtitle_lab; //自定义的类下面会讲 QLabel *weatherIcon_lab; //显示天气图标的标签 QLabel *city_lab; //显示城市的标签 QLabel *time_lab; //显示时间的标签 QLabel *weather_lab; //显示天气的标签 QLabel *tem_lab; //显示温度的标签 QMediaPlayer *player; //播放器 QVideoWidget *videoWidget; //生成视频的类 QMediaPlaylist *playlist; //播放器列表 QTcpSocket *socket; //客户端套接字 bool isStart; //判断值 QString Data; //数据解析得到的广告词 QString filename; //解析得到的广告名 qint64 fileSize; //文件大小 qint64 recvSize; //接收的文件大小 QFile file; //路径 QTimer *timer; //定时器 QTimer *timer1; //定时器2 int pos; //标志位 QNetworkAccessManager *mNet_Manager; //实时获取天气 Today today[15]; //自定义的类 QMap<QString,QString> mTypeMap; //放天气图标
用到的方法和槽
方法:
void paintEvent(QPaintEvent *event); //背景图片
void transmit(); //自动播放视频的方法
void getWeatherInfo(QString cityCode); //根据API接口获取天气信息
void parsejson(QByteArray &Bytearray); //解析获取的信息
槽函数:
void show_Time(); //时间的显示 void onReplied(QNetworkReply *replay); //请求天气数据 void timecity(); //没90秒获取一次数据
第二个.h文件
创建了一个Today的类用来储存等会得到的天气的数据
Today() { date = "2022-10-20"; city = "广州"; ganmao = "感冒指数"; wendu = 0; shidu = "0%";pm25 = 0; quality = "无数据"; type = "gX z"; fl = "2#k"; fx = "南风"; high = 30; low = 18; } QString date; QString city; QString ganmao; int wendu; QString shidu;int pm25 ; QString quality; QString type; QString fx; QString fl; QString high; QString low;
第三个.h文件
用来设置字幕广告的滚动效果
public:
explicit ticker(QWidget *parent = nullptr);
virtual void paintEvent(QPaintEvent* event) override; // 绘制事件
virtual void timerEvent(QTimerEvent* event) override; // 定时器事件
virtual void showEvent(QShowEvent* event) override; // 显示事件
virtual void hideEvent(QHideEvent* event) override; // 隐藏事件
void setText(const QString& newText);
QString text() const { return myText; }
QSize sizeHint() const;
private:
QString myText;
int offset;
int myTimerId;
ticker.cpp
ticker::ticker(QWidget *parent) : QWidget(parent)
{
offset = 0;
myTimerId = 0;
}
void ticker::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text());
if(textWidth < 1)
{
return;
}
int x = -offset;
while(x < width())
{
painter.drawText(x, 0, textWidth, height(), Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}
void ticker::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId)
{
++offset;
if(offset >= fontMetrics().width(text()))
{
offset = 0;
}
scroll(-1, 0);
}
else
{
QWidget::timerEvent(event);
}
}
void ticker::showEvent(QShowEvent *event)
{
Q_UNUSED(event);
myTimerId = startTimer(30);
}
void ticker::hideEvent(QHideEvent *event)
{
killTimer(myTimerId);
myTimerId = 0;
}
void ticker::setText(const QString &newText)
{
myText = newText;
update();
updateGeometry();
}
QSize ticker::sizeHint() const
{
return fontMetrics().size(0, text());
}
客户端.cpp
构造函数:
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->timer = new QTimer(this);
this->timer1 = new QTimer(this);
this->timer1->start(90000);
this->setWindowTitle("广告");
this->videoWidget=new QVideoWidget (this);
this->subtitle_lab = new ticker(this);
this->weatherIcon_lab = new QLabel(this);
this->city_lab = new QLabel(this);
this->time_lab = new QLabel(this);
this->weather_lab = new QLabel(this);
this->tem_lab= new QLabel(this);
this->mNet_Manager = new QNetworkAccessManager(this);// 上面全是申请空间
this->city_lab ->setAlignment(Qt::AlignCenter);
this->time_lab->setAlignment(Qt::AlignCenter);
this->weather_lab ->setAlignment(Qt::AlignCenter);
this->tem_lab ->setAlignment(Qt::AlignCenter); //将标签中的文字居中
QHBoxLayout *hbox = new QHBoxLayout; //水平布局
hbox->addWidget(this->time_lab,2);
hbox->addWidget(this->city_lab,1);
hbox->addWidget(this->weatherIcon_lab,1);
hbox->addWidget(this->weather_lab,2);
hbox->addWidget(this->tem_lab,2);
QVBoxLayout *vbox = new QVBoxLayout; //垂直布局
vbox->addWidget(this->videoWidget,7);
vbox->addWidget(this->subtitle_lab,2);
vbox->addLayout(hbox,1);
//给当前界面设置布局为垂直布局
this->setLayout(vbox);
//实例化播放器
this->player = new QMediaPlayer(this);
//给播放器设置初始的音量值
this->player->setVolume(40);
this->playlist =new QMediaPlaylist(player);
this->socket = new QTcpSocket(this);
QString iptex ="xxxxxx"; //填写你的IP地址
QString port ="xxxx"; //填写你的端口号
this->socket->connectToHost(QHostAddress(iptex),port.toUInt());
isStart =true;
connect(this->timer,&QTimer::timeout,this,&Widget::show_Time);
connect(this->timer1,&QTimer::timeout,this,&Widget::timecity); //定时器
connect(this->mNet_Manager,&QNetworkAccessManager::finished,this,&Widget::onReplied);
//西安的天气
getWeatherInfo("101110101"); //西安的编码
pos=0;
connect(this->socket,&QTcpSocket::readyRead,[=]()
{
QByteArray array = socket->readAll();
if(isStart == true)
{
isStart = false;
Data = QString(array).section("#",0,0);
this->subtitle_lab->setText(Data);
this->subtitle_lab->setStyleSheet("color: red;");
filename=QString(array).section("#",1,1);
fileSize=QString(array).section("#",2,2).toInt();
recvSize = 0;
file.setFileName(filename);
bool isok=file.open(QIODevice::WriteOnly);
if(false == isok)
{
qDebug()<<"文件打开失败";
}
}else
{
qint64 len = file.write(array);
recvSize +=len;
if(recvSize == fileSize)
{
this->socket->disconnectFromHost();
this->socket->close();
QMessageBox::information(this,"提示","文件接收完成已关闭网络并停止接收广告");
this->timer->start(1000);
Widget::transmit();
file.close();
}
}
}); // 上面是当客户端接收信息是就会触发这个函数 接收文件信息 和接收文件内容
QFontMetrics fm1(this->city_lab->font());
int fontSize1 = fm1.height();
QFont font1 = this->city_lab->font();
font1.setPointSize(fontSize1);
font1.setItalic(true);
font1.setBold(true);
this->city_lab->setFont(font1);
QFontMetrics fm2(this->time_lab->font());
int fontSize2 = fm2.height();
QFont font2 = this->time_lab->font();
font2.setPointSize(fontSize2);
font2.setItalic(true);
font2.setBold(true);
this->time_lab->setFont(font2);
QFontMetrics fm3(this->weather_lab->font());
int fontSize3 = fm3.height();
QFont font3 = this->weather_lab->font();
font3.setPointSize(fontSize3);
font3.setItalic(true);
font3.setBold(true);
this->weather_lab->setFont(font3);
QFontMetrics fm4(this->tem_lab->font());
int fontSize4 = fm4.height();
QFont font4 = this->tem_lab->font();
font4.setPointSize(fontSize4);
font4.setItalic(true);
font4.setBold(true);
this->tem_lab->setFont(font4);
QFontMetrics fm(this->subtitle_lab->font());
int fontSize = fm.height();
QFont font = this->subtitle_lab->font();
font.setPointSize(fontSize);
font.setFamily("KaiTi");
font.setBold(true);
this->subtitle_lab->setFont(font); //上述全是设置文字根据标签大小改变而改变并且设置加粗
mTypeMap.insert("暴雪",":/1/xue.png");
mTypeMap.insert("雪",":/1/xue.png");
mTypeMap.insert("小雪",":/1/xue.png");
mTypeMap.insert("大雪",":/1/xue.png");
mTypeMap.insert("中雪",":/1/xue.png");
mTypeMap.insert("多云",":/1/duoyun.png");
mTypeMap.insert("晴",":/1/qing.png");
mTypeMap.insert("小雨",":/1/yu.png");
mTypeMap.insert("雨",":/1/yu.png");
mTypeMap.insert("大雨",":/1/yu.png");
mTypeMap.insert("阵雨雨",":/1/yu.png");
mTypeMap.insert("大雾",":/1/dawu.png");
mTypeMap.insert("雨夹雪",":/1/yuandxue.png"); //设置图标给这个表 根据天气改变图标
}
其他的关键函数:
//背景图片
void Widget::paintEvent(QPaintEvent *event)
{
// 声明画家
QPainter painter(this);
QPixmap pix = QString(":/1/2.jpg");
// 设置图片伸缩
pix.scaled(this->width(), this->height());
// 贴背景图片
painter.drawPixmap(0, 0, this->width(), this->height(), pix);
}
//获取当前文件下所有wmv格式的视频并且按照列表顺序播放
void Widget::transmit()
{
QDir currentDir = QDir::current();
// 获取当前目录下所有的 .wmv 文件
this->filePaths =currentDir.entryList(QStringList("*.wmv"));
//得到歌曲完整路径
for(int i=0; i<this->filePaths.count(); i++)
{
QString aFile = this->filePaths.at(i);
this->playlist->addMedia(QUrl::fromLocalFile(aFile));
}
this->player->setPlaylist(this->playlist);
//设置媒体资源的画面输出
this->player->setVideoOutput(this->videoWidget);
this->playlist->setPlaybackMode(QMediaPlaylist::Loop);
this->player->play();
}
//定时器启动获取实时时间
void Widget::show_Time()
{
this->time_lab->setText(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"));
}
//根据API网址获取信息
void Widget::getWeatherInfo(QString cityCode)
{
QUrl url("http://t.weather.itboy.net/api/weather/city/" + cityCode);
//QUrl url("https://api.seniverse.com/v3/weather/now.json?key=SFGQ5NKpmBr99A8D2&location=xian&language=zh-Hans&unit=c/");
mNet_Manager->get(QNetworkRequest(url));
}
//请求天气信息
void Widget::onReplied(QNetworkReply *replay)
{
int status_code = replay->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(replay->error() != QNetworkReply::NoError || status_code != 200)
{
qDebug() << replay->errorString().toLatin1().data();
QMessageBox::warning(this,"天气","请求失败",QMessageBox::Ok);
}else
{
QByteArray bytearray = replay->readAll();
//qDebug() << "replay readAll:" << bytearray.data();
parsejson(bytearray);
}
replay->deleteLater();
}
//定时器启动以90秒获取一次天气数据
void Widget::timecity()
{
if(this->mNet_Manager !=nullptr)
{
delete this->mNet_Manager;
}
this->mNet_Manager = new QNetworkAccessManager(this);
getWeatherInfo("101110101");
connect(this->mNet_Manager,&QNetworkAccessManager::finished,this,&Widget::onReplied);
}
//解析获取的天气信息并且显示再标签上
void Widget::parsejson(QByteArray &Bytearray)
{
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(Bytearray,&err);
QJsonArray arr1[15];
if(err.error != QJsonParseError::NoError)
{
QMessageBox::warning(this,"解析","数据解析失败",QMessageBox::Ok);
return;
}
QJsonObject rootboj= doc.object();
//城市
QString city_text =rootboj.value("cityInfo").toObject().value("city").toString();
this->city_lab->setText("城市:"+city_text);
QJsonObject daydata = rootboj.value("data").toObject();
QJsonArray teday = daydata.value("forecast").toArray();
//天气类型
today[0].type =teday[0].toObject().value("type").toString();
QPixmap pix(mTypeMap[today[0].type]);
this->weatherIcon_lab->setPixmap(pix.scaled(this->weatherIcon_lab->size()));
today[0].fx= teday[0].toObject().value("fx").toString();
today[0].fl= teday[0].toObject().value("fl").toString();
this->weather_lab->setText("天气:"+today[0].type+" 风向为"+ today[0].fl + today[0].fx);
//高低温
today[0].high= teday[0].toObject().value("high").toString().split(" ").at(1);//高温度数
today[0].low= teday[0].toObject().value("low").toString().split(" ").at(1);//度数
this->tem_lab->setText("温度:" + today[0].low + "~" +today[0].high);
}
总结
1.视频的传输 在客户端下载服务器的视频时,遇到了粘包,与数据丢失的问题,使用了定时器每30毫秒发送一次,且第一次发送文件的信息如文件名与文件大小,使得客户端下载时知道什么时候传输结束。
2.滚动字幕的设计 因为是从服务器往客户端发送消息所以就将滚动字幕写进第一次发送的文件信息的包中,解析时第一个就是要打印的滚动字幕,滚动字幕设计时使它从标签的右端出现.
3.实时天气的设计 最开始想直接写死天气数据,但是觉得这样项目缺少重要内容,所以找了资源,根据别人的视频讲解,设计自己的实时天气显示的功能,以90s为一次更新,显示到标签上。可以学习一下这个作者。非常有用http:// https://www.bilibili.com/video/BV12e411V731/?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=3115a2dcffc6f5f49a70b7b7eb847b96
4.标签字体的设置 当其他功能完善时,觉得标签显示的文字太过单调,所以查阅了一下索引表找到了QFont这个类其中的setFamily("KaiTi")函数可以将字体设置成楷体,font.setBold(true)可以将字体加粗等等