zero_copy_video_player/mainwindow.cpp
2026-08-05 14:07:38 -03:00

176 lines
6.9 KiB
C++

#include "mainwindow.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QFileDialog>
#include <QScrollArea>
#include <QFrame>
#include <QGroupBox>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_updatingSeek(false)
{
setWindowTitle("Zero-Copy CUDA-GL Video Player");
resize(960, 600);
m_glWidget = new GLWidget(this);
m_videoDecoder = new VideoDecoder(this);
m_glWidget->setVideoDecoder(m_videoDecoder);
setupUI();
// Conectar las señales de posición y finalización del hilo decodificador
connect(m_videoDecoder, &VideoDecoder::positionChanged, this, &MainWindow::onPositionChanged);
connect(m_videoDecoder, &VideoDecoder::playbackFinished, this, &MainWindow::onPlaybackFinished);
}
MainWindow::~MainWindow() {
// Detiene de forma segura el hilo decodificador antes de destruir la ventana principal
m_videoDecoder->stopThread();
}
void MainWindow::setupUI() {
// Aplicar hoja de estilos (QSS) para un look oscuro premium
this->setStyleSheet(
"QMainWindow { background-color: #08080f; }"
"QWidget { color: #f0f0f5; font-family: 'Outfit', 'Segoe UI', sans-serif; font-size: 13px; }"
"QPushButton { background-color: #ff007f; color: white; border: none; padding: 8px 15px; border-radius: 6px; font-weight: bold; min-width: 60px; }"
"QPushButton:hover { background-color: #ff3399; }"
"QPushButton:pressed { background-color: #cc0066; }"
"QPushButton:disabled { background-color: #444; color: #888; }"
"QSlider::groove:horizontal { border: 1px solid #2d2d44; height: 6px; background: #1a1a2e; border-radius: 3px; }"
"QSlider::sub-page:horizontal { background: #ff007f; border-radius: 3px; }"
"QSlider::handle:horizontal { background: #ffffff; border: 1px solid #ff007f; width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; }"
"QSlider::handle:horizontal:hover { background: #ff007f; }"
"QLabel { color: #a0a0c0; }"
);
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
// Layout principal en columna vertical (Pintado OpenGL arriba, controles de reproducción abajo)
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(10);
mainLayout->addWidget(m_glWidget, 1);
// --- CONTROLES MULTIMEDIA ---
QHBoxLayout *controlsLayout = new QHBoxLayout();
// Botón para buscar y abrir archivos de video
QPushButton *openBtn = new QPushButton("Abrir Video", this);
openBtn->setStyleSheet("background-color: #00bfff;");
connect(openBtn, &QPushButton::clicked, this, &MainWindow::openVideoFile);
controlsLayout->addWidget(openBtn);
// Botón de Play / Pausa
m_playBtn = new QPushButton("▶", this);
connect(m_playBtn, &QPushButton::clicked, this, &MainWindow::togglePlayPause);
controlsLayout->addWidget(m_playBtn);
// Botón alternador de Silencio / Mute
m_muteBtn = new QPushButton("🔊", this);
m_muteBtn->setStyleSheet("background-color: #4a4e69;");
connect(m_muteBtn, &QPushButton::clicked, this, &MainWindow::toggleMute);
controlsLayout->addWidget(m_muteBtn);
// Barra de progreso horizontal (Seekbar)
m_seekBar = new QSlider(Qt::Horizontal, this);
m_seekBar->setRange(0, 1000); // 1000 pasos de precisión
m_seekBar->setEnabled(false);
connect(m_seekBar, &QSlider::sliderMoved, this, &MainWindow::setVideoPosition);
controlsLayout->addWidget(m_seekBar, 1); // El factor de estiramiento '1' hace que ocupe todo el espacio sobrante
// Etiqueta indicadora de tiempo transcurrido / tiempo total
m_timeLabel = new QLabel("00:00 / 00:00", this);
m_timeLabel->setMinimumWidth(90);
m_timeLabel->setAlignment(Qt::AlignCenter);
controlsLayout->addWidget(m_timeLabel);
mainLayout->addLayout(controlsLayout);
}
void MainWindow::openVideoFile() {
QString fileName = QFileDialog::getOpenFileName(this, "Abrir archivo de video", "", "Videos (*.mp4 *.avi *.mkv *.webm *.mov)");
if (!fileName.isEmpty()) {
openVideoFileDirect(fileName);
}
}
void MainWindow::openVideoFileDirect(const QString &fileName) {
// 1. Limpiamos cualquier textura y detenemos hilos anteriores
m_glWidget->clearVideo();
m_videoDecoder->stopThread();
// 2. Abrimos el nuevo archivo
if (m_videoDecoder->openFile(fileName)) {
// Habilitamos la barra de búsqueda si la duración es válida
m_seekBar->setEnabled(m_videoDecoder->getDuration() > 0.0);
m_playBtn->setText("⏸");
m_videoDecoder->start(); // Arranca el hilo secundario de decodificación (invoca a run())
m_videoDecoder->play(); // Inicia la reproducción del temporizador maestro
}
}
void MainWindow::togglePlayPause() {
if (m_videoDecoder->isPaused()) {
m_videoDecoder->play();
m_playBtn->setText("⏸");
} else {
m_videoDecoder->pause();
m_playBtn->setText("▶");
}
}
void MainWindow::toggleMute() {
static bool muted = false;
muted = !muted;
m_muteBtn->setText(muted ? "🔇" : "🔊");
// Nota: El decodificador no procesa pistas de audio en este ejemplo de interoperabilidad GPU-GPU,
// pero el botón de mute demuestra cómo interactuar con el control de reproducción.
}
void MainWindow::setVideoPosition(int position) {
if (m_videoDecoder->getDuration() > 0.0) {
// Activamos la bandera para que la señal del decodificador no mueva la seekbar mientras el usuario la arrastra
m_updatingSeek = true;
// Calculamos la posición en segundos en base al porcentaje (0 - 1000)
double targetSecs = (static_cast<double>(position) / 1000.0) * m_videoDecoder->getDuration();
m_videoDecoder->seek(targetSecs);
m_updatingSeek = false;
}
}
void MainWindow::onPositionChanged(qint64 positionMs) {
double durationSecs = m_videoDecoder->getDuration();
// Actualizamos la barra de progreso basándonos en la posición actual del PTS
if (durationSecs > 0.0 && !m_updatingSeek) {
m_updatingSeek = true;
double progress = static_cast<double>(positionMs) / (durationSecs * 1000.0);
m_seekBar->setValue(static_cast<int>(progress * 1000.0));
m_updatingSeek = false;
}
// Lambda para dar formato MM:SS a los milisegundos
auto format = [](qint64 ms) -> QString {
qint64 totalSecs = ms / 1000;
int mins = totalSecs / 60;
int secs = totalSecs % 60;
return QString("%1:%2").arg(mins, 2, 10, QChar('0')).arg(secs, 2, 10, QChar('0'));
};
// Mostramos la marca de tiempo actualizada
m_timeLabel->setText(format(positionMs) + " / " + format(static_cast<qint64>(durationSecs * 1000.0)));
}
void MainWindow::onPlaybackFinished() {
m_playBtn->setText("▶");
m_seekBar->setValue(0);
}