mirror of
https://github.com/LibreELEC/LibreELEC.tv.git
synced 2025-07-24 11:16:51 +00:00
Merge pull request #1808 from awiouy/librespot-104-8.2
librespot: switch from ffmpegx to pulseaudio to stream to Kodi
This commit is contained in:
commit
7f971be88b
@ -1,3 +1,8 @@
|
||||
104
|
||||
- Update to 910974e
|
||||
- Switch from ffmpegx to pulseaudio to stream to Kodi
|
||||
- Wait for sound.target
|
||||
|
||||
103
|
||||
- Update system_information_string
|
||||
|
||||
|
@ -19,12 +19,12 @@
|
||||
|
||||
PKG_NAME="librespot"
|
||||
PKG_VERSION="aa86ebf"
|
||||
PKG_REV="103"
|
||||
PKG_REV="104"
|
||||
PKG_ARCH="any"
|
||||
PKG_LICENSE="MIT"
|
||||
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 ffmpegx libvorbis pyalsaaudio rust"
|
||||
PKG_DEPENDS_TARGET="toolchain avahi libvorbis pulseaudio pyalsaaudio rust"
|
||||
PKG_SECTION="service"
|
||||
PKG_SHORTDESC="Librespot: play Spotify through LibreELEC using a Spotify app as a remote"
|
||||
PKG_LONGDESC="Librespot ($PKG_VERSION) plays Spotify through LibreELEC using the open source librespot library using a Spotify app as a remote."
|
||||
@ -43,7 +43,7 @@ configure_target() {
|
||||
|
||||
make_target() {
|
||||
cd src
|
||||
$CARGO_BUILD --no-default-features --features "alsa-backend with-avahi"
|
||||
$CARGO_BUILD --no-default-features --features "alsa-backend pulseaudio-backend with-avahi"
|
||||
cd "$PKG_BUILD/.$TARGET_NAME"/*/release
|
||||
$STRIP librespot
|
||||
}
|
||||
@ -59,7 +59,6 @@ addon() {
|
||||
|
||||
mkdir -p "$ADDON_BUILD/$PKG_ADDON_ID/bin"
|
||||
cp "$PKG_BUILD/.$TARGET_NAME"/*/release/librespot \
|
||||
"$(get_build_dir ffmpegx)/.install_pkg/usr/local/bin/ffmpegx" \
|
||||
"$ADDON_BUILD/$PKG_ADDON_ID/bin"
|
||||
|
||||
mkdir -p "$ADDON_BUILD/$PKG_ADDON_ID/lib"
|
||||
|
@ -0,0 +1,19 @@
|
||||
diff --git a/src/audio_backend/pulseaudio.rs b/src/audio_backend/pulseaudio.rs
|
||||
index 3b9a09b..ce78062 100644
|
||||
--- a/src/audio_backend/pulseaudio.rs
|
||||
+++ b/src/audio_backend/pulseaudio.rs
|
||||
@@ -23,12 +23,13 @@ impl Open for PulseAudioSink {
|
||||
|
||||
let name = CString::new("librespot").unwrap();
|
||||
let description = CString::new("A spoty client library").unwrap();
|
||||
+ let sink = CString::new("librespot_sink").unwrap();
|
||||
|
||||
let s = unsafe {
|
||||
pa_simple_new(null(), // Use the default server.
|
||||
name.as_ptr(), // Our application's name.
|
||||
PA_STREAM_PLAYBACK,
|
||||
- null(), // Use the default device.
|
||||
+ sink.as_ptr(), // Our sink.
|
||||
description.as_ptr(), // Description of our stream.
|
||||
&ss, // Our sample format.
|
||||
null(), // Use default channel map
|
@ -1,22 +0,0 @@
|
||||
From 697926d2058e8650a9dd559d90a8a130950c417a Mon Sep 17 00:00:00 2001
|
||||
From: Sasha Hilton <sashahilton00@users.noreply.github.com>
|
||||
Date: Tue, 27 Jun 2017 12:45:34 +0100
|
||||
Subject: [PATCH] Update system_information_string
|
||||
|
||||
---
|
||||
src/connection/mod.rs | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/connection/mod.rs b/src/connection/mod.rs
|
||||
index af134fc..cfb5000 100644
|
||||
--- a/src/connection/mod.rs
|
||||
+++ b/src/connection/mod.rs
|
||||
@@ -41,7 +41,7 @@ pub fn authenticate(transport: Transport, credentials: Credentials, device_id: S
|
||||
system_info => {
|
||||
cpu_family: CpuFamily::CPU_UNKNOWN,
|
||||
os: Os::OS_UNKNOWN,
|
||||
- system_information_string: "This is not the client you are looking for".to_owned(),
|
||||
+ system_information_string: "librespot".to_string() + "_" + &(version::short_sha()) + "_" + &(version::short_now()),
|
||||
device_id: device_id,
|
||||
},
|
||||
version_string: version::version_string(),
|
@ -0,0 +1,79 @@
|
||||
diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
|
||||
--- librespot/src/player.rs 2017-07-09 20:01:31.000000000 +0200
|
||||
+++ librespot-hooks/src/player.rs 2017-07-17 09:13:26.300852918 +0200
|
||||
@@ -212,7 +212,7 @@
|
||||
Some(Err(e)) => panic!("Vorbis error {:?}", e),
|
||||
None => {
|
||||
self.sink.stop().unwrap();
|
||||
- self.run_onstop();
|
||||
+ info!("onstop 1");
|
||||
|
||||
let old_state = mem::replace(&mut self.state, PlayerState::Stopped);
|
||||
old_state.signal_end_of_track();
|
||||
@@ -224,6 +224,8 @@
|
||||
debug!("command={:?}", cmd);
|
||||
match cmd {
|
||||
PlayerCommand::Load(track_id, play, position, end_of_track) => {
|
||||
+ self.run_onstart();
|
||||
+
|
||||
if self.state.is_playing() {
|
||||
self.sink.stop().unwrap();
|
||||
}
|
||||
@@ -232,7 +234,7 @@
|
||||
Some(decoder) => {
|
||||
if play {
|
||||
if !self.state.is_playing() {
|
||||
- self.run_onstart();
|
||||
+ info!("onstart 1");
|
||||
}
|
||||
self.sink.start().unwrap();
|
||||
|
||||
@@ -242,7 +244,7 @@
|
||||
};
|
||||
} else {
|
||||
if self.state.is_playing() {
|
||||
- self.run_onstop();
|
||||
+ info!("onstop 2");
|
||||
}
|
||||
|
||||
self.state = PlayerState::Paused {
|
||||
@@ -255,7 +257,7 @@
|
||||
None => {
|
||||
end_of_track.complete(());
|
||||
if self.state.is_playing() {
|
||||
- self.run_onstop();
|
||||
+ info!("onstart 3");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +278,7 @@
|
||||
if let PlayerState::Paused { .. } = self.state {
|
||||
self.state.paused_to_playing();
|
||||
|
||||
- self.run_onstart();
|
||||
+ info!("onstart 2");
|
||||
self.sink.start().unwrap();
|
||||
} else {
|
||||
warn!("Player::play called from invalid state");
|
||||
@@ -288,17 +290,19 @@
|
||||
self.state.playing_to_paused();
|
||||
|
||||
self.sink.stop().unwrap();
|
||||
- self.run_onstop();
|
||||
+ info!("onstop 4");
|
||||
} else {
|
||||
warn!("Player::pause called from invalid state");
|
||||
}
|
||||
}
|
||||
|
||||
PlayerCommand::Stop => {
|
||||
+ self.run_onstop();
|
||||
+
|
||||
match self.state {
|
||||
PlayerState::Playing { .. } => {
|
||||
self.sink.stop().unwrap();
|
||||
- self.run_onstop();
|
||||
+ info!("onstop 5");
|
||||
self.state = PlayerState::Stopped;
|
||||
}
|
||||
PlayerState::Paused { .. } => {
|
@ -84,6 +84,9 @@ init_alsa() {
|
||||
. /etc/profile
|
||||
oe_setup_addon service.librespot
|
||||
|
||||
LS_PORT="6666"
|
||||
LS_SINK="librespot_sink"
|
||||
|
||||
LIBRESPOT="librespot --cache \"$ADDON_HOME/cache\"
|
||||
--disable-audio-cache \
|
||||
--name \"Librespot@$HOSTNAME\""
|
||||
@ -99,14 +102,18 @@ if [ -n "$ls_p" -a -n "$ls_u" ]; then
|
||||
fi
|
||||
|
||||
if [ "$ls_O" == "Kodi" ]; then
|
||||
LIBRESPOT="$LIBRESPOT --backend pipe \
|
||||
--onstart=\"kodi-send \
|
||||
--action=RunAddon(service.librespot)\" \
|
||||
| ffmpegx -hide_banner -loglevel warning \
|
||||
-avioflags direct -fflags nobuffer \
|
||||
-re -ac 2 -ar 44100 -channel_layout stereo -f s16le -i pipe: \
|
||||
-sdp_file \"$ADDON_HOME/librespot.sdp\" \
|
||||
-f rtp rtp://127.0.0.1:5555"
|
||||
if [ -z "$(pactl list short modules | grep sink_name=$LS_SINK)" ]; then
|
||||
pactl load-module module-null-sink sink_name="$LS_SINK" > /dev/null
|
||||
fi
|
||||
pactl suspend-sink "$LS_SINK" 1
|
||||
if [ -z "$(pactl list short modules | grep source=$LS_SINK.monitor)" ]; then
|
||||
pactl load-module module-rtp-send source="$LS_SINK.monitor" \
|
||||
destination_ip=127.0.0.1 port="$LS_PORT" source_ip=127.0.0.1 > /dev/null
|
||||
fi
|
||||
LIBRESPOT="$LIBRESPOT \
|
||||
--backend pulseaudio \
|
||||
--onstart=\"kodi-send --action=RunAddon(service.librespot,start)\" \
|
||||
--onstop=\"kodi-send --action=RunAddon(service.librespot,stop)\""
|
||||
else
|
||||
init_alsa
|
||||
if [ -n "$ls_o" ]; then
|
||||
|
@ -24,21 +24,20 @@ import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
PORT = '6666'
|
||||
SINK = 'librespot_sink'
|
||||
|
||||
ICON = xbmcaddon.Addon().getAddonInfo('icon')
|
||||
ID = xbmcaddon.Addon().getAddonInfo('id')
|
||||
NAME = xbmcaddon.Addon().getAddonInfo('name')
|
||||
PROFILE = xbmcaddon.Addon().getAddonInfo('profile')
|
||||
STRINGS = xbmcaddon.Addon().getLocalizedString
|
||||
|
||||
ITEM = os.path.join(PROFILE, 'librespot.sdp')
|
||||
LISTITEM = xbmcgui.ListItem(NAME)
|
||||
LISTITEM.setArt({'thumb': ICON})
|
||||
|
||||
|
||||
def addon():
|
||||
if len(sys.argv) == 1:
|
||||
pass
|
||||
elif sys.argv[1] == 'start':
|
||||
Player().play()
|
||||
elif sys.argv[1] == 'stop':
|
||||
Player().stop()
|
||||
elif sys.argv[1] == 'wizard':
|
||||
dialog = xbmcgui.Dialog()
|
||||
while True:
|
||||
@ -54,10 +53,6 @@ def addon():
|
||||
break
|
||||
|
||||
|
||||
def systemctl(command):
|
||||
subprocess.call(['systemctl', command, ID])
|
||||
|
||||
|
||||
class Monitor(xbmc.Monitor):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -70,37 +65,69 @@ class Monitor(xbmc.Monitor):
|
||||
|
||||
class Player(xbmc.Player):
|
||||
|
||||
ITEM = 'rtp://127.0.0.1:{port}'.format(port=PORT)
|
||||
LISTITEM = xbmcgui.ListItem(NAME)
|
||||
LISTITEM.setArt({'thumb': xbmcaddon.Addon().getAddonInfo('icon')})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Player, self).__init__(self)
|
||||
self.service = Service()
|
||||
self.sink = Sink()
|
||||
if self.isPlaying():
|
||||
self.onPlayBackStarted()
|
||||
|
||||
def onPlayBackEnded(self):
|
||||
if not self.islibrespot:
|
||||
xbmc.sleep(5000)
|
||||
if not self.isPlaying():
|
||||
systemctl('start')
|
||||
xbmc.sleep(1000)
|
||||
if not self.isPlaying():
|
||||
self.service.restart()
|
||||
|
||||
def onPlayBackStarted(self):
|
||||
if self.getPlayingFile() == ITEM:
|
||||
self.islibrespot = True
|
||||
else:
|
||||
self.islibrespot = False
|
||||
systemctl('stop')
|
||||
if self.getPlayingFile() != self.ITEM:
|
||||
self.sink.suspend()
|
||||
self.service.stop()
|
||||
|
||||
def onPlayBackStopped(self):
|
||||
systemctl('restart')
|
||||
self.service.restart()
|
||||
|
||||
def play(self):
|
||||
if not self.isPlaying():
|
||||
super(Player, self).play(ITEM, LISTITEM)
|
||||
self.sink.unsuspend()
|
||||
super(Player, self).play(self.ITEM, self.LISTITEM)
|
||||
|
||||
def stop(self):
|
||||
if self.isPlaying():
|
||||
if self.getPlayingFile() == ITEM:
|
||||
super(Player, self).stop()
|
||||
self.sink.suspend()
|
||||
if self.isPlaying() and self.getPlayingFile() == self.ITEM:
|
||||
super(Player, self).stop()
|
||||
else:
|
||||
systemctl('restart')
|
||||
self.service.restart()
|
||||
|
||||
|
||||
class Service():
|
||||
|
||||
def __init__(self):
|
||||
self.id = xbmcaddon.Addon().getAddonInfo('id')
|
||||
|
||||
def restart(self):
|
||||
self.systemctl('restart')
|
||||
|
||||
def start(self):
|
||||
self.systemctl('start')
|
||||
|
||||
def stop(self):
|
||||
self.systemctl('stop')
|
||||
|
||||
def systemctl(self, command):
|
||||
subprocess.call(['systemctl', command, self.id])
|
||||
|
||||
|
||||
class Sink():
|
||||
|
||||
def suspend(self):
|
||||
subprocess.call(['pactl', 'suspend-sink', SINK, '1'])
|
||||
|
||||
def unsuspend(self):
|
||||
subprocess.call(['pactl', 'suspend-sink', SINK, '0'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Monitor().waitForAbort()
|
||||
|
@ -1,10 +1,11 @@
|
||||
[Unit]
|
||||
Description=librespot
|
||||
After=network-online.target
|
||||
Requires=network-online.target
|
||||
After=network-online.target sound.target
|
||||
Requires=network-online.target sound.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/sh /storage/.kodi/addons/service.librespot/bin/librespot.start
|
||||
ExecStopPost=/usr/bin/pactl suspend-sink librespot_sink 1
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
|
Loading…
x
Reference in New Issue
Block a user