From 902693bd404b58333eb3b4a34efca372c149c137 Mon Sep 17 00:00:00 2001 From: Dark Steveneq Date: Sun, 19 Oct 2025 02:58:12 +0200 Subject: [PATCH] heugh jazz --- .gitignore | 1 + QYRComponents/BusyIndicator.qml | 32 ++++++++ QYRComponents/CMakeLists.txt | 1 + QYRComponents/Slider.qml | 13 ++++ VideoPlayer.qml | 68 ++++++++++++++++- ViewVideoList.qml | 38 +++++++--- player.cpp | 127 ++++++++++++++++++++++++-------- player.h | 37 +++++++--- resources/qyouvideo.rc | 96 ++++++++++++++++++++++++ 9 files changed, 358 insertions(+), 55 deletions(-) create mode 100644 QYRComponents/BusyIndicator.qml create mode 100644 resources/qyouvideo.rc diff --git a/.gitignore b/.gitignore index d9ab11c..c5f8dab 100755 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ qrc_*.cpp Thumbs.db *.res *.rc +!resources/qyouvideo.rc /.qmake.cache /.qmake.stash diff --git a/QYRComponents/BusyIndicator.qml b/QYRComponents/BusyIndicator.qml new file mode 100644 index 0000000..8cdf62b --- /dev/null +++ b/QYRComponents/BusyIndicator.qml @@ -0,0 +1,32 @@ +import QtQuick 6.8 +import QtQuick.Controls 6.8 +import QtQuick.Controls.Basic 6.8 + +BusyIndicator { + contentItem: Rectangle { + implicitWidth: 44 + implicitHeight: 44 + color: "transparent" + radius: 44 + border.width: 4 + border.color: Colors.primaryAlt + + Rectangle { + width: 12 + height: 12 + anchors.top: parent.top + anchors.topMargin: 8 + color: Colors.primary + radius: 12 + } + + // RotationAnimator { + // target: parent + // running: parent.parent.visible && parent.parent.running + // from: 0 + // to: 360 + // loops: Animation.Infinite + // duration: 1250 + // } + } +} diff --git a/QYRComponents/CMakeLists.txt b/QYRComponents/CMakeLists.txt index 1708981..642c24c 100755 --- a/QYRComponents/CMakeLists.txt +++ b/QYRComponents/CMakeLists.txt @@ -25,6 +25,7 @@ qt_add_qml_module(QYRComponents Label.qml TabButton.qml YouAds.qml + QML_FILES BusyIndicator.qml # SOURCES qyrcomponents.cpp qyrcomponents.h ) diff --git a/QYRComponents/Slider.qml b/QYRComponents/Slider.qml index a851135..f420569 100644 --- a/QYRComponents/Slider.qml +++ b/QYRComponents/Slider.qml @@ -5,6 +5,8 @@ import QtQuick.Controls.Basic 6.8 Slider { snapMode: Slider.SnapAlways + property real buffered + implicitWidth: 130 implicitHeight: 20 @@ -26,6 +28,17 @@ Slider { background: Rectangle { color: "#555" radius: 5 + + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + visible: parent.parent.buffered != 0 + width: parent.width * parent.parent.buffered + color: "#888" + radius: 5 + } } handle: Rectangle { diff --git a/VideoPlayer.qml b/VideoPlayer.qml index b3b7033..688f219 100644 --- a/VideoPlayer.qml +++ b/VideoPlayer.qml @@ -6,18 +6,41 @@ import QtQuick.Layouts 6.8 import QYRComponents 1.0 Rectangle { - visible: Player.hasVideo + visible: Player.active color: Colors.surface1 + radius: 10 + + property bool unrolled: true // width: childrenRect.width - height: 64 + height: unrolled ? 560 : 96 + + MouseArea { + anchors.fill: parent + height: 20 + hoverEnabled: true + propagateComposedEvents: true + + onClicked: unrolled = true; + onContainsMouseChanged: function() { + if (!containsMouse) { + unrolled = false; + } + } + } ColumnLayout { anchors.fill: parent + Item { + visible: unrolled + Layout.fillHeight: true + } + RowLayout { Button { - text: Player.playing ? "Pause" : "Play" + text: Player.loading ? "Loading" : (Player.playing ? "Pause" : "Play") + enabled: !Player.loading onClicked: function() { if (Player.playing) { Player.pause() @@ -32,10 +55,28 @@ Rectangle { onClicked: Player.stop() } + Label { + heading: "h4" + text: new Date(Player.position).toISOString().slice(14, 19) + } + + Slider { Layout.fillWidth: true from: 0 - to: 106 + to: Player.duration + value: Player.position + buffered: Player.buffered + onMoved: Player.position = value + } + + Label { + heading: "h4" + text: new Date(Player.duration).toISOString().slice(14, 19) + } + + Label { + text: Player.buffered } Slider { @@ -45,5 +86,24 @@ Rectangle { onMoved: Player.volume = value } } + + Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + clip: true + text: Player.title + heading: "h2" + font.bold: true + } + + Label { + visible: unrolled + Layout.fillWidth: true + wrapMode: Text.WordWrap + clip: true + text: Player.description + heading: "h4" + font.bold: true + } } } diff --git a/ViewVideoList.qml b/ViewVideoList.qml index 8cca739..fcd7f0e 100644 --- a/ViewVideoList.qml +++ b/ViewVideoList.qml @@ -4,23 +4,19 @@ import QtQuick.Layouts 6.8 import QYRComponents 1.0 -ColumnLayout { +Item { anchors.fill: parent + property bool loading: true property bool failed: false - VideoPlayer { - Layout.fillWidth: true - Layout.fillHeight: false - } - GridView { - Layout.fillWidth: true - Layout.fillHeight: true - // anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: Player.active ? 96 : 0 visible: !loading || !failed - // anchors.fill: parent + anchors.fill: parent model: ListModel { id: model } clip: true @@ -28,8 +24,6 @@ ColumnLayout { cellHeight: 224 delegate: VideoEntry {} - - Component.onCompleted: parent.fetchData() } BusyIndicator { @@ -66,6 +60,12 @@ ColumnLayout { } } + Timer { + interval: 1000 + running: true + onTriggered: parent.fetchData() + } + function fetchData() { model.clear(); loading = true; @@ -76,15 +76,29 @@ ColumnLayout { if (xhr.readyState == XMLHttpRequest.DONE) { loading = false; if (xhr.status != 200) { + console.log("Invalid response"); failed = true; return; } const data = JSON.parse(xhr.responseText); + console.log("Received data, found " + data.length + " videos"); data.forEach(video => { model.append(video); }); } } + xhr.ontimeout = function() { + loading = false; + failed = true; + console.log("Request timed out"); + } xhr.send(); } + + VideoPlayer { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + Layout.fillWidth: true + } } diff --git a/player.cpp b/player.cpp index 60d4504..61a381d 100644 --- a/player.cpp +++ b/player.cpp @@ -1,15 +1,59 @@ #include "player.h" -// Public +// HOLY GRAIL +// https://doc.qt.io/qt-6/qaudiobufferoutput.htmli t + Player::Player(QObject *parent) : QObject(parent), player(this), output(this), manager(this) { QObject::connect(&this->player, &QMediaPlayer::mediaStatusChanged, this, [this] () { - emit this->hasVideoChanged(); - emit this->loadingChanged(); + const QMediaPlayer::MediaStatus status = this->player.mediaStatus(); + if (status == QMediaPlayer::NoMedia) + { + qDebug("Player::Player(): NoMedia"); + this->stop(); + } + else if (status == QMediaPlayer::BufferingMedia || status == QMediaPlayer::LoadingMedia) + { + qDebug("Player::Player() loadingChanged"); + this->m_loading = true; + emit this->loadingChanged(); + } + else if (status == QMediaPlayer::EndOfMedia) + { + qDebug("Player::Player() playingChanged"); + emit this->playingChanged(); + } + else + { + this->m_loading = false; + emit this->loadingChanged(); + if (!this->m_initialLoadFinished) + { + this->m_initialLoadFinished = true; + this->play(); + } + } + }); + + QObject::connect(&this->player, &QMediaPlayer::positionChanged, this, [this] () { + this->m_position = this->player.position(); + emit this->positionChanged(); + }); + + QObject::connect(&this->player, &QMediaPlayer::bufferProgressChanged, this, [this] () { + this->m_buffered = this->player.bufferedTimeRange().latestTime() / this->m_duration; + emit this->bufferedChanged(); + }); + + QObject::connect(&this->player, &QMediaPlayer::errorOccurred, this, [this] () { + this->m_failed = true; + qDebug("Player::Player() failedChanged"); + emit this->failedChanged(); }); QObject::connect(&this->player, &QMediaPlayer::playbackStateChanged, this, [this] () { + qDebug("Player::Player() playingChanged"); emit this->playingChanged(); }); @@ -28,20 +72,10 @@ Player::~Player() qDebug("Player::~Player(): Destructed"); } -bool Player::hasVideo() const -{ - return this->player.isAvailable(); -} -bool Player::loading() const -{ - return this->player.mediaStatus() == QMediaPlayer::LoadingMedia || this->player.mediaStatus() == QMediaPlayer::BufferingMedia; -} - -bool Player::failed() const -{ - return this->m_failed; -} +// ******** +// Read +// ******** bool Player::playing() const { @@ -53,15 +87,29 @@ float Player::volume() const return this->output.volume(); } +// ********* +// Write +// ********* + void Player::setVolume(float newVolume) { this->output.setVolume(newVolume); } -// Public slots +void Player::setPosition(float newPosition) +{ + this->player.setPosition(newPosition); +} + + + +// *********** +// Methods +// *********** + void Player::loadVideo(QString id) { - if (this->hasVideo()) + if (this->m_active) { this->unloadVideo(); } @@ -82,8 +130,32 @@ void Player::loadVideo(QString id) this->player.setSource(QUrl("https://youvideo.nonamesoft.xyz/youvideo/api/videofile/with_extension/" + id + data ["extension"].toString())); - emit this->hasVideoChanged(); + this->m_title = data["name"].toString(); + emit this->titleChanged(); + + this->m_description = data["description"].toString(); + emit this->descriptionChanged(); + + this->m_id = id; + emit this->idChanged(); + + this->m_initialLoadFinished = false; + + this->m_active = true; + emit this->activeChanged(); + + this->m_duration = data["metadata"]["duration"].toDouble() * 1000; + emit this->durationChanged(); + + this->m_position = 0; + emit this->positionChanged(); + + this->m_buffered = 0; + emit this->bufferedChanged(); + + this->m_loading = true; emit this->loadingChanged(); + this->m_failed = false; emit this->failedChanged(); @@ -97,17 +169,14 @@ void Player::unloadVideo() { this->player.setSource(QUrl("")); this->player.stop(); this->m_failed = false; - - emit this->hasVideoChanged(); - emit this->failedChanged(); - emit this->loadingChanged(); - emit this->playingChanged(); + emit this->activeChanged(); + this->m_active = false; + emit this->activeChanged(); } void Player::play() { - qDebug() << "Player::play(): Can't play video?" << this->hasVideo() << this->loading(); - if (!this->hasVideo() || this->loading()) + if (!this->m_active || this->m_loading) { return; } @@ -116,8 +185,7 @@ void Player::play() void Player::pause() { - qDebug() << "Player::pause(): Can't pause video?" << (!this->hasVideo() || this->loading()); - if (!this->hasVideo() || this->loading()) + if (!this->m_active || this->m_loading) { return; } @@ -126,8 +194,7 @@ void Player::pause() void Player::stop() { - qDebug() << "Player::stop(): Can't stop video?" << (!this->hasVideo() || this->loading()); - if (!this->hasVideo() || this->loading()) + if (!this->m_active || this->m_loading) { return; } diff --git a/player.h b/player.h index 8300e5a..e19b061 100644 --- a/player.h +++ b/player.h @@ -13,22 +13,26 @@ class Player : public QObject QML_SINGLETON QML_ELEMENT - Q_PROPERTY(bool hasVideo READ hasVideo NOTIFY hasVideoChanged FINAL) - Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged FINAL) - Q_PROPERTY(bool failed READ failed NOTIFY failedChanged FINAL) - Q_PROPERTY(bool playing READ playing NOTIFY playingChanged FINAL) - Q_PROPERTY(float volume READ volume WRITE setVolume NOTIFY volumeChanged FINAL) + Q_PROPERTY(QString title MEMBER m_title NOTIFY titleChanged FINAL) + Q_PROPERTY(QString description MEMBER m_description NOTIFY descriptionChanged FINAL) + Q_PROPERTY(QString id MEMBER m_id NOTIFY idChanged FINAL) + Q_PROPERTY(bool active MEMBER m_active NOTIFY activeChanged FINAL) + Q_PROPERTY(float duration MEMBER m_duration NOTIFY durationChanged FINAL) + Q_PROPERTY(float position MEMBER m_position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(float buffered MEMBER m_buffered NOTIFY bufferedChanged FINAL) + Q_PROPERTY(bool loading MEMBER m_loading NOTIFY loadingChanged FINAL) + Q_PROPERTY(bool failed MEMBER m_failed NOTIFY failedChanged FINAL) + Q_PROPERTY(bool playing READ playing NOTIFY playingChanged FINAL) + Q_PROPERTY(float volume READ volume WRITE setVolume NOTIFY volumeChanged FINAL) public: Player(QObject *parent = nullptr); ~Player(); - bool hasVideo() const; - bool loading() const; - bool failed() const; bool playing() const; float volume() const; + void setPosition(float newPosition); void setVolume(float newVolume); public slots: @@ -39,7 +43,13 @@ public slots: void stop(); signals: - void hasVideoChanged(); + void titleChanged(); + void descriptionChanged(); + void idChanged(); + void activeChanged(); + void durationChanged(); + void positionChanged(); + void bufferedChanged(); void loadingChanged(); void failedChanged(); void playingChanged(); @@ -50,5 +60,14 @@ private: QAudioOutput output; QNetworkAccessManager manager; + QString m_title = ""; + QString m_description = ""; + QString m_id = ""; + float m_duration = 0; + float m_position = 0; + float m_buffered = 0; + bool m_initialLoadFinished = false; + bool m_active = false; + bool m_loading = false; bool m_failed = false; }; diff --git a/resources/qyouvideo.rc b/resources/qyouvideo.rc new file mode 100644 index 0000000..3f290ec --- /dev/null +++ b/resources/qyouvideo.rc @@ -0,0 +1,96 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +//LANGUAGE 0x09, 0x01 +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN +"resource.h\0" +END + +2 TEXTINCLUDE +BEGIN +"\0" +END + +3 TEXTINCLUDE +BEGIN +"\r\n" +"\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +1 VERSIONINFO +FILEVERSION 1, 0, 0, 1 +PRODUCTVERSION 1, 0, 0, 1 +FILEFLAGSMASK 0x3fL +#ifdef _DEBUG +FILEFLAGS 0x29L +#else +FILEFLAGS 0x28L +#endif +FILEOS 0x40004L +FILETYPE 0x1L +FILESUBTYPE 0x0L +BEGIN +BLOCK "StringFileInfo" +BEGIN +BLOCK "040904b0" +BEGIN +VALUE "CompanyName", "LAMINAX CO." +VALUE "FileDescription", "QYouVideo" +VALUE "FileVersion", "1.0.0.1" +VALUE "InternalName", "qyouvideo" +VALUE "LegalCopyright", "Copyright (C) Ghostfox 2025" +//VALUE "OriginalFilename", "appqyouvideo.exe" +VALUE "ProductName", "QYouVideo" +VALUE "ProductVersion", "1.0.0.1" +END +END +BLOCK "VarFileInfo" +BEGIN +VALUE "Translation", 0x09, 1200 +END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "logo.ico" + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED