librespot: ux improvements

This commit is contained in:
awiouy 2017-07-22 14:50:45 +02:00
parent 24c448405a
commit c9dd60302d
11 changed files with 146 additions and 100 deletions

View File

@ -1,3 +1,7 @@
106
- Rework code
- Display artist and title on track load
105 105
- Update to 910974e - Update to 910974e

View File

@ -19,7 +19,7 @@
PKG_NAME="librespot" PKG_NAME="librespot"
PKG_VERSION="910974e" PKG_VERSION="910974e"
PKG_REV="105" PKG_REV="106"
PKG_ARCH="any" PKG_ARCH="any"
PKG_LICENSE="MIT" PKG_LICENSE="MIT"
PKG_SITE="https://github.com/plietar/$PKG_NAME/" PKG_SITE="https://github.com/plietar/$PKG_NAME/"

View File

@ -1,7 +1,24 @@
diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
--- librespot/src/player.rs 2017-07-09 20:01:31.000000000 +0200 --- 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 +++ librespot-hooks/src/player.rs 2017-07-22 13:46:06.741727001 +0200
@@ -212,7 +212,7 @@ @@ -2,6 +2,7 @@
use futures::{future, Future};
use std::borrow::Cow;
use std::io::{Read, Seek};
+use std::env;
use std::mem;
use std::sync::mpsc::{RecvError, TryRecvError};
use std::thread;
@@ -11,7 +12,7 @@
use audio_backend::Sink;
use audio_decrypt::AudioDecrypt;
use audio_file::AudioFile;
-use metadata::{FileFormat, Track};
+use metadata::{Artist, FileFormat, Track};
use session::{Bitrate, Session};
use mixer::AudioFilter;
use util::{self, SpotifyId, Subfile};
@@ -212,7 +213,7 @@
Some(Err(e)) => panic!("Vorbis error {:?}", e), Some(Err(e)) => panic!("Vorbis error {:?}", e),
None => { None => {
self.sink.stop().unwrap(); self.sink.stop().unwrap();
@ -10,16 +27,20 @@ diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
let old_state = mem::replace(&mut self.state, PlayerState::Stopped); let old_state = mem::replace(&mut self.state, PlayerState::Stopped);
old_state.signal_end_of_track(); old_state.signal_end_of_track();
@@ -224,6 +224,8 @@ @@ -224,6 +225,12 @@
debug!("command={:?}", cmd); debug!("command={:?}", cmd);
match cmd { match cmd {
PlayerCommand::Load(track_id, play, position, end_of_track) => { PlayerCommand::Load(track_id, play, position, end_of_track) => {
+ let track = self.session.metadata().get::<Track>(track_id).wait().unwrap();
+ let artist = self.session.metadata().get::<Artist>(track.artists[0]).wait().unwrap();
+ env::set_var("LS_ARTIST", artist.name);
+ env::set_var("LS_TITLE", track.name);
+ self.run_onstart(); + self.run_onstart();
+ +
if self.state.is_playing() { if self.state.is_playing() {
self.sink.stop().unwrap(); self.sink.stop().unwrap();
} }
@@ -232,7 +234,7 @@ @@ -232,7 +239,7 @@
Some(decoder) => { Some(decoder) => {
if play { if play {
if !self.state.is_playing() { if !self.state.is_playing() {
@ -28,7 +49,7 @@ diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
} }
self.sink.start().unwrap(); self.sink.start().unwrap();
@@ -242,7 +244,7 @@ @@ -242,7 +249,7 @@
}; };
} else { } else {
if self.state.is_playing() { if self.state.is_playing() {
@ -37,7 +58,7 @@ diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
} }
self.state = PlayerState::Paused { self.state = PlayerState::Paused {
@@ -255,7 +257,7 @@ @@ -255,7 +262,7 @@
None => { None => {
end_of_track.complete(()); end_of_track.complete(());
if self.state.is_playing() { if self.state.is_playing() {
@ -46,7 +67,7 @@ diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
} }
} }
} }
@@ -276,7 +278,7 @@ @@ -276,7 +283,7 @@
if let PlayerState::Paused { .. } = self.state { if let PlayerState::Paused { .. } = self.state {
self.state.paused_to_playing(); self.state.paused_to_playing();
@ -55,7 +76,7 @@ diff -Naur librespot/src/player.rs librespot-hooks/src/player.rs
self.sink.start().unwrap(); self.sink.start().unwrap();
} else { } else {
warn!("Player::play called from invalid state"); warn!("Player::play called from invalid state");
@@ -288,17 +290,19 @@ @@ -288,17 +295,19 @@
self.state.playing_to_paused(); self.state.playing_to_paused();
self.sink.stop().unwrap(); self.sink.stop().unwrap();

View File

@ -16,8 +16,24 @@
# along with LibreELEC. If not, see <http://www.gnu.org/licenses/>. # along with LibreELEC. If not, see <http://www.gnu.org/licenses/>.
################################################################################ ################################################################################
from default import addon as addon import alsaaudio as alsa
import xbmcaddon
import xbmcgui
dialog = xbmcgui.Dialog()
strings = xbmcaddon.Addon().getLocalizedString
while True:
pcms = alsa.pcms()[1:]
if len(pcms) == 0:
dialog.ok(xbmcaddon.Addon().getAddonInfo('name'), strings(30210))
break
pcmx = dialog.select(strings(30112), pcms)
if pcmx == -1:
break
pcm = pcms[pcmx]
xbmcaddon.Addon().setSetting('ls_o', pcm)
break
del dialog
if __name__ == '__main__':
addon()

View File

@ -0,0 +1,2 @@
LS_PORT="6666"
LS_SINK="librespot_sink"

View File

@ -0,0 +1,2 @@
#!/bin/sh
echo -e "play\n$LS_ARTIST\n$LS_TITLE" > "$LS_FIFO"

View File

@ -0,0 +1,2 @@
#!/bin/sh
echo -e "stop" > "$LS_FIFO"

View File

@ -84,12 +84,11 @@ init_alsa() {
. /etc/profile . /etc/profile
oe_setup_addon service.librespot oe_setup_addon service.librespot
LS_PORT="6666" LIBRESPOT="librespot --cache \"$ADDON_HOME/cache\" \
LS_SINK="librespot_sink"
LIBRESPOT="librespot --cache \"$ADDON_HOME/cache\"
--disable-audio-cache \ --disable-audio-cache \
--name \"Librespot@$HOSTNAME\"" --name \"Librespot@$HOSTNAME\" \
--onstart librespot.onstart \
--onstop librespot.onstop"
if [ -n "$ls_b" -a "$ls_b" != "-" ]; then if [ -n "$ls_b" -a "$ls_b" != "-" ]; then
LIBRESPOT="$LIBRESPOT --bitrate $ls_b" LIBRESPOT="$LIBRESPOT --bitrate $ls_b"
@ -102,6 +101,14 @@ if [ -n "$ls_p" -a -n "$ls_u" ]; then
fi fi
if [ "$ls_O" == "Kodi" ]; then if [ "$ls_O" == "Kodi" ]; then
LIBRESPOT="$LIBRESPOT --backend pulseaudio"
else
init_alsa
if [ -n "$ls_o" ]; then
LIBRESPOT="$LIBRESPOT --device \"$ls_o\""
fi
fi
if [ -z "$(pactl list short modules | grep sink_name=$LS_SINK)" ]; then if [ -z "$(pactl list short modules | grep sink_name=$LS_SINK)" ]; then
pactl load-module module-null-sink sink_name="$LS_SINK" > /dev/null pactl load-module module-null-sink sink_name="$LS_SINK" > /dev/null
fi fi
@ -110,15 +117,7 @@ if [ "$ls_O" == "Kodi" ]; then
pactl load-module module-rtp-send source="$LS_SINK.monitor" \ 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 destination_ip=127.0.0.1 port="$LS_PORT" source_ip=127.0.0.1 > /dev/null
fi fi
LIBRESPOT="$LIBRESPOT \
--backend pulseaudio \ export LS_FIFO="$ADDON_DIR/rc"
--onstart=\"kodi-send --action=RunAddon(service.librespot,start)\" \
--onstop=\"kodi-send --action=RunAddon(service.librespot,stop)\""
else
init_alsa
if [ -n "$ls_o" ]; then
LIBRESPOT="$LIBRESPOT --device \"$ls_o\""
fi
fi
eval $LIBRESPOT eval $LIBRESPOT

View File

@ -16,10 +16,11 @@
# along with LibreELEC. If not, see <http://www.gnu.org/licenses/>. # along with LibreELEC. If not, see <http://www.gnu.org/licenses/>.
################################################################################ ################################################################################
import alsaaudio as alsa
import os import os
import stat
import subprocess import subprocess
import sys import sys
import threading
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
@ -27,107 +28,105 @@ import xbmcgui
PORT = '6666' PORT = '6666'
SINK = 'librespot_sink' SINK = 'librespot_sink'
NAME = xbmcaddon.Addon().getAddonInfo('name')
STRINGS = xbmcaddon.Addon().getLocalizedString def suspendSink(bit):
subprocess.call(['pactl', 'suspend-sink', SINK, bit])
def systemctl(command):
subprocess.call(['systemctl', command, xbmcaddon.Addon().getAddonInfo('id')])
def addon(): class Controller(threading.Thread):
if len(sys.argv) == 1:
FIFO = os.path.join(xbmcaddon.Addon().getAddonInfo('path'), 'rc')
def __init__(self, player):
super(Controller, self).__init__()
self.player = player
def run(self):
try:
os.unlink(self.FIFO)
except OSError:
pass pass
elif sys.argv[1] == 'start': os.mkfifo(self.FIFO)
Player().play() while os.path.exists(self.FIFO) and stat.S_ISFIFO(os.stat(self.FIFO).st_mode):
elif sys.argv[1] == 'stop': with open(self.FIFO, 'r') as fifo:
Player().stop() command = fifo.read().splitlines()
elif sys.argv[1] == 'wizard': if len(command) == 0:
break
elif command[0] == 'play' and len(command) == 3:
dialog = xbmcgui.Dialog() dialog = xbmcgui.Dialog()
while True: dialog.notification(command[1],
pcms = alsa.pcms()[1:] command[2],
if len(pcms) == 0: icon=xbmcaddon.Addon().getAddonInfo('icon'),
dialog.ok(NAME, STRINGS(30210)) sound=False)
break del dialog
pcmx = dialog.select(STRINGS(30112), pcms) self.player.play()
if pcmx == -1: elif command[0] == 'stop':
break
pcm = pcms[pcmx]
xbmcaddon.Addon().setSetting('ls_o', pcm)
break
class Monitor(xbmc.Monitor):
def __init__(self, *args, **kwargs):
super(Monitor, self).__init__(self)
self.player = Player()
def onSettingsChanged(self):
self.player.stop() self.player.stop()
def stop(self):
try:
os.unlink(self.FIFO)
except OSError:
pass
class Player(xbmc.Player): class Player(xbmc.Player):
ITEM = 'rtp://127.0.0.1:{port}'.format(port=PORT) 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): def __init__(self):
super(Player, self).__init__(self) super(Player, self).__init__(self)
self.service = Service() self.window = xbmcgui.Window(12006)
self.sink = Sink()
if self.isPlaying(): if self.isPlaying():
self.onPlayBackStarted() self.onPlayBackStarted()
def onPlayBackEnded(self): def onPlayBackEnded(self):
suspendSink('1')
xbmc.sleep(1000) xbmc.sleep(1000)
if not self.isPlaying(): if not self.isPlaying():
self.service.restart() systemctl('restart')
def onPlayBackStarted(self): def onPlayBackStarted(self):
if self.getPlayingFile() != self.ITEM: if self.getPlayingFile() != self.ITEM:
self.sink.suspend() suspendSink('1')
self.service.stop() systemctl('stop')
def onPlayBackStopped(self): def onPlayBackStopped(self):
self.service.restart() systemctl('restart')
def play(self): def play(self):
if not self.isPlaying(): if not self.isPlaying() and xbmcaddon.Addon().getSetting('ls_O') == 'Kodi':
self.sink.unsuspend() suspendSink('0')
super(Player, self).play(self.ITEM, self.LISTITEM) listitem = xbmcgui.ListItem(xbmcaddon.Addon().getAddonInfo('name'))
listitem.setArt({'thumb': xbmcaddon.Addon().getAddonInfo('icon')})
super(Player, self).play(self.ITEM, listitem)
del listitem
self.window.show()
def stop(self): def stop(self):
self.sink.suspend() suspendSink('1')
if self.isPlaying() and self.getPlayingFile() == self.ITEM: if self.isPlaying() and self.getPlayingFile() == self.ITEM:
super(Player, self).stop() super(Player, self).stop()
else: else:
self.service.restart() systemctl('restart')
class Service(): class Monitor(xbmc.Monitor):
def __init__(self): def __init__(self, player):
self.id = xbmcaddon.Addon().getAddonInfo('id') super(Monitor, self).__init__(self)
self.player = player
def restart(self): def onSettingsChanged(self):
self.systemctl('restart') self.player.stop()
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__': if __name__ == '__main__':
Monitor().waitForAbort() player = Player()
controller = Controller(player)
controller.start()
Monitor(player).waitForAbort()
controller.stop()

View File

@ -10,7 +10,7 @@
</category> </category>
<category label="30110" > <category label="30110" >
<setting label="30106" type="labelenum" id="ls_O" lvalues="ALSA|Kodi" visible="false" /> <setting label="30106" type="labelenum" id="ls_O" lvalues="ALSA|Kodi" visible="false" />
<setting label="30111" type="action" action="RunAddon(service.librespot,wizard)" enable="eq(-1,0)" /> <setting label="30111" type="action" action="RunAddon(service.librespot)" enable="eq(-1,0)" />
<setting label="30112" type="text" id="ls_o" default="" enable="eq(-2,0)" /> <setting label="30112" type="text" id="ls_o" default="" enable="eq(-2,0)" />
<setting label="30113" type="enum" id="pcm_3" lvalues="30114|30115|30116" enable="eq(-3,0)" visible="eq(-1,default:CARD=ALSA)|eq(-1,sysdefault:CARD=ALSA)" /> <setting label="30113" type="enum" id="pcm_3" lvalues="30114|30115|30116" enable="eq(-3,0)" visible="eq(-1,default:CARD=ALSA)|eq(-1,sysdefault:CARD=ALSA)" />
</category> </category>

View File

@ -1,11 +1,12 @@
[Unit] [Unit]
Description=librespot Description=librespot
After=network-online.target sound.target After=kodi.service network-online.target sound.target
Requires=network-online.target sound.target Requires=kodi.service network-online.target sound.target
[Service] [Service]
EnvironmentFile=/storage/.kodi/addons/service.librespot/bin/librespot.env
ExecStart=/bin/sh /storage/.kodi/addons/service.librespot/bin/librespot.start ExecStart=/bin/sh /storage/.kodi/addons/service.librespot/bin/librespot.start
ExecStopPost=/usr/bin/pactl suspend-sink librespot_sink 1 ExecStopPost=/usr/bin/pactl suspend-sink "$LS_SINK" 1
Restart=on-failure Restart=on-failure
[Install] [Install]