Compare commits
16 Commits
009ed23571
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63d9c21e98 | ||
|
|
c99efcf9e3 | ||
|
|
01a3775ac7 | ||
| 1f821d58cc | |||
| 0847ca9ffd | |||
| 07e33ebb35 | |||
| 00b267e902 | |||
|
|
6d0689718d | ||
|
|
18ad69ba5d | ||
|
|
d46d37c465 | ||
|
|
def26f8fda | ||
| 5ffbede5a0 | |||
| 6fe5061898 | |||
| 3dd897d576 | |||
| 0672391721 | |||
| f0559753f3 |
127
.gitea/workflows/build-git.yaml
Normal file
127
.gitea/workflows/build-git.yaml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch
|
||||||
|
push # when to trigger this. Here, on every push
|
||||||
|
jobs:
|
||||||
|
build_linux:
|
||||||
|
name: "Build and test"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies (linux)
|
||||||
|
run: sudo apt install ninja-build
|
||||||
|
if: matrix.os == 'ubuntu-latest' # conditional, runs this step only on the Ubuntu runner
|
||||||
|
- name: Install Qt
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
version: '6.9.3'
|
||||||
|
- uses: https://github.com/actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
# We don't need to set up the environment variable for CMake to see Qt because the install-qt-action
|
||||||
|
# sets up the necessary variables automatically
|
||||||
|
run: cmake -S . -B build -G "Ninja Multi-Config" && cmake --build build --config Release
|
||||||
|
#
|
||||||
|
# Create the AppImage
|
||||||
|
#
|
||||||
|
- name: Create AppImage
|
||||||
|
run: |
|
||||||
|
make INSTALL_ROOT=appdir install
|
||||||
|
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" -O linuxdeployqt
|
||||||
|
chmod a+x linuxdeployqt
|
||||||
|
./linuxdeployqt appdir/usr/share/applications/*.desktop -appimage -bundle-non-qt-libs -extra-plugins=imageformats/libqsvg.so -qmldir="${{env.QML_DIR_NIX}}"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rename AppImage to match "%AppName%-%Version%-Linux.AppImage" format
|
||||||
|
#
|
||||||
|
- name: Rename AppImage
|
||||||
|
run: mv *.AppImage ${{env.EXECUTABLE}}-${{env.VERSION}}-Linux.AppImage
|
||||||
|
|
||||||
|
#
|
||||||
|
# Upload AppImage to build artifacts
|
||||||
|
#
|
||||||
|
- name: Upload AppImage
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{env.EXECUTABLE}}-${{env.VERSION}}-Linux.AppImage
|
||||||
|
path: ${{env.EXECUTABLE}}-${{env.VERSION}}-Linux.AppImage
|
||||||
|
|
||||||
|
#
|
||||||
|
# Windows build
|
||||||
|
#
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
#
|
||||||
|
# Checkout the repository
|
||||||
|
#
|
||||||
|
- name: Checkout repository and submodules
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
#
|
||||||
|
# Configure MSVC
|
||||||
|
#
|
||||||
|
- name: Configure MSVC
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
with:
|
||||||
|
arch: x64
|
||||||
|
spectre: true
|
||||||
|
|
||||||
|
- name: Install Qt
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
aqtversion: '==3.1.*'
|
||||||
|
version: '6.10.0'
|
||||||
|
host: 'windows'
|
||||||
|
target: 'desktop'
|
||||||
|
arch: 'win64_msvc2022_64'
|
||||||
|
archives: 'qtdeclarative qtbase opengl32sw d3dcompiler_47'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Install NSIS
|
||||||
|
#
|
||||||
|
- name: Install NSIS
|
||||||
|
run: |
|
||||||
|
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install nsis
|
||||||
|
|
||||||
|
#
|
||||||
|
# Compile application
|
||||||
|
#
|
||||||
|
- name: Compile
|
||||||
|
run: |
|
||||||
|
qmake ${{env.QMAKE_PROJECT}} CONFIG+=release
|
||||||
|
nmake
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copy Qt DLLs, compiler runtime & application icon
|
||||||
|
#
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
mkdir bin
|
||||||
|
move release/${{env.EXECUTABLE}}.exe bin
|
||||||
|
windeployqt bin/${{env.EXECUTABLE}}.exe -qmldir="${{env.QML_DIR_WIN}}" --compiler-runtime
|
||||||
|
mkdir "${{env.APPLICATION}}"
|
||||||
|
move bin "${{env.APPLICATION}}"
|
||||||
|
xcopy deploy\windows\resources\icon.ico "${{env.APPLICATION}}"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create NSIS installer
|
||||||
|
#
|
||||||
|
- name: Make NSIS installer
|
||||||
|
run: |
|
||||||
|
move "${{env.APPLICATION}}" deploy\windows\nsis\
|
||||||
|
cd deploy\windows\nsis
|
||||||
|
makensis /X"SetCompressor /FINAL lzma" setup.nsi
|
||||||
|
ren *.exe ${{env.EXECUTABLE}}-${{env.VERSION}}-Windows.exe
|
||||||
|
|
||||||
|
#
|
||||||
|
# Upload installer to build artifacts
|
||||||
|
#
|
||||||
|
- name: Upload NSIS installer
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: ${{env.EXECUTABLE}}-${{env.VERSION}}-Windows.exe
|
||||||
|
path: deploy/windows/nsis/${{env.EXECUTABLE}}-${{env.VERSION}}-Windows.exe
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -85,6 +85,11 @@ CMakeLists.txt.user*
|
|||||||
.vs/
|
.vs/
|
||||||
out/
|
out/
|
||||||
|
|
||||||
|
.cache
|
||||||
.direnv
|
.direnv
|
||||||
dist
|
dist
|
||||||
|
CMakePresets.json
|
||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
|
|
||||||
|
ext/*
|
||||||
|
!ext/.gitkeep
|
||||||
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"C_Cpp.workspaceParsingPriority": "medium",
|
||||||
|
"files.associations": {
|
||||||
|
"flake.lock": "json",
|
||||||
|
"qobject": "cpp",
|
||||||
|
"chrono": "cpp",
|
||||||
|
"variant": "cpp",
|
||||||
|
"qtmultimedia": "cpp",
|
||||||
|
"qdebug": "cpp"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
.vscode/tasks.json
vendored
Normal file
17
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "cmake",
|
||||||
|
"label": "CMake: build",
|
||||||
|
"command": "build",
|
||||||
|
"options": {"cwd": "${workspaceFolder}/build"},
|
||||||
|
"targets": [
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"detail": "CMake template build task"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
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(qyouradio VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS 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(appqyouradio
|
qt_add_executable(appqyouradio
|
||||||
main.cpp
|
main.cpp
|
||||||
|
buildFlags.h
|
||||||
|
player.cpp player.h
|
||||||
resources/qyouradio.rc
|
resources/qyouradio.rc
|
||||||
resources/resource.h
|
resources/resource.h
|
||||||
)
|
)
|
||||||
@@ -28,7 +35,7 @@ qt_add_qml_module(appqyouradio
|
|||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
QML_FILES
|
QML_FILES
|
||||||
Main.qml
|
Main.qml
|
||||||
Player.qml
|
# Player.qml
|
||||||
ViewPlayer.qml
|
ViewPlayer.qml
|
||||||
ViewSettings.qml
|
ViewSettings.qml
|
||||||
)
|
)
|
||||||
@@ -44,10 +51,18 @@ set_target_properties(appqyouradio PROPERTIES
|
|||||||
WIN32_EXECUTABLE TRUE
|
WIN32_EXECUTABLE TRUE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(appqyouradio PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
|
||||||
target_link_libraries(appqyouradio
|
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 appqyouradio
|
||||||
BUNDLE DESTINATION .
|
BUNDLE DESTINATION .
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"hidden": true,
|
|
||||||
"name": "Qt",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_PREFIX_PATH": "$env{QTDIR}"
|
|
||||||
},
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Qt": {
|
|
||||||
"checksum": "wVa86FgEkvdCTVp1/nxvrkaemJc="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vendor": {
|
|
||||||
"qt-project.org/Presets": {
|
|
||||||
"checksum": "67SmY24ZeVbebyKD0fGfIzb/bGI="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
191
Main.qml
191
Main.qml
@@ -11,98 +11,191 @@ ApplicationWindow {
|
|||||||
height: 800
|
height: 800
|
||||||
title: qsTr("QYouRadio")
|
title: qsTr("QYouRadio")
|
||||||
|
|
||||||
|
Component.onCompleted: function() {
|
||||||
|
console.log(Player.stations)
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
|
||||||
|
|
||||||
RowLayout {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
height: 43
|
||||||
|
|
||||||
Label {
|
color: Colors.surface0
|
||||||
text: "QYR"
|
|
||||||
heading: "h3"
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
anchors.fill: parent
|
||||||
}
|
anchors.margins: 3
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
visible: Player.playing
|
Layout.leftMargin: 5
|
||||||
text: "Playback active"
|
text: "QYouRadio"
|
||||||
heading: "h4"
|
heading: "h1"
|
||||||
font.bold: true
|
// font.bold: true
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
TabBar {
|
|
||||||
id: tabbar
|
|
||||||
spacing: 10
|
|
||||||
TabButton {
|
|
||||||
text: qsTr("Autoradio")
|
|
||||||
}
|
}
|
||||||
TabButton {
|
|
||||||
text: qsTr("Live Mix")
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TabButton {
|
|
||||||
text: qsTr("Deep Bass")
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Button {
|
TabBar {
|
||||||
text: "S"
|
id: tabbar
|
||||||
onClicked: function() {
|
spacing: 10
|
||||||
var component = Qt.createComponent("ViewSettings.qml")
|
|
||||||
var window = component.createObject(root)
|
background: Item{}
|
||||||
window.show()
|
|
||||||
|
TabButton {
|
||||||
|
text: qsTr("Autoradio")
|
||||||
|
}
|
||||||
|
TabButton {
|
||||||
|
text: qsTr("Live Mix")
|
||||||
|
}
|
||||||
|
TabButton {
|
||||||
|
text: qsTr("Deep Bass")
|
||||||
|
}
|
||||||
|
TabButton {
|
||||||
|
text: qsTr("Settings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SwipeView {
|
SwipeView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.margins: 10
|
Layout.margins: 20
|
||||||
|
|
||||||
interactive: false
|
interactive: false
|
||||||
currentIndex: tabbar.currentIndex
|
currentIndex: tabbar.currentIndex
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: tabbar.currentIndex == 0
|
// active: tabbar.currentIndex == 0
|
||||||
|
active: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status == Loader.Ready
|
visible: status == Loader.Ready
|
||||||
sourceComponent: ViewPlayer {
|
sourceComponent: ViewPlayer {
|
||||||
title: qsTr("Autoradio")
|
index: 0
|
||||||
streamURL: "https://youradio.nonamesoft.xyz/api/autoradio"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
active: tabbar.currentIndex == 1
|
// active: tabbar.currentIndex == 1
|
||||||
|
active: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status == Loader.Ready
|
visible: status == Loader.Ready
|
||||||
sourceComponent: ViewPlayer {
|
sourceComponent: ViewPlayer {
|
||||||
title: qsTr("Live Mix")
|
index: 1
|
||||||
streamURL: "https://youradio.nonamesoft.xyz/api/live"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
active: tabbar.currentIndex == 2
|
// active: tabbar.currentIndex == 2
|
||||||
|
active: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status == Loader.Ready
|
visible: status == Loader.Ready
|
||||||
sourceComponent: ViewPlayer {
|
sourceComponent: ViewPlayer {
|
||||||
title: qsTr("Deep Bass")
|
index: 2
|
||||||
streamURL: "https://youradio.nonamesoft.xyz/api/deepbass"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: contentItem.highlightMoveDuration = 160;
|
||||||
}
|
}
|
||||||
|
|
||||||
YouAds {
|
RowLayout {
|
||||||
Layout.fillWidth: false
|
Layout.fillWidth: false
|
||||||
Layout.alignment: Qt.AlignHCenter
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
Player.qml
30
Player.qml
@@ -1,30 +0,0 @@
|
|||||||
import QtMultimedia 6.8
|
|
||||||
|
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
MediaPlayer {
|
|
||||||
source: ""
|
|
||||||
|
|
||||||
function startPlaying(url) {
|
|
||||||
if (playing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("Starting playback from " + url);
|
|
||||||
source = url;
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPlaying() {
|
|
||||||
console.log("Stopping playback...");
|
|
||||||
source = "";
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
volume: 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
onErrorOccurred: function(error, errorString) {
|
|
||||||
stopPlaying();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
122
Player.qml.prev
Normal file
122
Player.qml.prev
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,22 +23,22 @@ Item {
|
|||||||
primaryAlt: "#0056b3",
|
primaryAlt: "#0056b3",
|
||||||
secondary: "#009eff",
|
secondary: "#009eff",
|
||||||
secondaryAlt: "#0076b3",
|
secondaryAlt: "#0076b3",
|
||||||
surface1: "#323232",
|
surface1: "#373737",
|
||||||
surface0: "#282828",
|
surface0: "#2a2a2a",
|
||||||
background: "#1f1f1f"
|
background: "#1f1f1f"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string fontFamily: "Arial"
|
readonly property string fontFamily: "Arial"
|
||||||
readonly property var fontSize: ({
|
readonly property var fontSize: ({
|
||||||
h1: 32,
|
h1: 26,
|
||||||
h2: 24,
|
h2: 24,
|
||||||
h3: 18,
|
h3: 18,
|
||||||
h4: 16,
|
h4: 16,
|
||||||
h5: 13,
|
h5: 12,
|
||||||
h6: 10,
|
// h6: 10,
|
||||||
p: 14,
|
p: 12,
|
||||||
base: 14,
|
base: 12,
|
||||||
button: 12
|
button: 12
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import QtQuick.Controls.Basic 6.8
|
|||||||
Label {
|
Label {
|
||||||
property string heading: "base"
|
property string heading: "base"
|
||||||
|
|
||||||
|
color: Colors.text
|
||||||
font.family: Colors.fontFamily
|
font.family: Colors.fontFamily
|
||||||
font.pixelSize: Colors.fontSize[heading]
|
font.pixelSize: Colors.fontSize[heading]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,85 +5,47 @@ import QtQuick.Layouts 6.8
|
|||||||
import QYRComponents 1.0
|
import QYRComponents 1.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
property string title: ""
|
property var index: null
|
||||||
property string streamURL: ""
|
|
||||||
property string metaURL: ""
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.bottomMargin: 20
|
Layout.bottomMargin: 20
|
||||||
text: title
|
text: qsTr(Player.stations[index].name)
|
||||||
|
font.bold: true
|
||||||
heading: "h2"
|
heading: "h2"
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Title: "
|
|
||||||
heading: "h3"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Artist: "
|
|
||||||
heading: "h3"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Genre: "
|
|
||||||
heading: "h3"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Bitrate: " + " kbps"
|
|
||||||
heading: "h3"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Listeners: "
|
|
||||||
heading: "h3"
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: false
|
|
||||||
Layout.topMargin: 20
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
width: 220
|
|
||||||
|
|
||||||
Button {
|
|
||||||
Layout.rightMargin: 5
|
|
||||||
text: Player.loading ? "Loading" : (Player.playing ? "Pause" : "Play")
|
|
||||||
|
|
||||||
onClicked: function() {
|
|
||||||
if (Player.playing) {
|
|
||||||
Player.stopPlaying();
|
|
||||||
} else {
|
|
||||||
Player.startPlaying(parent.parent.streamURL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: 5
|
|
||||||
from: 0.1
|
|
||||||
to: 1.1
|
|
||||||
stepSize: 0.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillHeight: true
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
{ stdenv
|
{ stdenv
|
||||||
, lib
|
, lib
|
||||||
|
, alsa-lib
|
||||||
|
, xorg
|
||||||
, clang
|
, clang
|
||||||
, qtbase
|
, qtbase
|
||||||
, qtdeclarative
|
, qtdeclarative
|
||||||
@@ -18,6 +20,10 @@ stdenv.mkDerivation {
|
|||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
|
# alsa-lib
|
||||||
|
# xorg.libX11
|
||||||
|
# xorg.libXext
|
||||||
|
|
||||||
qtbase
|
qtbase
|
||||||
qtdeclarative
|
qtdeclarative
|
||||||
qtlocation
|
qtlocation
|
||||||
|
|||||||
1
buildFlags.h.in
Normal file
1
buildFlags.h.in
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#cmakedefine01 DiscordSDK_FOUND
|
||||||
103
cmake/FindDiscordSDK.cmake
Normal file
103
cmake/FindDiscordSDK.cmake
Normal file
@@ -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()
|
||||||
0
ext/.gitkeep
Normal file
0
ext/.gitkeep
Normal file
8
main.cpp
8
main.cpp
@@ -1,7 +1,9 @@
|
|||||||
#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[])
|
||||||
{
|
{
|
||||||
@@ -15,5 +17,11 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|||||||
113
player.cpp
Normal file
113
player.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#include "player.h"
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
qDebug("Player::Player(): Constructed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Player::~Player()
|
||||||
|
{
|
||||||
|
qDebug("Player::~Player(): Destructed");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Player::playing() const
|
||||||
|
{
|
||||||
|
return this->player.playbackState() == QMediaPlayer::PlayingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float Player::volume() const
|
||||||
|
{
|
||||||
|
return this->output.volume();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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->player.play();
|
||||||
|
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->m_currentIndex = -1;
|
||||||
|
emit this->currentIndexChanged();
|
||||||
|
emit this->currentStationChanged();
|
||||||
|
|
||||||
|
qDebug("Player::stopPlaying(): Stopping playback...");
|
||||||
|
}
|
||||||
73
player.h
Normal file
73
player.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtMultimedia>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QTimer>
|
||||||
|
#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 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 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();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void playingChanged();
|
||||||
|
void volumeChanged();
|
||||||
|
void stationsChanged();
|
||||||
|
void currentIndexChanged();
|
||||||
|
void currentStationChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMediaPlayer player;
|
||||||
|
QAudioOutput output;
|
||||||
|
QTimer timer;
|
||||||
|
|
||||||
|
int m_currentIndex;
|
||||||
|
QList<Station> m_stations;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user