fist comin

This commit is contained in:
Dark Steveneq
2025-10-18 21:40:02 +02:00
parent 63d9c21e98
commit 6fc0736c2d
12 changed files with 402 additions and 358 deletions

View File

@@ -1,49 +1,42 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
project(qyouradio VERSION 0.1 LANGUAGES CXX)
project(qyouvideo VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core Quick Multimedia)
find_package(DiscordSDK)
qt_standard_project_setup(REQUIRES 6.8)
configure_file(buildFlags.h.in buildFlags.h)
qt_add_executable(appqyouradio
qt_add_executable(appqyouvideo
main.cpp
buildFlags.h
player.cpp player.h
resources/qyouradio.rc
resources/qyouvideo.rc
resources/resource.h
)
qt_add_resources(appqyouradio "resources"
qt_add_resources(appqyouvideo "resources"
PREFIX "/"
FILES
resources/logo.png
)
set_source_files_properties(Player.qml
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_qml_module(appqyouradio
URI qyouradio
qt_add_qml_module(appqyouvideo
URI qyouvideo
VERSION 1.0
QML_FILES
Main.qml
# Player.qml
ViewPlayer.qml
ViewSettings.qml
VideoEntry.qml
ViewAbout.qml
ViewVideoList.qml
VideoPlayer.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appqyouradio PROPERTIES
set_target_properties(appqyouvideo PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appqyouradio
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
@@ -51,30 +44,25 @@ set_target_properties(appqyouradio PROPERTIES
WIN32_EXECUTABLE TRUE
)
target_include_directories(appqyouradio PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
target_link_libraries(appqyouradio
target_link_libraries(appqyouvideo
PUBLIC Qt6::Quick Qt6::Multimedia
)
if (DiscordSDK_FOUND)
target_link_libraries(appqyouradio
PRIVATE DiscordSDK::DiscordSDK
)
endif()
include(GNUInstallDirs)
install(TARGETS appqyouradio
install(TARGETS appqyouvideo
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
qt_generate_deploy_app_script(
TARGET appqyouradio
TARGET appqyouvideo
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})
add_subdirectory(QYRComponents)
project(qyouvideo VERSION 0.1 LANGUAGES CXX)

134
Main.qml
View File

@@ -9,11 +9,7 @@ ApplicationWindow {
id: root
width: 1280
height: 800
title: qsTr("QYouRadio")
Component.onCompleted: function() {
console.log(Player.stations)
}
title: qsTr("QYouVideo")
ColumnLayout {
anchors.fill: parent
@@ -30,76 +26,8 @@ ApplicationWindow {
Label {
Layout.leftMargin: 5
text: "QYouRadio"
text: "QYouVideo"
heading: "h1"
// font.bold: true
}
Item {
Layout.leftMargin: 5
width: 320
height: 36
Rectangle {
anchors.fill: parent
id: nowplaying_root
// visible: Player.currentIndex != null
visible: anchors.bottomMargin < 42
color: Colors.primary
radius: 5
clip: true
NumberAnimation {
running: Player.playing && tabbar.currentIndex != Player.currentIndex && nowplaying_root.anchors.bottomMargin >= 42
target: nowplaying_root
property: "anchors.bottomMargin"
from: 42
to: 0
duration: 145
}
NumberAnimation {
running: !Player.playing || (tabbar.currentIndex == Player.currentIndex && nowplaying_root.anchors.bottomMargin < 42)
target: nowplaying_root
property: "anchors.bottomMargin"
from: 0
to: 42
duration: 145
}
Label {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 1
anchors.leftMargin: 4
anchors.rightMargin: 4
text: (Player.loading ? "Loading " : "Playing ") + qsTr(Player.currentStation.name)
heading: "h5"
}
Label {
id: nowplaying_title
anchors.bottom: parent.bottom
anchors.left: parent.left
// anchors.right: parent.right
anchors.bottomMargin: 1
anchors.leftMargin: 4
// anchors.rightMargin: 4
text: qsTr(Player.currentStation.songTitle)
heading: "h3"
SequentialAnimation {
running: nowplaying_title.width > 320
loops: Animation.Infinite
NumberAnimation { target: nowplaying_title; property: "anchors.leftMargin"; from: 4; to: 4; duration: 10000 }
NumberAnimation { target: nowplaying_title; property: "anchors.leftMargin"; from: 4; to: -nowplaying_title.width + 324; duration: nowplaying_title.width * 4 }
NumberAnimation { target: nowplaying_title; property: "anchors.leftMargin"; from: -nowplaying_title.width + 324; to: -nowplaying_title.width + 324; duration: 10000 }
NumberAnimation { target: nowplaying_title; property: "anchors.leftMargin"; from: -nowplaying_title.width + 324; to: 4; duration: nowplaying_title.width * 4 }
}
}
}
}
Item {
@@ -113,16 +41,17 @@ ApplicationWindow {
background: Item{}
TabButton {
text: qsTr("Autoradio")
text: qsTr("Videos")
outlined: true
implicitWidth: 80
}
TabButton {
text: qsTr("Live Mix")
text: qsTr("About")
outlined: true
implicitWidth: 80
}
TabButton {
text: qsTr("Deep Bass")
}
TabButton {
text: qsTr("Settings")
text: qsTr("Upload")
}
}
}
@@ -141,61 +70,28 @@ ApplicationWindow {
active: true
asynchronous: true
visible: status == Loader.Ready
sourceComponent: ViewPlayer {
index: 0
}
source: "ViewVideoList.qml"
}
Loader {
// active: tabbar.currentIndex == 1
active: true
asynchronous: true
visible: status == Loader.Ready
sourceComponent: ViewPlayer {
index: 1
}
source: "ViewAbout.qml"
}
Loader {
// active: tabbar.currentIndex == 2
active: true
asynchronous: true
visible: status == Loader.Ready
sourceComponent: ViewPlayer {
index: 2
sourceComponent: Label {
heading: "h1"
text: "No idea if I'll ever implement this so don't worry >w<"
font.bold: true
}
}
Component.onCompleted: contentItem.highlightMoveDuration = 160;
}
RowLayout {
Layout.fillWidth: false
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 30
Layout.bottomMargin: 30
width: 220
Button {
Layout.rightMargin: 5
text: Player.loading ? "Loading" : (Player.playing ? "Pause" : "Play")
onClicked: function() {
if (Player.playing) {
Player.stopPlaying();
} else {
Player.startPlaying(tabbar.currentIndex);
}
}
}
Slider {
Layout.fillWidth: true
Layout.leftMargin: 5
from: 0.1
to: 1.1
stepSize: 0.05
value: Player.volume + 0.1
onMoved: Player.volume = value - 0.1
}
}
}
}

80
VideoEntry.qml Normal file
View File

@@ -0,0 +1,80 @@
import QtQuick 6.8
import QtQuick.Controls 6.8
import QtQuick.Layouts 6.8
import QYRComponents 1.0
Rectangle {
anchors.margins: 10
width: 384
height: 224
color: area.containsMouse ? Colors.surface1 : Colors.surface0
radius: 10
required property string name
required property string id
required property var metadata
ColumnLayout {
anchors.fill: parent
anchors.margins: 5
Image {
Layout.fillWidth: true
Layout.fillHeight: true
asynchronous: true
cache: true
source: "https://youvideo.nonamesoft.xyz/thumbnails/" + parent.parent.id
fillMode: Image.PreserveAspectFit
onStatusChanged: if (status == Image.Error) {
source = "https://youvideo.nonamesoft.xyz/thumbnails/audio.png";
children[0].visible = true
}
// This is a fucking mess
Label {
visible: false
anchors.fill: parent
heading: "h3"
font.bold: true
text: parent.parent.parent.name
wrapMode: Text.Wrap
clip: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
color: Colors.background
width: children[0].paintedWidth
height: children[0].paintedHeight
Label {
anchors.fill: parent
heading: "h4"
// text: new Date(parent.parent.parent.parent.metadata.duration * 1000).toISOString().slice(11, 19)
}
}
}
Label {
heading: "h3"
font.bold: true
text: parent.parent.name
Layout.fillWidth: true
clip: true
}
}
MouseArea {
anchors.fill: parent
id: area
hoverEnabled: true
onClicked: Player.loadVideo(parent.id)
}
}

49
VideoPlayer.qml Normal file
View File

@@ -0,0 +1,49 @@
import QtQuick 6.8
import QtMultimedia 6.8
import QtQuick.Controls 6.8
import QtQuick.Layouts 6.8
import QYRComponents 1.0
Rectangle {
visible: Player.hasVideo
color: Colors.surface1
width: childrenRect.width
height: childrenRect.height
ColumnLayout {
anchors.fill: parent
RowLayout {
Button {
text: Player.playing ? "Pause" : "Play"
onClicked: function() {
if (Player.playing) {
Player.pause()
} else {
Player.play();
}
}
}
Button {
text: "Stop"
onClicked: Player.stop()
}
Slider {
Layout.fillWidth: true
from: 0
to: 106
}
Slider {
from: 0
to: 1
value: Player.volume
onMoved: Player.volume = value
}
}
}
}

59
ViewAbout.qml Normal file
View File

@@ -0,0 +1,59 @@
import QtQuick 6.8
import QtQuick.Controls 6.8
import QtQuick.Layouts 6.8
import QYRComponents 1.0
ColumnLayout {
Label {
heading: "h2"
text: "About"
font.bold: true
}
Label {
heading: "h3"
text: "Youpiter"
font.bold: true
}
Label {
heading: "h4"
text: "Creator of YouVideo"
font.bold: true
}
Label {
heading: "h3"
text: "Ghostfox"
}
Label {
heading: "h4"
text: "Creator of QYouRadio"
}
Label {
heading: "h3"
text: "Ghostfox"
}
Label {
heading: "h4"
text: "Creator of QYouVideo"
}
Label {
heading: "h3"
text: "Qt Group"
}
Label {
heading: "h4"
text: "Creator of Qt, QML and QtQuick"
}
Item {
Layout.fillHeight: true
}
}

85
ViewVideoList.qml Normal file
View File

@@ -0,0 +1,85 @@
import QtQuick 6.8
import QtQuick.Controls 6.8
import QtQuick.Layouts 6.8
import QYRComponents 1.0
Item {
anchors.fill: parent
property bool loading: true
property bool failed: false
VideoPlayer {}
GridView {
anchors.horizontalCenter: parent.horizontalCenter
visible: !loading || !failed
anchors.fill: parent
model: ListModel { id: model }
clip: true
cellWidth: 384
cellHeight: 224
delegate: VideoEntry {}
Component.onCompleted: parent.fetchData()
}
BusyIndicator {
anchors.centerIn: parent
visible: loading
running: loading
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
anchors.centerIn: parent
visible: failed
Label {
text: "Couldn't get videos!"
heading: "h2"
font.bold: true
}
Label {
text: "This could mean that either your internet or YouVideo is down."
heading: "h4"
}
Label {
Layout.bottomMargin: 25
text: "Check your network connection and try again!"
heading: "h4"
}
Button {
text: "Retry"
onClicked: parent.fetchData()
}
}
function fetchData() {
model.clear();
loading = true;
failed = false;
const xhr = new XMLHttpRequest;
xhr.open("GET", "http://youvideo.nonamesoft.xyz/youvideo/api/videos");
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
loading = false;
if (xhr.status != 200) {
failed = true;
return;
}
const data = JSON.parse(xhr.responseText);
data.forEach(video => {
model.append(video);
});
}
}
xhr.send();
}
}

View File

@@ -14,7 +14,7 @@
, wrapQtAppsHook
}:
stdenv.mkDerivation {
pname = "qyouradio";
pname = "qyouvideo";
version = "1.0";
src = ./.;

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1760315168,
"narHash": "sha256-qWDhFoiz6VSd+S+rVzC2m5u8xuAzxRsuDI8OojbPEZ4=",
"lastModified": 1760807995,
"narHash": "sha256-Xpg9h3/uNVMJXZq2LzBDaDM1u057ox0wnZGvxSogNY0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "160e3dfce302bb2fcd03761b84248c7534f3b948",
"rev": "a2975bcaf36af01a1279b84ab575fbc12d472b6a",
"type": "github"
},
"original": {

View File

@@ -1,9 +1,6 @@
#include <QIcon>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
#include "buildFlags.h"
int main(int argc, char *argv[])
{
@@ -13,15 +10,9 @@ int main(int argc, char *argv[])
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("qyouradio", "Main");
engine.loadFromModule("qyouvideo", "Main");
app.setWindowIcon(QIcon(":/resources/logo.png"));
#if DiscordSDK_FOUND
qInfo("Has Discord Social SDK: true");
#else
qInfo("Has Discord Social SDK: false");
#endif
return app.exec();
}

View File

@@ -2,32 +2,21 @@
// Public
Player::Player(QObject *parent) : QObject(parent),
player(this), output(this), timer(this), m_currentIndex(0), m_stations({
Station{"Autoradio", "autoradio", "", 0},
Station{"Live Mix", "live", "", 0},
Station{"Bass Boosted", "bassboosted", "", 0}
})
player(this), output(this), manager(this)
{
QObject::connect(&this->player, &QMediaPlayer::mediaStatusChanged, this, [this] () {
emit this->hasVideoChanged();
emit this->loadingChanged();
});
QObject::connect(&this->player, &QMediaPlayer::playbackStateChanged, this, [this] () {
qDebug("Player::Player()->Lambda: Playing got changed!");
emit this->playingChanged();
});
QObject::connect(&this->output, &QAudioOutput::volumeChanged, this, [this] () {
qDebug("Player::Player()->Lambda: Volume got changed!");
emit this->volumeChanged();
});
this->timer.start(metadataFetchTimeout);
QObject::connect(&this->timer, &QTimer::timeout, this, [this] () {
qDebug("Player::Player()->Lambda: Timer triggered!");
if (this->m_currentIndex > -1)
{
emit this->currentStationChanged();
}
emit this->stationsChanged();
});
this->output.setVolume(0.2);
this->player.setAudioOutput(&this->output);
@@ -39,12 +28,26 @@ 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;
}
bool Player::playing() const
{
return this->player.playbackState() == QMediaPlayer::PlayingState;
}
float Player::volume() const
{
return this->output.volume();
@@ -55,59 +58,67 @@ void Player::setVolume(float newVolume)
this->output.setVolume(newVolume);
}
const QList<Station> Player::stations() const
{
return this->m_stations;
}
const int Player::currentIndex() const
{
return this->m_currentIndex;
}
const Station* Player::currentStation() const
{
if (this->m_currentIndex < 0 || this->m_currentIndex >= this->m_stations.length())
{
qWarning("Player::currentStation(): Tried accessing out of bounds.");
return nullptr;
}
return &this->m_stations[this->m_currentIndex];
}
// Public slots
void Player::startPlaying(u_int8_t index)
void Player::loadVideo(QString id)
{
if (index < 0 || index >= this->m_stations.length())
if (this->hasVideo())
{
qWarning("Player::startPlaying(): Tried accessing out of bounds.");
this->unloadVideo();
}
const QNetworkReply* reply = this->manager.get(QNetworkRequest(QUrl("https://youvideo.nonamesoft.xyz/youvideo/video/" + id)));
if (reply->error() != QNetworkReply::NoError)
{
qDebug() << "Player::loadVideo(): Video " << id << " doesn't exist!\n";
delete reply;
return;
}
if (this->playing()) {
this->stopPlaying();
}
this->m_currentIndex = index;
emit this->currentIndexChanged();
emit this->currentStationChanged();
this->player.setSource(QUrl("https://youradio.nonamesoft.xyz/youradio/api/" + this->m_stations[index].m_slug));
qDebug() << "Player::fetchMetadata(): Yippie! Got video " << id << "\n";
this->player.setSource(QUrl("https://youvideo.nonamesoft.xyz/youvideo/api/" + id));
// Signals will be sent by shit sent from player
this->m_failed = false;
emit this->failedChanged();
delete reply;
}
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();
}
void Player::play()
{
if (!this->hasVideo() || !this->loading())
{
return;
}
this->player.play();
qDebug("Player::startPlaying(): Starting playing");
}
void Player::stopPlaying()
void Player::pause()
{
if (!this->playing())
if (!this->hasVideo() || !this->loading())
{
return;
}
this->player.pause();
}
void Player::stop()
{
if (!this->hasVideo() || !this->loading())
{
qWarning("Player::stopPlaying(): Prevented redundant stopPlaying() call.");
return;
}
this->player.stop();
this->player.setSource(QUrl(""));
this->m_currentIndex = -1;
emit this->currentIndexChanged();
emit this->currentStationChanged();
qDebug("Player::stopPlaying(): Stopping playback...");
this->unloadVideo();
}

View File

@@ -1,73 +1,54 @@
#pragma once
#include <QtMultimedia>
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QObject>
#include <QList>
#include <QTimer>
#include <QNetworkAccessManager>
#include <QtQmlIntegration/qqmlintegration.h>
constexpr int metadataFetchTimeout = 10000;
constexpr std::string_view metadataEndpoint = "https://youradio.nonamesoft.xyz/youradio/api/status-json.xsl";
struct Station
{
Q_GADGET
Q_PROPERTY(QString name MEMBER m_name);
Q_PROPERTY(QString slug MEMBER m_slug);
Q_PROPERTY(QString songTitle MEMBER m_songTitle);
Q_PROPERTY(int listeners MEMBER m_listeners);
public:
QString m_name;
QString m_slug;
QString m_songTitle;
int m_listeners;
};
class Player : public QObject
{
Q_OBJECT
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(const QList<Station> stations READ stations NOTIFY stationsChanged FINAL)
Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged FINAL)
Q_PROPERTY(const Station* currentStation READ currentStation NOTIFY currentStationChanged FINAL)
public:
Player(QObject *parent = nullptr);
~Player();
bool hasVideo() const;
bool loading() const;
bool failed() const;
bool playing() const;
float volume() const;
void setVolume(float newVolume);
const QList<Station> stations() const;
const int currentIndex() const;
const Station* currentStation() const;
public slots:
void startPlaying(u_int8_t index);
void stopPlaying();
void loadVideo(QString id);
void unloadVideo();
void play();
void pause();
void stop();
signals:
void hasVideoChanged();
void loadingChanged();
void failedChanged();
void playingChanged();
void volumeChanged();
void stationsChanged();
void currentIndexChanged();
void currentStationChanged();
private:
QMediaPlayer player;
QAudioOutput output;
QTimer timer;
QNetworkAccessManager manager;
int m_currentIndex;
QList<Station> m_stations;
bool m_failed = false;
};

View File

@@ -1,96 +0,0 @@
// 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", "QYouRadio"
VALUE "FileVersion", "1.0.0.1"
VALUE "InternalName", "qyouradio"
VALUE "LegalCopyright", "Copyright (C) Ghostfox 2025"
//VALUE "OriginalFilename", "appqyouradio.exe"
VALUE "ProductName", "QYouRadio"
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