diff --git a/packages/addons/addon-depends/librespot-depends/rust/package.mk b/packages/addons/addon-depends/librespot-depends/rust/package.mk index ebe11a889c..ba88b82ccc 100644 --- a/packages/addons/addon-depends/librespot-depends/rust/package.mk +++ b/packages/addons/addon-depends/librespot-depends/rust/package.mk @@ -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" diff --git a/packages/addons/service/librespot/changelog.txt b/packages/addons/service/librespot/changelog.txt index 66f706c298..da06063d81 100644 --- a/packages/addons/service/librespot/changelog.txt +++ b/packages/addons/service/librespot/changelog.txt @@ -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 diff --git a/packages/addons/service/librespot/package.mk b/packages/addons/service/librespot/package.mk index 6a16df51e4..4af0e4ba39 100644 --- a/packages/addons/service/librespot/package.mk +++ b/packages/addons/service/librespot/package.mk @@ -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/" diff --git a/packages/addons/service/librespot/patches/librespot-02_kodi_hooks.patch b/packages/addons/service/librespot/patches/librespot-02_kodi_hooks.patch index f66f163499..3e57b4b7e1 100644 --- a/packages/addons/service/librespot/patches/librespot-02_kodi_hooks.patch +++ b/packages/addons/service/librespot/patches/librespot-02_kodi_hooks.patch @@ -1,30 +1,23 @@ +commit 55439529ae313eac5d946aa751387fa747cc6bc4 +Author: awiouy +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()); } } diff --git a/packages/addons/service/librespot/patches/librespot-04_status_instead_of_spawn.patch b/packages/addons/service/librespot/patches/librespot-04_status_instead_of_spawn.patch new file mode 100644 index 0000000000..1469a1f59f --- /dev/null +++ b/packages/addons/service/librespot/patches/librespot-04_status_instead_of_spawn.patch @@ -0,0 +1,23 @@ +From abcd8697b46924f8a18d733fc9d2bf884e901a46 Mon Sep 17 00:00:00 2001 +From: leszekb +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"); + } + + diff --git a/packages/addons/service/librespot/source/addon.py b/packages/addons/service/librespot/source/addon.py index fcd3cdf7d0..4c75451ca6 100644 --- a/packages/addons/service/librespot/source/addon.py +++ b/packages/addons/service/librespot/source/addon.py @@ -1,24 +1,22 @@ # 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 -dialog = xbmcgui.Dialog() -strings = xbmcaddon.Addon().getLocalizedString +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(30115), pcms) - if pcmx == -1: - break - pcm = pcms[pcmx] - xbmcaddon.Addon().setSetting('ls_o', pcm) - break + pcms = alsaaudio.pcms()[1:] + if len(pcms) == 0: + dialog.ok(xbmcaddon.Addon().getAddonInfo('name'), strings(30210)) + break + pcmx = dialog.select(strings(30115), pcms) + if pcmx == -1: + break + pcm = pcms[pcmx] + xbmcaddon.Addon().setSetting('ls_o', pcm) + break del dialog - - diff --git a/packages/addons/service/librespot/source/bin/librespot.onevent b/packages/addons/service/librespot/source/bin/librespot.onevent index 8966b5d6bc..d528c6f537 100755 --- a/packages/addons/service/librespot/source/bin/librespot.onevent +++ b/packages/addons/service/librespot/source/bin/librespot.onevent @@ -1,2 +1,2 @@ #!/bin/sh -echo -e "$STATE\n$ARTIST\n$TITLE" > "$LS_FIFO" \ No newline at end of file +echo -e "$STATE\n$TRACK_ID" > "$LS_FIFO" diff --git a/packages/addons/service/librespot/source/bin/librespot.start b/packages/addons/service/librespot/source/bin/librespot.start index 4b9e501316..df06cd5593 100755 --- a/packages/addons/service/librespot/source/bin/librespot.start +++ b/packages/addons/service/librespot/source/bin/librespot.start @@ -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 @@ -50,7 +53,7 @@ init_alsa() { index="${index%%,*}" card="card$index" activate_card "$card" - ;; + ;; *) if [ -n "$ls_o" ]; then echo "Unknown playback device specification $ls_o" @@ -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 diff --git a/packages/addons/service/librespot/source/default.py b/packages/addons/service/librespot/source/default.py index f17caff830..0c2f7e2d8c 100644 --- a/packages/addons/service/librespot/source/default.py +++ b/packages/addons/service/librespot/source/default.py @@ -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() diff --git a/packages/addons/service/librespot/source/resources/language/English/strings.po b/packages/addons/service/librespot/source/resources/language/English/strings.po index 7d99755b11..17bd1db8e6 100644 --- a/packages/addons/service/librespot/source/resources/language/English/strings.po +++ b/packages/addons/service/librespot/source/resources/language/English/strings.po @@ -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" diff --git a/packages/addons/service/librespot/source/resources/lib/ls_librespot.py b/packages/addons/service/librespot/source/resources/lib/ls_librespot.py new file mode 100644 index 0000000000..3a803bd6c1 --- /dev/null +++ b/packages/addons/service/librespot/source/resources/lib/ls_librespot.py @@ -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') diff --git a/packages/addons/service/librespot/source/resources/lib/ls_log.py b/packages/addons/service/librespot/source/resources/lib/ls_log.py new file mode 100644 index 0000000000..abf51f1ab4 --- /dev/null +++ b/packages/addons/service/librespot/source/resources/lib/ls_log.py @@ -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) diff --git a/packages/addons/service/librespot/source/resources/lib/ls_monitor.py b/packages/addons/service/librespot/source/resources/lib/ls_monitor.py new file mode 100644 index 0000000000..664843ebdd --- /dev/null +++ b/packages/addons/service/librespot/source/resources/lib/ls_monitor.py @@ -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') diff --git a/packages/addons/service/librespot/source/resources/lib/ls_player.py b/packages/addons/service/librespot/source/resources/lib/ls_player.py new file mode 100644 index 0000000000..834e9e3647 --- /dev/null +++ b/packages/addons/service/librespot/source/resources/lib/ls_player.py @@ -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') diff --git a/packages/addons/service/librespot/source/resources/lib/ls_spotify.py b/packages/addons/service/librespot/source/resources/lib/ls_spotify.py new file mode 100644 index 0000000000..7660f7dbc6 --- /dev/null +++ b/packages/addons/service/librespot/source/resources/lib/ls_spotify.py @@ -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') + } diff --git a/packages/addons/service/librespot/source/resources/settings.xml b/packages/addons/service/librespot/source/resources/settings.xml index 6c6984bb7e..d2fcf1766d 100644 --- a/packages/addons/service/librespot/source/resources/settings.xml +++ b/packages/addons/service/librespot/source/resources/settings.xml @@ -1,17 +1,11 @@ - - - - - - - - - - - - - - + + + + + + + + diff --git a/packages/addons/service/librespot/source/settings-default.xml b/packages/addons/service/librespot/source/settings-default.xml index 3f68d80531..8caeebcf80 100644 --- a/packages/addons/service/librespot/source/settings-default.xml +++ b/packages/addons/service/librespot/source/settings-default.xml @@ -1,10 +1,9 @@ - - - - - - - - - + + false + 320 + ALSA + + + + 0