diff --git a/; b/; new file mode 100644 index 0000000..e4080bd --- /dev/null +++ b/; @@ -0,0 +1,66 @@ +#ifndef DOUBLEBUFFERPLAYER_H +#ifndef DOUBLEBUFFERPLAYER_H +#define DOUBLEBUFFERPLAYER_H + +#include +#include +#include +#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 diff --git a/lib/playlist-controller.hpp b/lib/playlist-controller.hpp index fcd6705..32b8fb0 100644 --- a/lib/playlist-controller.hpp +++ b/lib/playlist-controller.hpp @@ -1,60 +1,225 @@ #pragma once -#include -#include -#include -#include -#include -#include #include "logger.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include class PlaylistController : public QObject { Q_OBJECT - Q_PROPERTY(QUrl currentVideoSource READ currentVideoSource NOTIFY - currentVideoSourceChanged) + Q_PROPERTY(QObject *videoOutputA READ videoOutputA WRITE setVideoOutputA + 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: - 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() { - if (m_queue.size() == 0) { - Logger::info("Nothing left to play"); - m_playing = false; - return; - } + connect(m_playerA, &QMediaPlayer::mediaStatusChanged, this, + [this](QMediaPlayer::MediaStatus status) { + onMediaStatusChanged(m_playerA, status); + }); - QString video = m_queue.front(); - Logger::info(std::format("Playing: {} in video {}", video.toStdString(), 1)); - m_queue.erase(m_queue.begin()); + connect(m_playerB, &QMediaPlayer::mediaStatusChanged, this, + [this](QMediaPlayer::MediaStatus status) { + onMediaStatusChanged(m_playerB, status); + }); + } - QUrl newUrl = QUrl::fromLocalFile(video); - if (m_currentVideoSource != newUrl) { - m_currentVideoSource = newUrl; - emit currentVideoSourceChanged(); + QObject *videoOutputA() const { return m_videoOutputA; } + QObject *videoOutputB() const { return m_videoOutputB; } + QObject *audioOutputA() const { return m_audioOutputA; } + 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 - // Y ya que me pongo con ello usar el doble buffer y permitir cargar listas enteras - Q_INVOKABLE void addToQueue(const QString path) { - int n_elements = m_queue.size(); - m_queue.push_back(path); - Logger::info(std::format("Adding: {}", path.toStdString())); + void setAudioOutputA(QObject *output) { + if (m_audioOutputA != output) { + m_audioOutputA = output; - if (n_elements == 0 && !m_playing) { - m_playing = true; - loadNextVideo(); + if (m_videoOutputA) { + if (auto *audio = qobject_cast(m_audioOutputA)) { + m_playerA->setAudioOutput(audio); + } + } + + emit audioOutputChangedA(); } } + 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(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(m_audioOutputA)) { + audio->setVolume(std::clamp(m_volume + volume, 0.0f, 1.0f)); + } + + if (auto *audio = qobject_cast(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: - void currentVideoSourceChanged(); + void videoOutputChanged(); + void videoOutputChangedA(); + void audioOutputChangedA(); + void videoOutputChangedB(); + void audioOutputChangedB(); + void playingStateChanged(); private: - bool m_playing = false; std::vector m_queue = std::vector(); - 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; + } + } }; diff --git a/src/main.cpp b/src/main.cpp index 8c09392..38c721e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,8 +13,13 @@ int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); + // qmlRegisterType("MediaSystem", 1, 0, + // "PlaylistController"); + QQmlApplicationEngine engine; + // engine.addImportPath(app.applicationDirPath()); + PlaylistController playlist; engine.rootContext()->setContextProperty("playlist", &playlist); @@ -26,10 +31,7 @@ int main(int argc, char *argv[]) { engine.load(url); - Logger::info("Engine loaded"); - - playlist.addToQueue("../ui/media/video.mp4"); - playlist.addToQueue("../ui/media/Puta.mp4"); + Logger::info("Engine loaded"); return app.exec(); } diff --git a/ui/main.qml b/ui/main.qml index 7d53b78..e05ba81 100644 --- a/ui/main.qml +++ b/ui/main.qml @@ -14,7 +14,8 @@ Window { color: "#1e1e2e" function togglePause() { - videoPlayer.playbackState == MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play(); + // videoPlayer.playbackState == MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play(); + playlist.togglePause(); seekFeedback.hide(); playFeedback.trigger(); @@ -38,7 +39,8 @@ Window { } 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); } else if (time <= 0) { if (dir > 0) { @@ -46,7 +48,8 @@ Window { } 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); } @@ -83,31 +86,34 @@ Window { horizontalAlignment: Text.AlignHCenter } - // Estudiar el doble buffer, parece ser no tan coñazo como podria esperar - // mañana lo intento implementar - Video { - id: videoPlayer + AudioOutput { + id: audioOutputA + } + + VideoOutput { + id: displayOutputA anchors.fill: parent - anchors.centerIn: parent - source: playlist.currentVideoSource - visible: true - loops: 0 - playbackRate: 1 + } - onSourceChanged: { - console.log("Now playing:", source); - if (source.toString() !== "") { - videoPlayer.play(); - } - } + AudioOutput { + id: audioOutputB + } - onStopped: { - playlist.loadNextVideo(); - } + VideoOutput { + id: displayOutputB + anchors.fill: parent + } - onVisibleChanged: { - console.log("Cambio la visibilidad"); - } + Component.onCompleted: { + 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 { @@ -117,7 +123,8 @@ Window { id: innerIcon anchors.centerIn: 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" fillMode: Image.PreserveAspectFit