#include "mainwindow.h" #include #include #include #include #include #include #include #include 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(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(positionMs) / (durationSecs * 1000.0); m_seekBar->setValue(static_cast(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(durationSecs * 1000.0))); } void MainWindow::onPlaybackFinished() { m_playBtn->setText("▶"); m_seekBar->setValue(0); }