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)
|
||||
|
||||
PKG_NAME="rust"
|
||||
PKG_VERSION="1.26.0"
|
||||
PKG_VERSION="1.28.0"
|
||||
PKG_ARCH="any"
|
||||
PKG_LICENSE="MIT"
|
||||
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
|
||||
- Update to 431be9e
|
||||
- Fix delay with Kodi playback option
|
||||
|
@ -3,9 +3,9 @@
|
||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||
|
||||
PKG_NAME="librespot"
|
||||
PKG_VERSION="431be9e"
|
||||
PKG_SHA256="2e336c5415b6ee6f669e673282daccdd770b15d35dd6d71b39b17dc2aa3424c0"
|
||||
PKG_REV="111"
|
||||
PKG_VERSION="a4e0f582a8c705b05c8abba58d9e9c1c06ad532d"
|
||||
PKG_SHA256="63ed879d7185f16963316b0c3149a40875260f5403b2c55c6cdb470e91b7741d"
|
||||
PKG_REV="112"
|
||||
PKG_ARCH="any"
|
||||
PKG_LICENSE="MIT"
|
||||
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
|
||||
index dd99423..365c108 100644
|
||||
index ab1a8ab..0aa0630 100644
|
||||
--- a/playback/src/player.rs
|
||||
+++ b/playback/src/player.rs
|
||||
@@ -17,7 +17,7 @@ use core::spotify_id::SpotifyId;
|
||||
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 {
|
||||
@@ -49,15 +49,18 @@ enum PlayerCommand {
|
||||
pub enum PlayerEvent {
|
||||
Started {
|
||||
track_id: SpotifyId,
|
||||
+ track: Track,
|
||||
+ artist: Artist,
|
||||
+ new_state: String,
|
||||
},
|
||||
|
||||
Changed {
|
||||
old_track_id: SpotifyId,
|
||||
new_track_id: SpotifyId,
|
||||
+ track: Track,
|
||||
+ artist: Artist,
|
||||
+ new_state: String,
|
||||
},
|
||||
|
||||
@ -34,17 +27,7 @@ index dd99423..365c108 100644
|
||||
},
|
||||
}
|
||||
|
||||
@@ -404,6 +411,9 @@ 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 {
|
||||
@@ -413,11 +416,18 @@ impl PlayerInternal {
|
||||
| PlayerState::EndOfTrack {
|
||||
track_id: old_track_id,
|
||||
..
|
||||
@ -58,19 +41,17 @@ index dd99423..365c108 100644
|
||||
+ self.send_event(PlayerEvent::Changed {
|
||||
+ old_track_id: old_track_id,
|
||||
+ new_track_id: track_id,
|
||||
+ track: track,
|
||||
+ artist: artist,
|
||||
+ new_state: new_state,
|
||||
+ });
|
||||
+ },
|
||||
+ _ => {
|
||||
+ 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();
|
||||
@@ -443,13 +462,20 @@ impl PlayerInternal {
|
||||
@@ -443,13 +453,18 @@ impl PlayerInternal {
|
||||
| PlayerState::EndOfTrack {
|
||||
track_id: old_track_id,
|
||||
..
|
||||
@ -83,8 +64,6 @@ index dd99423..365c108 100644
|
||||
+ self.send_event(PlayerEvent::Changed {
|
||||
+ old_track_id: old_track_id,
|
||||
+ new_track_id: track_id,
|
||||
+ track: track,
|
||||
+ artist: artist,
|
||||
+ 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 {
|
||||
self.state.paused_to_playing();
|
||||
|
||||
- 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();
|
||||
+ self.send_event(PlayerEvent::Started { track_id, track, artist, new_state });
|
||||
+ self.send_event(PlayerEvent::Started { track_id, new_state });
|
||||
self.start_sink();
|
||||
} else {
|
||||
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.stop_sink_if_running();
|
||||
@ -118,7 +95,7 @@ index dd99423..365c108 100644
|
||||
} else {
|
||||
warn!("Player::pause called from invalid state");
|
||||
}
|
||||
@@ -497,7 +527,8 @@ impl PlayerInternal {
|
||||
@@ -497,7 +514,8 @@ impl PlayerInternal {
|
||||
| PlayerState::Paused { track_id, .. }
|
||||
| PlayerState::EndOfTrack { track_id } => {
|
||||
self.stop_sink_if_running();
|
||||
@ -129,36 +106,34 @@ index dd99423..365c108 100644
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
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
|
||||
+++ 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 {
|
||||
old_track_id,
|
||||
new_track_id,
|
||||
+ track,
|
||||
+ artist,
|
||||
+ new_state,
|
||||
} => {
|
||||
env_vars.insert("PLAYER_EVENT", "change".to_string());
|
||||
env_vars.insert("OLD_TRACK_ID", old_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("ARTIST", artist.name.to_string());
|
||||
- env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
||||
- env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
||||
+ env_vars.insert("OLD_TRACK_ID", old_track_id.to_base62());
|
||||
+ env_vars.insert("TRACK_ID", new_track_id.to_base62());
|
||||
+ env_vars.insert("STATE", new_state.to_string());
|
||||
}
|
||||
- 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("TRACK_ID", track_id.to_base16());
|
||||
+ env_vars.insert("TITLE", track.name.to_string());
|
||||
+ env_vars.insert("ARTIST", artist.name.to_string());
|
||||
- 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());
|
||||
}
|
||||
- PlayerEvent::Stopped { track_id } => {
|
||||
+ PlayerEvent::Stopped { track_id, new_state } => {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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,7 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||
|
||||
import alsaaudio as alsa
|
||||
import alsaaudio
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
@ -9,7 +9,7 @@ import xbmcgui
|
||||
dialog = xbmcgui.Dialog()
|
||||
strings = xbmcaddon.Addon().getLocalizedString
|
||||
while True:
|
||||
pcms = alsa.pcms()[1:]
|
||||
pcms = alsaaudio.pcms()[1:]
|
||||
if len(pcms) == 0:
|
||||
dialog.ok(xbmcaddon.Addon().getAddonInfo('name'), strings(30210))
|
||||
break
|
||||
@ -20,5 +20,3 @@ while True:
|
||||
xbmcaddon.Addon().setSetting('ls_o', pcm)
|
||||
break
|
||||
del dialog
|
||||
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
#!/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
|
||||
# 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() {
|
||||
if [ -e "/proc/asound/$1" ]; then
|
||||
return
|
||||
@ -85,7 +88,7 @@ if [ -n "$ls_p" -a -n "$ls_u" ]; then
|
||||
--username \"$ls_u\""
|
||||
fi
|
||||
|
||||
if [ "$ls_O" == "Kodi" ]; then
|
||||
if [ "$ls_m" == "Kodi" ]; then
|
||||
LIBRESPOT="$LIBRESPOT --backend pulseaudio --device-type TV"
|
||||
else
|
||||
init_alsa
|
||||
|
@ -2,125 +2,12 @@
|
||||
# Copyright (C) 2017-present Team LibreELEC (https://libreelec.tv)
|
||||
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
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'
|
||||
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()
|
||||
from ls_monitor import Monitor as Monitor
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
player = Player()
|
||||
controller = Controller(player)
|
||||
controller.start()
|
||||
Monitor(player).waitForAbort()
|
||||
controller.stop()
|
||||
controller.join()
|
||||
Monitor().waitForAbort()
|
||||
|
@ -28,7 +28,7 @@ msgid "320"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30107"
|
||||
msgid "Output"
|
||||
msgid "Output mode"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30108"
|
||||
@ -48,7 +48,7 @@ msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30112"
|
||||
msgid "Discovery mode (set username and password to disable)"
|
||||
msgid "Discovery mode"
|
||||
msgstr ""
|
||||
|
||||
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"?>
|
||||
<settings>
|
||||
<category label="30100" >
|
||||
<setting label="30102" type="labelenum" id="ls_b" lvalues="30103|30104|30105|30106" />
|
||||
<setting label="30107" type="labelenum" id="ls_O" lvalues="30108|30109" />
|
||||
<setting label="30110" type="text" id="ls_u" />
|
||||
<setting label="30111" type="text" id="ls_p" option="hidden" visible="!eq(-1,)" />
|
||||
<setting label="30112" type="bool" id="ls_d" default="true" enable="false" visible="eq(-1,)|eq(-2,)" />
|
||||
<setting label="30112" type="bool" id="ls_D" default="false" enable="false" visible="!eq(-2,)+!eq(-3,)" />
|
||||
</category>
|
||||
<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>
|
||||
<setting id="ls_m" label="30107" type="labelenum" lvalues="30108|30109" />
|
||||
<setting label="30114" type="action" subsetting="true" visible="eq(-1,0)" action="RunAddon(service.librespot)" />
|
||||
<setting id="ls_o" label="30115" type="text" subsetting="true" visible="eq(-2,0)" default="" />
|
||||
<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 id="ls_b" label="30102" type="labelenum" lvalues="30103|30104|30105|30106" />
|
||||
<setting id="ls_a" label="30112" type="bool" default="false" />
|
||||
<setting id="ls_u" label="30110" type="text" subsetting="true" visible="eq(-1,true)" default="" />
|
||||
<setting id="ls_p" label="30111" type="text" subsetting="true" visible="eq(-2,true)" default="" />
|
||||
</settings>
|
||||
|
@ -1,10 +1,9 @@
|
||||
<settings>
|
||||
<setting id="ls_D" value="false" />
|
||||
<setting id="ls_O" value="ALSA" />
|
||||
<setting id="ls_b" value="320" />
|
||||
<setting id="ls_d" value="true" />
|
||||
<setting id="ls_o" value="" />
|
||||
<setting id="ls_p" value="" />
|
||||
<setting id="ls_u" value="" />
|
||||
<setting id="pcm_3" value="0" />
|
||||
<settings version="2">
|
||||
<setting id="ls_a" default="true">false</setting>
|
||||
<setting id="ls_b">320</setting>
|
||||
<setting id="ls_m">ALSA</setting>
|
||||
<setting id="ls_o" default="true"></setting>
|
||||
<setting id="ls_p" default="true"></setting>
|
||||
<setting id="ls_u" default="true"></setting>
|
||||
<setting id="pcm_3" default="true">0</setting>
|
||||
</settings>
|
||||
|
Loading…
x
Reference in New Issue
Block a user