Qt-视频九宫格布局

一、概述

最近在做一个Qt项目,涉及视频播放功能,怎么做都感觉差点意思,在刷抖音的时候发现这种九宫格布局看这挺舒服的于是就用qt写了一下,涉及一点ffmpeg的知识,不过不多(只是显示视频的第一帧做封面),想记录一下,于是就有了这个文章,希望能交流学下一哈......

二、项目结构

homepage:主页面,创建九宫格,协调videoitemwidget和videoplayerwindow

videoitemwidget:利用ffmpeg生成缩略图,以及其他视频信息展示

videoplayerwindow:播放视频(利用qt内置的api:Multimedia实现视频播放功能)

三、具体代码和注释

homepage.h

cpp 复制代码
#ifndef HOMEPAGE_H
#define HOMEPAGE_H

#include <QWidget>
#include <QGridLayout>
#include <QScrollArea>

namespace Ui {
class homepage;
}

class homepage : public QWidget
{
    Q_OBJECT

public:
    explicit homepage(QWidget *parent = nullptr);
    ~homepage();

private slots:
    void on_openButton_clicked();
    void onVideoItemClicked();

private:
    Ui::homepage *ui;
    QGridLayout *gridLayout;
    QWidget *gridContainer;

    void clearPlaceholder();
    void setupGridLayout();
};

#endif // HOMEPAGE_H

homepage.cpp

cpp 复制代码
#include "homepage.h"
#include "ui_homepage.h"
#include "videoplayerwindow.h"
#include "videoitemwidget.h"
#include <QDir>
#include <QFileDialog>
#include <QLabel>

homepage::homepage(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::homepage)
    , gridLayout(nullptr)//初始化网格布局
    , gridContainer(nullptr)//初始化网格容器指针为空
{
    ui->setupUi(this);

    // 设置九宫格网格布局
    setupGridLayout();

    // 初始提示项
    QLabel *placeholderLabel = new QLabel("点击「添加视频」按钮导入视频文件");
    placeholderLabel->setAlignment(Qt::AlignCenter);
    placeholderLabel->setStyleSheet(R"(
        color: #7f8c8d;
        font-size: 16px;
        padding: 100px;
    )");
    gridLayout->addWidget(placeholderLabel, 0, 0, 1, 1);
}

homepage::~homepage()
{
    delete ui;
}

void homepage::setupGridLayout()
{
    // 创建网格容器
    gridContainer = new QWidget();
    //创建网格布局
    gridLayout = new QGridLayout(gridContainer);
    gridLayout->setSpacing(20);
    gridLayout->setContentsMargins(20, 20, 20, 20);
    gridLayout->setAlignment(Qt::AlignTop);

    // 设置滚动区域
    ui->scrollArea->setWidget(gridContainer);//将网络容器设置为滚动区域的空间
    ui->scrollArea->setWidgetResizable(true);//允许滚动区域调整大小
    ui->scrollArea->setStyleSheet("QScrollArea { border: none; background: transparent; }");
}

void homepage::clearPlaceholder()
{
    // 清除初始提示
    if (gridLayout->count() == 1) {
        QLayoutItem *item = gridLayout->itemAt(0);
        if (item && item->widget()) {
            QLabel *label = qobject_cast<QLabel*>(item->widget());
            if (label && label->text() == "点击「添加视频」按钮导入视频文件") {
                delete item->widget();
            }
        }
    }
}

void homepage::on_openButton_clicked()
{
    const QString currentPath = QDir::currentPath();
    const QString dlgTitle = "选择视频文件";
    const QString filter = QStringLiteral("视频文件 (*.mp4 *.avi *.mkv *.mov *.wmv *.flv);;所有文件 (*.*)");

    const QStringList filePaths = QFileDialog::getOpenFileNames(this, dlgTitle, currentPath, filter);

    if (filePaths.isEmpty()) {
        qDebug() << "未选择文件";
        return;
    }

    // 清除初始提示
    clearPlaceholder();

    // 添加视频文件到网格布局
    for (const QString &filePath : filePaths) {//遍历所有选择的文件路径
        QFileInfo fileInfo(filePath);//获取文件信息
        const QString fileName = fileInfo.fileName();//提取文件名

        // 创建视频项控件
        VideoItemWidget *videoItem = new VideoItemWidget(fileName, filePath);

        // 连接点击信号
        connect(videoItem, &VideoItemWidget::clicked, this, &homepage::onVideoItemClicked);

        // 计算网格位置
        int count = gridLayout->count();//当前网格项数量
        int row = count / 3;  // 计算行号每行3个
        int col = count % 3;//计算列号

        // 添加到网格布局
        gridLayout->addWidget(videoItem, row, col);

        qDebug() << "已添加视频:" << fileName;
    }
}
//视频项点击槽函数:播放选中的视频
void homepage::onVideoItemClicked()
{
    VideoItemWidget *videoItem = qobject_cast<VideoItemWidget*>(sender());//获取发送信号的视频项
    if (!videoItem) return;

    const QString filePath = videoItem->getFilePath();//获取视频路径
    if (!filePath.isEmpty()) {
        // 创建独立的播放窗口
        VideoPlayerWindow *playerWindow = new VideoPlayerWindow(filePath, nullptr);
        playerWindow->setWindowTitle("视频播放 - " + QFileInfo(filePath).fileName());
        playerWindow->resize(800, 600);

        // 设置窗口标志,使其完全独立
        playerWindow->setWindowFlags(Qt::Window);
        playerWindow->setAttribute(Qt::WA_DeleteOnClose);
        //显示并激活窗口
        playerWindow->show();
        playerWindow->raise();//将窗口置于台前
        playerWindow->activateWindow();//激活窗口

        qDebug() << "正在播放视频:" << filePath;
    }
}

videoitemwidget.h

cpp 复制代码
#ifndef VIDEOITEMWIDGET_H
#define VIDEOITEMWIDGET_H

#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <QMouseEvent>
#include <QGraphicsDropShadowEffect>
#include <QEnterEvent>
#include <QPaintEvent>
#include <QFuture>
#include <QtConcurrent>
#include <QProcess>

class VideoItemWidget : public QWidget
{
    Q_OBJECT

public:
    explicit VideoItemWidget(const QString &fileName, const QString &filePath, QWidget *parent = nullptr);
    ~VideoItemWidget();
    QString getFilePath() const { return filePath; }

signals:
    void clicked();
    void thumbnailReady(const QPixmap& pixmap);

protected:
    void mousePressEvent(QMouseEvent *event) override;
    void enterEvent(QEnterEvent *event) override;
    void leaveEvent(QEvent *event) override;
    void paintEvent(QPaintEvent *event) override;

private slots:
    void onThumbnailReady(const QPixmap& pixmap);

private:
    void setupUI(const QString &fileName);
    void loadVideoThumbnail();
    void createDefaultThumbnail();
    QPixmap generateThumbnailWithFFmpeg();
    QString findFFmpegExecutable();

    QString filePath;
    QLabel *thumbnailLabel;
    QLabel *titleLabel;
    bool thumbnailLoaded;
};

#endif // VIDEOITEMWIDGET_H

videoitemwidget.cpp

cpp 复制代码
#include "videoitemwidget.h"
#include <QPainter>
#include <QStyleOption>
#include <QImage>
#include <QPixmap>
#include <QFileInfo>
#include <QDir>
#include <QApplication>
#include <QStandardPaths>
#include <QProcess>
#include <QDebug>
//初始化视频项目小部件
VideoItemWidget::VideoItemWidget(const QString &fileName, const QString &filePath, QWidget *parent)
    : QWidget(parent), filePath(filePath), thumbnailLoaded(false)
{
    setupUI(fileName);//设置用户界面
    loadVideoThumbnail();//加载视频缩略图
}

VideoItemWidget::~VideoItemWidget()
{
}

void VideoItemWidget::setupUI(const QString &fileName)
{
    // 设置固定大小,适应九宫格布局
    setFixedSize(240, 200);
    // 设置样式
    setStyleSheet(R"(
        VideoItemWidget {
            background-color: white;
            border-radius: 8px;
            border: 1px solid #e0e0e0;
        }
        VideoItemWidget:hover {
            border: 2px solid #3498db;
            background-color: #f8f9fa;
        }
    )");
    // 创建主布局
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->setContentsMargins(8, 8, 8, 8);
    mainLayout->setSpacing(8);
    // 创建缩略图标签
    thumbnailLabel = new QLabel();
    thumbnailLabel->setFixedSize(224, 140);//固定大小
    thumbnailLabel->setAlignment(Qt::AlignCenter);//居中对齐
    thumbnailLabel->setScaledContents(true);//缩放内容以适应标签
    // 先创建默认缩略图
    createDefaultThumbnail();
    // 创建标题标签
    titleLabel = new QLabel(fileName);
    titleLabel->setStyleSheet(R"(
        color: #2c3e50;
        font-size: 14px;
        font-weight: 500;
    )");
    titleLabel->setWordWrap(true);
    titleLabel->setMaximumHeight(40);
    titleLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    // 添加到布局
    mainLayout->addWidget(thumbnailLabel);
    mainLayout->addWidget(titleLabel);
    // 设置光标形状
    setCursor(Qt::PointingHandCursor);
    // 连接缩略图准备信号
    connect(this, &VideoItemWidget::thumbnailReady, this, &VideoItemWidget::onThumbnailReady);
}

void VideoItemWidget::loadVideoThumbnail()
{
    // 在后台线程中使用FFmpeg生成缩略图
    //QtConcurrent::run()会在单独的线程中执行给定的函数,并返回QFuture对象
    QFuture<QPixmap> future = QtConcurrent::run([this]() {
        return this->generateThumbnailWithFFmpeg();
    });

    // 使用QFutureWatcher来监视异步操作的完成状态
    //QFutureWatcher是一个观察者类可以监视QFuture的状态变化
    QFutureWatcher<QPixmap> *watcher = new QFutureWatcher<QPixmap>;
    connect(watcher, &QFutureWatcher<QPixmap>::finished, this, [this, watcher]() {
        QPixmap pixmap = watcher->result();
        if (!pixmap.isNull()) {
            emit thumbnailReady(pixmap);
        }
        watcher->deleteLater();//清理对象
    });//当后台线程完成时会触发这个信号
    watcher->setFuture(future);
}

QString VideoItemWidget::findFFmpegExecutable()//查找FFmpeg可执行文件
{
    // 尝试在多个可能的位置查找 ffmpeg.exe
    QStringList possiblePaths = {
        "ffmpeg.exe",  // 在系统 PATH 中
        "ffmpeg",      // 在系统 PATH 中(非Windows)
        QApplication::applicationDirPath() + "/ffmpeg.exe",//应用程序目录
        QApplication::applicationDirPath() + "/ffmpeg",
        "D:/z_demo/ffmpeg-master-latest-win64-gpl-shared/ffmpeg-master-latest-win64-gpl-shared/bin/ffmpeg.exe"
    };
//遍历所有可能的路径
    for (const QString &path : possiblePaths) {
        QFileInfo fileInfo(path);
        if (fileInfo.exists() && fileInfo.isFile()) {
            qDebug() << "找到 FFmpeg:" << path;
            return path;
        }
    }

    qDebug() << "未找到 FFmpeg 可执行文件";
    return QString();
}

QPixmap VideoItemWidget::generateThumbnailWithFFmpeg()//创建缩略图
{
    QString ffmpegPath = findFFmpegExecutable();//查找 ffmpeg.exe
    if (ffmpegPath.isEmpty()) {
        qDebug() << "无法找到 FFmpeg,使用默认缩略图";
        return QPixmap();
    }

    // 创建临时文件路径
    QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
    QString thumbnailPath = tempDir + "/thumbnail_" +
                            QString::number(QDateTime::currentMSecsSinceEpoch()) +
                            "_" + QString::number(QRandomGenerator::global()->generate()) + ".jpg";

    QProcess ffmpegProcess;

    // 构建 FFmpeg 命令参数
    QStringList arguments;
    arguments << "-i" << filePath                    // 输入文件
              << "-ss" << "00:00:01"                 // 定位到第1秒
              << "-vframes" << "1"                   // 只取1帧
              << "-vf" << "scale=224:140:force_original_aspect_ratio=decrease"  // 缩放
              << "-y"                                // 覆盖输出文件
              << thumbnailPath;                      // 输出文件

    qDebug() << "执行 FFmpeg 命令:" << ffmpegPath << arguments;

    // 启动 FFmpeg 进程
    ffmpegProcess.start(ffmpegPath, arguments);

    // 等待进程完成(最多10秒)
    if (!ffmpegProcess.waitForFinished(10000)) {
        qDebug() << "FFmpeg 处理超时:" << filePath;
        ffmpegProcess.kill();
        return QPixmap();
    }

    // 检查退出状态
    if (ffmpegProcess.exitCode() != 0) {
        QString errorOutput = ffmpegProcess.readAllStandardError();
        qDebug() << "FFmpeg 错误:" << errorOutput;
        return QPixmap();
    }

    // 检查输出文件是否存在
    if (!QFile::exists(thumbnailPath)) {
        qDebug() << "FFmpeg 未生成输出文件:" << thumbnailPath;
        return QPixmap();
    }

    // 加载生成的缩略图
    QPixmap result;
    if (result.load(thumbnailPath)) {
        qDebug() << "成功生成缩略图:" << filePath;
        // 清理临时文件
        QFile::remove(thumbnailPath);
        return result;
    } else {
        qDebug() << "无法加载生成的缩略图:" << thumbnailPath;
        // 清理临时文件
        QFile::remove(thumbnailPath);
        return QPixmap();
    }
}

void VideoItemWidget::createDefaultThumbnail()
{
    QPixmap thumbnail(224, 140);

    // 根据文件扩展名选择不同的图标
    QString extension = QFileInfo(filePath).suffix().toLower();
    QString icon = "🎬"; // 默认图标

    if (extension == "mp4") icon = "📹";
    else if (extension == "avi") icon = "🎞️";
    else if (extension == "mkv") icon = "📺";
    else if (extension == "mov") icon = "🎥";
    else if (extension == "wmv") icon = "📽️";
    else if (extension == "flv") icon = "🔴";

    // 创建渐变背景
    QLinearGradient gradient(0, 0, 0, 140);
    gradient.setColorAt(0, QColor(52, 152, 219));
    gradient.setColorAt(1, QColor(41, 128, 185));

    thumbnail.fill(Qt::transparent);

    QPainter painter(&thumbnail);
    painter.setRenderHint(QPainter::Antialiasing);

    // 绘制背景
    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);
    painter.drawRoundedRect(0, 0, 224, 140, 6, 6);

    // 绘制图标和文字
    painter.setPen(Qt::white);
    painter.setFont(QFont("Arial", 24, QFont::Bold));
    painter.drawText(QRect(0, 30, 224, 50), Qt::AlignCenter, icon);

    painter.setFont(QFont("Arial", 10, QFont::Normal));
    painter.drawText(QRect(0, 80, 224, 40), Qt::AlignCenter, "视频文件");
    painter.drawText(QRect(0, 100, 224, 20), Qt::AlignCenter, extension.toUpper());

    painter.end();

    thumbnailLabel->setPixmap(thumbnail);
}

void VideoItemWidget::onThumbnailReady(const QPixmap& pixmap)
{
    if (!pixmap.isNull()) {
        thumbnailLabel->setPixmap(pixmap);
        thumbnailLabel->setText("");
        thumbnailLoaded = true;
        qDebug() << "成功加载视频缩略图:" << QFileInfo(filePath).fileName();
    }
}

void VideoItemWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        emit clicked();//发射点击信号
    }
    QWidget::mousePressEvent(event);//调用基类处理
}

void VideoItemWidget::enterEvent(QEnterEvent *event)//鼠标进入事件:添加阴影效果
{
    QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect(this);
    effect->setBlurRadius(20);
    effect->setColor(QColor(0, 0, 0, 80));
    effect->setOffset(0, 4);
    setGraphicsEffect(effect);

    QWidget::enterEvent(event);
}

void VideoItemWidget::leaveEvent(QEvent *event)
{
    setGraphicsEffect(nullptr);//移除阴影效果
    QWidget::leaveEvent(event);
}

void VideoItemWidget::paintEvent(QPaintEvent *event)
{
    QStyleOption opt;
    opt.initFrom(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

videoplayerwindow.h

cpp 复制代码
#ifndef VIDEOPLAYERWINDOW_H
#define VIDEOPLAYERWINDOW_H

#include <QWidget>
#include <QMediaPlayer>
#include <QAudioOutput>
#include <QVideoWidget>
#include <QSlider>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QKeyEvent>

class VideoPlayerWindow : public QWidget
{
    Q_OBJECT

public:
    explicit VideoPlayerWindow(const QString &videoPath, QWidget *parent = nullptr);
    ~VideoPlayerWindow();

protected:
    void keyPressEvent(QKeyEvent *event) override;

private slots:
    void onPlaybackStateChanged(QMediaPlayer::PlaybackState state);
    void onDurationChanged(qint64 duration);
    void onPositionChanged(qint64 position);
    void onPlayButtonClicked();
    void onPauseButtonClicked();
    void onMuteButtonClicked();
    void onVolumeSliderValueChanged(int value);
    void onProgressSliderMoved(int position);
    void toggleFullscreen();

private:
    void setupUI();

    QMediaPlayer *player;
    QAudioOutput *audioOutput;
    QVideoWidget *videoWidget;

    QPushButton *playButton;
    QPushButton *pauseButton;
    QPushButton *muteButton;
    QPushButton *fullScreenButton;
    QSlider *volumeSlider;
    QSlider *progressSlider;
    QLabel *timeLabel;
    QLabel *titleLabel;

    bool isMuted = false;
    int previousVolume = 70;
    bool isFullScreen = false;
    QString videoFilePath;
};

#endif // VIDEOPLAYERWINDOW_H

videoplayerwindow.cpp

cpp 复制代码
#include "videoplayerwindow.h"
#include <QFileInfo>
#include <QDebug>

VideoPlayerWindow::VideoPlayerWindow(const QString &videoPath, QWidget *parent)
    : QWidget(parent), videoFilePath(videoPath)
{
    setupUI();

    // 初始化媒体播放器
    player = new QMediaPlayer(this);
    audioOutput = new QAudioOutput(this);
    player->setAudioOutput(audioOutput);
    player->setVideoOutput(videoWidget);

    // 设置初始音量
    audioOutput->setVolume(0.7);

    // 连接信号槽
    connect(player, &QMediaPlayer::playbackStateChanged, this, &VideoPlayerWindow::onPlaybackStateChanged);
    connect(player, &QMediaPlayer::durationChanged, this, &VideoPlayerWindow::onDurationChanged);
    connect(player, &QMediaPlayer::positionChanged, this, &VideoPlayerWindow::onPositionChanged);

    // 设置视频文件
    QUrl fileUrl = QUrl::fromLocalFile(videoPath);
    player->setSource(fileUrl);

    // 设置窗口标题
    QFileInfo fileInfo(videoPath);
    setWindowTitle(fileInfo.fileName());
    titleLabel->setText(fileInfo.fileName());

    qDebug() << "Video player window created for:" << videoPath;
}

VideoPlayerWindow::~VideoPlayerWindow()
{
}

void VideoPlayerWindow::setupUI()
{
    setWindowTitle("视频播放器");
    setMinimumSize(800, 600);

    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);

    // 视频控件
    videoWidget = new QVideoWidget(this);
    videoWidget->setMinimumSize(640, 360);
    videoWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    mainLayout->addWidget(videoWidget,1);

    // 控制面板
    QWidget *controlPanel = new QWidget(this);
    controlPanel->setStyleSheet("background-color: #2c3e50;");
    controlPanel->setMaximumHeight(60);

    QHBoxLayout *controlLayout = new QHBoxLayout(controlPanel);
    controlLayout->setContentsMargins(10, 5, 10, 5);
    controlLayout->setSpacing(10);

    // 文件名标签
    titleLabel = new QLabel("视频播放器");
    titleLabel->setStyleSheet("color: white; font-size: 14px; font-weight: bold;");
    titleLabel->setMaximumWidth(200);
    titleLabel->setWordWrap(true);

    // 播放控制按钮
    playButton = new QPushButton("播放");
    pauseButton = new QPushButton("暂停");
    muteButton = new QPushButton("🔊");
    fullScreenButton = new QPushButton("全屏");

    QString buttonStyle = "QPushButton {"
                          "background-color: #3498db;"
                          "color: white;"
                          "border: none;"
                          "padding: 8px 12px;"
                          "border-radius: 4px;"
                          "font-size: 12px;"
                          "}"
                          "QPushButton:hover {"
                          "background-color: #2980b9;"
                          "}"
                          "QPushButton:disabled {"
                          "background-color: #7f8c8d;"
                          "}";

    playButton->setStyleSheet(buttonStyle);
    pauseButton->setStyleSheet(buttonStyle);
    muteButton->setStyleSheet(buttonStyle);
    fullScreenButton->setStyleSheet(buttonStyle);

    // 音量滑块
    volumeSlider = new QSlider(Qt::Horizontal);
    volumeSlider->setRange(0, 100);
    volumeSlider->setValue(70);
    volumeSlider->setMaximumWidth(100);
    volumeSlider->setStyleSheet(
        "QSlider::groove:horizontal {"
        "border: 1px solid #999999;"
        "height: 4px;"
        "background: #34495e;"
        "border-radius: 2px;"
        "}"
        "QSlider::handle:horizontal {"
        "background: #e74c3c;"
        "border: 1px solid #e74c3c;"
        "width: 10px;"
        "margin: -3px 0;"
        "border-radius: 5px;"
        "}"
        "QSlider::sub-page:horizontal {"
        "background: #e74c3c;"
        "border-radius: 2px;"
        "}");

    // 进度条
    progressSlider = new QSlider(Qt::Horizontal);
    progressSlider->setStyleSheet(
        "QSlider::groove:horizontal {"
        "border: 1px solid #999999;"
        "height: 6px;"
        "background: #34495e;"
        "border-radius: 3px;"
        "}"
        "QSlider::handle:horizontal {"
        "background: #3498db;"
        "border: 1px solid #3498db;"
        "width: 12px;"
        "margin: -5px 0;"
        "border-radius: 6px;"
        "}"
        "QSlider::sub-page:horizontal {"
        "background: #3498db;"
        "border-radius: 3px;"
        "}");

    // 时间标签
    timeLabel = new QLabel("00:00 / 00:00");
    timeLabel->setStyleSheet("color: white; font-size: 12px;");

    // 添加到控制面板
    controlLayout->addWidget(titleLabel);
    controlLayout->addWidget(playButton);
    controlLayout->addWidget(pauseButton);
    controlLayout->addWidget(muteButton);
    controlLayout->addWidget(volumeSlider);
    controlLayout->addWidget(progressSlider, 1); // 给进度条更多空间
    controlLayout->addWidget(timeLabel);
    controlLayout->addWidget(fullScreenButton);

    mainLayout->addWidget(controlPanel);

    // 连接按钮信号
    connect(playButton, &QPushButton::clicked, this, &VideoPlayerWindow::onPlayButtonClicked);
    connect(pauseButton, &QPushButton::clicked, this, &VideoPlayerWindow::onPauseButtonClicked);
    connect(muteButton, &QPushButton::clicked, this, &VideoPlayerWindow::onMuteButtonClicked);
    connect(fullScreenButton, &QPushButton::clicked, this, &VideoPlayerWindow::toggleFullscreen);
    connect(volumeSlider, &QSlider::valueChanged, this, &VideoPlayerWindow::onVolumeSliderValueChanged);
    connect(progressSlider, &QSlider::sliderMoved, this, &VideoPlayerWindow::onProgressSliderMoved);

    // 初始按钮状态
    playButton->setEnabled(true);
    pauseButton->setEnabled(false);
}

void VideoPlayerWindow::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Escape && isFullScreen) {
        toggleFullscreen();
        event->accept();
    }
    else if(event->key() == Qt::Key_F11)
    {
        toggleFullscreen();
        event->accept();
    }
    else if (event->key() == Qt::Key_Space) {
        if (player->playbackState() == QMediaPlayer::PlayingState) {
            onPauseButtonClicked();
        } else {
            onPlayButtonClicked();
        }
        event->accept();
    } else {
        QWidget::keyPressEvent(event);
    }
}

void VideoPlayerWindow::onPlaybackStateChanged(QMediaPlayer::PlaybackState state)
{
    switch (state) {
    case QMediaPlayer::StoppedState:
        playButton->setEnabled(true);
        pauseButton->setEnabled(false);
        break;
    case QMediaPlayer::PlayingState:
        playButton->setEnabled(false);
        pauseButton->setEnabled(true);
        break;
    case QMediaPlayer::PausedState:
        playButton->setEnabled(true);
        pauseButton->setEnabled(false);
        break;
    default:
        break;
    }
}

void VideoPlayerWindow::onDurationChanged(qint64 duration)
{
    progressSlider->setRange(0, duration);

    int sec = duration / 1000;
    int min = sec / 60;
    sec = sec % 60;
    int hour = min / 60;
    min = min % 60;

    QString totalTime;
    if (hour > 0) {
        totalTime = QString::asprintf("%02d:%02d:%02d", hour, min, sec);
    } else {
        totalTime = QString::asprintf("%02d:%02d", min, sec);
    }

    QString currentTime = timeLabel->text().split(" / ").first();
    timeLabel->setText(currentTime + " / " + totalTime);
}

void VideoPlayerWindow::onPositionChanged(qint64 position)
{
    if (!progressSlider->isSliderDown()) {
        progressSlider->setValue(position);
    }

    int sec = position / 1000;
    int min = sec / 60;
    sec = sec % 60;
    int hour = min / 60;
    min = min % 60;

    QString currentTime;
    if (hour > 0) {
        currentTime = QString::asprintf("%02d:%02d:%02d", hour, min, sec);
    } else {
        currentTime = QString::asprintf("%02d:%02d", min, sec);
    }

    QString totalTime = timeLabel->text().split(" / ").last();
    timeLabel->setText(currentTime + " / " + totalTime);
}

void VideoPlayerWindow::onPlayButtonClicked()
{
    player->play();
}

void VideoPlayerWindow::onPauseButtonClicked()
{
    player->pause();
}

void VideoPlayerWindow::onMuteButtonClicked()
{
    isMuted = !isMuted;
    audioOutput->setMuted(isMuted);

    if (isMuted) {
        muteButton->setText("🔇");
        previousVolume = volumeSlider->value();
        volumeSlider->setValue(0);
    } else {
        muteButton->setText("🔊");
        volumeSlider->setValue(previousVolume);
    }
}

void VideoPlayerWindow::onVolumeSliderValueChanged(int value)
{
    float vol = value / 100.0f;
    audioOutput->setVolume(vol);

    if (value == 0) {
        muteButton->setText("🔇");
        isMuted = true;
        audioOutput->setMuted(true);
    } else {
        muteButton->setText("🔊");
        isMuted = false;
        audioOutput->setMuted(false);
    }
}

void VideoPlayerWindow::onProgressSliderMoved(int position)
{
    player->setPosition(position);
}

void VideoPlayerWindow::toggleFullscreen()
{
    if (!isFullScreen) {
        // 改为设置整个窗口全屏,而不是只设置videoWidget
        showFullScreen();
        fullScreenButton->setText("退出全屏");
        isFullScreen = true;
    } else {
        showNormal(); // 退出全屏,恢复正常窗口
        fullScreenButton->setText("全屏");
        isFullScreen = false;
    }
}

四、效果

九宫格效果

播放效果:

相关推荐
f***R81 小时前
go测试问题记录
开发语言·后端·golang
sunshine6411 小时前
JS实现悬浮可拖拽vue组件封装
开发语言·前端·javascript
v***44671 小时前
PLC(电力载波通信)网络机制介绍
开发语言·网络·php
JienDa1 小时前
JienDa聊PHP:盲盒电商实战中主流PHP框架的协同架构方略
开发语言·架构·php
小邓   ༽1 小时前
C语言课件(非常详细)
java·c语言·开发语言·python·eclipse·c#·c语言课件
JienDa1 小时前
JienDa聊PHP:今日头条仿站实战架构深度解析
开发语言·架构·php
A***07171 小时前
Rust在网络中的Actix Web
开发语言·后端·rust
雨田哥1 小时前
Qt AFSim雷达探测显示
qt·afsim·qt雷达·qt仿真·雷达显控端·qt雷达模拟器
执笔论英雄1 小时前
【RL】Slime异步 routout 过程7 AsyncLoopThread
开发语言·python