#include "Widget.h" #include "Channel.h" #include "Constant.h" #include "Json.h" #include "Log.h" #include "SerialPortTool.h" #include "TcpServer.h" #include "Tool.h" #include "ui_Widget.h" #include #include #include #include #include #include // 认为两个视频是连续的时间间隔阈值 #define TIME_GAP_THRRSHOLD 2 // 多线程中使用 QMutex mutex; QWaitCondition condition; QString curFilename; extern QList channelList; extern Constant::PlaybackMode playbackMode; extern SerialPortTool* serialPortTool; Widget::Widget(QWidget* parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); ui->lblImgA->setPixmap(QPixmap(":/images/no-record.png")); ui->lblImgB->setPixmap(QPixmap(":/images/no-record.png")); ui->lblTxtA->setText("未录制"); ui->lblTxtB->setText("未录制"); ui->recordWidget->hide(); ui->timeSlider->hide(); ui->timeSlider->setOpacity(0.5); menu = new Menu(); menu->hide(); // 设置是否显示通道选择 menu->setChannelSelectVisible(playbackMode == Constant::OneChannelPlayback); connect(menu, SIGNAL(btnPlaybackClicked(QVariantMap, int)), this, SLOT(onBtnPlaybackClicked(QVariantMap, int))); connect(menu, SIGNAL(resolutionOutAChanged(int)), this, SLOT(onResolutionOutAChanged(int))); connect(menu, SIGNAL(resolutionOutBChanged(int)), this, SLOT(onResolutionOutBChanged(int))); // 每5秒更新一次进度条 progressTimer = new QTimer(); progressTimer->setInterval(200); 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))); connect(chn, SIGNAL(appendOneVideo()), this, SLOT(onAppendVideo())); // 监听输入信号的变化 connect(chn->videoInput, &LinkObject::newEvent, [=](QString type, QVariant msg) { if (type == "signal") { QVariantMap data = msg.toMap(); bool available = data["avalible"].toBool(); if (available) { Log::info("video input {} is available", chn->channelName == Constant::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel); if (chn->channelName == Constant::MainChannel) { serialPortTool->onMainChannelOn(); QThread::msleep(100); } else { serialPortTool->onSecondaryChannelOn(); QThread::msleep(100); } } else { Log::info("video input {} is not available", chn->channelName == Constant::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel); if (chn->channelName == Constant::MainChannel) { serialPortTool->onMainChannelOff(); QThread::msleep(100); } else { serialPortTool->onSecondaryChannelOff(); QThread::msleep(100); } } } }); } connect(serialPortTool, SIGNAL(btnMenuClicked()), this, SLOT(onBtnMenuClicked())); connect(serialPortTool, SIGNAL(btnUpClicked()), this, SLOT(onBtnUpClicked())); connect(serialPortTool, SIGNAL(btnDownClicked()), this, SLOT(onBtnDownClicked())); connect(serialPortTool, SIGNAL(btnLeftClicked()), this, SLOT(onBtnLeftClicked())); connect(serialPortTool, SIGNAL(btnRightClicked()), this, SLOT(onBtnRightClicked())); connect(serialPortTool, SIGNAL(btnConfirmClicked()), this, SLOT(onBtnConfirmClicked())); connect(serialPortTool, SIGNAL(btnReturnClicked()), this, SLOT(onBtnReturnClicked())); connect(serialPortTool, SIGNAL(btnVolumnUpClicked()), this, SLOT(onBtnVolumnUpClicked())); connect(serialPortTool, SIGNAL(btnVolumnDownClicked()), this, SLOT(onBtnVolumnDownClicked())); } Widget::~Widget() { delete ui; delete progressTimer; // delete menu; } /** * @brief 实时获取当前回放视频的位置 */ void Widget::onProgressTimeout() { // 获取当前回放的通道名称 QString channelName; // 如果时一路回放则取当前回放的时候 if (playbackMode == Constant::OneChannelPlayback) { DatabaseManager::Channel curChn = static_cast(playbackParams.value("channel", 1).toInt()); channelName = curChn == DatabaseManager::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel; } // 否则取主通道 else { channelName = Constant::MainChannel; } Channel* chn = findChannelByName(channelName); if (chn) { // 获取当前播放位置,单位s int pos = chn->inputFile->invoke("getPosition").toInt() / 1000; int cur = segments[curSegmentIndex].startTime + pos; ui->timeSlider->setCurrent(cur); } } /** * @brief 按下菜单按键 */ void Widget::onBtnMenuClicked() { if (!menu->isVisible()) { menu->show(); menu->moveToCenter(); } } /** * @brief 按下返回按键。优先级:菜单 > 播放 */ void Widget::onBtnReturnClicked() { // 关闭菜单 if (menu->isVisible()) { menu->hide(); return; } // 关闭回放 if (isPlayback) { // 停止回放 for (Channel* chn : channelList) { if (chn->state != Channel::Stop) { chn->startPlayLive(); serialPortTool->onPlaybackEnd(); } } progressTimer->stop(); isPlayback = false; ui->timeSlider->hide(); } } /** * @brief 按下上键。优先级: 菜单 > 播放 */ void Widget::onBtnUpClicked() { if (menu->isVisible()) { menu->move(Menu::Up); return; } // 时间轴选中移动到上一个视频的开始 if (isPlayback) { playPreSegment(); } } /** * @brief 按下下键。 优先级: 菜单 > 播放 */ void Widget::onBtnDownClicked() { if (menu->isVisible()) { menu->move(Menu::Down); return; } // 时间轴移动到下一个视频 if (isPlayback) { playNextSegemnt(); } } /** * @brief 按下左键。优先级: 菜单 > 播放。 */ void Widget::onBtnLeftClicked() { if (menu->isVisible()) { menu->move(Menu::Left); return; } if (isPlayback) { seek("back"); } } /** * @brief 按下左键。优先级: 菜单 > 播放 */ void Widget::onBtnRightClicked() { if (menu->isVisible()) { menu->move(Menu::Right); return; } if (isPlayback) { seek("forward"); } } /** * @brief 按下ok键。优先级: 菜单 > 回放 */ void Widget::onBtnConfirmClicked() { if (menu->isVisible()) { menu->confirm(); return; } if (isPlayback) { // 切换暂停和播放 for (Channel* chn : channelList) { if (chn->state == Channel::Playback || chn->state == Channel::Pause) { chn->togglePause(); if (chn->channelName == Constant::MainChannel) { if (chn->state == Channel::Pause) { // 打开暂停灯 serialPortTool->onPlayPause(); } else { // 关闭暂停灯 serialPortTool->onPlayResume(); } } } } } } /** * @brief 增大音量 */ void Widget::onBtnVolumnUpClicked() { Channel::volumeUp(); } /** * @brief 减小音量 */ void Widget::onBtnVolumnDownClicked() { Channel::volumeDown(); } /** * @brief 播放视频 * @param params 参数(通道,时间) * @param segmentIndex 时间片索引 */ void Widget::onBtnPlaybackClicked(QVariantMap params, int segmentIndex) { if (segmentIndex < 0) { return; } // 获取当前通道 segments = Tool::calTimeSegments(params); if (segmentIndex > segments.length() - 1) { return; } playbackParams = params; curSegmentIndex = segmentIndex; isPlayback = true; // 显示时间轴 ui->timeSlider->show(); ui->timeSlider->setTimeSegments(segments); ui->timeSlider->setCurrent(segments[segmentIndex].startTime); QString filename = segments[segmentIndex].filename; // 开始回放 if (playbackMode == Constant::OneChannelPlayback) { playOneChannel(filename); } else { playTwoChannels(filename); } } /** * @brief 通道A分辨率修改 * @param resolution 分辨率字符串 */ void Widget::onResolutionOutAChanged(int resolution) { for (Channel* chn : channelList) { if (chn->channelName == Constant::MainChannel) { chn->changeOutputResolution(static_cast(resolution)); switch (resolution) { case 0: setFixedSize(1920, 1080); break; case 1: setFixedSize(1600, 1200); break; case 2: setFixedSize(1280, 1024); break; case 3: setFixedSize(1024, 768); break; case 4: setFixedSize(800, 600); break; default: break; } menu->restartUI(); menu->update(); update(); } } } /** * @brief 通道B分辨率修改 * @param resolution */ void Widget::onResolutionOutBChanged(int resolution) { for (Channel* chn : channelList) { if (chn->channelName == Constant::SecondaryChannel) { chn->changeOutputResolution(static_cast(resolution)); } } } void Widget::onResolutionInAChanged(int width, int height) { for (Channel* chn : channelList) { if (chn->channelName == Constant::MainChannel) { chn->changeInputResolution(width, height); } } } void Widget::onResolutionInBChanged(int width, int height) { for (Channel* chn : channelList) { if (chn->channelName == Constant::SecondaryChannel) { chn->changeInputResolution(width, height); } } } /** * @brief 一路回放,将当前选中播放的视频从HDMI-OUT0口输出 */ void Widget::playOneChannel(QString filename) { // 获取当前回放的通道 QString curPlayChannel = menu->getCurPlayChannel() == DatabaseManager::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel; QString path = QString("%1/%2/%3").arg(Constant::VideoPath).arg(curPlayChannel).arg(filename); Channel* channel = findChannelByName(Constant::MainChannel); for (Channel* chn : channelList) { chn->startPlayLive(); } bool ret = channel->startPlayback(path); if (!ret) { Log::info("play back error"); } isPlayback = true; // 全局保存当前播放的视频的文件名 mutex.lock(); curFilename = filename; condition.wakeAll(); mutex.unlock(); if (progressTimer->isActive()) { progressTimer->stop(); progressTimer->start(); } else { progressTimer->start(); } ui->recordWidget->hide(); } /** * @brief 两路回放,以主通道的视频为主分别从HDMI-OUT0和HDMI-OUT1输出 */ void Widget::playTwoChannels(QString filename) { 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) { if (progressTimer->isActive()) { progressTimer->stop(); progressTimer->start(); } else { progressTimer->start(); } ui->recordWidget->hide(); } else { ui->recordWidget->hide(); } } } isPlayback = true; // 全局保存正在播放的视频的文件名 mutex.lock(); curFilename = filename; condition.wakeAll(); mutex.unlock(); } /** * @brief 视频跳转 */ void Widget::seek(QString type) { if (!isPlayback) return; QString channelName; if (playbackMode == Constant::OneChannelPlayback) { DatabaseManager::Channel curChn = static_cast(playbackParams.value("channel", 1).toInt()); channelName = curChn == DatabaseManager::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel; } else { channelName = Constant::MainChannel; } Channel* chn = findChannelByName(channelName); int pos; // 获取当前播放的位置 if (chn) { pos = chn->inputFile->invoke("getPosition").toInt() / 1000; } if (type == "forward") { pos += 10; // 目标位置超过当前视频的时长 if (pos > segments[curSegmentIndex].duration) { // 如果是最后一个视频,则跳转到视频最后为止 if (curSegmentIndex == segments.length() - 1) { pos = segments[curSegmentIndex].duration; } // 如果不是最后一个视频 else { int gap = segments[curSegmentIndex + 1].startTime - segments[curSegmentIndex].startTime - segments[curSegmentIndex].duration; // 视频是连续的就跳转到响应为止,认为连续的条件是间隔小于2S if (gap <= TIME_GAP_THRRSHOLD) { pos = pos - segments[curSegmentIndex].duration - gap; curSegmentIndex++; QString filename = segments[curSegmentIndex].filename; // 开始回放 if (playbackMode == Constant::OneChannelPlayback) { playOneChannel(filename); } else { playTwoChannels(filename); } } // 视频不是连续的 else { pos = segments[curSegmentIndex].duration; } } } } else { pos -= 10; // 目标位置小于0 if (pos < 0) { if (curSegmentIndex == 0) { pos = 0; } else { int gap = segments[curSegmentIndex].startTime - segments[curSegmentIndex - 1].startTime - segments[curSegmentIndex - 1].duration; // 视频是连续的 if (gap <= TIME_GAP_THRRSHOLD) { pos = segments[curSegmentIndex - 1].duration + gap + pos; curSegmentIndex--; QString filename = segments[curSegmentIndex].filename; // 重新回放 if (playbackMode == Constant::OneChannelPlayback) { playOneChannel(filename); } else { playTwoChannels(filename); } } // 视频不是连续的 else { pos = 0; } } } } for (Channel* chn : channelList) { if (chn->state == Channel::Playback) { chn->inputFile->invoke("seek", pos * 1000); Log::info("{} seek {} 10s", chn->channelName.toStdString(), type.toStdString()); } } } /** * @brief 当前视频播放完毕 */ void Widget::onPlayEnd() { // 停下回放计时器 progressTimer->stop(); // 当两路回放时,只处理一次槽函数 if (playbackMode == Constant::TwoChannelPlayback) { LinkObject* file = static_cast(sender()); for (Channel* chn : channelList) { if (chn->inputFile == file) { if (chn->channelName != Constant::MainChannel) { return; } } } } // 如果时列表最后一个视频则显示播放结束 if (curSegmentIndex == segments.length() - 1) { for (Channel* chn : channelList) { chn->showFinishPromot(); } } // 不是列表最后一个就播放下一个视频 else { curSegmentIndex++; ui->timeSlider->setCurrent(segments[curSegmentIndex].startTime); QString filename = segments[curSegmentIndex].filename; if (playbackMode == Constant::OneChannelPlayback) { playOneChannel(filename); } else { playTwoChannels(filename); } } } /** * @brief 显示录制状态的槽函数 * @param show 是否正在录制 */ void Widget::onShowRecordLabel(bool show) { Channel* chn = static_cast(sender()); QLabel* lblImg; QLabel* lblTxt; if (chn->channelName == Constant::MainChannel) { lblImg = ui->lblImgA; lblTxt = ui->lblTxtA; } else { lblImg = ui->lblImgB; lblTxt = ui->lblTxtB; } if (show) { lblImg->setPixmap(QPixmap(":/images/record.png")); lblTxt->setText("录制中"); } else { lblImg->setPixmap(QPixmap(":/images/no-record.png")); lblTxt->setText("未录制"); } } /** * @brief 根据名称查找通道 * @param name 通道名称 */ Channel* Widget::findChannelByName(QString name) { for (Channel* chn : channelList) { if (chn->channelName == name) { return chn; } } return nullptr; } /** * @brief 播放下一个视频片段 */ void Widget::playNextSegemnt() { if (curSegmentIndex < segments.length() - 1) { curSegmentIndex++; // 设置时间轴指针的位置 int cur = segments[curSegmentIndex].startTime; ui->timeSlider->setCurrent(cur); // 跳转到下一个视频 QString filename = segments[curSegmentIndex].filename; if (playbackMode == Constant::OneChannelPlayback) { playOneChannel(filename); } else { playTwoChannels(filename); } Log::info("play next segment, filename: {}", filename.toStdString()); } } /** * @brief 播放上一个视频片段 */ void Widget::playPreSegment() { if (curSegmentIndex > 0) { curSegmentIndex--; // 设置时间轴指针的位置 int cur = segments[curSegmentIndex].startTime; ui->timeSlider->setCurrent(cur); // 跳转到下一个视频 QString filename = segments[curSegmentIndex].filename; if (playbackMode == Constant::OneChannelPlayback) { playOneChannel(filename); } else { playTwoChannels(filename); } Log::info("play previous segment, filename: {}", filename.toStdString()); } } /** * @brief 一个录制片段完成的槽函数 */ void Widget::onAppendVideo() { if (!isPlayback) { return; } Channel* chn = static_cast(sender()); // 两路回放需要是主通道 if (playbackMode == Constant::TwoChannelPlayback) { if (chn->channelName != Constant::MainChannel) { return; } } // 一路回放需要回放通道和录制通道一致 else { DatabaseManager::Channel c = static_cast(playbackParams.value("channel", 1).toInt()); QString channelName = c == DatabaseManager::MainChannel ? Constant::MainChannel : Constant::SecondaryChannel; if (chn->channelName != channelName) { return; } } // 刷新时间轴中时间片的显示 segments = Tool::calTimeSegments(playbackParams); ui->timeSlider->setTimeSegments(segments); } void Widget::update() { QRect deskRect = QApplication::desktop()->availableGeometry(); qDebug() << "desktop rect:" << deskRect; QWidget::update(); }