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:
66
;
Normal file
66
;
Normal 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
|
||||
@@ -1,60 +1,225 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <format>
|
||||
#include <qcoreapplication.h>
|
||||
#include <vector>
|
||||
#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 {
|
||||
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();
|
||||
void setAudioOutputA(QObject *output) {
|
||||
if (m_audioOutputA != output) {
|
||||
m_audioOutputA = output;
|
||||
|
||||
m_queue.push_back(path);
|
||||
Logger::info(std::format("Adding: {}", path.toStdString()));
|
||||
if (m_videoOutputA) {
|
||||
if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputA)) {
|
||||
m_playerA->setAudioOutput(audio);
|
||||
}
|
||||
}
|
||||
|
||||
if (n_elements == 0 && !m_playing) {
|
||||
m_playing = true;
|
||||
loadNextVideo();
|
||||
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<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:
|
||||
void currentVideoSourceChanged();
|
||||
void videoOutputChanged();
|
||||
void videoOutputChangedA();
|
||||
void audioOutputChangedA();
|
||||
void videoOutputChangedB();
|
||||
void audioOutputChangedB();
|
||||
void playingStateChanged();
|
||||
|
||||
private:
|
||||
bool m_playing = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,8 +13,13 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
// qmlRegisterType<PlaylistController>("MediaSystem", 1, 0,
|
||||
// "PlaylistController");
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
// engine.addImportPath(app.applicationDirPath());
|
||||
|
||||
PlaylistController playlist;
|
||||
engine.rootContext()->setContextProperty("playlist", &playlist);
|
||||
|
||||
@@ -28,8 +33,5 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
Logger::info("Engine loaded");
|
||||
|
||||
playlist.addToQueue("../ui/media/video.mp4");
|
||||
playlist.addToQueue("../ui/media/Puta.mp4");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
57
ui/main.qml
57
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user