Initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import QtQuick 2.0
|
||||
import Sailfish.Silica 1.0
|
||||
|
||||
CoverBackground {
|
||||
Image {
|
||||
source: thumbnail
|
||||
}
|
||||
Label {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("My Cover")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import QtQuick 2.0
|
||||
import Sailfish.Silica 1.0
|
||||
import "pages"
|
||||
|
||||
ApplicationWindow {
|
||||
property url thumbnail: ""
|
||||
|
||||
initialPage: Component { VolumIo { } }
|
||||
cover: Qt.resolvedUrl("cover/CoverPage.qml")
|
||||
allowedOrientations: Orientation.All
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import QtQuick 2.0
|
||||
import Sailfish.Silica 1.0
|
||||
import Sailfish.WebView 1.0
|
||||
import Sailfish.WebEngine 1.0
|
||||
import QtQuick.XmlListModel 2.0
|
||||
import io.thp.pyotherside 1.5
|
||||
|
||||
Page {
|
||||
id: page
|
||||
property variant devices: squeezeboxSettings.value("devices",[])
|
||||
//property variant defaultDevice: squeezeboxSettings.value("defaultDevice","") // squeezeboxSettings.setValue("defaultSource",[ip,port,icon,friendlyName,modelName,source])
|
||||
property bool running: true
|
||||
property bool discovering: false
|
||||
property int retries: 0
|
||||
|
||||
|
||||
function getXml(source) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("get", source,false);
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState === 4 && req.status === 200) {
|
||||
var xml = req.responseText
|
||||
var friendlyName
|
||||
var modelName
|
||||
var icon
|
||||
var ip
|
||||
var port
|
||||
var tmp = source.toString()
|
||||
tmp = tmp.split("//")
|
||||
tmp = tmp[1].split(":")
|
||||
ip = tmp[0]
|
||||
tmp = tmp[1].split("/")
|
||||
port = tmp[0]
|
||||
|
||||
xml.match(/<friendlyName>(.*?)<\/friendlyName>/g).map(function(val){
|
||||
friendlyName = val.replace(/<\/?friendlyName>/g,'')
|
||||
//console.log(friendlyName)
|
||||
});
|
||||
|
||||
xml.match(/<modelName>(.*?)<\/modelName>/g).map(function(val){
|
||||
modelName = val.replace(/<\/?modelName>/g,'')
|
||||
//console.log(modelName+" ("+ip+")")
|
||||
});
|
||||
|
||||
xml.match(/<url>(.*?)<\/url>/g).map(function(val){
|
||||
|
||||
icon = val.replace(/<\/?url>/g,'')
|
||||
console.log(val+" ****** 120x120 *****")
|
||||
});
|
||||
var patt = /^UpMPD/;
|
||||
console.log(" ************ PATT: "+patt)
|
||||
if (patt.test(modelName))
|
||||
deviceModel.append({"friendlyName": friendlyName,"modelName": modelName,"ip":ip,"port":port,"icon":icon,"source":source});
|
||||
}
|
||||
};
|
||||
req.send();
|
||||
}
|
||||
|
||||
ListModel {id: deviceModel}
|
||||
|
||||
Label {
|
||||
id: volum
|
||||
text: "VOLUMIO"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.paddingLarge
|
||||
color: Theme.primaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraLarge * 2
|
||||
}
|
||||
Label {
|
||||
text: "THE MUSIC PLAYER"
|
||||
anchors.top: volum.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
//anchors.topMargin: Theme.paddingLarge
|
||||
color: Theme.primaryColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: !page.discovering
|
||||
text: "Choose a device"
|
||||
anchors.bottom: listview.top
|
||||
anchors.bottomMargin: Theme.paddingLarge
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primaryColor
|
||||
//font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
SilicaListView {
|
||||
id: listview
|
||||
spacing: Theme.paddingMedium
|
||||
visible: true
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: Theme.paddingSmall
|
||||
height: contentHeight
|
||||
width: parent.width
|
||||
//anchors.fill: parent
|
||||
//anchors.bottomMargin: button.height
|
||||
clip: true
|
||||
model: deviceModel
|
||||
delegate: BackgroundItem {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.highlightDimmerColor
|
||||
}
|
||||
property variant defVal: []
|
||||
width: ListView.view.width
|
||||
|
||||
height: Theme.itemSizeExtraLarge
|
||||
Image {
|
||||
id: img
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.paddingLarge
|
||||
height: Theme.itemSizeLarge
|
||||
width: height
|
||||
source: "http://"+ip+":"+port+icon
|
||||
}
|
||||
Item {
|
||||
anchors.left: img.right
|
||||
height: friendlyLabel.height + nameIp.height
|
||||
anchors.leftMargin: Theme.paddingLarge
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Label {
|
||||
id: friendlyLabel
|
||||
text: friendlyName
|
||||
//color: Theme.highlightColor
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
}
|
||||
Label {
|
||||
id: nameIp
|
||||
text: modelName + " ("+ip+")"
|
||||
anchors.top: friendlyLabel.bottom
|
||||
//color: Theme.highlightColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
console.log(" ********** http://"+ip+":"+port)
|
||||
running = false
|
||||
webPageAddress = "http://"+ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyLabel {
|
||||
//width: parent.width
|
||||
text: "Searching for UPnP devices"
|
||||
running: page.discovering
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
text: "Discover"
|
||||
enabled: !page.discovering
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
deviceModel.clear()
|
||||
python.startDownload();
|
||||
}
|
||||
}
|
||||
|
||||
function getIp(url) {
|
||||
var res = url.split(":");
|
||||
return res
|
||||
}
|
||||
|
||||
function checkDevices() {
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
|
||||
}
|
||||
squeezeboxSettings.setValue("devices",devices)
|
||||
}
|
||||
|
||||
Python {
|
||||
id: python
|
||||
|
||||
Component.onCompleted: {
|
||||
addImportPath(Qt.resolvedUrl('.'));
|
||||
|
||||
setHandler('progress', function(ratio) {
|
||||
|
||||
devices.push(ratio)
|
||||
});
|
||||
setHandler('finished', function(newvalue) {
|
||||
page.discovering = false;
|
||||
button.text = "Discover";
|
||||
if (retries<3 && devices.length === 0) {
|
||||
retries = retries + 1
|
||||
python.startDownload()
|
||||
} else {
|
||||
retries = 0
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
getXml(devices[i])
|
||||
console.log(devices[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
importModule('upnpscan', function () {});
|
||||
|
||||
}
|
||||
|
||||
function startDownload() {
|
||||
devices = []
|
||||
page.discovering = true;
|
||||
button.text = "Discovering";
|
||||
call('upnpscan.discoverer.discover', function() {});
|
||||
}
|
||||
|
||||
onError: {
|
||||
console.log('python error: ' + traceback);
|
||||
}
|
||||
|
||||
onReceived: {
|
||||
console.log('got message from python: ' + data);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
python.startDownload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import QtQuick 2.0
|
||||
import Sailfish.Silica 1.0
|
||||
import Sailfish.WebView 1.0
|
||||
|
||||
WebViewPage {
|
||||
id: webViewPage
|
||||
allowedOrientations: Orientation.Portrait | Orientation.Landscape
|
||||
|
||||
property string webPageAddress: ""
|
||||
property string webTitle: ""
|
||||
property bool webViewLoading: false
|
||||
property int webViewLoadProgress: 0
|
||||
|
||||
Splash {
|
||||
id: splashItem
|
||||
visible: running
|
||||
}
|
||||
|
||||
WebView {
|
||||
id: webView
|
||||
visible: !splashItem.running
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
active: true
|
||||
url: webPageAddress
|
||||
|
||||
onLoadingChanged: {
|
||||
webViewPage.webViewLoading = loading
|
||||
webViewPage.webViewLoadProgress = 0
|
||||
}
|
||||
|
||||
onLoadProgressChanged: {
|
||||
webViewPage.webViewLoadProgress = loadProgress
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: panel
|
||||
color: Theme.highlightDimmerColor
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
//margins: Theme.padding
|
||||
}
|
||||
width: parent.width
|
||||
height: opacity === 0.0 ? 0 : Theme.paddingSmall / 3
|
||||
radius: 5
|
||||
|
||||
opacity: (webViewPage.webViewLoading || loadStatusShowTimer.running) ? 0.75 : 0.0
|
||||
Behavior on opacity { FadeAnimator {} }
|
||||
|
||||
Timer {
|
||||
id: loadStatusShowTimer
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
color: Theme.secondaryHighlightColor
|
||||
width: webViewPage.webViewLoading ? parent.width * (webViewPage.webViewLoadProgress / 100) : 0
|
||||
height: parent.height
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import pyotherside
|
||||
import threading
|
||||
import socket
|
||||
import re
|
||||
|
||||
# 'ST:urn:schemas-upnp-org:device:MediaRenderer:1',
|
||||
|
||||
|
||||
def slow_function():
|
||||
msg = "\r\n".join([
|
||||
'M-SEARCH * HTTP/1.1',
|
||||
'HOST:239.255.255.250:1900',
|
||||
'MAN:"ssdp:discover"',
|
||||
'ST:urn:schemas-upnp-org:device:MediaRenderer:1',
|
||||
'MX:2',
|
||||
'',
|
||||
'',
|
||||
])
|
||||
|
||||
bytesmsg = msg.encode()
|
||||
mr = "urn:schemas-upnp-org:device:MediaRenderer:1".encode()
|
||||
# mr = ".".encode()
|
||||
se = "(?P<url>https?://[^\s]+)".encode()
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
s.settimeout(3)
|
||||
s.sendto(bytesmsg, ('239.255.255.250', 1900))
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = s.recvfrom(65507)
|
||||
for line in data:
|
||||
if mr in line:
|
||||
url = re.search(se, line).group("url")
|
||||
pyotherside.send('progress', url)
|
||||
|
||||
except socket.timeout:
|
||||
pyotherside.send('finished', "DONE")
|
||||
|
||||
class Discoverer:
|
||||
def __init__(self):
|
||||
self.bgthread = threading.Thread()
|
||||
self.bgthread.start()
|
||||
|
||||
def discover(self):
|
||||
if self.bgthread.is_alive():
|
||||
return
|
||||
self.bgthread = threading.Thread(target=slow_function)
|
||||
self.bgthread.start()
|
||||
|
||||
discoverer = Discoverer()
|
||||
Reference in New Issue
Block a user