Merge pull request #1825 from awiouy/lsrw-8.2

librespot: ux improvements
This commit is contained in:
Christian Hewitt 2017-07-25 09:24:16 +04:00 committed by GitHub
commit 08e2d1080f
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,18 +101,7 @@ if [ -n "$ls_p" -a -n "$ls_u" ]; then
fi fi
if [ "$ls_O" == "Kodi" ]; then if [ "$ls_O" == "Kodi" ]; then
if [ -z "$(pactl list short modules | grep sink_name=$LS_SINK)" ]; then LIBRESPOT="$LIBRESPOT --backend pulseaudio"
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 else
init_alsa init_alsa
if [ -n "$ls_o" ]; then if [ -n "$ls_o" ]; then
@ -121,4 +109,15 @@ else
fi fi
fi fi
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
export LS_FIFO="$ADDON_DIR/rc"
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:
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:
pcms = alsa.pcms()[1:]
if len(pcms) == 0:
dialog.ok(NAME, STRINGS(30210))
break
pcmx = dialog.select(STRINGS(30112), pcms)
if pcmx == -1:
break
pcm = pcms[pcmx]
xbmcaddon.Addon().setSetting('ls_o', pcm)
break
FIFO = os.path.join(xbmcaddon.Addon().getAddonInfo('path'), 'rc')
class Monitor(xbmc.Monitor): def __init__(self, player):
super(Controller, self).__init__()
self.player = player
def __init__(self, *args, **kwargs): def run(self):
super(Monitor, self).__init__(self) try:
self.player = Player() os.unlink(self.FIFO)
except OSError:
pass
os.mkfifo(self.FIFO)
while os.path.exists(self.FIFO) and stat.S_ISFIFO(os.stat(self.FIFO).st_mode):
with open(self.FIFO, 'r') as fifo:
command = fifo.read().splitlines()
if len(command) == 0:
break
elif command[0] == 'play' and len(command) == 3:
dialog = xbmcgui.Dialog()
dialog.notification(command[1],
command[2],
icon=xbmcaddon.Addon().getAddonInfo('icon'),
sound=False)
del dialog
self.player.play()
elif command[0] == 'stop':
self.player.stop()
def onSettingsChanged(self): def stop(self):
self.player.stop() 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]