mirror of
https://github.com/LibreELEC/LibreELEC.tv.git
synced 2025-07-30 14:16:40 +00:00
Merge pull request #2953 from awiouy/ls-next
librespot: update to a4e0f58
This commit is contained in:
commit
a3c73c3370
@ -2,7 +2,7 @@
|
|||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
PKG_NAME="rust"
|
PKG_NAME="rust"
|
||||||
PKG_VERSION="1.26.0"
|
PKG_VERSION="1.28.0"
|
||||||
PKG_ARCH="any"
|
PKG_ARCH="any"
|
||||||
PKG_LICENSE="MIT"
|
PKG_LICENSE="MIT"
|
||||||
PKG_SITE="https://www.rust-lang.org"
|
PKG_SITE="https://www.rust-lang.org"
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
112
|
||||||
|
- Update to a4e0f58
|
||||||
|
- Rework Python
|
||||||
|
- Correct codec in Kodi mode
|
||||||
|
- Fix setting change
|
||||||
|
- Fix wizard
|
||||||
|
- Fix zapping issue
|
||||||
|
- Display album, artist, icon and title
|
||||||
|
- Wait for librespot.onevent to finish
|
||||||
|
|
||||||
111
|
111
|
||||||
- Update to 431be9e
|
- Update to 431be9e
|
||||||
- Fix delay with Kodi playback option
|
- Fix delay with Kodi playback option
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
PKG_NAME="librespot"
|
PKG_NAME="librespot"
|
||||||
PKG_VERSION="431be9e"
|
PKG_VERSION="a4e0f582a8c705b05c8abba58d9e9c1c06ad532d"
|
||||||
PKG_SHA256="2e336c5415b6ee6f669e673282daccdd770b15d35dd6d71b39b17dc2aa3424c0"
|
PKG_SHA256="63ed879d7185f16963316b0c3149a40875260f5403b2c55c6cdb470e91b7741d"
|
||||||
PKG_REV="111"
|
PKG_REV="112"
|
||||||
PKG_ARCH="any"
|
PKG_ARCH="any"
|
||||||
PKG_LICENSE="MIT"
|
PKG_LICENSE="MIT"
|
||||||
PKG_SITE="https://github.com/librespot-org/librespot/"
|
PKG_SITE="https://github.com/librespot-org/librespot/"
|
||||||
|
@ -1,30 +1,23 @@
|
|||||||
|
commit 55439529ae313eac5d946aa751387fa747cc6bc4
|
||||||
|
Author: awiouy <awiouy@gmail.com>
|
||||||
|
Date: Wed Jun 13 17:39:54 2018 +0200
|
||||||
|
|
||||||
|
libreelec: kodi hooks
|
||||||
|
|
||||||
diff --git a/playback/src/player.rs b/playback/src/player.rs
|
diff --git a/playback/src/player.rs b/playback/src/player.rs
|
||||||
index dd99423..365c108 100644
|
index ab1a8ab..0aa0630 100644
|
||||||
--- a/playback/src/player.rs
|
--- a/playback/src/player.rs
|
||||||
+++ b/playback/src/player.rs
|
+++ b/playback/src/player.rs
|
||||||
@@ -17,7 +17,7 @@ use core::spotify_id::SpotifyId;
|
@@ -49,15 +49,18 @@ enum PlayerCommand {
|
||||||
use audio::{AudioDecrypt, AudioFile};
|
|
||||||
use audio::{VorbisDecoder, VorbisPacket};
|
|
||||||
use audio_backend::Sink;
|
|
||||||
-use metadata::{FileFormat, Metadata, Track};
|
|
||||||
+use metadata::{FileFormat, Metadata, Track, Artist};
|
|
||||||
use mixer::AudioFilter;
|
|
||||||
|
|
||||||
pub struct Player {
|
|
||||||
@@ -49,15 +49,22 @@ enum PlayerCommand {
|
|
||||||
pub enum PlayerEvent {
|
pub enum PlayerEvent {
|
||||||
Started {
|
Started {
|
||||||
track_id: SpotifyId,
|
track_id: SpotifyId,
|
||||||
+ track: Track,
|
|
||||||
+ artist: Artist,
|
|
||||||
+ new_state: String,
|
+ new_state: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
Changed {
|
Changed {
|
||||||
old_track_id: SpotifyId,
|
old_track_id: SpotifyId,
|
||||||
new_track_id: SpotifyId,
|
new_track_id: SpotifyId,
|
||||||
+ track: Track,
|
|
||||||
+ artist: Artist,
|
|
||||||
+ new_state: String,
|
+ new_state: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -34,17 +27,7 @@ index dd99423..365c108 100644
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,6 +411,9 @@ impl PlayerInternal {
|
@@ -413,11 +416,18 @@ impl PlayerInternal {
|
||||||
|
|
||||||
match self.load_track(track_id, position as i64) {
|
|
||||||
Some((decoder, normalisation_factor)) => {
|
|
||||||
+ let track = Track::get(&self.session, track_id).wait().unwrap();
|
|
||||||
+ let artist = Artist::get(&self.session, track.artists[0]).wait().unwrap();
|
|
||||||
+
|
|
||||||
if play {
|
|
||||||
match self.state {
|
|
||||||
PlayerState::Playing {
|
|
||||||
@@ -413,11 +423,20 @@ impl PlayerInternal {
|
|
||||||
| PlayerState::EndOfTrack {
|
| PlayerState::EndOfTrack {
|
||||||
track_id: old_track_id,
|
track_id: old_track_id,
|
||||||
..
|
..
|
||||||
@ -58,19 +41,17 @@ index dd99423..365c108 100644
|
|||||||
+ self.send_event(PlayerEvent::Changed {
|
+ self.send_event(PlayerEvent::Changed {
|
||||||
+ old_track_id: old_track_id,
|
+ old_track_id: old_track_id,
|
||||||
+ new_track_id: track_id,
|
+ new_track_id: track_id,
|
||||||
+ track: track,
|
|
||||||
+ artist: artist,
|
|
||||||
+ new_state: new_state,
|
+ new_state: new_state,
|
||||||
+ });
|
+ });
|
||||||
+ },
|
+ },
|
||||||
+ _ => {
|
+ _ => {
|
||||||
+ let new_state = "play".to_string();
|
+ let new_state = "play".to_string();
|
||||||
+ self.send_event(PlayerEvent::Started { track_id, track, artist, new_state });
|
+ self.send_event(PlayerEvent::Started { track_id, new_state });
|
||||||
+ },
|
+ },
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_sink();
|
self.start_sink();
|
||||||
@@ -443,13 +462,20 @@ impl PlayerInternal {
|
@@ -443,13 +453,18 @@ impl PlayerInternal {
|
||||||
| PlayerState::EndOfTrack {
|
| PlayerState::EndOfTrack {
|
||||||
track_id: old_track_id,
|
track_id: old_track_id,
|
||||||
..
|
..
|
||||||
@ -83,8 +64,6 @@ index dd99423..365c108 100644
|
|||||||
+ self.send_event(PlayerEvent::Changed {
|
+ self.send_event(PlayerEvent::Changed {
|
||||||
+ old_track_id: old_track_id,
|
+ old_track_id: old_track_id,
|
||||||
+ new_track_id: track_id,
|
+ new_track_id: track_id,
|
||||||
+ track: track,
|
|
||||||
+ artist: artist,
|
|
||||||
+ new_state: new_state,
|
+ new_state: new_state,
|
||||||
+ })
|
+ })
|
||||||
+ },
|
+ },
|
||||||
@ -96,19 +75,17 @@ index dd99423..365c108 100644
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +500,10 @@ impl PlayerInternal {
|
@@ -474,7 +489,8 @@ impl PlayerInternal {
|
||||||
if let PlayerState::Paused { track_id, .. } = self.state {
|
if let PlayerState::Paused { track_id, .. } = self.state {
|
||||||
self.state.paused_to_playing();
|
self.state.paused_to_playing();
|
||||||
|
|
||||||
- self.send_event(PlayerEvent::Started { track_id });
|
- self.send_event(PlayerEvent::Started { track_id });
|
||||||
+ let track = Track::get(&self.session, track_id).wait().unwrap();
|
|
||||||
+ let artist = Artist::get(&self.session, track.artists[0]).wait().unwrap();
|
|
||||||
+ let new_state = "play".to_string();
|
+ let new_state = "play".to_string();
|
||||||
+ self.send_event(PlayerEvent::Started { track_id, track, artist, new_state });
|
+ self.send_event(PlayerEvent::Started { track_id, new_state });
|
||||||
self.start_sink();
|
self.start_sink();
|
||||||
} else {
|
} else {
|
||||||
warn!("Player::play called from invalid state");
|
warn!("Player::play called from invalid state");
|
||||||
@@ -486,7 +515,8 @@ impl PlayerInternal {
|
@@ -486,7 +502,8 @@ impl PlayerInternal {
|
||||||
self.state.playing_to_paused();
|
self.state.playing_to_paused();
|
||||||
|
|
||||||
self.stop_sink_if_running();
|
self.stop_sink_if_running();
|
||||||
@ -118,7 +95,7 @@ index dd99423..365c108 100644
|
|||||||
} else {
|
} else {
|
||||||
warn!("Player::pause called from invalid state");
|
warn!("Player::pause called from invalid state");
|
||||||
}
|
}
|
||||||
@@ -497,7 +527,8 @@ impl PlayerInternal {
|
@@ -497,7 +514,8 @@ impl PlayerInternal {
|
||||||
| PlayerState::Paused { track_id, .. }
|
| PlayerState::Paused { track_id, .. }
|
||||||
| PlayerState::EndOfTrack { track_id } => {
|
| PlayerState::EndOfTrack { track_id } => {
|
||||||
self.stop_sink_if_running();
|
self.stop_sink_if_running();
|
||||||
@ -129,36 +106,34 @@ index dd99423..365c108 100644
|
|||||||
}
|
}
|
||||||
PlayerState::Stopped => {
|
PlayerState::Stopped => {
|
||||||
diff --git a/src/player_event_handler.rs b/src/player_event_handler.rs
|
diff --git a/src/player_event_handler.rs b/src/player_event_handler.rs
|
||||||
index b6a653d..f746c8f 100644
|
index b6a653d..7549e00 100644
|
||||||
--- a/src/player_event_handler.rs
|
--- a/src/player_event_handler.rs
|
||||||
+++ b/src/player_event_handler.rs
|
+++ b/src/player_event_handler.rs
|
||||||
@@ -18,18 +18,28 @@ pub fn run_program_on_events(event: PlayerEvent, onevent: &str) {
|
@@ -18,18 +18,22 @@ pub fn run_program_on_events(event: PlayerEvent, onevent: &str) {
|
||||||
PlayerEvent::Changed {
|
PlayerEvent::Changed {
|
||||||
old_track_id,
|
old_track_id,
|
||||||
new_track_id,
|
new_track_id,
|
||||||
+ track,
|
|
||||||
+ artist,
|
|
||||||
+ new_state,
|
+ new_state,
|
||||||
} => {
|
} => {
|
||||||
env_vars.insert("PLAYER_EVENT", "change".to_string());
|
env_vars.insert("PLAYER_EVENT", "change".to_string());
|
||||||
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
- env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
||||||
env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
- env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
||||||
+ env_vars.insert("TITLE", track.name.to_string());
|
+ env_vars.insert("OLD_TRACK_ID", old_track_id.to_base62());
|
||||||
+ env_vars.insert("ARTIST", artist.name.to_string());
|
+ env_vars.insert("TRACK_ID", new_track_id.to_base62());
|
||||||
+ env_vars.insert("STATE", new_state.to_string());
|
+ env_vars.insert("STATE", new_state.to_string());
|
||||||
}
|
}
|
||||||
- PlayerEvent::Started { track_id } => {
|
- PlayerEvent::Started { track_id } => {
|
||||||
+ PlayerEvent::Started { track_id, track, artist, new_state } => {
|
+ PlayerEvent::Started { track_id, new_state } => {
|
||||||
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base16());
|
- env_vars.insert("TRACK_ID", track_id.to_base16());
|
||||||
+ env_vars.insert("TITLE", track.name.to_string());
|
+ env_vars.insert("TRACK_ID", track_id.to_base62());
|
||||||
+ env_vars.insert("ARTIST", artist.name.to_string());
|
|
||||||
+ env_vars.insert("STATE", new_state.to_string());
|
+ env_vars.insert("STATE", new_state.to_string());
|
||||||
}
|
}
|
||||||
- PlayerEvent::Stopped { track_id } => {
|
- PlayerEvent::Stopped { track_id } => {
|
||||||
+ PlayerEvent::Stopped { track_id, new_state } => {
|
+ PlayerEvent::Stopped { track_id, new_state } => {
|
||||||
env_vars.insert("PLAYER_EVENT", "stop".to_string());
|
env_vars.insert("PLAYER_EVENT", "stop".to_string());
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base16());
|
- env_vars.insert("TRACK_ID", track_id.to_base16());
|
||||||
|
+ env_vars.insert("TRACK_ID", track_id.to_base62());
|
||||||
+ env_vars.insert("STATE", new_state.to_string());
|
+ env_vars.insert("STATE", new_state.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
From abcd8697b46924f8a18d733fc9d2bf884e901a46 Mon Sep 17 00:00:00 2001
|
||||||
|
From: leszekb <leszek@control-by.net>
|
||||||
|
Date: Mon, 14 May 2018 22:42:34 +0200
|
||||||
|
Subject: [PATCH] Update player_event_handler.rs
|
||||||
|
|
||||||
|
---
|
||||||
|
src/player_event_handler.rs | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/src/player_event_handler.rs b/src/player_event_handler.rs
|
||||||
|
index b6a653d..6064bc0 100644
|
||||||
|
--- a/src/player_event_handler.rs
|
||||||
|
+++ b/src/player_event_handler.rs
|
||||||
|
@@ -8,7 +8,7 @@ fn run_program(program: &str, env_vars: HashMap<&str, String>) {
|
||||||
|
Command::new(&v.remove(0))
|
||||||
|
.args(&v)
|
||||||
|
.envs(env_vars.iter())
|
||||||
|
- .spawn()
|
||||||
|
+ .status()
|
||||||
|
.expect("program failed to start");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,24 +1,22 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
import alsaaudio as alsa
|
import alsaaudio
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
dialog = xbmcgui.Dialog()
|
||||||
strings = xbmcaddon.Addon().getLocalizedString
|
strings = xbmcaddon.Addon().getLocalizedString
|
||||||
while True:
|
while True:
|
||||||
pcms = alsa.pcms()[1:]
|
pcms = alsaaudio.pcms()[1:]
|
||||||
if len(pcms) == 0:
|
if len(pcms) == 0:
|
||||||
dialog.ok(xbmcaddon.Addon().getAddonInfo('name'), strings(30210))
|
dialog.ok(xbmcaddon.Addon().getAddonInfo('name'), strings(30210))
|
||||||
break
|
break
|
||||||
pcmx = dialog.select(strings(30115), pcms)
|
pcmx = dialog.select(strings(30115), pcms)
|
||||||
if pcmx == -1:
|
if pcmx == -1:
|
||||||
break
|
break
|
||||||
pcm = pcms[pcmx]
|
pcm = pcms[pcmx]
|
||||||
xbmcaddon.Addon().setSetting('ls_o', pcm)
|
xbmcaddon.Addon().setSetting('ls_o', pcm)
|
||||||
break
|
break
|
||||||
del dialog
|
del dialog
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo -e "$STATE\n$ARTIST\n$TITLE" > "$LS_FIFO"
|
echo -e "$STATE\n$TRACK_ID" > "$LS_FIFO"
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
f="/storage/.kodi/userdata/addon_data/service.librespot/settings.xml"
|
||||||
|
[ -f "$f" ] && sed -i 's/ls_O/ls_m/g' "$f"
|
||||||
|
|
||||||
activate_card() {
|
activate_card() {
|
||||||
if [ -e "/proc/asound/$1" ]; then
|
if [ -e "/proc/asound/$1" ]; then
|
||||||
return
|
return
|
||||||
@ -50,7 +53,7 @@ init_alsa() {
|
|||||||
index="${index%%,*}"
|
index="${index%%,*}"
|
||||||
card="card$index"
|
card="card$index"
|
||||||
activate_card "$card"
|
activate_card "$card"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if [ -n "$ls_o" ]; then
|
if [ -n "$ls_o" ]; then
|
||||||
echo "Unknown playback device specification $ls_o"
|
echo "Unknown playback device specification $ls_o"
|
||||||
@ -85,7 +88,7 @@ if [ -n "$ls_p" -a -n "$ls_u" ]; then
|
|||||||
--username \"$ls_u\""
|
--username \"$ls_u\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ls_O" == "Kodi" ]; then
|
if [ "$ls_m" == "Kodi" ]; then
|
||||||
LIBRESPOT="$LIBRESPOT --backend pulseaudio --device-type TV"
|
LIBRESPOT="$LIBRESPOT --backend pulseaudio --device-type TV"
|
||||||
else
|
else
|
||||||
init_alsa
|
init_alsa
|
||||||
|
@ -2,125 +2,12 @@
|
|||||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import stat
|
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
import xbmc
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmcgui
|
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'resources', 'lib'))
|
||||||
|
|
||||||
PORT = '6666'
|
from ls_monitor import Monitor as Monitor
|
||||||
SINK = 'librespot_sink'
|
|
||||||
|
|
||||||
def suspendSink(bit):
|
|
||||||
subprocess.call(['pactl', 'suspend-sink', SINK, bit])
|
|
||||||
|
|
||||||
def systemctl(command):
|
|
||||||
subprocess.call(['systemctl', command, xbmcaddon.Addon().getAddonInfo('id')])
|
|
||||||
|
|
||||||
|
|
||||||
class Controller(threading.Thread):
|
|
||||||
|
|
||||||
FIFO = '/var/run/librespot'
|
|
||||||
|
|
||||||
def __init__(self, player):
|
|
||||||
super(Controller, self).__init__()
|
|
||||||
self.player = player
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
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()
|
|
||||||
elif command[0] == 'pause':
|
|
||||||
self.player.pause()
|
|
||||||
try:
|
|
||||||
os.unlink(self.FIFO)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
with open(self.FIFO, 'w') as fifo:
|
|
||||||
fifo.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Player(xbmc.Player):
|
|
||||||
|
|
||||||
ITEM = 'rtp://127.0.0.1:{port}'.format(port=PORT)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(Player, self).__init__(self)
|
|
||||||
if self.isPlaying():
|
|
||||||
self.onPlayBackStarted()
|
|
||||||
|
|
||||||
def onPlayBackEnded(self):
|
|
||||||
suspendSink('1')
|
|
||||||
xbmc.sleep(1000)
|
|
||||||
if not self.isPlaying():
|
|
||||||
systemctl('restart')
|
|
||||||
|
|
||||||
def onPlayBackStarted(self):
|
|
||||||
if self.getPlayingFile() != self.ITEM:
|
|
||||||
suspendSink('1')
|
|
||||||
systemctl('stop')
|
|
||||||
|
|
||||||
def onPlayBackStopped(self):
|
|
||||||
systemctl('restart')
|
|
||||||
|
|
||||||
def play(self):
|
|
||||||
if not self.isPlaying() and xbmcaddon.Addon().getSetting('ls_O') == 'Kodi':
|
|
||||||
suspendSink('0')
|
|
||||||
listitem = xbmcgui.ListItem(xbmcaddon.Addon().getAddonInfo('name'))
|
|
||||||
listitem.addStreamInfo('audio',{'codec': 'mp3'})
|
|
||||||
listitem.setArt({'thumb': xbmcaddon.Addon().getAddonInfo('icon')})
|
|
||||||
super(Player, self).play(self.ITEM, listitem)
|
|
||||||
del listitem
|
|
||||||
xbmcgui.Window(12006).show()
|
|
||||||
|
|
||||||
def pause(self):
|
|
||||||
if self.isPlaying() and self.getPlayingFile() == self.ITEM:
|
|
||||||
super(Player, self).pause()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
suspendSink('1')
|
|
||||||
if self.isPlaying() and self.getPlayingFile() == self.ITEM:
|
|
||||||
super(Player, self).stop()
|
|
||||||
else:
|
|
||||||
systemctl('restart')
|
|
||||||
|
|
||||||
|
|
||||||
class Monitor(xbmc.Monitor):
|
|
||||||
|
|
||||||
def __init__(self, player):
|
|
||||||
super(Monitor, self).__init__(self)
|
|
||||||
self.player = player
|
|
||||||
|
|
||||||
def onSettingsChanged(self):
|
|
||||||
self.player.stop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
player = Player()
|
Monitor().waitForAbort()
|
||||||
controller = Controller(player)
|
|
||||||
controller.start()
|
|
||||||
Monitor(player).waitForAbort()
|
|
||||||
controller.stop()
|
|
||||||
controller.join()
|
|
||||||
|
@ -28,7 +28,7 @@ msgid "320"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30107"
|
msgctxt "#30107"
|
||||||
msgid "Output"
|
msgid "Output mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30108"
|
msgctxt "#30108"
|
||||||
@ -48,7 +48,7 @@ msgid "Password"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30112"
|
msgctxt "#30112"
|
||||||
msgid "Discovery mode (set username and password to disable)"
|
msgid "Discovery mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30113"
|
msgctxt "#30113"
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
from ls_log import log as log
|
||||||
|
|
||||||
|
|
||||||
|
_SERVICE = xbmcaddon.Addon().getAddonInfo('id')
|
||||||
|
_SINK = 'librespot_sink'
|
||||||
|
|
||||||
|
|
||||||
|
def _pactl(bit):
|
||||||
|
log('pactl {}'.format(bit))
|
||||||
|
subprocess.call(['pactl', 'suspend-sink', _SINK, bit])
|
||||||
|
|
||||||
|
|
||||||
|
def _systemctl(command):
|
||||||
|
log('systemctl {}'.format(command))
|
||||||
|
subprocess.call(['systemctl', command, _SERVICE])
|
||||||
|
_pactl('1')
|
||||||
|
|
||||||
|
|
||||||
|
class Librespot():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.state = True
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
log('restarting librespot')
|
||||||
|
_systemctl('restart')
|
||||||
|
self.state = True
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.state:
|
||||||
|
log('stopping librespot')
|
||||||
|
_systemctl('stop')
|
||||||
|
self.state = False
|
||||||
|
|
||||||
|
def unsuspend(self):
|
||||||
|
_pactl('0')
|
@ -0,0 +1,12 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
# Copyright (C) 2018-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
|
||||||
|
|
||||||
|
_MESSAGE = xbmcaddon.Addon().getAddonInfo('name') + ': {}'
|
||||||
|
|
||||||
|
|
||||||
|
def log(message):
|
||||||
|
xbmc.log(_MESSAGE.format(message), xbmc.LOGNOTICE)
|
@ -0,0 +1,22 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
|
from ls_log import log as log
|
||||||
|
from ls_player import Player as Player
|
||||||
|
|
||||||
|
|
||||||
|
class Monitor(xbmc.Monitor):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
log('monitor started')
|
||||||
|
self.player = Player()
|
||||||
|
|
||||||
|
def onSettingsChanged(self):
|
||||||
|
self.player.onSettingsChanged()
|
||||||
|
|
||||||
|
def waitForAbort(self):
|
||||||
|
super(Monitor, self).waitForAbort()
|
||||||
|
self.player.onAbortRequested()
|
||||||
|
log('monitor stopped')
|
@ -0,0 +1,134 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import threading
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
import xbmcgui
|
||||||
|
|
||||||
|
from ls_librespot import Librespot as Librespot
|
||||||
|
from ls_log import log as log
|
||||||
|
from ls_spotify import Spotify as Spotify
|
||||||
|
|
||||||
|
_CODEC = 'pcm_s16le'
|
||||||
|
_FIFO = '/var/run/librespot'
|
||||||
|
_STREAM = 'rtp://127.0.0.1:6666'
|
||||||
|
|
||||||
|
_DEFAULT_ICON = xbmcaddon.Addon().getAddonInfo('icon')
|
||||||
|
_DEFAULT_TITLE = xbmcaddon.Addon().getAddonInfo('name')
|
||||||
|
|
||||||
|
|
||||||
|
class Player(threading.Thread, xbmc.Player):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Player, self).__init__()
|
||||||
|
self.updateSettings()
|
||||||
|
self.dialog = xbmcgui.Dialog()
|
||||||
|
self.librespot = Librespot()
|
||||||
|
self.listitem = xbmcgui.ListItem()
|
||||||
|
self.listitem.addStreamInfo('audio', {'codec': _CODEC})
|
||||||
|
self.listitem.setPath(_STREAM)
|
||||||
|
self.spotify = Spotify()
|
||||||
|
self.start()
|
||||||
|
if self.isPlaying():
|
||||||
|
self.onPlayBackStarted()
|
||||||
|
|
||||||
|
def onAbortRequested(self):
|
||||||
|
log('abort requested')
|
||||||
|
with open(_FIFO, 'w') as fifo:
|
||||||
|
fifo.close()
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def onPlayBackEnded(self):
|
||||||
|
log('a playback ended')
|
||||||
|
self.librespot.restart()
|
||||||
|
|
||||||
|
def onPlayBackStarted(self):
|
||||||
|
log('a playback started')
|
||||||
|
if self.getPlayingFile() != _STREAM:
|
||||||
|
self.librespot.stop()
|
||||||
|
|
||||||
|
def onPlayBackStopped(self):
|
||||||
|
log('a playback stopped')
|
||||||
|
self.librespot.restart()
|
||||||
|
|
||||||
|
def onSettingsChanged(self):
|
||||||
|
log('settings changed')
|
||||||
|
self.stop()
|
||||||
|
self.updateSettings()
|
||||||
|
|
||||||
|
def pause(self):
|
||||||
|
if self.isPlaying() and self.getPlayingFile() == _STREAM:
|
||||||
|
log('pausing librespot playback')
|
||||||
|
super(Player, self).pause()
|
||||||
|
|
||||||
|
def play(self, track_id):
|
||||||
|
track = self.spotify.getTrack(track_id)
|
||||||
|
if track['thumb'] == '':
|
||||||
|
track['thumb'] = _DEFAULT_ICON
|
||||||
|
if track['title'] == '':
|
||||||
|
track['title'] = _DEFAULT_TITLE
|
||||||
|
if self.kodi:
|
||||||
|
self.listitem.setArt({'thumb': track['thumb']})
|
||||||
|
self.listitem.setInfo(
|
||||||
|
'music',
|
||||||
|
{
|
||||||
|
'album': track['album'],
|
||||||
|
'artist': track['artist'],
|
||||||
|
'title': track['title']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if self.isPlaying() and self.getPlayingFile() == _STREAM:
|
||||||
|
log('updating librespot playback')
|
||||||
|
self.updateInfoTag(self.listitem)
|
||||||
|
else:
|
||||||
|
self.librespot.unsuspend()
|
||||||
|
log('starting librespot playback')
|
||||||
|
super(Player, self).play(_STREAM, self.listitem)
|
||||||
|
else:
|
||||||
|
self.dialog.notification(
|
||||||
|
track['title'],
|
||||||
|
track['artist'],
|
||||||
|
icon=track['thumb'],
|
||||||
|
sound=False)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
log('named pipe started')
|
||||||
|
try:
|
||||||
|
os.unlink(_FIFO)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
os.mkfifo(_FIFO)
|
||||||
|
while (os.path.exists(_FIFO) and
|
||||||
|
stat.S_ISFIFO(os.stat(_FIFO).st_mode)):
|
||||||
|
with open(_FIFO, 'r') as fifo:
|
||||||
|
command = fifo.read().splitlines()
|
||||||
|
log('named pipe received {}'.format(str(command)))
|
||||||
|
if len(command) == 0:
|
||||||
|
break
|
||||||
|
elif command[0] == 'play':
|
||||||
|
self.play(command[1])
|
||||||
|
elif command[0] == 'stop':
|
||||||
|
self.stop()
|
||||||
|
elif command[0] == 'pause':
|
||||||
|
self.pause()
|
||||||
|
try:
|
||||||
|
os.unlink(_FIFO)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
log('named pipe stopped')
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.isPlaying():
|
||||||
|
if self.getPlayingFile() == _STREAM:
|
||||||
|
log('stopping librespot playback')
|
||||||
|
super(Player, self).stop()
|
||||||
|
else:
|
||||||
|
self.librespot.stop()
|
||||||
|
else:
|
||||||
|
self.librespot.restart()
|
||||||
|
|
||||||
|
def updateSettings(self):
|
||||||
|
self.kodi = (xbmcaddon.Addon().getSetting('ls_m') == 'Kodi')
|
@ -0,0 +1,63 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
# Copyright (C) 2018-present Team LibreELEC (https://libreelec.tv)
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
from ls_log import log as log
|
||||||
|
|
||||||
|
_CLIENT_ID = '169df5532dee47a59913f8528e83ae71'
|
||||||
|
_CLIENT_SECRET = '1f3d8b507bbe4f68beb3a4472e8ad411'
|
||||||
|
|
||||||
|
|
||||||
|
def _get(default, tree, *indices):
|
||||||
|
try:
|
||||||
|
for index in indices:
|
||||||
|
tree = tree[index]
|
||||||
|
except LookupError:
|
||||||
|
tree = default
|
||||||
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
class Spotify():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.headers = None
|
||||||
|
self.expiration = time.time()
|
||||||
|
self.request = [
|
||||||
|
'https://accounts.spotify.com/api/token',
|
||||||
|
urllib.urlencode({'grant_type': 'client_credentials'}),
|
||||||
|
{'Authorization': 'Basic {}'.format(base64.b64encode(
|
||||||
|
'{}:{}'.format(_CLIENT_ID, _CLIENT_SECRET)))}
|
||||||
|
]
|
||||||
|
|
||||||
|
def getTrack(self, track_id):
|
||||||
|
try:
|
||||||
|
if time.time() > self.expiration:
|
||||||
|
log('token expired')
|
||||||
|
token = json.loads(urllib2.urlopen(
|
||||||
|
urllib2.Request(*self.request)).read())
|
||||||
|
log('token {} expires in {} seconds'.format(
|
||||||
|
token['access_token'], token['expires_in']))
|
||||||
|
self.expiration = time.time() + float(token['expires_in']) - 60
|
||||||
|
self.headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer {}'.format(token['access_token'])
|
||||||
|
}
|
||||||
|
track = json.loads(urllib2.urlopen(urllib2.Request(
|
||||||
|
'https://api.spotify.com/v1/tracks/{}'.format(track_id), None,
|
||||||
|
self.headers)).read())
|
||||||
|
except Exception as e:
|
||||||
|
log('failed to get track {} from Spotify: {}'.format(e))
|
||||||
|
track = dict()
|
||||||
|
return {
|
||||||
|
'album': _get('', track, 'album', 'name'),
|
||||||
|
'artist': _get('', track, 'artists', 0, 'name'),
|
||||||
|
'duration': _get(0, track, 'duration_ms') / 1000,
|
||||||
|
'thumb': _get('', track, 'album', 'images', 0, 'url'),
|
||||||
|
'title': _get('', track, 'name')
|
||||||
|
}
|
@ -1,17 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings>
|
||||||
<category label="30100" >
|
<setting id="ls_m" label="30107" type="labelenum" lvalues="30108|30109" />
|
||||||
<setting label="30102" type="labelenum" id="ls_b" lvalues="30103|30104|30105|30106" />
|
<setting label="30114" type="action" subsetting="true" visible="eq(-1,0)" action="RunAddon(service.librespot)" />
|
||||||
<setting label="30107" type="labelenum" id="ls_O" lvalues="30108|30109" />
|
<setting id="ls_o" label="30115" type="text" subsetting="true" visible="eq(-2,0)" default="" />
|
||||||
<setting label="30110" type="text" id="ls_u" />
|
<setting id="pcm_3" label="30116" type="enum" subsetting="true" visible="eq(-3,0)" lvalues="30117|30118|30119" enable="eq(-1,default:CARD=ALSA)|eq(-1,sysdefault:CARD=ALSA)" />
|
||||||
<setting label="30111" type="text" id="ls_p" option="hidden" visible="!eq(-1,)" />
|
<setting id="ls_b" label="30102" type="labelenum" lvalues="30103|30104|30105|30106" />
|
||||||
<setting label="30112" type="bool" id="ls_d" default="true" enable="false" visible="eq(-1,)|eq(-2,)" />
|
<setting id="ls_a" label="30112" type="bool" default="false" />
|
||||||
<setting label="30112" type="bool" id="ls_D" default="false" enable="false" visible="!eq(-2,)+!eq(-3,)" />
|
<setting id="ls_u" label="30110" type="text" subsetting="true" visible="eq(-1,true)" default="" />
|
||||||
</category>
|
<setting id="ls_p" label="30111" type="text" subsetting="true" visible="eq(-2,true)" default="" />
|
||||||
<category label="30113" >
|
|
||||||
<setting label="30107" type="labelenum" id="ls_O" lvalues="30108|30109" visible="false" />
|
|
||||||
<setting label="30114" type="action" action="RunAddon(service.librespot)" enable="eq(-1,0)" />
|
|
||||||
<setting label="30115" type="text" id="ls_o" default="" enable="eq(-2,0)" />
|
|
||||||
<setting label="30116" type="enum" id="pcm_3" lvalues="30117|30118|30119" enable="eq(-3,0)" visible="eq(-1,default:CARD=ALSA)|eq(-1,sysdefault:CARD=ALSA)" />
|
|
||||||
</category>
|
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<settings>
|
<settings version="2">
|
||||||
<setting id="ls_D" value="false" />
|
<setting id="ls_a" default="true">false</setting>
|
||||||
<setting id="ls_O" value="ALSA" />
|
<setting id="ls_b">320</setting>
|
||||||
<setting id="ls_b" value="320" />
|
<setting id="ls_m">ALSA</setting>
|
||||||
<setting id="ls_d" value="true" />
|
<setting id="ls_o" default="true"></setting>
|
||||||
<setting id="ls_o" value="" />
|
<setting id="ls_p" default="true"></setting>
|
||||||
<setting id="ls_p" value="" />
|
<setting id="ls_u" default="true"></setting>
|
||||||
<setting id="ls_u" value="" />
|
<setting id="pcm_3" default="true">0</setting>
|
||||||
<setting id="pcm_3" value="0" />
|
|
||||||
</settings>
|
</settings>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user