Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

librespot: switch from ffmpegx to pulseaudio to stream to Kodi #1781

Merged
merged 1 commit into from Jul 19, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -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 "$ROOT/$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

This file was deleted.

@@ -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 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 @@ def onSettingsChanged(self):

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]
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.