diff --git a/Main.qml b/Main.qml index cdeeca5..7f77792 100755 --- a/Main.qml +++ b/Main.qml @@ -13,80 +13,96 @@ ApplicationWindow { ColumnLayout { anchors.fill: parent - anchors.margins: 10 - RowLayout { + Rectangle { Layout.fillWidth: true + height: 43 - Label { - text: "QYouRadio" - heading: "h3" - font.bold: true - } + color: Colors.surface0 - // Item { - // Layout.fillWidth: true - // } - - Rectangle { - Layout.leftMargin: 20 - visible: Player.currentIndex != null - color: Colors.primary - radius: 5 - clip: true - width: 320 - height: 36 + RowLayout { + anchors.fill: parent + anchors.margins: 3 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 ") + (Player.currentIndex != null ? qsTr(Player.currentStream.name) : "") - heading: "h2" + Layout.leftMargin: 5 + text: "QYouRadio" + heading: "h1" + // font.bold: true } - Label { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.bottomMargin: 1 - anchors.leftMargin: 4 - anchors.rightMargin: 4 - text: "Title will go here" - heading: "h4" - } - } + Rectangle { + Layout.leftMargin: 5 + visible: Player.currentIndex != null + color: Colors.primary + radius: 5 + clip: true + width: 320 + height: 36 - Item { - Layout.fillWidth: true - } + 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 ") + (Player.currentIndex != null ? qsTr(Player.currentStream.name) : "") + heading: "h5" + } - TabBar { - id: tabbar - spacing: 10 + 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: (Player.currentIndex != null ? qsTr(Player.currentStream.title) : "") + heading: "h3" - background: Item{} + SequentialAnimation { + running: true + loops: Animation.Infinite - TabButton { - text: qsTr("Autoradio") + NumberAnimation { target: nowplaying_title; property: "anchors.rightMargin"; from: 0; to: 320; duration: 5000 } + NumberAnimation { target: nowplaying_title; property: "anchors.leftMargin"; from: 0; to: 320; duration: 5000 } + } + } } - TabButton { - text: qsTr("Live Mix") + + Item { + Layout.fillWidth: true } - TabButton { - text: qsTr("Deep Bass") - } - } - Button { - text: "S" - onClicked: function() { - var component = Qt.createComponent("ViewSettings.qml") - var window = component.createObject(root) - window.show() + + 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") + } } + // Button { + // text: "S" + // onClicked: function() { + // var component = Qt.createComponent("ViewSettings.qml") + // var window = component.createObject(root) + // window.show() + // } + // } } } @@ -94,7 +110,7 @@ ApplicationWindow { SwipeView { Layout.fillWidth: true Layout.fillHeight: true - Layout.margins: 10 + Layout.margins: 20 interactive: false currentIndex: tabbar.currentIndex diff --git a/Player.qml b/Player.qml index 63f5628..20ef2a4 100644 --- a/Player.qml +++ b/Player.qml @@ -3,38 +3,39 @@ import QtMultimedia 6.8 pragma Singleton -MediaPlayer { - readonly property var streams: ([ +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", - streamURL: "https://youradio.nonamesoft.xyz/api/autoradio" + slug: "autoradio", + title: "", + listeners: 0 }, { name: "Live Mix", - streamURL: "https://youradio.nonamesoft.xyz/api/live" + slug: "live", + title: "", + listeners: 0 }, { name: "Deep Bass", - streamURL: "https://youradio.nonamesoft.xyz/api/bassboosted" + slug: "bassboosted", + title: "", + listeners: 0 } ]) + property var failedConnAttempts: 0 - id: player - source: "" - - audioOutput: AudioOutput { - id: output - volume: 0.4 - } - - onErrorOccurred: function(error, errorString) { - const index = currentIndex - currentIndex = null - currentIndex = index; - } - + property alias playing: player.playing property alias volume: output.volume - readonly property var loading: mediaStatus == Qt.LoadingMedia + readonly property var loading: player.mediaStatus == Qt.LoadingMedia property var currentIndex: null property var currentStream: null @@ -49,7 +50,7 @@ MediaPlayer { print("Starting playing stream no. " + index); currentIndex = index; currentStream = streams[index]; - source = currentStream.streamURL; + player.source = streamURLPrefix + currentStream.slug; player.play(); } @@ -60,7 +61,63 @@ MediaPlayer { print("Stopping playback"); currentIndex = null; currentStream = null; - source = ""; + player.source = ""; player.stop() } + + MediaPlayer { + id: player + source: "" + + audioOutput: AudioOutput { + id: output + volume: 0.4 + } + + 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; + parent.streams[index].listeners = station.listeners; + if (index == parent.currentIndex) { + parent.currentStream.title = station.title; + parent.currentStream.listeners = station.listeners; + } + }); + // } 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/QYRComponents/Colors.qml b/QYRComponents/Colors.qml index 6d4afee..aec00c3 100644 --- a/QYRComponents/Colors.qml +++ b/QYRComponents/Colors.qml @@ -23,22 +23,22 @@ Item { primaryAlt: "#0056b3", secondary: "#009eff", secondaryAlt: "#0076b3", - surface1: "#323232", - surface0: "#282828", + surface1: "#373737", + surface0: "#2a2a2a", background: "#1f1f1f" }) } readonly property string fontFamily: "Arial" readonly property var fontSize: ({ - h1: 32, + h1: 26, h2: 24, h3: 18, h4: 16, - h5: 13, - h6: 10, - p: 14, - base: 14, + h5: 12, + // h6: 10, + p: 12, + base: 12, button: 12 }) diff --git a/ViewPlayer.qml b/ViewPlayer.qml index 217b2f5..a034c48 100644 --- a/ViewPlayer.qml +++ b/ViewPlayer.qml @@ -20,14 +20,14 @@ ColumnLayout { Label { Layout.fillWidth: true - text: "Title: " + text: "Title: " + (index != null ? Player.streams[index].title : "") heading: "h3" horizontalAlignment: Text.AlignHCenter } Label { Layout.fillWidth: true - text: "Listeners: " + text: "Listeners: " + (index != null ? Player.streams[index].listeners : 0) heading: "h3" horizontalAlignment: Text.AlignHCenter } @@ -62,7 +62,7 @@ ColumnLayout { Layout.leftMargin: 5 from: 0.1 to: 1.1 - stepSize: 0.1 + stepSize: 0.05 value: Player.volume + 0.1 onMoved: Player.volume = value - 0.1 }