Skip to content

Commit

Permalink
Clean code (local playback only) + improve lms event handling in play…
Browse files Browse the repository at this point in the history
…er_monitor
  • Loading branch information
jbkaiser committed Apr 11, 2020
1 parent f249c0a commit 9197d46
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 170 deletions.
4 changes: 4 additions & 0 deletions resources/language/English/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,8 @@ msgstr ""

msgctxt "#11067"
msgid "Are you sure you want to logoff ?"
msgstr ""

msgctxt "#11068"
msgid "Local playback is not supported on your device. Spotify Headless only supports local playback. You might want to try the official Spotify plugin instead."
msgstr ""
2 changes: 1 addition & 1 deletion resources/lib/connect_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def run(self):
line = self.__spotty_proc.stdout.readline()
if self.__spotty_proc.returncode and self.__spotty_proc.returncode > 0 and not self.__exit:
# daemon crashed ? restart ?
log_msg("spotty stopped!", xbmc.LOGNOTICE)
log_msg("spotty stopped!", xbmc.LOGWARNING)
break
xbmc.sleep(100)
self.daemon_active = False
Expand Down
1 change: 1 addition & 0 deletions resources/lib/httpproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def lms(self, filename, **kwargs):
if spotty_user != cur_user:
log_msg("user change detected")
xbmc.executebuiltin("SetProperty(spotify-cmd,__LOGOUT__,Home)")

if "start" in event:
log_msg("playback start requested by connect")
self.__connect_player.handle_lms_event_change()
Expand Down
70 changes: 41 additions & 29 deletions resources/lib/player_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ConnectPlayer(xbmc.Player):
__playlist = None
__sp = None

__handling_lms_event = False
__lms_event_stack = [] # stack of LMS events being handled

def __init__(self, **kwargs):
if ConnectPlayer.__instance != None:
Expand All @@ -44,25 +44,29 @@ def close(self):

def onPlayBackPaused(self):
'''Kodi event fired when playback is paused'''
if self.connect_playing and not self.__handling_lms_event:
log_msg("Kodi event fired: onPlayBackPaused")
if "PAUSE" in self.__lms_event_stack:
self.__lms_event_stack.remove("PAUSE")
elif self.connect_playing:
log_msg("Run Spotipy command: pause_playback")
self.__sp.pause_playback()
log_msg("Playback paused")
elif self.__handling_lms_event:
self.__handling_lms_event = False

def onPlayBackResumed(self):
'''Kodi event fired when playback is resumed after pause'''
if self.connect_playing and not self.__handling_lms_event:
log_msg("Kodi event fired: onPlayBackResumed")
if "RESUME" in self.__lms_event_stack:
self.__lms_event_stack.remove("RESUME")
elif self.connect_playing:
log_msg("Run Spotipy command: start_playback")
self.__sp.start_playback()
log_msg("Playback resumed")
elif self.__handling_lms_event:
self.__handling_lms_event = False

def onPlayBackEnded(self):
pass
'''Kodi event fired when playback is ended, eg. at the end of current track'''
log_msg("Kodi event fired: onPlayBackEnded")

def onPlayBackStarted(self):
'''Kodi event fired when playback is started (including next tracks)'''
log_msg("Kodi event fired: onPlayBackStarted")
filename = ""
if self.isPlaying():
filename = self.getPlayingFile()
Expand All @@ -72,52 +76,59 @@ def onPlayBackStarted(self):
# we started playback with (remote) connect player
log_msg("Playback started of Spotify Connect stream")
self.connect_playing = True
if "nexttrack" in filename and not self.__handling_lms_event:
# next track requested for kodi player
self.__sp.next_track()
elif self.__handling_lms_event:
self.__handling_lms_event = False
elif "nexttrack" in filename:
if not "NEXTTRACK" in self.__lms_event_stack:
log_msg("Run Spotipy command: next_track")
self.__sp.next_track()
self.__lms_event_stack.append("NEXTTRACK")

def onPlayBackSpeedChanged(self, speed):
'''Kodi event fired when player is fast forwarding/rewinding'''
pass
log_msg("Kodi event fired: onPlayBackSpeedChanged")

def onPlayBackSeek(self, seekTime, seekOffset):
'''Kodi event fired when the user is seeking'''
log_msg("Kodi event fired: onPlayBackSeek")
if self.connect_playing:
log_msg("Kodiplayer seekto: %s" % seekTime)
log_msg("Run Spotipy command: seek_track")
self.__sp.seek_track(seekTime)

def onPlayBackStopped(self):
'''Kodi event fired when playback is stopped'''
# event is called after every track
# check playlist postition to detect if playback is realy stopped
log_msg("Kodi event fired: onPlayBackStopped")
if self.connect_playing:
self.connect_playing = False
log_msg("Run Spotipy command: pause_playback")
self.__sp.pause_playback()
log_msg("Playback stopped")

def add_nexttrack_to_playlist(self):
def __add_nexttrack_to_playlist(self):
'''Update the playlist: add fake item at the end which allows us to skip'''
url = "http://localhost:%s/nexttrack" % PROXY_PORT
li = xbmcgui.ListItem('...', path=url)
self.__playlist.add(url, li)

def start_new_playback(self, track_id):
'''Create the playlist to start playback of a new track'''
log_msg("Creating playlist to start playback of a new track")
self.connect_playing = True
self.__playlist.clear()
trackdetails = self.__sp.track(track_id)
url, li = parse_spotify_track(trackdetails, silenced=False, is_connect=True)
self.__playlist.add(url, li)
self.add_nexttrack_to_playlist()
self.__sp.seek_track(0) # for now we always start a track at the beginning
self.__add_nexttrack_to_playlist()
log_msg("Run Spotipy command: seek_track")
self.__sp.seek_track(0) # this is done to sync remote devices with current playback position
self.play(self.__playlist)
if "NEXTTRACK" in self.__lms_event_stack:
self.__lms_event_stack.remove("NEXTTRACK") # is done loading next track

def update_info(self):
log_msg("Called update_info()!")

def handle_lms_event_change(self):

'''Handle LMS event in case of playback start or change'''
log_msg("Handling LMS event start or change")
log_msg("Run Spotipy command: current_playback")
cur_playback = self.__sp.current_playback()

if not cur_playback:
Expand All @@ -136,16 +147,17 @@ def handle_lms_event_change(self):

if not kodi_player_title or kodi_player_title != trackdetails["name"]:
log_msg("New track requested by Spotify Connect player")
self.__handling_lms_event = True
self.start_new_playback(trackdetails["id"])

elif xbmc.getCondVisibility("Player.Paused"):
log_msg("Playback resumed from pause requested by Spotify Connect")
self.__handling_lms_event = True
self.__lms_event_stack.append("RESUME")
self.pause() # pause() is also used to resume playback

def handle_lms_event_stop(self):
'''Handle LMS event in case of playback stop'''
log_msg("Handling LMS event stop")
if not xbmc.getCondVisibility("Player.Paused"):
log_msg("Pause requested by Spotify Connect")
self.__handling_lms_event = True
self.pause()
log_msg("Stop requested by Spotify Connect")
self.__lms_event_stack.append("PAUSE")
self.pause()
132 changes: 16 additions & 116 deletions resources/lib/plugin_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,6 @@ def parse_params(self):
# default settings
self.append_artist_to_title = self.addon.getSetting("appendArtistToTitle") == "true"

def refresh_connected_device(self):
'''set reconnect flag for main_loop'''
if self.addon.getSetting("playback_device") == "connect":
self.win.setProperty("spotify-cmd", "__RECONNECT__")

def switch_user(self):
'''switch or logout user'''
return self.logoff_user()
Expand All @@ -124,10 +119,6 @@ def browse_main(self):
# main listing
xbmcplugin.setContent(self.addon_handle, "files")
items = []
items.append(
("%s: %s" % (self.addon.getLocalizedString(11039), self.playername),
"plugin://plugin.audio.spotify-headless/?action=browse_playback_devices",
"DefaultMusicPlugins.png", True))
cur_user_label = self.sp.me()["display_name"]
if not cur_user_label:
cur_user_label = self.sp.me()["id"]
Expand All @@ -148,117 +139,26 @@ def browse_main(self):
xbmcplugin.addDirectoryItem(handle=self.addon_handle, url=item[1], listitem=li, isFolder=item[3])
xbmcplugin.addSortMethod(self.addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
xbmcplugin.endOfDirectory(handle=self.addon_handle)
self.refresh_connected_device()

def set_playback_device(self):
'''set the active playback device'''
deviceid = self.params["deviceid"][0]
if deviceid == "local":
self.addon.setSetting("playback_device", "local")
elif deviceid == "remote":
headertxt = self.addon.getLocalizedString(11039)
bodytxt = self.addon.getLocalizedString(11061)
dialog = xbmcgui.Dialog()
dialog.textviewer(headertxt, bodytxt)
result = dialog.input(self.addon.getLocalizedString(11062))
if result:
self.addon.setSetting("playback_device", "remote")
self.addon.setSetting("connect_id", result)
del dialog
elif deviceid == "squeezebox":
self.addon.setSetting("playback_device", "squeezebox")
else:
cur_playback = self.sp.current_playback()
self.sp.transfer_playback(deviceid, False)
# resume play if connect player was playing berfore transfer_playback
if cur_playback and cur_playback["is_playing"]:
self.sp.start_playback()
self.addon.setSetting("playback_device", "connect")
self.addon.setSetting("connect_id", deviceid)

self.refresh_connected_device()
xbmc.executebuiltin("Container.Refresh")

def browse_playback_devices(self):
'''set the active playback device'''
xbmcplugin.setContent(self.addon_handle, "files")
items = []
if self.win.getProperty("spotify.supportsplayback"):
# local playback
label = self.addon.getLocalizedString(11037)
if self.local_playback:
label += " [%s]" % self.addon.getLocalizedString(11040)
url = "plugin://plugin.audio.spotify-headless/?action=set_playback_device&deviceid=local"
li = xbmcgui.ListItem(label, iconImage="DefaultMusicCompilations.png")
li.setProperty("isPlayable", "false")
li.setArt({"fanart": "special://home/addons/plugin.audio.spotify-headless/fanart.jpg"})
li.addContextMenuItems([], True)
xbmcplugin.addDirectoryItem(handle=self.addon_handle, url=url, listitem=li, isFolder=False)
else:
# local playback using a remote service
label = self.addon.getLocalizedString(11060)
if self.addon.getSetting("playback_device") == "remote":
label += " [%s]" % self.addon.getLocalizedString(11040)
url = "plugin://plugin.audio.spotify-headless/?action=set_playback_device&deviceid=remote"
li = xbmcgui.ListItem(label, iconImage="DefaultMusicCompilations.png")
li.setProperty("isPlayable", "false")
li.setArt({"fanart": "special://home/addons/plugin.audio.spotify-headless/fanart.jpg"})
li.addContextMenuItems([], True)
xbmcplugin.addDirectoryItem(handle=self.addon_handle, url=url, listitem=li, isFolder=False)
# connect devices
for device in self.sp.devices()["devices"]:
label = "Spotify Connect: %s" % device["name"]
if device["is_active"] and self.addon.getSetting("playback_device") == "connect":
label += " [%s]" % self.addon.getLocalizedString(11040)
self.refresh_connected_device()
url = "plugin://plugin.audio.spotify-headless/?action=set_playback_device&deviceid=%s" % device["id"]
li = xbmcgui.ListItem(label, iconImage="DefaultMusicCompilations.png")
li.setProperty("isPlayable", "false")
li.setArt({"fanart": "special://home/addons/plugin.audio.spotify-headless/fanart.jpg"})
li.addContextMenuItems([], True)
xbmcplugin.addDirectoryItem(handle=self.addon_handle, url=url, listitem=li, isFolder=False)
if xbmc.getCondVisibility("System.HasAddon(plugin.audio.squeezebox)"):
# LMS playback
label = xbmc.getInfoLabel("System.AddonTitle(plugin.audio.squeezebox)")
if self.addon.getSetting("playback_device") == "squeezebox":
label += " [%s]" % self.addon.getLocalizedString(11040)
url = "plugin://plugin.audio.spotify-headless/?action=set_playback_device&deviceid=squeezebox"
li = xbmcgui.ListItem(label, iconImage="DefaultMusicCompilations.png")
li.setProperty("isPlayable", "false")
li.setArt({"fanart": "special://home/addons/plugin.audio.spotify-headless/fanart.jpg"})
li.addContextMenuItems([], True)
xbmcplugin.addDirectoryItem(handle=self.addon_handle, url=url, listitem=li, isFolder=False)
xbmcplugin.addSortMethod(self.addon_handle, xbmcplugin.SORT_METHOD_UNSORTED)
xbmcplugin.endOfDirectory(handle=self.addon_handle)

def active_playback_device(self):
'''determine if we should use local playback or connect playback'''
'''always return local playback if supported'''
playback = self.addon.getSetting("playback_device")
connect_id = ""

if not playback:
# set default to local playback if supported
if self.win.getProperty("spotify.supportsplayback"):
playback = "local"
else:
playback = "connect"
self.addon.setSetting("playback_device", playback)
# set device name
if playback == "local":
is_local = True
devicename = self.addon.getLocalizedString(11037)
elif playback == "remote":
is_local = True
connect_id = self.addon.getSetting("connect_id")
devicename = self.addon.getLocalizedString(11063) % connect_id
elif playback == "squeezebox":
is_local = False
devicename = xbmc.getInfoLabel("System.AddonTitle(plugin.audio.squeezebox)")
else:
is_local = False
devicename = "Spotify Connect" # placeholder value
for device in self.sp.devices()["devices"]:
if device["is_active"]:
devicename = device["name"]
# check if local playback if supported
if not self.win.getProperty("spotify.supportsplayback"):
msg = self.addon.getLocalizedString(11068)
dialog = xbmcgui.Dialog()
header = self.addon.getAddonInfo("name")
dialog.ok(header, msg)
del dialog

self.addon.setSetting("playback_device", "local")

connect_id = ""
is_local = True
devicename = self.addon.getLocalizedString(11037)

return is_local, devicename, connect_id


3 changes: 2 additions & 1 deletion resources/lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ def run_spotty(self, arguments=None, use_creds=False, disable_discovery=True, ap
self.__spotty_binary,
"-c", self.__cache_path,
"--disable-audio-cache",
## "-b", "320"
"-b", "320",
"--ap-port",ap_port
]
if use_creds:
Expand All @@ -473,6 +473,7 @@ def run_spotty(self, arguments=None, use_creds=False, disable_discovery=True, ap
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
log_msg("Running spotty command: %s" % args, xbmc.LOGDEBUG)
return subprocess.Popen(args, startupinfo=startupinfo, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except Exception as exc:
Expand Down
23 changes: 0 additions & 23 deletions resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,6 @@
<category label="11028" visible="!IsEmpty(Window(Home).Property(spotify.supportsplayback))">
<setting id="username" type="text" label="11001" default=""/>
<setting id="password" type="text" option="hidden" label="11002" default=""/>
<!-- <setting id="multi_account" type="bool" label="11041" default="false"/>
<setting id="username1" type="text" label="11042" default="" visible="eq(-1,true)"/>
<setting id="password1" type="text" option="hidden" label="11002" default="" visible="eq(-2,true)"/>
<setting type="sep"/>
<setting id="username2" type="text" label="11043" default="" visible="eq(-4,true)"/>
<setting id="password2" type="text" option="hidden" label="11002" default="" visible="eq(-5,true)"/>
<setting type="sep"/>
<setting id="username3" type="text" label="11044" default="" visible="eq(-7,true)"/>
<setting id="password3" type="text" option="hidden" label="11002" default="" visible="eq(-8,true)"/>
<setting type="sep"/>
<setting id="username4" type="text" label="11045" default="" visible="eq(-10,true)"/>
<setting id="password4" type="text" option="hidden" label="11002" default="" visible="eq(-11,true)"/>
<setting type="sep"/>
<setting id="username5" type="text" label="11046" default="" visible="eq(-13,true)"/>
<setting id="password5" type="text" option="hidden" label="11002" default="" visible="eq(-14,true)"/> -->
</category>

<category label="11054" visible="!IsEmpty(Window(Home).Property(spotify.supportsplayback))">
Expand All @@ -27,14 +12,6 @@
</category>

<category label="11055">
<!-- <setting id="categoryDefaultView" type="text" label="11020" default=""/>
<setting id="playlistDefaultView" type="text" label="11031" default=""/>
<setting id="artistDefaultView" type="text" label="11032" default=""/>
<setting id="albumDefaultView" type="text" label="11033" default=""/>
<setting id="songDefaultView" type="text" label="11034" default=""/>
<setting type="sep" />
<setting id="prefer_kodi_osd" type="bool" label="11064" default="false"/>
<setting type="sep" /> -->
<setting id="appendArtistToTitle" type="bool" label="11030" default="false"/>
</category>

Expand Down

0 comments on commit 9197d46

Please sign in to comment.