diff --git a/.gitea/workflows/build-git.yaml b/.gitea/workflows/build-git.yaml index 9218606..b49fb32 100644 --- a/.gitea/workflows/build-git.yaml +++ b/.gitea/workflows/build-git.yaml @@ -72,7 +72,7 @@ jobs: uses: jurplel/install-qt-action@v3 with: aqtversion: '==3.1.*' - version: '6.8.0' + version: '6.10.0' host: 'windows' target: 'desktop' arch: 'win64_msvc2022_64' diff --git a/.vscode/launch.json b/.vscode/launch.json index ae62e49..4690e6d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/build/appqyouvideo", + "program": "${workspaceFolder}/build/appqyouradio", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/build", diff --git a/Player.qml.prev b/Player.qml.prev new file mode 100644 index 0000000..44e1139 --- /dev/null +++ b/Player.qml.prev @@ -0,0 +1,122 @@ +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(); + } + } +} diff --git a/ViewPlayer.qml b/ViewPlayer.qml new file mode 100644 index 0000000..5559012 --- /dev/null +++ b/ViewPlayer.qml @@ -0,0 +1,51 @@ +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 + } +} diff --git a/ViewSettings.qml b/ViewSettings.qml new file mode 100644 index 0000000..3aaa416 --- /dev/null +++ b/ViewSettings.qml @@ -0,0 +1,180 @@ +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 + } + } + } + } + } +} diff --git a/buildFlags.h.in b/buildFlags.h.in new file mode 100644 index 0000000..b192e0f --- /dev/null +++ b/buildFlags.h.in @@ -0,0 +1 @@ +#cmakedefine01 DiscordSDK_FOUND \ No newline at end of file diff --git a/cmake/FindDiscordSDK.cmake b/cmake/FindDiscordSDK.cmake new file mode 100644 index 0000000..af9d0b2 --- /dev/null +++ b/cmake/FindDiscordSDK.cmake @@ -0,0 +1,103 @@ +# 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() \ No newline at end of file diff --git a/ext/.gitkeep b/ext/.gitkeep new file mode 100644 index 0000000..e69de29