Pues ahora hay doble buffer

Falta el hilo que descargue el video de youtube, que se dice mas pronto que se hace
This commit is contained in:
Ruben Garcalia
2026-08-12 01:48:29 +02:00
parent 4d1232821a
commit 81d9952fa3
4 changed files with 305 additions and 65 deletions

66
; Normal file
View File

@@ -0,0 +1,66 @@
#ifndef DOUBLEBUFFERPLAYER_H
#ifndef DOUBLEBUFFERPLAYER_H
#define DOUBLEBUFFERPLAYER_H
#include <QObject>
#include <QMediaPlayer>
#include <QUrl>
#include "logger.hpp"
class DoubleBufferPlayer : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* videoOutput READ videoOutput WRITE setVideoOutput NOTIFY videoOutputChanged)
public:
explicit DoubleBufferPlayer(QObject *parent = nullptr) : QObject(parent)
{
m_playerA = new QMediaPlayer(this);
m_playerB = new QMediaPlayer(this);
m_activePlayer = m_playerA;
m_nextPlayer = m_playerB;
}
QObject* videoOutput() const { return m_videoOutput; }
void setVideoOutput(QObject *output)
{
if (m_videoOutput != output) {
m_videoOutput = output;
if (m_videoOutput) {
m_activePlayer->setVideoOutput(m_videoOutput);
}
emit videoOutputChanged();
}
}
Q_INVOKABLE void prepareNext(const QUrl &url)
{
m_nextPlayer->setSource(url);
}
Q_INVOKABLE void swapAndPlay()
{
if (!m_videoOutput) return;
m_activePlayer->stop();
std::swap(m_activePlayer, m_nextPlayer);
m_activePlayer->setVideoOutput(m_videoOutput);
m_activePlayer->play();
}
signals:
void videoOutputChanged();
private:
QMediaPlayer *m_playerA{nullptr};
QMediaPlayer *m_playerB{nullptr};
QMediaPlayer *m_activePlayer{nullptr};
QMediaPlayer *m_nextPlayer{nullptr};
QObject *m_videoOutput{nullptr};
};
#endif

View File

@@ -1,60 +1,225 @@
#pragma once #pragma once
#include <QObject>
#include <QString>
#include <QUrl>
#include <format>
#include <qcoreapplication.h>
#include <vector>
#include "logger.hpp" #include "logger.hpp"
#include <QAudioOutput>
#include <QMediaPlayer>
#include <QObject>
#include <QUrl>
#include <algorithm>
#include <qcoreapplication.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qurl.h>
#include <string>
#include <vector>
class PlaylistController : public QObject { class PlaylistController : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QUrl currentVideoSource READ currentVideoSource NOTIFY Q_PROPERTY(QObject *videoOutputA READ videoOutputA WRITE setVideoOutputA
currentVideoSourceChanged) NOTIFY videoOutputChangedA)
Q_PROPERTY(QObject *audioOutputA READ audioOutputA WRITE setAudioOutputA
NOTIFY audioOutputChangedA)
Q_PROPERTY(QObject *videoOutputB READ videoOutputB WRITE setVideoOutputB
NOTIFY videoOutputChanged)
Q_PROPERTY(QObject *audioOutputB READ audioOutputB WRITE setAudioOutputB
NOTIFY audioOutputChangedB)
Q_PROPERTY(QString playingState READ playingState NOTIFY playingStateChanged)
public: public:
explicit PlaylistController(QObject *parent = nullptr) : QObject(parent) {} explicit PlaylistController(QObject *parent = nullptr) : QObject(parent) {
m_playerA = new QMediaPlayer(this);
m_playerB = new QMediaPlayer(this);
QUrl currentVideoSource() const { return m_currentVideoSource; } m_activePlayer = m_playerA;
m_nextPlayer = m_playerB;
Q_INVOKABLE void loadNextVideo() { connect(m_playerA, &QMediaPlayer::mediaStatusChanged, this,
if (m_queue.size() == 0) { [this](QMediaPlayer::MediaStatus status) {
Logger::info("Nothing left to play"); onMediaStatusChanged(m_playerA, status);
m_playing = false; });
return;
}
QString video = m_queue.front(); connect(m_playerB, &QMediaPlayer::mediaStatusChanged, this,
Logger::info(std::format("Playing: {} in video {}", video.toStdString(), 1)); [this](QMediaPlayer::MediaStatus status) {
m_queue.erase(m_queue.begin()); onMediaStatusChanged(m_playerB, status);
});
}
QUrl newUrl = QUrl::fromLocalFile(video); QObject *videoOutputA() const { return m_videoOutputA; }
if (m_currentVideoSource != newUrl) { QObject *videoOutputB() const { return m_videoOutputB; }
m_currentVideoSource = newUrl; QObject *audioOutputA() const { return m_audioOutputA; }
emit currentVideoSourceChanged(); QObject *audioOutputB() const { return m_audioOutputB; }
QString playingState() const { return m_playingState ? "playing" : "pause"; }
void setVideoOutputA(QObject *output) {
if (m_videoOutputA != output) {
m_videoOutputA = output;
if (m_videoOutputA) {
m_playerA->setVideoOutput(m_videoOutputA);
}
emit videoOutputChangedA();
} }
} }
// Tengo que rehacer esto en algun momento para que tome la url y la procese en otro thread void setAudioOutputA(QObject *output) {
// Y ya que me pongo con ello usar el doble buffer y permitir cargar listas enteras if (m_audioOutputA != output) {
Q_INVOKABLE void addToQueue(const QString path) { m_audioOutputA = output;
int n_elements = m_queue.size();
m_queue.push_back(path); if (m_videoOutputA) {
Logger::info(std::format("Adding: {}", path.toStdString())); if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputA)) {
m_playerA->setAudioOutput(audio);
}
}
if (n_elements == 0 && !m_playing) { emit audioOutputChangedA();
m_playing = true;
loadNextVideo();
} }
} }
void setVideoOutputB(QObject *output) {
if (m_videoOutputB != output) {
m_videoOutputB = output;
if (m_videoOutputB) {
m_playerB->setVideoOutput(m_videoOutputB);
}
emit videoOutputChangedB();
}
}
void setAudioOutputB(QObject *output) {
if (m_audioOutputB != output) {
m_audioOutputB = output;
if (m_videoOutputB) {
if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputB)) {
m_playerB->setAudioOutput(audio);
}
}
emit audioOutputChangedB();
}
}
Q_INVOKABLE void prepareNext(const QUrl &url) {
// Desplegar hilo que descargue y gestione el mediaplayer
Logger::info(url.toString().toStdString());
m_nextPlayer->setSource(url);
// Para que se quede listo
m_nextPlayer->play();
m_nextPlayer->pause();
m_nextPlayer->setPosition(0);
}
Q_INVOKABLE void swapAndPlay() {
std::swap(m_activePlayer, m_nextPlayer);
m_activePlayer->play();
m_playingState = true;
// m_activePlayer->setPlaybackRate(1);
if (m_queue.size() - 1 > m_queue_index) {
prepareNext(QUrl::fromLocalFile(m_queue.at(m_queue_index + 1)));
}
}
Q_INVOKABLE void setVolume(float volume) {
if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputA)) {
audio->setVolume(std::clamp(m_volume + volume, 0.0f, 1.0f));
}
if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputB)) {
audio->setVolume(std::clamp(m_volume + volume, 0.0f, 1.0f));
}
}
Q_INVOKABLE void seek(long long time) {
m_activePlayer->setPosition(std::clamp(m_activePlayer->position() + time,
0ll, m_activePlayer->duration()));
}
Q_INVOKABLE void addToQueue(const QString &url) {
int n_ele = m_queue.size();
m_queue.push_back(url);
if (m_nextPlayer->source() == QUrl()) {
prepareNext(m_queue.at(m_queue_index));
}
}
Q_INVOKABLE void togglePause() {
m_playingState = !m_playingState;
if (!m_playingState)
m_activePlayer->pause();
else
m_activePlayer->play();
emit playingStateChanged();
}
signals: signals:
void currentVideoSourceChanged(); void videoOutputChanged();
void videoOutputChangedA();
void audioOutputChangedA();
void videoOutputChangedB();
void audioOutputChangedB();
void playingStateChanged();
private: private:
bool m_playing = false;
std::vector<QString> m_queue = std::vector<QString>(); std::vector<QString> m_queue = std::vector<QString>();
QUrl m_currentVideoSource = QString(""); bool m_playing = false;
QMediaPlayer *m_playerA{nullptr};
QMediaPlayer *m_playerB{nullptr};
QMediaPlayer *m_activePlayer{nullptr};
QMediaPlayer *m_nextPlayer{nullptr};
QObject *m_videoOutputA{nullptr};
QObject *m_videoOutputB{nullptr};
QObject *m_audioOutputA{nullptr};
QObject *m_audioOutputB{nullptr};
float m_volume = 1;
bool m_playingState = false;
int m_queue_index = 0;
void onMediaStatusChanged(QMediaPlayer *player,
QMediaPlayer::MediaStatus status) {
bool isActive = (player == m_activePlayer);
switch (status) {
case QMediaPlayer::LoadedMedia:
Logger::info(isActive ? "Active media loaded"
: "Next media loaded and ready");
break;
case QMediaPlayer::BufferedMedia:
Logger::info("Media fully buffered");
if (!isActive) {
Logger::info("Next media fully buffered");
}
break;
case QMediaPlayer::EndOfMedia:
if (isActive) {
if (m_queue.size() - 1 > m_queue_index) {
m_queue_index++;
swapAndPlay();
}
}
break;
case QMediaPlayer::InvalidMedia:
Logger::error("Failed to load or play media");
break;
default:
break;
}
}
}; };

View File

@@ -13,8 +13,13 @@ int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
// qmlRegisterType<PlaylistController>("MediaSystem", 1, 0,
// "PlaylistController");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
// engine.addImportPath(app.applicationDirPath());
PlaylistController playlist; PlaylistController playlist;
engine.rootContext()->setContextProperty("playlist", &playlist); engine.rootContext()->setContextProperty("playlist", &playlist);
@@ -28,8 +33,5 @@ int main(int argc, char *argv[]) {
Logger::info("Engine loaded"); Logger::info("Engine loaded");
playlist.addToQueue("../ui/media/video.mp4");
playlist.addToQueue("../ui/media/Puta.mp4");
return app.exec(); return app.exec();
} }

View File

@@ -14,7 +14,8 @@ Window {
color: "#1e1e2e" color: "#1e1e2e"
function togglePause() { function togglePause() {
videoPlayer.playbackState == MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play(); // videoPlayer.playbackState == MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play();
playlist.togglePause();
seekFeedback.hide(); seekFeedback.hide();
playFeedback.trigger(); playFeedback.trigger();
@@ -38,7 +39,8 @@ Window {
} }
dir = 1; dir = 1;
videoPlayer.position = Math.min(videoPlayer.duration, videoPlayer.position + time); // videoPlayer.position = Math.min(videoPlayer.duration, videoPlayer.position + time);
playlist.seek(time);
innerText.text = "+" + (5 * window.times); innerText.text = "+" + (5 * window.times);
} else if (time <= 0) { } else if (time <= 0) {
if (dir > 0) { if (dir > 0) {
@@ -46,7 +48,8 @@ Window {
} }
dir = -1; dir = -1;
videoPlayer.position = Math.max(0, videoPlayer.position + time); // videoPlayer.position = Math.max(0, videoPlayer.position + time);
playlist.seek(time);
innerText.text = "-" + (5 * window.times); innerText.text = "-" + (5 * window.times);
} }
@@ -83,31 +86,34 @@ Window {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
// Estudiar el doble buffer, parece ser no tan coñazo como podria esperar AudioOutput {
// mañana lo intento implementar id: audioOutputA
Video { }
id: videoPlayer
VideoOutput {
id: displayOutputA
anchors.fill: parent anchors.fill: parent
anchors.centerIn: parent }
source: playlist.currentVideoSource
visible: true
loops: 0
playbackRate: 1
onSourceChanged: { AudioOutput {
console.log("Now playing:", source); id: audioOutputB
if (source.toString() !== "") { }
videoPlayer.play();
}
}
onStopped: { VideoOutput {
playlist.loadNextVideo(); id: displayOutputB
} anchors.fill: parent
}
onVisibleChanged: { Component.onCompleted: {
console.log("Cambio la visibilidad"); playlist.videoOutputA = displayOutputA;
} playlist.audioOutputA = audioOutputA;
playlist.videoOutputB = displayOutputB;
playlist.audioOutputB = audioOutputB;
playlist.addToQueue("../ui/media/video.mp4");
playlist.addToQueue("../ui/media/Puta.mp4");
playlist.swapAndPlay();
} }
FadingFeedback { FadingFeedback {
@@ -117,7 +123,8 @@ Window {
id: innerIcon id: innerIcon
anchors.centerIn: parent anchors.centerIn: parent
anchors.fill: parent anchors.fill: parent
source: videoPlayer.playbackState === MediaPlayer.PlayingState ? "qrc:/App/ui/icons/play.svg" : "qrc:/App/ui/icons/pause.svg" // source: videoPlayer.playbackState === MediaPlayer.PlayingState ? "qrc:/App/ui/icons/play.svg" : "qrc:/App/ui/icons/pause.svg"
source: playlist.playingState === "playing" ? "qrc:/App/ui/icons/play.svg" : "qrc:/App/ui/icons/pause.svg"
color: "white" color: "white"
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit