558 lines
15 KiB
C++
Executable File
558 lines
15 KiB
C++
Executable File
#include "Widget.h"
|
||
#include "Channel.h"
|
||
#include "Constant.h"
|
||
#include "Json.h"
|
||
#include "Log.h"
|
||
#include "TcpServer.h"
|
||
#include "Tool.h"
|
||
#include "ui_Widget.h"
|
||
#include <QDir>
|
||
#include <QMutex>
|
||
#include <QScrollBar>
|
||
#include <QWaitCondition>
|
||
|
||
// 多线程中使用
|
||
QMutex mutex;
|
||
QWaitCondition condition;
|
||
QString curFilename;
|
||
|
||
extern QList<Channel*> channelList;
|
||
extern Constant::PlaybackMode playbackMode;
|
||
|
||
Widget::Widget(QWidget* parent)
|
||
: QWidget(parent)
|
||
, ui(new Ui::Widget)
|
||
{
|
||
ui->setupUi(this);
|
||
ui->lblA->setPixmap(QPixmap(":/images/no-record.png"));
|
||
ui->lblB->setPixmap(QPixmap(":/images/no-record.png"));
|
||
|
||
menu = new Menu(this);
|
||
menu->hide();
|
||
|
||
progressBar = new ProgressBar(this);
|
||
progressBar->hide();
|
||
|
||
message = new Message(this);
|
||
|
||
// 设置窗体背景透明
|
||
QPalette pal = palette();
|
||
pal.setBrush(QPalette::Base, Qt::transparent);
|
||
setPalette(pal);
|
||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||
|
||
// 设置listwidget无滚动条
|
||
ui->listWidget->horizontalScrollBar()->hide();
|
||
ui->listWidget->verticalScrollBar()->hide();
|
||
ui->listWidget->hide();
|
||
|
||
// 初始化串口
|
||
initSerialPort();
|
||
|
||
// 初始化指令列表
|
||
initCommandMap();
|
||
|
||
// 每5秒更新一次进度条
|
||
progressTimer = new QTimer();
|
||
progressTimer->setInterval(500);
|
||
connect(progressTimer, SIGNAL(timeout()), this, SLOT(onProgressTimeout()));
|
||
|
||
for (Channel* chn : channelList) {
|
||
connect(chn, SIGNAL(playEnd()), this, SLOT(onPlayEnd()));
|
||
connect(chn, SIGNAL(showRecordState(bool)), this, SLOT(onShowRecordLabel(bool)));
|
||
}
|
||
}
|
||
|
||
Widget::~Widget()
|
||
{
|
||
serialPort->close();
|
||
delete ui;
|
||
delete timer;
|
||
delete progressTimer;
|
||
delete menu;
|
||
delete progressBar;
|
||
delete message;
|
||
}
|
||
|
||
/**
|
||
* @brief 初始化串口
|
||
*/
|
||
void Widget::initSerialPort()
|
||
{
|
||
// 用于重连串口的timer
|
||
timer = new QTimer();
|
||
timer->setInterval(3000);
|
||
timer->stop();
|
||
|
||
serialPort = new QSerialPort();
|
||
serialPort->setPortName("ttyAMA2");
|
||
serialPort->setBaudRate(QSerialPort::Baud115200);
|
||
serialPort->setDataBits(QSerialPort::Data8);
|
||
serialPort->setParity(QSerialPort::NoParity);
|
||
serialPort->setStopBits(QSerialPort::OneStop);
|
||
serialPort->setFlowControl(QSerialPort::NoFlowControl);
|
||
if (serialPort->open(QIODevice::ReadWrite)) {
|
||
Log::info("open serial port: /dev/ttyAMA2 success");
|
||
} else {
|
||
Log::error("open serial port: /dev/ttyAMA2 failed, ready to retry...");
|
||
timer->start();
|
||
}
|
||
connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onSerialError(QSerialPort::SerialPortError)));
|
||
connect(serialPort, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
|
||
}
|
||
|
||
/**
|
||
* @brief 获取视频文件列表并显示
|
||
*/
|
||
void Widget::showPlayList()
|
||
{
|
||
QString dirPath = QString("%1/%2").arg(Constant::VideoPath).arg(curSelectChannel);
|
||
QStringList files = Tool::getFileList(dirPath);
|
||
|
||
ui->listWidget->clear();
|
||
for (QString& file : files) {
|
||
QListWidgetItem* item = new QListWidgetItem(file);
|
||
ui->listWidget->addItem(item);
|
||
}
|
||
ui->listWidget->show();
|
||
ui->listWidget->setCurrentRow(0);
|
||
}
|
||
|
||
/**
|
||
* @brief 重连串口
|
||
*/
|
||
void Widget::onTimeout()
|
||
{
|
||
timer->stop();
|
||
bool ret = serialPort->open(QIODevice::ReadWrite);
|
||
if (!ret) {
|
||
Log::info("reopen serial port: /dev/ttyAMA2 success");
|
||
timer->start();
|
||
} else {
|
||
Log::error("reopen serial port: /dev/ttyAMA2 failed, ready to retry...");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 更新进度条
|
||
*/
|
||
void Widget::onProgressTimeout()
|
||
{
|
||
Channel* chn = findChannelByName(Constant::MainChannel);
|
||
int pos = chn->file->invoke("getPosition").toInt() / 1000;
|
||
progressBar->setCurrent(pos);
|
||
}
|
||
|
||
/**
|
||
* @brief 串口错误处理
|
||
* @param error
|
||
*/
|
||
void Widget::onSerialError(QSerialPort::SerialPortError error)
|
||
{
|
||
if (error == QSerialPort::ResourceError) {
|
||
Log::error("serial port break, try to reopen");
|
||
serialPort->close();
|
||
timer->start();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 映射指令列表
|
||
*/
|
||
void Widget::initCommandMap()
|
||
{
|
||
commandMap.insert("A1", CmdPlayback);
|
||
commandMap.insert("A2", CmdNext);
|
||
commandMap.insert("A3", CmdPrevious);
|
||
commandMap.insert("A4", CmdForward);
|
||
commandMap.insert("A5", CmdBack);
|
||
commandMap.insert("A6", CmdPause);
|
||
commandMap.insert("A7", CmdReturn);
|
||
commandMap.insert("A8", CmdEnter);
|
||
}
|
||
|
||
/**
|
||
* @brief 从串口缓冲区接收数据
|
||
*/
|
||
void Widget::onReadyRead()
|
||
{
|
||
serialPort->waitForReadyRead(100);
|
||
QString data = serialPort->readLine();
|
||
if (!commandMap.contains(data)) {
|
||
Log::error("receive unkown command {} from serial port: /dev/ttyAMA2", data.toStdString());
|
||
return;
|
||
}
|
||
Command cmd = commandMap.value(data);
|
||
switch (cmd) {
|
||
case CmdPlayback:
|
||
receivePlayback();
|
||
break;
|
||
case CmdPrevious:
|
||
receivePrevious();
|
||
break;
|
||
case CmdNext:
|
||
receiveNext();
|
||
break;
|
||
case CmdForward:
|
||
seek("forward");
|
||
break;
|
||
case CmdBack:
|
||
seek("next");
|
||
break;
|
||
case CmdPause:
|
||
receivePasue();
|
||
break;
|
||
case CmdEnter:
|
||
receiveEnter();
|
||
break;
|
||
case CmdReturn:
|
||
receiveReturn();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 收到回放指令
|
||
*/
|
||
void Widget::receivePlayback()
|
||
{
|
||
Log::info("receive command playback, playback mode: {}", playbackMode);
|
||
// 如果是一路回放,显示选择菜单
|
||
if (playbackMode == Constant::OneChannelPlayback) {
|
||
if (!menu->isVisible())
|
||
menu->show();
|
||
}
|
||
// 两路回放不显示菜单,直接显示列表
|
||
else if (playbackMode == Constant::TwoChannelPlayback) {
|
||
curSelectChannel = Constant::MainChannel;
|
||
showPlayList();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 收到上段指令
|
||
*/
|
||
void Widget::receivePrevious()
|
||
{
|
||
// 菜单显示则切换菜单选中
|
||
if (menu->isVisible()) {
|
||
menu->setCurChannel(Constant::MainChannel);
|
||
return;
|
||
}
|
||
// 列表显示,则选中上一个视频
|
||
if (ui->listWidget->isVisible()) {
|
||
int curRow = ui->listWidget->currentRow();
|
||
if (curRow == 0)
|
||
curRow = ui->listWidget->count();
|
||
ui->listWidget->setCurrentRow(curRow - 1);
|
||
return;
|
||
}
|
||
if (isPlayback) {
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 收到下段指令
|
||
*/
|
||
void Widget::receiveNext()
|
||
{
|
||
if (menu->isVisible()) {
|
||
menu->setCurChannel(Constant::SecondaryChannel);
|
||
return;
|
||
}
|
||
if (ui->listWidget->isVisible()) {
|
||
int curRow = ui->listWidget->currentRow();
|
||
if (curRow == ui->listWidget->count() - 1) {
|
||
curRow = -1;
|
||
}
|
||
ui->listWidget->setCurrentRow(curRow + 1);
|
||
return;
|
||
}
|
||
if (isPlayback) {
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 收到暂停指令
|
||
*/
|
||
void Widget::receivePasue()
|
||
{
|
||
if (!isPlayback)
|
||
return;
|
||
for (Channel* chn : channelList) {
|
||
if (chn->state == Channel::Playback || chn->state == Channel::Pause) {
|
||
chn->togglePause();
|
||
if (chn->channelName == Constant::MainChannel) {
|
||
progressBar->showMax();
|
||
if (chn->state == Channel::Pause) {
|
||
progressBar->setState(ProgressBar::Pause);
|
||
} else {
|
||
progressBar->setState(ProgressBar::Play);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 收到确定指令
|
||
*/
|
||
void Widget::receiveEnter()
|
||
{
|
||
// 菜单显示,则关闭菜单显示列表
|
||
if (menu->isVisible()) {
|
||
curSelectChannel = menu->getCurChannel();
|
||
menu->hide();
|
||
showPlayList();
|
||
return;
|
||
}
|
||
if (ui->listWidget->isVisible()) {
|
||
if (ui->listWidget->count() == 0)
|
||
return;
|
||
if (playbackMode == Constant::OneChannelPlayback)
|
||
playOneChannel();
|
||
else if (playbackMode == Constant::TwoChannelPlayback)
|
||
playTwoChannels();
|
||
return;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 收到返回指令
|
||
* 处理优先级: 菜单 ==> 列表 ==> 回放
|
||
*/
|
||
void Widget::receiveReturn()
|
||
{
|
||
if (menu->isVisible()) {
|
||
menu->hide();
|
||
return;
|
||
}
|
||
if (ui->listWidget->isVisible()) {
|
||
ui->listWidget->hide();
|
||
return;
|
||
}
|
||
if (isPlayback) {
|
||
// 停止回放
|
||
for (Channel* chn : channelList) {
|
||
if (chn->state != Channel::Stop)
|
||
chn->startPlayLive();
|
||
}
|
||
progressBar->hide();
|
||
ui->lblA->show();
|
||
ui->lblB->show();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 视频跳转
|
||
*/
|
||
void Widget::seek(QString type)
|
||
{
|
||
if (!isPlayback)
|
||
return;
|
||
for (Channel* chn : channelList) {
|
||
if (chn->state == Channel::Pause || chn->state == Channel::Playback) {
|
||
if (type == "forward")
|
||
chn->forward();
|
||
else
|
||
chn->back();
|
||
if (chn->channelName == Constant::MainChannel)
|
||
progressBar->showMax();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 一路回放,将当前选中播放的视频从HDMI-OUT0口输出
|
||
*/
|
||
void Widget::playOneChannel()
|
||
{
|
||
QString filename = ui->listWidget->currentItem()->text();
|
||
QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(curSelectChannel).arg(filename);
|
||
Channel* channel = findChannelByName(Constant::MainChannel);
|
||
for (Channel* chn : channelList) {
|
||
chn->startPlayLive();
|
||
}
|
||
bool ret = channel->startPlayback(path);
|
||
if (!ret)
|
||
progressBar->hide();
|
||
|
||
isPlayback = true;
|
||
curPlayChannel = curSelectChannel;
|
||
curPlayFilename = filename;
|
||
|
||
// 全局保存当前播放的视频
|
||
mutex.lock();
|
||
curFilename = filename;
|
||
condition.wakeAll();
|
||
mutex.unlock();
|
||
|
||
// 保存当前播放视频列表
|
||
QString dirPath = QString("%1/%2").arg(Constant::VideoPath).arg(curPlayChannel);
|
||
fileList = Tool::getFileList(dirPath);
|
||
|
||
int duration = channel->playbackDuration;
|
||
progressBar->setDuration(duration);
|
||
progressBar->setCurrent(0);
|
||
progressBar->show();
|
||
progressBar->showMax();
|
||
progressBar->setState(ProgressBar::Play);
|
||
|
||
if (progressTimer->isActive()) {
|
||
progressTimer->stop();
|
||
progressTimer->start();
|
||
} else {
|
||
progressTimer->start();
|
||
}
|
||
ui->lblA->hide();
|
||
ui->lblB->hide();
|
||
}
|
||
|
||
/**
|
||
* @brief 两路回放,以HDMI-A口的视频为主分别从HDMI-OUT0和HDMI-OUT1输出
|
||
*/
|
||
void Widget::playTwoChannels()
|
||
{
|
||
QString filename = ui->listWidget->currentItem()->text();
|
||
for (Channel* chn : channelList) {
|
||
QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(chn->channelName).arg(filename);
|
||
int ret = chn->startPlayback(path);
|
||
if (chn->channelName == Constant::MainChannel) {
|
||
if (ret) {
|
||
int duration = chn->playbackDuration;
|
||
progressBar->setDuration(duration);
|
||
progressBar->setCurrent(0);
|
||
progressBar->show();
|
||
progressBar->showMax();
|
||
progressBar->setState(ProgressBar::Play);
|
||
if (progressTimer->isActive()) {
|
||
progressTimer->stop();
|
||
progressTimer->start();
|
||
} else {
|
||
progressTimer->start();
|
||
}
|
||
ui->lblA->hide();
|
||
ui->lblB->hide();
|
||
} else {
|
||
progressBar->hide();
|
||
}
|
||
}
|
||
}
|
||
|
||
curPlayFilename = filename;
|
||
curPlayChannel = Constant::MainChannel;
|
||
isPlayback = true;
|
||
|
||
// 全局保存正在播放的视频
|
||
mutex.lock();
|
||
curFilename = filename;
|
||
condition.wakeAll();
|
||
mutex.unlock();
|
||
|
||
// 将HDMI-A通道的视频列表作为主列表
|
||
QString dirPath = QString("%1/%2").arg(Constant::VideoPath).arg(Constant::MainChannel);
|
||
fileList = Tool::getFileList(dirPath);
|
||
}
|
||
|
||
/**
|
||
* @brief 当前视频播放完毕
|
||
*/
|
||
void Widget::onPlayEnd()
|
||
{
|
||
// 当两路回放时,只处理一次槽函数
|
||
if (playbackMode == Constant::TwoChannelPlayback) {
|
||
LinkObject* file = static_cast<LinkObject*>(sender());
|
||
for (Channel* chn : channelList) {
|
||
if (chn->file == file) {
|
||
if (chn->channelName != Constant::MainChannel) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 计算下一个文件名
|
||
int index = fileList.indexOf(curPlayFilename);
|
||
if (index == fileList.length() - 1) {
|
||
QString dirPath = QString("%1/%2").arg(Constant::VideoPath).arg(curPlayChannel);
|
||
fileList = Tool::getFileList(dirPath);
|
||
if (index == fileList.length() - 1) {
|
||
Log::info("no file to play");
|
||
return;
|
||
}
|
||
Log::info("refresh current play file list");
|
||
}
|
||
curPlayFilename = fileList.at(index + 1);
|
||
|
||
// 播放下一个视频
|
||
if (playbackMode == Constant::OneChannelPlayback) {
|
||
Channel* chn = findChannelByName(Constant::MainChannel);
|
||
if (chn) {
|
||
QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(curPlayChannel).arg(curPlayFilename);
|
||
bool ret = chn->startPlayback(path);
|
||
if (chn->channelName == Constant::MainChannel) {
|
||
if (!ret) {
|
||
progressBar->hide();
|
||
return;
|
||
}
|
||
int duration = chn->playbackDuration;
|
||
progressBar->setDuration(duration);
|
||
progressBar->showMax();
|
||
}
|
||
}
|
||
} else if (playbackMode == Constant::TwoChannelPlayback) {
|
||
for (Channel* chn : channelList) {
|
||
QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(chn->channelName).arg(curPlayFilename);
|
||
bool ret = chn->startPlayback(path);
|
||
if (chn->channelName == Constant::MainChannel) {
|
||
if (!ret) {
|
||
progressBar->hide();
|
||
continue;
|
||
}
|
||
int duration = chn->playbackDuration;
|
||
progressBar->setDuration(duration);
|
||
progressBar->showMax();
|
||
}
|
||
}
|
||
}
|
||
|
||
mutex.lock();
|
||
curFilename = curPlayFilename;
|
||
condition.wakeAll();
|
||
mutex.unlock();
|
||
}
|
||
|
||
/**
|
||
* @brief 显示录制状态的槽函数
|
||
* @param show 是否正在录制
|
||
*/
|
||
void Widget::onShowRecordLabel(bool show)
|
||
{
|
||
Channel* chn = static_cast<Channel*>(sender());
|
||
QLabel* label;
|
||
if (chn->channelName == Constant::MainChannel) {
|
||
label = ui->lblA;
|
||
} else {
|
||
label = ui->lblB;
|
||
}
|
||
if (show) {
|
||
label->setPixmap(QPixmap(":/images/record.png"));
|
||
} else
|
||
label->setPixmap(QPixmap(":/images/no-record.png"));
|
||
}
|
||
|
||
/**
|
||
* @brief 根据名称查找通道
|
||
* @param name 通道名称
|
||
*/
|
||
Channel* Widget::findChannelByName(QString name)
|
||
{
|
||
for (Channel* chn : channelList) {
|
||
if (chn->channelName == name) {
|
||
return chn;
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|