Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac6248c66 | ||
|
|
c168086601 | ||
| 629408b02e | |||
|
|
902693bd40 | ||
|
|
e66cf1b137 | ||
|
|
e091c97305 | ||
|
|
5c9b0ec49a | ||
|
|
c91fba3ee2 | ||
|
|
6fc0736c2d |
@@ -72,7 +72,7 @@ jobs:
|
|||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
aqtversion: '==3.1.*'
|
aqtversion: '==3.1.*'
|
||||||
version: '6.10.0'
|
version: '6.8.0'
|
||||||
host: 'windows'
|
host: 'windows'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'win64_msvc2022_64'
|
arch: 'win64_msvc2022_64'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,6 +32,7 @@ qrc_*.cpp
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
*.res
|
*.res
|
||||||
*.rc
|
*.rc
|
||||||
|
!resources/qyouvideo.rc
|
||||||
/.qmake.cache
|
/.qmake.cache
|
||||||
/.qmake.stash
|
/.qmake.stash
|
||||||
|
|
||||||
|
|||||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -8,7 +8,7 @@
|
|||||||
"name": "(gdb) Launch",
|
"name": "(gdb) Launch",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/build/appqyouradio",
|
"program": "${workspaceFolder}/build/appqyouvideo",
|
||||||
"args": [],
|
"args": [],
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}/build",
|
"cwd": "${workspaceFolder}/build",
|
||||||
|
|||||||
@@ -1,49 +1,45 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
project(qyouvideo VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
project(qyouradio VERSION 0.1 LANGUAGES CXX)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core Quick Multimedia)
|
find_package(Qt6 REQUIRED COMPONENTS Core Quick Multimedia)
|
||||||
find_package(DiscordSDK)
|
|
||||||
|
|
||||||
qt_standard_project_setup(REQUIRES 6.8)
|
qt_standard_project_setup(REQUIRES 6.8)
|
||||||
|
|
||||||
configure_file(buildFlags.h.in buildFlags.h)
|
qt_add_executable(appqyouvideo
|
||||||
|
|
||||||
qt_add_executable(appqyouradio
|
|
||||||
main.cpp
|
main.cpp
|
||||||
buildFlags.h
|
|
||||||
player.cpp player.h
|
player.cpp player.h
|
||||||
resources/qyouradio.rc
|
resources/qyouvideo.rc
|
||||||
resources/resource.h
|
resources/resource.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(appqyouradio "resources"
|
qt_add_resources(appqyouvideo "resources"
|
||||||
PREFIX "/"
|
PREFIX "/"
|
||||||
FILES
|
FILES
|
||||||
resources/logo.png
|
resources/logo.png
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(Player.qml
|
set_source_files_properties(ComponentCache.qml
|
||||||
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
|
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
|
||||||
|
|
||||||
qt_add_qml_module(appqyouradio
|
qt_add_qml_module(appqyouvideo
|
||||||
URI qyouradio
|
URI qyouvideo
|
||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
QML_FILES
|
QML_FILES
|
||||||
Main.qml
|
Main.qml
|
||||||
# Player.qml
|
VideoEntry.qml
|
||||||
ViewPlayer.qml
|
ViewAbout.qml
|
||||||
ViewSettings.qml
|
ViewVideoList.qml
|
||||||
|
VideoPlayer.qml
|
||||||
|
ComponentCache.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
# 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
|
# If you are developing for iOS or macOS you should consider setting an
|
||||||
# explicit, fixed bundle identifier manually though.
|
# 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_GUI_IDENTIFIER com.example.appqyouradio
|
||||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||||
@@ -51,30 +47,24 @@ set_target_properties(appqyouradio PROPERTIES
|
|||||||
WIN32_EXECUTABLE TRUE
|
WIN32_EXECUTABLE TRUE
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(appqyouradio PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
|
target_link_libraries(appqyouvideo
|
||||||
|
|
||||||
target_link_libraries(appqyouradio
|
|
||||||
PUBLIC Qt6::Quick Qt6::Multimedia
|
PUBLIC Qt6::Quick Qt6::Multimedia
|
||||||
)
|
)
|
||||||
|
|
||||||
if (DiscordSDK_FOUND)
|
|
||||||
target_link_libraries(appqyouradio
|
|
||||||
PRIVATE DiscordSDK::DiscordSDK
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
install(TARGETS appqyouradio
|
install(TARGETS appqyouvideo
|
||||||
BUNDLE DESTINATION .
|
BUNDLE DESTINATION .
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_generate_deploy_app_script(
|
qt_generate_deploy_app_script(
|
||||||
TARGET appqyouradio
|
TARGET appqyouvideo
|
||||||
OUTPUT_SCRIPT deploy_script
|
OUTPUT_SCRIPT deploy_script
|
||||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||||
)
|
)
|
||||||
install(SCRIPT ${deploy_script})
|
install(SCRIPT ${deploy_script})
|
||||||
|
|
||||||
add_subdirectory(QYRComponents)
|
add_subdirectory(QYRComponents)
|
||||||
|
|
||||||
|
|||||||
11
ComponentCache.qml
Normal file
11
ComponentCache.qml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick 6.8
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property var videoPlayer: Qt.createComponent("VideoPlayer.qml")
|
||||||
|
property var viewVideoList: Qt.createComponent("ViewVideoList.qml")
|
||||||
|
property var viewAbout: Qt.createComponent("ViewAbout.qml")
|
||||||
|
|
||||||
|
Component.onCompleted: Player.unloadVideo()
|
||||||
|
}
|
||||||
240
Main.qml
240
Main.qml
@@ -9,193 +9,81 @@ ApplicationWindow {
|
|||||||
id: root
|
id: root
|
||||||
width: 1280
|
width: 1280
|
||||||
height: 800
|
height: 800
|
||||||
title: qsTr("QYouRadio")
|
title: qsTr("QYouVideo")
|
||||||
|
|
||||||
Component.onCompleted: function() {
|
flags: Qt.Dialog
|
||||||
console.log(Player.stations)
|
modality: Qt.ApplicationModal
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
header: Rectangle {
|
||||||
anchors.fill: parent
|
Layout.fillWidth: true
|
||||||
|
height: 43
|
||||||
|
|
||||||
Rectangle {
|
color: Colors.surface0
|
||||||
Layout.fillWidth: true
|
|
||||||
height: 43
|
|
||||||
|
|
||||||
color: Colors.surface0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 3
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.leftMargin: 5
|
|
||||||
text: "QYouRadio"
|
|
||||||
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 {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
TabBar {
|
|
||||||
id: tabbar
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
background: Item{}
|
|
||||||
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("Autoradio")
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("Live Mix")
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("Deep Bass")
|
|
||||||
}
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("Settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.margins: 20
|
|
||||||
|
|
||||||
interactive: false
|
|
||||||
currentIndex: tabbar.currentIndex
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 0
|
|
||||||
active: true
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ViewPlayer {
|
|
||||||
index: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 1
|
|
||||||
active: true
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ViewPlayer {
|
|
||||||
index: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 2
|
|
||||||
active: true
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ViewPlayer {
|
|
||||||
index: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: contentItem.highlightMoveDuration = 160;
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: false
|
anchors.fill: parent
|
||||||
Layout.alignment: Qt.AlignHCenter
|
anchors.margins: 3
|
||||||
Layout.topMargin: 30
|
|
||||||
Layout.bottomMargin: 30
|
|
||||||
width: 220
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Layout.rightMargin: 5
|
// visible: stack.depth > 1
|
||||||
text: Player.loading ? "Loading" : (Player.playing ? "Pause" : "Play")
|
text: "Back"
|
||||||
|
onClicked: stack.popCurrentItem()
|
||||||
onClicked: function() {
|
|
||||||
if (Player.playing) {
|
|
||||||
Player.stopPlaying();
|
|
||||||
} else {
|
|
||||||
Player.startPlaying(tabbar.currentIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
Label {
|
||||||
Layout.fillWidth: true
|
id: logo
|
||||||
|
// visible: stack.currentItem.StackView.index == 0
|
||||||
Layout.leftMargin: 5
|
Layout.leftMargin: 5
|
||||||
from: 0.1
|
text: "QYouVideo"
|
||||||
to: 1.1
|
heading: "h1"
|
||||||
stepSize: 0.05
|
clip: true
|
||||||
value: Player.volume + 0.1
|
|
||||||
onMoved: Player.volume = value - 0.1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Videos")
|
||||||
|
outlined: true
|
||||||
|
implicitWidth: 80
|
||||||
|
onClicked: if (!(stack.currentItem instanceof ViewVideoList)) stack.push(ComponentCache.viewVideoList.createObject(stack))
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr("About")
|
||||||
|
outlined: true
|
||||||
|
implicitWidth: 80
|
||||||
|
onClicked: if (!(stack.currentItem instanceof ViewAbout)) stack.push(ComponentCache.viewAbout.createObject(stack))
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: qsTr("Upload")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackView {
|
||||||
|
id: stack
|
||||||
|
// __wheelAreaScrollSpeed: 50
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
|
||||||
|
initialItem: ViewVideoList {}
|
||||||
|
|
||||||
|
function openVideo(id) {
|
||||||
|
// if (stack.find((item, index) => {
|
||||||
|
// // If found player instance
|
||||||
|
// if (item.id == id) {
|
||||||
|
// // stack.pop(index, StackView.Immediate);
|
||||||
|
// stack.push(item)
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }) == null) {
|
||||||
|
// If didn't find player instance
|
||||||
|
stack.push(ComponentCache.videoPlayer.createObject(stack, {id: id}));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
Player.qml.prev
122
Player.qml.prev
@@ -1,122 +0,0 @@
|
|||||||
import QtQuick 6.8
|
|
||||||
import QtMultimedia 6.8
|
|
||||||
|
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
Item {
|
|
||||||
readonly property string streamURLPrefix: "https://youradio.nonamesoft.xyz/youradio/api/"
|
|
||||||
readonly property string metadataURL: "https://youradio.nonamesoft.xyz/youradio/api/status-json.xsl"
|
|
||||||
readonly property var slugLookup: ({
|
|
||||||
"autoradio": 0,
|
|
||||||
"live": 1,
|
|
||||||
"bassboosted": 2
|
|
||||||
})
|
|
||||||
property var streams: ([
|
|
||||||
{
|
|
||||||
name: "Autoradio",
|
|
||||||
slug: "autoradio",
|
|
||||||
title: "",
|
|
||||||
listeners: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Live Mix",
|
|
||||||
slug: "live",
|
|
||||||
title: "",
|
|
||||||
listeners: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Deep Bass",
|
|
||||||
slug: "bassboosted",
|
|
||||||
title: "",
|
|
||||||
listeners: 0
|
|
||||||
}
|
|
||||||
])
|
|
||||||
property var failedConnAttempts: 0
|
|
||||||
|
|
||||||
property alias playing: player.playing
|
|
||||||
property alias volume: output.volume
|
|
||||||
property var loading: player.mediaStatus == Qt.LoadingMedia
|
|
||||||
|
|
||||||
property var currentIndex: null
|
|
||||||
property var currentStream: null
|
|
||||||
|
|
||||||
function startPlaying(index) {
|
|
||||||
if ((playing && index == currentIndex) || index < 0 || index >= streams.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (playing) {
|
|
||||||
player.stop();
|
|
||||||
}
|
|
||||||
print("Starting playing stream no. " + index);
|
|
||||||
currentIndex = index;
|
|
||||||
currentStream = streams[index];
|
|
||||||
player.source = streamURLPrefix + currentStream.slug;
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPlaying() {
|
|
||||||
if (!playing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
print("Stopping playback");
|
|
||||||
currentIndex = null;
|
|
||||||
currentStream = null;
|
|
||||||
player.source = "";
|
|
||||||
player.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaPlayer {
|
|
||||||
id: player
|
|
||||||
source: ""
|
|
||||||
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
id: output
|
|
||||||
volume: 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
onErrorOccurred: function(error, errorString) {
|
|
||||||
const index = currentIndex
|
|
||||||
currentIndex = null
|
|
||||||
currentIndex = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 10000
|
|
||||||
repeat: true
|
|
||||||
running: true
|
|
||||||
triggeredOnStart: true
|
|
||||||
onTriggered: function() {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.onreadystatechange = function() {
|
|
||||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
|
||||||
parent.failedConnAttempts = 0;
|
|
||||||
// try {
|
|
||||||
const object = JSON.parse(xhr.responseText.toString()).icestats;
|
|
||||||
object.source.forEach(station => {
|
|
||||||
const index = parent.slugLookup[station.server_name];
|
|
||||||
if (index == null) {
|
|
||||||
console.warn("Unknown slug encountered in metadata: " + station.server_name);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parent.streams[index].title = station.title.replace(/\[[a-zA-Z0-9]{11}\]/, "");
|
|
||||||
parent.streams[index].listeners = station.listeners;
|
|
||||||
if (index == parent.currentIndex) {
|
|
||||||
parent.currentStream = parent.streams[index];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// } catch {
|
|
||||||
// console.error("Failed deserializing metadata response");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xhr.open("GET", parent.metadataURL);
|
|
||||||
xhr.timeout = 10000;
|
|
||||||
xhr.ontimeout = function() {
|
|
||||||
console.log("Metadata request timed out after 10 seconds");
|
|
||||||
parent.failedConnAttempts++;
|
|
||||||
}
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
33
QYRComponents/BusyIndicator.qml
Normal file
33
QYRComponents/BusyIndicator.qml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import QtQuick 6.8
|
||||||
|
import QtQuick.Controls 6.8
|
||||||
|
import QtQuick.Controls.Basic 6.8
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: spinner
|
||||||
|
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: spinner
|
||||||
|
running: spinner.visible && spinner.running
|
||||||
|
from: 0
|
||||||
|
to: 360
|
||||||
|
loops: Animation.Infinite
|
||||||
|
duration: 1250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ qt_add_qml_module(QYRComponents
|
|||||||
Label.qml
|
Label.qml
|
||||||
TabButton.qml
|
TabButton.qml
|
||||||
YouAds.qml
|
YouAds.qml
|
||||||
|
QML_FILES BusyIndicator.qml
|
||||||
# SOURCES qyrcomponents.cpp qyrcomponents.h
|
# SOURCES qyrcomponents.cpp qyrcomponents.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import QtQuick.Controls.Basic 6.8
|
|||||||
Slider {
|
Slider {
|
||||||
snapMode: Slider.SnapAlways
|
snapMode: Slider.SnapAlways
|
||||||
|
|
||||||
|
property real buffered
|
||||||
|
|
||||||
implicitWidth: 130
|
implicitWidth: 130
|
||||||
implicitHeight: 20
|
implicitHeight: 20
|
||||||
|
|
||||||
@@ -26,6 +28,17 @@ Slider {
|
|||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: "#555"
|
color: "#555"
|
||||||
radius: 5
|
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 {
|
handle: Rectangle {
|
||||||
|
|||||||
80
VideoEntry.qml
Normal file
80
VideoEntry.qml
Normal 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: stack.openVideo(parent.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
200
VideoPlayer.qml
Normal file
200
VideoPlayer.qml
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import QtQuick 6.8
|
||||||
|
import QtMultimedia 6.8
|
||||||
|
import QtQuick.Controls 6.8
|
||||||
|
import QtQuick.Layouts 6.8
|
||||||
|
|
||||||
|
import QYRComponents 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
// color: Colors.surface1
|
||||||
|
// radius: 10
|
||||||
|
|
||||||
|
StackView.onRemoved: destroy()
|
||||||
|
|
||||||
|
required property string id
|
||||||
|
property bool hasThumbnail: false
|
||||||
|
property bool loading: true
|
||||||
|
property bool failed: false
|
||||||
|
property string title
|
||||||
|
property string description
|
||||||
|
property string extension
|
||||||
|
property real position: 0
|
||||||
|
property real duration: 0
|
||||||
|
property real ratio
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Component.onCompleted: Player.onPositionChanged = () => {
|
||||||
|
console.log("test print");
|
||||||
|
if (Player.id == id) {
|
||||||
|
position = Player.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onDestruction: if (Player.id == id) Player.unloadVideo()
|
||||||
|
onPositionChanged: if (Player.id == id) Player.position = position
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
visible: !loading && !failed
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
// height: width * parent.parent.ratio
|
||||||
|
asynchronous: true
|
||||||
|
cache: true
|
||||||
|
source: parent.parent.hasThumbnail ? ("https://youvideo.nonamesoft.xyz/thumbnails/" + parent.parent.id) : "https://youvideo.nonamesoft.xyz/thumbnails/audio.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Button {
|
||||||
|
text: Player.id == id ? (Player.loading ? "Loading" : (Player.playing ? "Pause" : "Play")) : "Play"
|
||||||
|
enabled: Player.id != id || !(Player.loading && Player.id == id)
|
||||||
|
onClicked: function() {
|
||||||
|
if (Player.id != id) {
|
||||||
|
Player.loadVideo(parent.parent.parent.id, parent.parent.parent.extension, parent.parent.parent.title, parent.parent.parent.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Player.playing) {
|
||||||
|
Player.pause();
|
||||||
|
} else {
|
||||||
|
Player.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
heading: "h4"
|
||||||
|
text: new Date(parent.parent.parent.position).toISOString().slice(14, 19)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 0
|
||||||
|
to: parent.parent.parent.duration
|
||||||
|
value: parent.parent.parent.position
|
||||||
|
buffered: Player.id == id ? Player.buffered : 0
|
||||||
|
onMoved: parent.parent.parent.position = value
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
heading: "h4"
|
||||||
|
text: new Date(parent.parent.parent.duration).toISOString().slice(14, 19)
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: Player.buffered
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
value: Player.volume
|
||||||
|
onMoved: Player.volume = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
clip: true
|
||||||
|
text: parent.parent.title
|
||||||
|
heading: "h2"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
clip: true
|
||||||
|
text: parent.parent.description
|
||||||
|
heading: "h4"
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: loading
|
||||||
|
running: loading
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: failed
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Couldn't get video details!"
|
||||||
|
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.parent.fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 0
|
||||||
|
running: true
|
||||||
|
onTriggered: parent.fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchData() {
|
||||||
|
loading = true;
|
||||||
|
failed = false;
|
||||||
|
const xhr = new XMLHttpRequest;
|
||||||
|
xhr.open("GET", "https://youvideo.nonamesoft.xyz/youvideo/video/" + id);
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
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 video metadata");
|
||||||
|
title = data.name;
|
||||||
|
description = data.description;
|
||||||
|
extension = data.extension;
|
||||||
|
duration = data.metadata.duration * 1000;
|
||||||
|
ratio = data.metadata.size[0] / data.metadata.size[1];
|
||||||
|
if (ratio == NaN) {
|
||||||
|
ratio = 1.77777;
|
||||||
|
}
|
||||||
|
console.log(ratio);
|
||||||
|
if (Player.id == id) {
|
||||||
|
console.log("Already playing, this is not intended but possible in its current state so it's a feature, not a bug :3");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player.loadVideo(id, extension, title, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
loading = false;
|
||||||
|
failed = true;
|
||||||
|
console.log("Request timed out");
|
||||||
|
}
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
ViewAbout.qml
Normal file
59
ViewAbout.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import QtQuick 6.8
|
|
||||||
import QtQuick.Controls 6.8
|
|
||||||
import QtQuick.Controls.Basic 6.8
|
|
||||||
import QtQuick.Layouts 6.8
|
|
||||||
import QYRComponents 1.0
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
property var index: null
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.bottomMargin: 20
|
|
||||||
text: qsTr(Player.stations[index].name)
|
|
||||||
font.bold: true
|
|
||||||
heading: "h2"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: Player.stations[index].songTitle
|
|
||||||
font.pixelSize: 72
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Listening: " + Player.stations[index].listeners
|
|
||||||
heading: "h2"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 20
|
|
||||||
visible: (Player.currentIndex > -1 && Player.currentIndex != parent.index)
|
|
||||||
text: qsTr("Another station is currently playing")
|
|
||||||
heading: "h2"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
180
ViewSettings.qml
180
ViewSettings.qml
@@ -1,180 +0,0 @@
|
|||||||
import QtQuick 6.8
|
|
||||||
import QtQuick.Controls 6.8
|
|
||||||
import QtQuick.Controls.Basic 6.8
|
|
||||||
import QtQuick.Layouts 6.8
|
|
||||||
import QYRComponents 1.0
|
|
||||||
|
|
||||||
ApplicationWindow {
|
|
||||||
width: 840
|
|
||||||
height: 560
|
|
||||||
visible: true
|
|
||||||
title: qsTr("Settings")
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Container {
|
|
||||||
id: settingsCategory
|
|
||||||
Layout.fillWidth: false
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.rightMargin: 15
|
|
||||||
|
|
||||||
implicitWidth: 240
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
contentItem: ListView {
|
|
||||||
spacing: 7.5
|
|
||||||
model: settingsCategory.contentModel
|
|
||||||
snapMode: ListView.SnapOneItem
|
|
||||||
orientation: ListView.Vertical
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Appearance")
|
|
||||||
width: 240
|
|
||||||
outlined: true
|
|
||||||
onClicked: settingsCategory.currentIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Language")
|
|
||||||
width: 240
|
|
||||||
outlined: true
|
|
||||||
onClicked: settingsCategory.currentIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Playback Settings")
|
|
||||||
width: 240
|
|
||||||
outlined: true
|
|
||||||
onClicked: settingsCategory.currentIndex = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("About")
|
|
||||||
width: 240
|
|
||||||
outlined: true
|
|
||||||
onClicked: settingsCategory.currentIndex = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
orientation: Qt.Vertical
|
|
||||||
interactive: true
|
|
||||||
currentIndex: tabbar.currentIndex
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 0
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ColumnLayout {
|
|
||||||
Label {
|
|
||||||
text: "Appearance"
|
|
||||||
heading: "h1"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 1
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ColumnLayout {
|
|
||||||
Label {
|
|
||||||
text: "Language"
|
|
||||||
heading: "h1"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 2
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ColumnLayout {
|
|
||||||
Label {
|
|
||||||
text: "Playback Settings"
|
|
||||||
heading: "h1"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
// active: tabbar.currentIndex == 3
|
|
||||||
asynchronous: true
|
|
||||||
visible: status == Loader.Ready
|
|
||||||
sourceComponent: ColumnLayout {
|
|
||||||
Label {
|
|
||||||
text: "About"
|
|
||||||
heading: "h1"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "YouRadio"
|
|
||||||
heading: "h2"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "by Youpiter"
|
|
||||||
heading: "h3"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Music source"
|
|
||||||
heading: "base"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "QYouRadio"
|
|
||||||
heading: "h3"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "by Ghostfox"
|
|
||||||
heading: "h3"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Client development"
|
|
||||||
heading: "base"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Attribution"
|
|
||||||
heading: "h1"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Qt"
|
|
||||||
heading: "h2"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "by Qt Group Inc."
|
|
||||||
heading: "h3"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Open-Source library on which QYouRadio is built uppon, licensed under LGPL-3.0"
|
|
||||||
heading: "base"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
97
ViewVideoList.qml
Normal file
97
ViewVideoList.qml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
width: Math.floor(parent.width / cellWidth) * cellWidth
|
||||||
|
visible: !loading || !failed
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
model: ListModel { id: model }
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
cellWidth: 384
|
||||||
|
cellHeight: 224
|
||||||
|
|
||||||
|
delegate: VideoEntry {}
|
||||||
|
|
||||||
|
Component.onCompleted: console.log(parent.width / cellWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.parent.fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 0
|
||||||
|
running: true
|
||||||
|
onTriggered: parent.fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchData() {
|
||||||
|
model.clear();
|
||||||
|
loading = true;
|
||||||
|
failed = false;
|
||||||
|
const xhr = new XMLHttpRequest;
|
||||||
|
xhr.open("GET", "https://youvideo.nonamesoft.xyz/youvideo/api/videos");
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
, wrapQtAppsHook
|
, wrapQtAppsHook
|
||||||
}:
|
}:
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
pname = "qyouradio";
|
pname = "qyouvideo";
|
||||||
version = "1.0";
|
version = "1.0";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
#cmakedefine01 DiscordSDK_FOUND
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# Locate Discord Social SDK library and headers.
|
|
||||||
#
|
|
||||||
# Copyright (c) 2025 hat_kid
|
|
||||||
# https://github.com/thehatkid/DiscordSocialSDKExample
|
|
||||||
#
|
|
||||||
# Usage of this module as follows:
|
|
||||||
# find_package(DiscordSDK)
|
|
||||||
#
|
|
||||||
# Variables defined by this module:
|
|
||||||
# DISCORDSDK_FOUND Whether was found library and headers.
|
|
||||||
# DISCORDSDK_INCLUDE_DIR SDK include path.
|
|
||||||
# DISCORDSDK_LIBRARY SDK shared library path.
|
|
||||||
# DISCORDSDK_IMPLIB Win32: SDK object library path to link.
|
|
||||||
|
|
||||||
# Set SDK root directory path (You can change it to different path)
|
|
||||||
set(DISCORDSDK_ROOT_DIR "${CMAKE_SOURCE_DIR}/ext/discord_social_sdk" CACHE PATH "Discord Social SDK root path")
|
|
||||||
|
|
||||||
# Set SDK library build variant
|
|
||||||
set(DISCORDSDK_VARIANT "release" CACHE STRING "Discord Social SDK library variant")
|
|
||||||
|
|
||||||
set_property(CACHE DISCORDSDK_VARIANT PROPERTY STRINGS "release" "debug")
|
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
||||||
set(DISCORDSDK_VARIANT "debug")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Find SDK include directory path
|
|
||||||
find_path(
|
|
||||||
DISCORDSDK_INCLUDE_DIR
|
|
||||||
NAMES cdiscord.h discordpp.h
|
|
||||||
PATHS ${DISCORDSDK_ROOT_DIR}/include
|
|
||||||
DOC "Discord Social SDK include directory"
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
# Find SDK library path
|
|
||||||
if(WIN32)
|
|
||||||
find_file(
|
|
||||||
DISCORDSDK_LIBRARY
|
|
||||||
NAMES discord_partner_sdk.dll
|
|
||||||
PATHS ${DISCORDSDK_ROOT_DIR}/bin/${DISCORDSDK_VARIANT}
|
|
||||||
DOC "Discord Social SDK Windows Dynamic Link Library (.dll)"
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
)
|
|
||||||
find_file(
|
|
||||||
DISCORDSDK_IMPLIB
|
|
||||||
NAMES discord_partner_sdk.lib
|
|
||||||
PATHS ${DISCORDSDK_ROOT_DIR}/lib/${DISCORDSDK_VARIANT}
|
|
||||||
DOC "Discord Social SDK Windows Object Library (.lib)"
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
find_library(
|
|
||||||
DISCORDSDK_LIBRARY
|
|
||||||
NAMES libdiscord_partner_sdk discord_partner_sdk
|
|
||||||
PATHS ${DISCORDSDK_ROOT_DIR}/lib/${DISCORDSDK_VARIANT}
|
|
||||||
DOC "Discord Social SDK shared library"
|
|
||||||
NO_DEFAULT_PATH
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
mark_as_advanced(
|
|
||||||
DISCORDSDK_ROOT_DIR
|
|
||||||
DISCORDSDK_VARIANT
|
|
||||||
DISCORDSDK_INCLUDE_DIR
|
|
||||||
DISCORDSDK_LIBRARY
|
|
||||||
)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
find_package_handle_standard_args(
|
|
||||||
DiscordSDK
|
|
||||||
REQUIRED_VARS DISCORDSDK_IMPLIB DISCORDSDK_LIBRARY DISCORDSDK_INCLUDE_DIR
|
|
||||||
)
|
|
||||||
mark_as_advanced(DISCORDSDK_IMPLIB)
|
|
||||||
else()
|
|
||||||
find_package_handle_standard_args(
|
|
||||||
DiscordSDK
|
|
||||||
REQUIRED_VARS DISCORDSDK_LIBRARY DISCORDSDK_INCLUDE_DIR
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT DiscordSDK_FOUND)
|
|
||||||
message("Could NOT find Discord Social SDK redistributable! Please check for SDK files in ${DISCORDSDK_ROOT_DIR}")
|
|
||||||
else()
|
|
||||||
# Add imported shared library as DiscordSDK::DiscordSDK
|
|
||||||
add_library(DiscordSDK::DiscordSDK SHARED IMPORTED)
|
|
||||||
|
|
||||||
set_target_properties(
|
|
||||||
DiscordSDK::DiscordSDK PROPERTIES
|
|
||||||
IMPORTED_LOCATION ${DISCORDSDK_LIBRARY}
|
|
||||||
INTERFACE_COMPILE_DEFINITIONS DISCORDPP_IMPLEMENTATION
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES ${DISCORDSDK_INCLUDE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set_target_properties(
|
|
||||||
DiscordSDK::DiscordSDK PROPERTIES
|
|
||||||
IMPORTED_IMPLIB ${DISCORDSDK_IMPLIB}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760315168,
|
"lastModified": 1760807995,
|
||||||
"narHash": "sha256-qWDhFoiz6VSd+S+rVzC2m5u8xuAzxRsuDI8OojbPEZ4=",
|
"narHash": "sha256-Xpg9h3/uNVMJXZq2LzBDaDM1u057ox0wnZGvxSogNY0=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "160e3dfce302bb2fcd03761b84248c7534f3b948",
|
"rev": "a2975bcaf36af01a1279b84ab575fbc12d472b6a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
11
main.cpp
11
main.cpp
@@ -1,9 +1,6 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include "buildFlags.h"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
@@ -13,15 +10,9 @@ int main(int argc, char *argv[])
|
|||||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
|
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
|
||||||
&app, []() { QCoreApplication::exit(-1); },
|
&app, []() { QCoreApplication::exit(-1); },
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
engine.loadFromModule("qyouradio", "Main");
|
engine.loadFromModule("qyouvideo", "Main");
|
||||||
|
|
||||||
app.setWindowIcon(QIcon(":/resources/logo.png"));
|
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();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
188
player.cpp
188
player.cpp
@@ -1,33 +1,65 @@
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
// Public
|
// HOLY GRAIL
|
||||||
|
// https://doc.qt.io/qt-6/qaudiobufferoutput.html
|
||||||
|
|
||||||
Player::Player(QObject *parent) : QObject(parent),
|
Player::Player(QObject *parent) : QObject(parent),
|
||||||
player(this), output(this), timer(this), m_currentIndex(0), m_stations({
|
player(this), output(this)
|
||||||
Station{"Autoradio", "autoradio", "", 0},
|
|
||||||
Station{"Live Mix", "live", "", 0},
|
|
||||||
Station{"Bass Boosted", "bassboosted", "", 0}
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
|
QObject::connect(&this->player, &QMediaPlayer::mediaStatusChanged, this, [this] () {
|
||||||
|
const QMediaPlayer::MediaStatus status = this->player.mediaStatus();
|
||||||
|
if (status == QMediaPlayer::NoMedia)
|
||||||
|
{
|
||||||
|
qDebug("Player::Player(): NoMedia");
|
||||||
|
this->unloadVideo();
|
||||||
|
}
|
||||||
|
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
|
||||||
|
{
|
||||||
|
if (this->m_position == 0)
|
||||||
|
{
|
||||||
|
this->player.play();
|
||||||
|
}
|
||||||
|
this->m_loading = false;
|
||||||
|
emit this->loadingChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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] () {
|
QObject::connect(&this->player, &QMediaPlayer::playbackStateChanged, this, [this] () {
|
||||||
qDebug("Player::Player()->Lambda: Playing got changed!");
|
qDebug("Player::Player() playingChanged");
|
||||||
emit this->playingChanged();
|
emit this->playingChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(&this->output, &QAudioOutput::volumeChanged, this, [this] () {
|
QObject::connect(&this->output, &QAudioOutput::volumeChanged, this, [this] () {
|
||||||
qDebug("Player::Player()->Lambda: Volume got changed!");
|
|
||||||
emit this->volumeChanged();
|
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->output.setVolume(0.2);
|
||||||
this->player.setAudioOutput(&this->output);
|
this->player.setAudioOutput(&this->output);
|
||||||
|
|
||||||
@@ -39,75 +71,111 @@ Player::~Player()
|
|||||||
qDebug("Player::~Player(): Destructed");
|
qDebug("Player::~Player(): Destructed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ********
|
||||||
|
// Read
|
||||||
|
// ********
|
||||||
|
|
||||||
bool Player::playing() const
|
bool Player::playing() const
|
||||||
{
|
{
|
||||||
return this->player.playbackState() == QMediaPlayer::PlayingState;
|
return this->player.playbackState() == QMediaPlayer::PlayingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float Player::volume() const
|
float Player::volume() const
|
||||||
{
|
{
|
||||||
return this->output.volume();
|
return this->output.volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *********
|
||||||
|
// Write
|
||||||
|
// *********
|
||||||
|
|
||||||
void Player::setVolume(float newVolume)
|
void Player::setVolume(float newVolume)
|
||||||
{
|
{
|
||||||
this->output.setVolume(newVolume);
|
this->output.setVolume(newVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QList<Station> Player::stations() const
|
void Player::setPosition(float newPosition)
|
||||||
{
|
{
|
||||||
return this->m_stations;
|
this->player.setPosition(newPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int Player::currentIndex() const
|
|
||||||
{
|
|
||||||
return this->m_currentIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Station* Player::currentStation() const
|
|
||||||
|
// ***********
|
||||||
|
// Methods
|
||||||
|
// ***********
|
||||||
|
|
||||||
|
// For QYouRadio:
|
||||||
|
// QEventLoop loop(this);
|
||||||
|
// QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
// loop.exec();
|
||||||
|
// const QJsonDocument data = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
|
void Player::loadVideo(QString id, QString extension, QString title, float position)
|
||||||
{
|
{
|
||||||
if (this->m_currentIndex < 0 || this->m_currentIndex >= this->m_stations.length())
|
if (this->m_id != id)
|
||||||
{
|
{
|
||||||
qWarning("Player::currentStation(): Tried accessing out of bounds.");
|
this->unloadVideo();
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
return &this->m_stations[this->m_currentIndex];
|
|
||||||
|
this->m_id = id;
|
||||||
|
emit this->idChanged();
|
||||||
|
|
||||||
|
this->player.setSource(QUrl("https://youvideo.nonamesoft.xyz/youvideo/api/videofile/with_extension/" + id + extension));
|
||||||
|
|
||||||
|
this->m_title = title;
|
||||||
|
emit this->titleChanged();
|
||||||
|
|
||||||
|
this->m_position = position;
|
||||||
|
emit this->positionChanged();
|
||||||
|
|
||||||
|
this->m_buffered = 0;
|
||||||
|
emit this->bufferedChanged();
|
||||||
|
|
||||||
|
this->m_loading = true;
|
||||||
|
emit this->loadingChanged();
|
||||||
|
|
||||||
|
this->m_failed = false;
|
||||||
|
emit this->failedChanged();
|
||||||
|
|
||||||
|
qDebug() << "Player::loadVideo(): Loaded video https://youvideo.nonamesoft.xyz/youvideo/api/videofile/with_extension/" + id + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public slots
|
void Player::unloadVideo() {
|
||||||
void Player::startPlaying(u_int8_t index)
|
qDebug("Player::unloadVideo(): Video unloaded");
|
||||||
{
|
|
||||||
if (index < 0 || index >= this->m_stations.length())
|
|
||||||
{
|
|
||||||
qWarning("Player::startPlaying(): Tried accessing out of bounds.");
|
|
||||||
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));
|
this->m_id = "";
|
||||||
this->player.play();
|
emit this->idChanged();
|
||||||
qDebug("Player::startPlaying(): Starting playing");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::stopPlaying()
|
|
||||||
{
|
|
||||||
if (!this->playing())
|
|
||||||
{
|
|
||||||
qWarning("Player::stopPlaying(): Prevented redundant stopPlaying() call.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->player.stop();
|
|
||||||
this->player.setSource(QUrl(""));
|
this->player.setSource(QUrl(""));
|
||||||
|
this->player.stop();
|
||||||
|
|
||||||
this->m_currentIndex = -1;
|
this->m_title = "";
|
||||||
emit this->currentIndexChanged();
|
emit this->titleChanged();
|
||||||
emit this->currentStationChanged();
|
|
||||||
|
|
||||||
qDebug("Player::stopPlaying(): Stopping playback...");
|
this->m_loading = false;
|
||||||
|
emit this->loadingChanged();
|
||||||
|
|
||||||
|
this->m_failed = false;
|
||||||
|
emit this->failedChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::play()
|
||||||
|
{
|
||||||
|
if (this->m_id.length() == 0 || this->m_loading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->player.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::pause()
|
||||||
|
{
|
||||||
|
if (this->m_id.length() == 0 || this->m_loading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->player.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
71
player.h
71
player.h
@@ -1,73 +1,60 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QtMultimedia>
|
#include <QtMultimedia>
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QList>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QtQmlIntegration/qqmlintegration.h>
|
#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
|
class Player : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(bool playing READ playing NOTIFY playingChanged FINAL)
|
Q_PROPERTY(QString title MEMBER m_title NOTIFY titleChanged FINAL)
|
||||||
Q_PROPERTY(float volume READ volume WRITE setVolume NOTIFY volumeChanged FINAL)
|
Q_PROPERTY(QString id MEMBER m_id NOTIFY idChanged FINAL)
|
||||||
Q_PROPERTY(const QList<Station> stations READ stations NOTIFY stationsChanged FINAL)
|
Q_PROPERTY(float position MEMBER m_position WRITE setPosition NOTIFY positionChanged FINAL)
|
||||||
Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged FINAL)
|
Q_PROPERTY(float buffered MEMBER m_buffered NOTIFY bufferedChanged FINAL)
|
||||||
Q_PROPERTY(const Station* currentStation READ currentStation NOTIFY currentStationChanged 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:
|
public:
|
||||||
Player(QObject *parent = nullptr);
|
Player(QObject *parent = nullptr);
|
||||||
~Player();
|
~Player();
|
||||||
|
|
||||||
bool playing() const;
|
bool playing() const;
|
||||||
|
float volume() const;
|
||||||
|
|
||||||
float volume() const;
|
void setPosition(float newPosition);
|
||||||
void setVolume(float newVolume);
|
void setVolume(float newVolume);
|
||||||
|
|
||||||
const QList<Station> stations() const;
|
|
||||||
|
|
||||||
const int currentIndex() const;
|
|
||||||
const Station* currentStation() const;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void startPlaying(u_int8_t index);
|
void loadVideo(QString id, QString extension, QString title, float position);
|
||||||
void stopPlaying();
|
void unloadVideo();
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void titleChanged();
|
||||||
|
void idChanged();
|
||||||
|
void positionChanged();
|
||||||
|
void bufferedChanged();
|
||||||
|
void loadingChanged();
|
||||||
|
void failedChanged();
|
||||||
void playingChanged();
|
void playingChanged();
|
||||||
void volumeChanged();
|
void volumeChanged();
|
||||||
void stationsChanged();
|
|
||||||
void currentIndexChanged();
|
|
||||||
void currentStationChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMediaPlayer player;
|
QMediaPlayer player;
|
||||||
QAudioOutput output;
|
QAudioOutput output;
|
||||||
QTimer timer;
|
\
|
||||||
|
QString m_title = "";
|
||||||
int m_currentIndex;
|
QString m_id = "";
|
||||||
QList<Station> m_stations;
|
float m_position = 0;
|
||||||
|
float m_buffered = 0;
|
||||||
|
bool m_loading = false;
|
||||||
|
bool m_failed = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ BEGIN
|
|||||||
BLOCK "040904b0"
|
BLOCK "040904b0"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "LAMINAX CO."
|
VALUE "CompanyName", "LAMINAX CO."
|
||||||
VALUE "FileDescription", "QYouRadio"
|
VALUE "FileDescription", "QYouVideo"
|
||||||
VALUE "FileVersion", "1.0.0.1"
|
VALUE "FileVersion", "1.0.0.1"
|
||||||
VALUE "InternalName", "qyouradio"
|
VALUE "InternalName", "qyouvideo"
|
||||||
VALUE "LegalCopyright", "Copyright (C) Ghostfox 2025"
|
VALUE "LegalCopyright", "Copyright (C) Ghostfox 2025"
|
||||||
//VALUE "OriginalFilename", "appqyouradio.exe"
|
//VALUE "OriginalFilename", "appqyouvideo.exe"
|
||||||
VALUE "ProductName", "QYouRadio"
|
VALUE "ProductName", "QYouVideo"
|
||||||
VALUE "ProductVersion", "1.0.0.1"
|
VALUE "ProductVersion", "1.0.0.1"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
Reference in New Issue
Block a user