From 1c203aabede08afeaf5fde00d823af4521879463 Mon Sep 17 00:00:00 2001 From: awiouy Date: Mon, 19 Jun 2017 13:54:00 +0200 Subject: [PATCH 1/5] avahi: build libdns_sd without installing it --- packages/network/avahi/package.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/network/avahi/package.mk b/packages/network/avahi/package.mk index 9ee6d38b52..d66b8e1b34 100644 --- a/packages/network/avahi/package.mk +++ b/packages/network/avahi/package.mk @@ -67,7 +67,7 @@ PKG_CONFIGURE_OPTS_TARGET="py_cv_mod_gtk_=yes \ --disable-manpages \ --disable-xmltoman \ --disable-tests \ - --disable-compat-libdns_sd \ + --enable-compat-libdns_sd \ --disable-compat-howl \ --with-xml=expat \ --with-avahi-user=avahi \ @@ -101,6 +101,7 @@ post_makeinstall_target() { rm -f $INSTALL/usr/bin/avahi-bookmarks rm -f $INSTALL/usr/bin/avahi-publish* rm -f $INSTALL/usr/bin/avahi-resolve* + rm -f $INSTALL/usr/lib/libdns_sd* mkdir -p $INSTALL/usr/share/services cp -P $PKG_DIR/default.d/*.conf $INSTALL/usr/share/services From a48579e50be441972959629e27ea9b3f613cf094 Mon Sep 17 00:00:00 2001 From: awiouy Date: Mon, 19 Jun 2017 22:51:33 +0200 Subject: [PATCH 2/5] pyalsaaudio: initial package --- .../librespot-depends/pyalsaaudio/package.mk | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/addons/addon-depends/librespot-depends/pyalsaaudio/package.mk diff --git a/packages/addons/addon-depends/librespot-depends/pyalsaaudio/package.mk b/packages/addons/addon-depends/librespot-depends/pyalsaaudio/package.mk new file mode 100644 index 0000000000..c0d21f1dd6 --- /dev/null +++ b/packages/addons/addon-depends/librespot-depends/pyalsaaudio/package.mk @@ -0,0 +1,38 @@ +################################################################################ +# This file is part of LibreELEC - https://libreelec.tv +# Copyright (C) 2017-present Team LibreELEC +# +# LibreELEC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# LibreELEC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LibreELEC. If not, see . +################################################################################ + +PKG_NAME="pyalsaaudio" +PKG_VERSION="0.8.4" +PKG_LICENSE="PSF" +PKG_SITE="http://larsimmisch.github.io/pyalsaaudio/" +PKG_URL="https://files.pythonhosted.org/packages/source/${PKG_NAME:0:1}/$PKG_NAME/$PKG_NAME-$PKG_VERSION.tar.gz" +PKG_DEPENDS_TARGET="toolchain Python distutilscross:host alsa-lib" +PKG_LONGDESC="ALSA bindings" + +make_target() { + export LDSHARED="$CC -shared" + export PYTHONXCPREFIX="$SYSROOT_PREFIX/usr" + python setup.py build --cross-compile +} + +makeinstall_target() { + python setup.py install --root=$INSTALL --prefix=/usr + find $INSTALL/usr/lib -name "*.py" -exec rm -rf "{}" ";" + rm -rf $INSTALL/usr/lib/python*/site-packages/*.egg-info \ + $INSTALL/usr/lib/python*/site-packages/*/tests +} From 89248156b392e2706bf2e83cfd5d34424e4ff093 Mon Sep 17 00:00:00 2001 From: awiouy Date: Mon, 19 Jun 2017 13:52:33 +0200 Subject: [PATCH 3/5] rust: initial package --- .../librespot-depends/rust/package.mk | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 packages/addons/addon-depends/librespot-depends/rust/package.mk diff --git a/packages/addons/addon-depends/librespot-depends/rust/package.mk b/packages/addons/addon-depends/librespot-depends/rust/package.mk new file mode 100644 index 0000000000..aad308aca1 --- /dev/null +++ b/packages/addons/addon-depends/librespot-depends/rust/package.mk @@ -0,0 +1,81 @@ +################################################################################ +# This file is part of LibreELEC - https://libreelec.tv +# Copyright (C) 2017-present Team LibreELEC +# +# LibreELEC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# LibreELEC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LibreELEC. If not, see . +################################################################################ + +PKG_NAME="rust" +PKG_VERSION="1.18.0" +PKG_ARCH="any" +PKG_LICENSE="MIT" +PKG_SITE="https://www.rust-lang.org" +PKG_URL="" +PKG_DEPENDS="toolchain" +PKG_SECTION="devel" +PKG_LONGDESC="Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety." +PKG_IS_ADDON="no" + +PKG_AUTORECONF="no" + +unpack() { + : +} + +configure_target() { + : +} + +make_target() { + export CARGO_HOME="$TOOLCHAIN/.cargo" + export RUSTUP_HOME="$CARGO_HOME" + export PATH="$CARGO_HOME/bin:$PATH" + rm -rf "$CARGO_HOME" + curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path -y + rustup default "$PKG_VERSION" + case "$TARGET_ARCH" in + aarch64) + RUST_TRIPLE="aarch64-unknown-linux-gnu" + ;; + arm) + RUST_TRIPLE="arm-unknown-linux-gnueabihf" + ;; + x86_64) + RUST_TRIPLE="x86_64-unknown-linux-gnu" + ;; + esac + if [ "$TARGET_ARCH" != "x86_64" ]; then + rustup target add "$RUST_TRIPLE" + fi + + cat <"$CARGO_HOME/config" +[target.$RUST_TRIPLE] +linker = "$CC" +EOF + + cat <<'EOF' >"$CARGO_HOME/env" +export CARGO_HOME="$TOOLCHAIN/.cargo" +export CARGO_TARGET_DIR="$PKG_BUILD/.$TARGET_NAME" +export PATH="$CARGO_HOME/bin:$PATH" +export RUSTUP_HOME="$CARGO_HOME" +mkdir -p "$CARGO_TARGET_DIR" +EOF + + echo "CARGO_BUILD=\"cargo build --release --target $RUST_TRIPLE\"" \ + >>"$CARGO_HOME/env" +} + +makeinstall_target() { + : +} From 69b71ee3bb5888ae26fde1470a103320a0e7aa66 Mon Sep 17 00:00:00 2001 From: awiouy Date: Tue, 20 Jun 2017 19:34:27 +0200 Subject: [PATCH 4/5] libvorbis: compile with -fPIC --- packages/audio/libvorbis/package.mk | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/audio/libvorbis/package.mk b/packages/audio/libvorbis/package.mk index 13be9c254c..91fb98aeb3 100644 --- a/packages/audio/libvorbis/package.mk +++ b/packages/audio/libvorbis/package.mk @@ -36,3 +36,7 @@ PKG_CONFIGURE_OPTS_TARGET="--enable-static --disable-shared \ --disable-oggtest \ --disable-docs \ --disable-examples" + +pre_configure_target() { + export CFLAGS="$CFLAGS -fPIC" +} From 8d01934c5c7de850fea8362e3a83c7398b85c78c Mon Sep 17 00:00:00 2001 From: awiouy Date: Mon, 19 Jun 2017 13:53:18 +0200 Subject: [PATCH 5/5] librespot: initial addon --- .../addons/service/librespot/changelog.txt | 2 + .../addons/service/librespot/icon/icon.png | Bin 0 -> 26983 bytes packages/addons/service/librespot/package.mk | 64 +++++ .../patches/librespot-01_avahi.patch | 222 ++++++++++++++++++ .../librespot-02_disable_audio_cache.patch | 87 +++++++ .../librespot/source/bin/librespot.start | 100 ++++++++ .../service/librespot/source/default.py | 35 +++ .../resources/language/English/strings.po | 72 ++++++ .../librespot/source/resources/settings.xml | 14 ++ .../source/system.d/service.librespot.service | 11 + .../service/librespot/source/wizard/wizard.py | 39 +++ 11 files changed, 646 insertions(+) create mode 100644 packages/addons/service/librespot/changelog.txt create mode 100644 packages/addons/service/librespot/icon/icon.png create mode 100644 packages/addons/service/librespot/package.mk create mode 100644 packages/addons/service/librespot/patches/librespot-01_avahi.patch create mode 100644 packages/addons/service/librespot/patches/librespot-02_disable_audio_cache.patch create mode 100755 packages/addons/service/librespot/source/bin/librespot.start create mode 100644 packages/addons/service/librespot/source/default.py create mode 100644 packages/addons/service/librespot/source/resources/language/English/strings.po create mode 100644 packages/addons/service/librespot/source/resources/settings.xml create mode 100644 packages/addons/service/librespot/source/system.d/service.librespot.service create mode 100644 packages/addons/service/librespot/source/wizard/wizard.py diff --git a/packages/addons/service/librespot/changelog.txt b/packages/addons/service/librespot/changelog.txt new file mode 100644 index 0000000000..ffbebe8438 --- /dev/null +++ b/packages/addons/service/librespot/changelog.txt @@ -0,0 +1,2 @@ +100 +- Initial addon diff --git a/packages/addons/service/librespot/icon/icon.png b/packages/addons/service/librespot/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9e5e8400caccd1a225524c3b64153fd06efc79 GIT binary patch literal 26983 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 z zmImG==YMh?V8}O+rX3O5F>01pGvYvBzvNbQnW$S8V-!pN%xMHLohy8V z`r$uwIfm&@KJZT7`1M353T1a-!-=Q^$JM@gfemBrsX&P(`V>jugFXWlOiluX{F(~W zM2LKhPmJDsc8N6k1yczrZ-klvzoxavd~K_;+xwy*Y8kQrMwS;jd|!I^9i8yd z0QK&%WITni$NAdlBUet*7wDTb5e;~KzIXIL)t~m#4q?NnHD-y%&uBj&al$g7rQczs z@p=MG7pRLx#BJi64j=jY35naF!vSvrZL2A^5ua#HO#4QhwOXgY?_}*+J?%4yA1_b?Vqpzh4JkNH=G<~FJsQkGI|gQBFfmcp zCU7B5)?QvKEsG{_tX6#%BUr$S77q%EC$0SF1^(`9Oww1@KB9=88UG7cNmv&4+nKks znKKY&w=(!i0jfb;UBNE|Rmva-FmQRH>F%JAcb|)kT>KFd8u;KDC@+|lAW06NlYnOa zho45^Fnd~i*^XZPKOTw1a@2K3^a1+CAYS|&qW-Q*S@rb!0Jr8#%;npxG0#?$-6*$n{u?0bY^%`Qe+L z6*Z43M&*;m?co$qp`UN=V}giTi*~gPPRJsyA4k>gq7y%c_u~yr+scvHgWyYSK5I0K zMBuTqaX$&M*X4?;oluX)%j%iObO6x>B-pgc)91rN;vIKPOw7u~zdi9ca|~LKP{LDA zuIulc@Ne0KPod(E@R5Gp+mFNiO38m{#w zG_AnvFYTp{$tMm?=TXEcp1ST*f?S-n)SnV>FSSd*P|PD*;_+cx`rl@64}!nGqUt-P zu+tP`nS`RJ+b^#h|9WO6jRLCFMi&>TTf4&y2`+!0 z#q0=Adz9U+z3Tm_u|5|$ltqm(Stv)hE*q`I&~6`#(EYN`zq`H@k6k&-K2CKj0HtE@ zeSTlC>F?))gV% z0Zqtt2iRR{7P92YJKdQ{jTKGBwk+=f=Lp*hq+(4!?4AGG-uCm>A}l49$I(69K&qR7 z(=#*J9)RQl?^k28N+sfeq7ju$Etknt{HL0u3Ft_ur%Y*{C;Gp9Fh=tw6VFlxTY&eHZwERrWxm{#(pt1r8@Cp0vXf9 zLCNHG+4cCVt=Y&oSzqy*Z5w1Lab3AroH^GfJPXbGHAwSqEvE=i|JGAHEh1;)M2Kx& zMeu=}Bpv;x%4V@c#_}<+AwRE|8KmKbq887$#wUmxs}X?W*tZAHyIh{R zuZLo4I%Z<|*q$uu1rNhi)d?HtVkDg}vYndi{1jnIHm*`wWj$(-yTYdr7_X z-Ac^%bSsF57Ol=2H8wVO^4R|F?(SX*pyJEKVCKs&ZE$>3ogYODojhr(cfyFUs>L z)r*J#^O6M9T3YxUh@P>llJN&XLA`J||G$BOL&pI@8wnHGZCY@@E(`(@^yGh=|JCEa i|1J7IH%M1HL9j5VcbV|9)T>+n5m%H|lc|w13Hg77$yZwd literal 0 HcmV?d00001 diff --git a/packages/addons/service/librespot/package.mk b/packages/addons/service/librespot/package.mk new file mode 100644 index 0000000000..d954fe6880 --- /dev/null +++ b/packages/addons/service/librespot/package.mk @@ -0,0 +1,64 @@ +################################################################################ +# This file is part of LibreELEC - https://libreelec.tv +# Copyright (C) 2017-present Team LibreELEC +# Copyright (C) 2017 Shane Meagher (shanemeagher) +# +# LibreELEC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# LibreELEC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LibreELEC. If not, see . +################################################################################ + +PKG_NAME="librespot" +PKG_VERSION="2259188" +PKG_REV="100" +PKG_ARCH="any" +PKG_LICENSE="prop." +PKG_SITE="https://github.com/plietar/$PKG_NAME/" +PKG_URL="https://github.com/plietar/$PKG_NAME/archive/$PKG_VERSION.zip" +PKG_DEPENDS_TARGET="toolchain avahi libvorbis pyalsaaudio rust" +PKG_SECTION="service" +PKG_LONGDESC="Librespot ($PKG_VERSION) plays Spotify through LibreELEC using the opensource librespot library using a Spotify app as a remote." +PKG_AUTORECONF="no" + +PKG_IS_ADDON="yes" +PKG_ADDON_NAME="Librespot" +PKG_ADDON_TYPE="xbmc.service" +PKG_MAINTAINER="Anton Voyl (awiouy)" + +configure_target() { + . "$TOOLCHAIN/.cargo/env" + export PKG_CONFIG_ALLOW_CROSS=0 + strip_lto +} + +make_target() { + cd src + $CARGO_BUILD --no-default-features --features "alsa-backend with-avahi" +} + +makeinstall_target() { + : +} + +addon() { + mkdir -p "$ADDON_BUILD/$PKG_ADDON_ID/bin" + cp "$PKG_BUILD/.$TARGET_NAME"/*/release/librespot \ + "$ADDON_BUILD/$PKG_ADDON_ID/bin" + + mkdir -p "$ADDON_BUILD/$PKG_ADDON_ID/lib" + cp "$(get_build_dir avahi)/avahi-compat-libdns_sd/.libs/libdns_sd.so.1" \ + "$ADDON_BUILD/$PKG_ADDON_ID/lib" + + mkdir -p "$ADDON_BUILD/$PKG_ADDON_ID/wizard" + cp "$(get_build_dir pyalsaaudio)/.install_pkg/usr/lib/python2.7/site-packages/alsaaudio.so" \ + "$ADDON_BUILD/$PKG_ADDON_ID/wizard/" +} diff --git a/packages/addons/service/librespot/patches/librespot-01_avahi.patch b/packages/addons/service/librespot/patches/librespot-01_avahi.patch new file mode 100644 index 0000000000..36d0ba66fb --- /dev/null +++ b/packages/addons/service/librespot/patches/librespot-01_avahi.patch @@ -0,0 +1,222 @@ +From a825f84d9d00b196232fcccc5b5e441654c4e5a0 Mon Sep 17 00:00:00 2001 +From: shanemeagher +Date: Fri, 9 Jun 2017 22:43:54 +0800 +Subject: [PATCH] Build librespot with avahi support for Discovery + +rust-mdns is still the default and can be specified explicitly with --with-rust-mdns switch. +Added --with-avahi switch to build librespot to use avahi for discovery using dns-sd package. +--- + Cargo.lock | 10 ++++++++++ + Cargo.toml | 7 +++++-- + contrib/Dockerfile | 3 +++ + contrib/docker-build-avahi.sh | 24 ++++++++++++++++++++++++ + src/authentication/discovery.rs | 27 ++++++++++++++++++++++++++- + src/lib.rs | 6 +++++- + 6 files changed, 73 insertions(+), 4 deletions(-) + create mode 100755 contrib/docker-build-avahi.sh + +diff --git a/Cargo.lock b/Cargo.lock +index 30fafca..eff0925 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6,6 +6,7 @@ dependencies = [ + "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "dns-sd 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -144,6 +145,15 @@ dependencies = [ + ] + + [[package]] ++name = "dns-sd" ++version = "0.1.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++dependencies = [ ++ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", ++ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ++] ++ ++[[package]] + name = "dtoa" + version = "0.4.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +diff --git a/Cargo.toml b/Cargo.toml +index 5d64719..c543e92 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -52,7 +52,8 @@ alsa = { git = "https://github.com/plietar/rust-alsa", optional = tru + portaudio-rs = { version = "0.3.0", optional = true } + libpulse-sys = { git = "https://github.com/astro/libpulse-sys", optional = true } + +-mdns = { git = "https://github.com/plietar/rust-mdns" } ++mdns = { git = "https://github.com/plietar/rust-mdns", optional = true } ++dns-sd = { version = "~0.1.3", optional = true } + + error-chain = { version = "0.9.0", default_features = false } + futures = "0.1.8" +@@ -71,8 +72,10 @@ portaudio-backend = ["portaudio-rs"] + pulseaudio-backend = ["libpulse-sys"] + + with-tremor = ["tremor"] ++with-rust-mdns = ["mdns"] ++with-avahi = ["dns-sd"] + +-default = ["portaudio-backend"] ++default = ["portaudio-backend","with-rust-mdns"] + + [package.metadata.deb] + maintainer = "nobody" +diff --git a/contrib/Dockerfile b/contrib/Dockerfile +index 68a39b7..f6aec14 100644 +--- a/contrib/Dockerfile ++++ b/contrib/Dockerfile +@@ -4,6 +4,8 @@ + # + # The resulting image can be used to build librespot for linux x86_64, armhf and armel. + # $ docker run -v /tmp/librespot-build:/build librespot-cross ++# To build librespot with avahi support ++# $ docker run -v /tmp/librespot-build:/build librespot-cross /src/contrib/docker-build-avahi.sh + # + # The compiled binaries will be located in /tmp/librespot-build + # +@@ -23,6 +25,7 @@ RUN apt-get update + + RUN apt-get install -y curl git build-essential crossbuild-essential-arm64 crossbuild-essential-armel crossbuild-essential-armhf crossbuild-essential-mipsel + RUN apt-get install -y libasound2-dev libasound2-dev:arm64 libasound2-dev:armel libasound2-dev:armhf libasound2-dev:mipsel ++RUN apt-get install -y libavahi-compat-libdnssd-dev libavahi-compat-libdnssd-dev:arm64 libavahi-compat-libdnssd-dev:armel libavahi-compat-libdnssd-dev:armhf libavahi-compat-libdnssd-dev:mipsel + + RUN curl https://sh.rustup.rs -sSf | sh -s -- -y + ENV PATH="/root/.cargo/bin/:${PATH}" +diff --git a/contrib/docker-build-avahi.sh b/contrib/docker-build-avahi.sh +new file mode 100755 +index 0000000..c25b248 +--- /dev/null ++++ b/contrib/docker-build-avahi.sh +@@ -0,0 +1,24 @@ ++#!/usr/bin/env bash ++set -eux ++ ++cargo build --release --no-default-features --features "alsa-backend with-avahi" ++cp /usr/lib/x86_64-linux-gnu/libdns_sd.so.1 /build/release ++ ++export PKG_CONFIG_ALLOW_CROSS=0 ++ ++export PKG_CONFIG_PATH=/usr/lib/aarch64-unknown-linux-gnu/pkgconfig ++cargo build --release --target aarch64-unknown-linux-gnu --no-default-features --features "alsa-backend with-avahi" ++cp /usr/lib/aarch64-linux-gnu/libdns_sd.so.1 /build/aarch64-unknown-linux-gnu/release ++ ++export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabi/pkgconfig ++cargo build --release --target arm-unknown-linux-gnueabi --no-default-features --features "alsa-backend with-avahi" ++cp /usr/lib/arm-linux-gnueabi/libdns_sd.so.1 /build/arm-unknown-linux-gnueabi/release ++ ++export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig ++cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features "alsa-backend with-avahi" ++cp /usr/lib/arm-linux-gnueabihf/libdns_sd.so.1 /build/arm-unknown-linux-gnueabihf/release ++ ++export PKG_CONFIG_PATH=/usr/lib/mipsel-linux-gnu/pkgconfig ++cargo build --release --target mipsel-unknown-linux-gnu --no-default-features --features "alsa-backend with-avahi" ++cp /usr/libmipsel-linux-gnu/libdns_sd.so.1 /build/mipsel-unknown-linux-gnu/release ++ +diff --git a/src/authentication/discovery.rs b/src/authentication/discovery.rs +index 8c5b005..d385294 100644 +--- a/src/authentication/discovery.rs ++++ b/src/authentication/discovery.rs +@@ -7,7 +7,6 @@ use futures::sync::mpsc; + use futures::{Future, Stream, BoxFuture, Poll, Async}; + use hyper::server::{Service, NewService, Request, Response, Http}; + use hyper::{self, Get, Post, StatusCode}; +-use mdns; + use num_bigint::BigUint; + use rand; + use std::collections::BTreeMap; +@@ -20,6 +19,12 @@ use url; + use authentication::Credentials; + use util; + ++#[cfg(feature = "with-rust-mdns")] ++use mdns; ++ ++#[cfg(feature = "with-avahi")] ++use dns_sd::DNSService; ++ + #[derive(Clone)] + struct Discovery(Arc); + struct DiscoveryInner { +@@ -202,7 +207,10 @@ impl NewService for Discovery { + + pub struct DiscoveryStream { + credentials: mpsc::UnboundedReceiver, ++ #[cfg(feature = "with-rust-mdns")] + _svc: mdns::Service, ++ #[cfg(feature = "with-avahi")] ++ _svc: DNSService, + task: Box>, + } + +@@ -212,8 +220,13 @@ pub fn discovery(handle: &Handle, device_name: String, device_id: String) + let (discovery, creds_rx) = Discovery::new(device_name.clone(), device_id); + + let listener = TcpListener::bind(&"0.0.0.0:0".parse().unwrap(), handle)?; ++ ++ #[cfg(feature = "with-rust-mdns")] + let addr = listener.local_addr()?; + ++ #[cfg(feature = "with-avahi")] ++ let port = listener.local_addr().unwrap().port(); ++ + let http = Http::new(); + let handle_ = handle.clone(); + let task = Box::new(listener.incoming().for_each(move |(socket, addr)| { +@@ -221,13 +234,25 @@ pub fn discovery(handle: &Handle, device_name: String, device_id: String) + Ok(()) + })); + ++ #[cfg(feature = "with-rust-mdns")] + let responder = mdns::Responder::spawn(&handle)?; ++ ++ #[cfg(feature = "with-rust-mdns")] + let svc = responder.register( + "_spotify-connect._tcp".to_owned(), + device_name, + addr.port(), + &["VERSION=1.0", "CPath=/"]); + ++ #[cfg(feature = "with-avahi")] ++ let svc = DNSService::register(Some(&*device_name), ++ "_spotify-connect._tcp", ++ None, ++ None, ++ port, ++ &["VERSION=1.0", "CPath=/"]) ++ .unwrap(); ++ + Ok(DiscoveryStream { + credentials: creds_rx, + _svc: svc, +diff --git a/src/lib.rs b/src/lib.rs +index 2a50249..b1b77ef 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -19,7 +19,6 @@ extern crate crypto; + extern crate getopts; + extern crate hyper; + extern crate linear_map; +-extern crate mdns; + extern crate num_bigint; + extern crate num_integer; + extern crate num_traits; +@@ -50,6 +49,11 @@ extern crate portaudio_rs; + #[cfg(feature = "libpulse-sys")] + extern crate libpulse_sys; + ++#[cfg(feature = "with-rust-mdns")] ++extern crate mdns; ++ ++#[cfg(feature = "with-avahi")] ++extern crate dns_sd; + + #[macro_use] mod component; + pub mod album_cover; diff --git a/packages/addons/service/librespot/patches/librespot-02_disable_audio_cache.patch b/packages/addons/service/librespot/patches/librespot-02_disable_audio_cache.patch new file mode 100644 index 0000000000..42502b0df3 --- /dev/null +++ b/packages/addons/service/librespot/patches/librespot-02_disable_audio_cache.patch @@ -0,0 +1,87 @@ +From 031cc0a420db9d3ae8dd3543d07ff8503bdc508d Mon Sep 17 00:00:00 2001 +From: Michael Herger +Date: Tue, 20 Jun 2017 12:31:55 +0200 +Subject: [PATCH] Add --disable-audio-cache startup parameter + +Disable caching of downloaded audio files at runtime. Comes in handy when running librespot on a small device with SD card or other small storage. +--- + src/audio_file.rs | 24 +++++++++++++----------- + src/main.rs | 2 ++ + src/session.rs | 2 ++ + 3 files changed, 17 insertions(+), 11 deletions(-) + +diff --git a/src/audio_file.rs b/src/audio_file.rs +index 369d5ca..d014ba2 100644 +--- a/src/audio_file.rs ++++ b/src/audio_file.rs +@@ -151,17 +151,19 @@ impl AudioFileManager { + complete_tx: Some(complete_tx), + }; + +- let session = self.session(); +- self.session().spawn(move |_| { +- complete_rx.map(move |mut file| { +- if let Some(cache) = session.cache() { +- cache.save_file(file_id, &mut file); +- debug!("File {} complete, saving to cache", file_id); +- } else { +- debug!("File {} complete", file_id); +- } +- }).or_else(|oneshot::Canceled| Ok(())) +- }); ++ if self.session().config().use_audio_cache { ++ let session = self.session(); ++ self.session().spawn(move |_| { ++ complete_rx.map(move |mut file| { ++ if let Some(cache) = session.cache() { ++ cache.save_file(file_id, &mut file); ++ debug!("File {} complete, saving to cache", file_id); ++ } else { ++ debug!("File {} complete", file_id); ++ } ++ }).or_else(|oneshot::Canceled| Ok(())) ++ }); ++ } + + AudioFileOpen::Streaming(open) + } +diff --git a/src/main.rs b/src/main.rs +index 38c57fd..8a31a44 100644 +--- a/src/main.rs ++++ b/src/main.rs +@@ -86,6 +86,7 @@ struct Setup { + fn setup(args: &[String]) -> Setup { + let mut opts = getopts::Options::new(); + opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE") ++ .optflag("", "disable-audio-cache", "Disable caching of the audio data.") + .reqopt("n", "name", "Device name", "NAME") + .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE") + .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM") +@@ -152,6 +153,7 @@ fn setup(args: &[String]) -> Setup { + bitrate: bitrate, + onstart: matches.opt_str("onstart"), + onstop: matches.opt_str("onstop"), ++ use_audio_cache: !matches.opt_present("disable-audio-cache"), + }; + + let device = matches.opt_str("device"); +diff --git a/src/session.rs b/src/session.rs +index 86162bd..a5d397e 100644 +--- a/src/session.rs ++++ b/src/session.rs +@@ -49,6 +49,7 @@ pub struct Config { + pub bitrate: Bitrate, + pub onstart: Option, + pub onstop: Option, ++ pub use_audio_cache: bool, + } + + impl Default for Config { +@@ -60,6 +61,7 @@ impl Default for Config { + bitrate: Bitrate::Bitrate160, + onstart: None, + onstop: None, ++ use_audio_cache: true, + } + } + } diff --git a/packages/addons/service/librespot/source/bin/librespot.start b/packages/addons/service/librespot/source/bin/librespot.start new file mode 100755 index 0000000000..0d5aaed6b4 --- /dev/null +++ b/packages/addons/service/librespot/source/bin/librespot.start @@ -0,0 +1,100 @@ +#!/bin/sh +################################################################################ +# This file is part of LibreELEC - https://libreelec.tv +# Copyright (C) 2017-present Team LibreELEC +# +# LibreELEC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# LibreELEC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LibreELEC. If not, see . +################################################################################ + +. /etc/os-release +. /etc/profile +oe_setup_addon service.librespot + +activate_card() { + if [ -e "/proc/asound/$1" ]; then + return + fi + case "$LIBREELEC_ARCH" in + RPi*.arm) + if [ "$1" = "ALSA" ]; then + dtparam audio=on + sleep 1 + fi + ;; + *) + echo "Unable to activate card $1 on $LIBREELEC_ARCH" + exit + ;; + esac +} + +if [ ! "$(cat /proc/asound/pcm 2> /dev/null)" ]; then + case "$LIBREELEC_ARCH" in + RPi*.arm) + activate_card "ALSA" + ;; + *) + echo "Unable to activate an audio interface on $LIBREELEC_ARCH" + exit + ;; + esac +fi + +case "$ls_o" in + *:CARD=*) + card="${ls_o##*:CARD=}" + card="${card%%,*}" + activate_card "$card" + index="$(readlink /proc/asound/$card)" + index="${index##*card}" + ;; + hw:*,*) + echo "The hw:d,s specification is unreliable, use device:CARD=card instead" + index="${ls_o##hw:}" + index="${index%%,*}" + card="card$index" + activate_card "$card" + ;; + *) + if [ -n "$ls_o" ]; then + echo "Unknown playback device specification $ls_o" + fi + ;; +esac + +if [ -n "$ls_b" -a "$ls_b" != "-" ]; then + bitrate="--bitrate $ls_b" +fi + +if [ -n "$ls_o" ]; then + device="--device $ls_o" +fi + +if [ -n "$ls_p" -a -n "$ls_u" ]; then + discovery="--disable-discovery --password $ls_p --username $ls_u" +fi + +case "$LIBREELEC_ARCH" in + RPi*.arm) + [ "$(readlink /proc/asound/ALSA)" == "card$index" ] && [ "$pcm_3" ] && + amixer -c "$index" cset name="PCM Playback Route" "$pcm_3" + ;; +esac + +librespot $bitrate \ + --cache "$ADDON_HOME/cache" \ + $device \ + --disable-audio-cache \ + $discovery \ + --name "Librespot@$HOSTNAME" diff --git a/packages/addons/service/librespot/source/default.py b/packages/addons/service/librespot/source/default.py new file mode 100644 index 0000000000..1388ab8636 --- /dev/null +++ b/packages/addons/service/librespot/source/default.py @@ -0,0 +1,35 @@ +################################################################################ +# This file is part of LibreELEC - https://libreelec.tv +# Copyright (C) 2017-present Team LibreELEC +# +# LibreELEC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# LibreELEC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LibreELEC. If not, see . +################################################################################ + +import subprocess +import xbmc +import xbmcaddon + + +class Monitor(xbmc.Monitor): + + def __init__(self, *args, **kwargs): + xbmc.Monitor.__init__(self) + self.id = xbmcaddon.Addon().getAddonInfo('id') + + def onSettingsChanged(self): + subprocess.call(['systemctl', 'restart', self.id]) + + +if __name__ == '__main__': + Monitor().waitForAbort() diff --git a/packages/addons/service/librespot/source/resources/language/English/strings.po b/packages/addons/service/librespot/source/resources/language/English/strings.po new file mode 100644 index 0000000000..02ec26527d --- /dev/null +++ b/packages/addons/service/librespot/source/resources/language/English/strings.po @@ -0,0 +1,72 @@ +# Kodi Media Center language file +# Addon Name: librespot +msgid "" +msgstr "" + +msgctxt "#30100" +msgid "Configuration" +msgstr "" + +msgctxt "#30101" +msgid "ALSA" +msgstr "" + +msgctxt "#30102" +msgid "Configuration wizard" +msgstr "" + +msgctxt "#30103" +msgid "Playback device" +msgstr "" + +msgctxt "#30104" +msgid "Playback route" +msgstr "" + +msgctxt "#30105" +msgid "auto detect" +msgstr "" + +msgctxt "#30106" +msgid "headphone jack" +msgstr "" + +msgctxt "#30107" +msgid "HDMI" +msgstr "" + +msgctxt "#30108" +msgid "Spotify" +msgstr "" + +msgctxt "#30109" +msgid "Username" +msgstr "" + +msgctxt "#30110" +msgid "Password" +msgstr "" + +msgctxt "#30111" +msgid "Discovery mode (set username and password to disable)" +msgstr "" + +msgctxt "#30112" +msgid "Bit rate" +msgstr "" + +msgctxt "#30113" +msgid "-" +msgstr "" + +msgctxt "#30114" +msgid "90" +msgstr "" + +msgctxt "#30115" +msgid "160" +msgstr "" + +msgctxt "#30116" +msgid "320" +msgstr "" diff --git a/packages/addons/service/librespot/source/resources/settings.xml b/packages/addons/service/librespot/source/resources/settings.xml new file mode 100644 index 0000000000..7292787476 --- /dev/null +++ b/packages/addons/service/librespot/source/resources/settings.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/addons/service/librespot/source/system.d/service.librespot.service b/packages/addons/service/librespot/source/system.d/service.librespot.service new file mode 100644 index 0000000000..3d34f79bc5 --- /dev/null +++ b/packages/addons/service/librespot/source/system.d/service.librespot.service @@ -0,0 +1,11 @@ +[Unit] +Description=librespot +After=network-online.target +Requires=network-online.target + +[Service] +ExecStart=/bin/sh /storage/.kodi/addons/service.librespot/bin/librespot.start +Restart=on-failure + +[Install] +WantedBy=kodi.target diff --git a/packages/addons/service/librespot/source/wizard/wizard.py b/packages/addons/service/librespot/source/wizard/wizard.py new file mode 100644 index 0000000000..585a4b92dc --- /dev/null +++ b/packages/addons/service/librespot/source/wizard/wizard.py @@ -0,0 +1,39 @@ +################################################################################ +# This file is part of LibreELEC - https://libreelec.tv +# Copyright (C) 2017-present Team LibreELEC +# +# LibreELEC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# LibreELEC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LibreELEC. If not, see . +################################################################################ + +import alsaaudio as alsa +import xbmcaddon +import xbmcgui + +if __name__ == '__main__': + + addon = xbmcaddon.Addon('service.librespot') + dialog = xbmcgui.Dialog() + strings = addon.getLocalizedString + + while True: + pcms = alsa.pcms()[1:] + if len(pcms) == 0: + dialog.ok(strings(30211), strings(30212)) + break + pcmx = dialog.select(strings(30113), pcms) + if pcmx == -1: + break + pcm = pcms[pcmx] + addon.setSetting('ls_o', pcm) + break