Added track caching option & more debugging.

This commit is contained in:
Andrew Dunai 2018-02-01 17:25:52 +02:00
parent 75e62e5fe3
commit c4cb66a759
7 changed files with 108 additions and 3 deletions

View file

@ -63,6 +63,7 @@ Documentation is ![available here](http://clay.readthedocs.io/en/latest/).
- Notifications - Notifications
- Audio equalizer - Audio equalizer
- Global hotkeys - Global hotkeys
- Song file caching
- Configuration UI - Configuration UI
- Token caching for faster authorizations - Token caching for faster authorizations
- Song operations (add to library, start station etc.) - Song operations (add to library, start station etc.)

View file

@ -615,3 +615,10 @@ class GP(object):
Return True if user is authenticated on Google Play Music, false otherwise. Return True if user is authenticated on Google Play Music, false otherwise.
""" """
return self.mobile_client.is_authenticated() return self.mobile_client.is_authenticated()
@property
def is_subscribed(self):
"""
Return True if user is subscribed on Google Play Music, false otherwise.
"""
return self.mobile_client.is_subscribed

View file

@ -7,7 +7,7 @@ except ImportError:
codename = None codename = None
APP_NAME = 'Clay Player' APP_NAME = 'Clay Player'
VERSION = '0.5.6' VERSION = '0.6.0'
if codename is not None: if codename is not None:
VERSION_WITH_CODENAME = VERSION + '-' + codename(separator='-', id=VERSION) VERSION_WITH_CODENAME = VERSION + '-' + codename(separator='-', id=VERSION)
else: else:

View file

@ -6,6 +6,7 @@ import urwid
from clay.pages.page import AbstractPage from clay.pages.page import AbstractPage
from clay.log import Logger from clay.log import Logger
from clay.clipboard import copy from clay.clipboard import copy
from clay.gp import GP
class DebugItem(urwid.AttrMap): class DebugItem(urwid.AttrMap):
@ -52,12 +53,38 @@ class DebugPage(urwid.Pile, AbstractPage):
self._append_log(log_record) self._append_log(log_record)
Logger.get().on_log_event += self._append_log Logger.get().on_log_event += self._append_log
self.listbox = urwid.ListBox(self.walker) self.listbox = urwid.ListBox(self.walker)
self.debug_data = urwid.Text('')
super(DebugPage, self).__init__([ super(DebugPage, self).__init__([
('pack', self.debug_data),
('pack', urwid.Text('')),
('pack', urwid.Text('Hit "Enter" to copy selected message to clipboard.')), ('pack', urwid.Text('Hit "Enter" to copy selected message to clipboard.')),
('pack', urwid.Divider(u'\u2550')),
self.listbox self.listbox
]) ])
GP.get().auth_state_changed += self.update
self.update()
def update(self, *_):
"""
Update this widget.
"""
gpclient = GP.get()
self.debug_data.set_text(
'- Is authenticated: {}\n'
'- Is subscribed: {}'.format(
gpclient.is_authenticated,
gpclient.is_subscribed if gpclient.is_authenticated else None
)
)
def _append_log(self, log_record): def _append_log(self, log_record):
"""
Add log record to list.
"""
self.walker.insert(0, urwid.Divider(u'\u2500')) self.walker.insert(0, urwid.Divider(u'\u2500'))
self.walker.insert(0, DebugItem(log_record)) self.walker.insert(0, DebugItem(log_record))

View file

@ -138,6 +138,10 @@ class SettingsPage(urwid.Columns, AbstractPage):
self.device_id = urwid.Edit( self.device_id = urwid.Edit(
edit_text=config.get('device_id', '') edit_text=config.get('device_id', '')
) )
self.download_tracks = urwid.CheckBox(
'Download tracks before playback',
state=config.get('download_tracks', False)
)
self.equalizer = Equalizer() self.equalizer = Equalizer()
super(SettingsPage, self).__init__([urwid.ListBox(urwid.SimpleListWalker([ super(SettingsPage, self).__init__([urwid.ListBox(urwid.SimpleListWalker([
urwid.Text('Settings'), urwid.Text('Settings'),
@ -151,6 +155,8 @@ class SettingsPage(urwid.Columns, AbstractPage):
urwid.Text('Device ID'), urwid.Text('Device ID'),
urwid.AttrWrap(self.device_id, 'input', 'input_focus'), urwid.AttrWrap(self.device_id, 'input', 'input_focus'),
urwid.Divider(' '), urwid.Divider(' '),
self.download_tracks,
urwid.Divider(' '),
urwid.AttrWrap(urwid.Button( urwid.AttrWrap(urwid.Button(
'Save', on_press=self.on_save 'Save', on_press=self.on_save
), 'input', 'input_focus'), ), 'input', 'input_focus'),
@ -165,7 +171,8 @@ class SettingsPage(urwid.Columns, AbstractPage):
Settings.set_config(dict( Settings.set_config(dict(
username=self.username.edit_text, username=self.username.edit_text,
password=self.password.edit_text, password=self.password.edit_text,
device_id=self.device_id.edit_text device_id=self.device_id.edit_text,
download_tracks=self.download_tracks.state
)) ))
self.app.set_page('MyLibraryPage') self.app.set_page('MyLibraryPage')
self.app.log_in() self.app.log_in()

View file

@ -5,12 +5,18 @@ Media player built using libVLC.
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
from random import randint from random import randint
import json import json
try: # Python 3.x
from urllib.request import urlopen
except ImportError: # Python 2.x
from urllib2 import urlopen
from clay import vlc from clay import vlc
from clay.eventhook import EventHook from clay.eventhook import EventHook
from clay.notifications import NotificationArea from clay.notifications import NotificationArea
from clay.hotkeys import HotkeyManager from clay.hotkeys import HotkeyManager
from clay.settings import Settings
from clay import meta from clay import meta
from clay.log import Logger
class Queue(object): class Queue(object):
@ -329,10 +335,34 @@ class Player(object):
if track is None: if track is None:
return return
self._is_loading = True self._is_loading = True
track.get_url(callback=self._play_ready)
self.broadcast_state() self.broadcast_state()
self.track_changed.fire(track) self.track_changed.fire(track)
if Settings.get_config().get('download_tracks', False):
path = Settings.get_cached_file_path(track.store_id + '.mp3')
if path is None:
Logger.get().debug('Track %s not in cache, downloading...', track.store_id)
track.get_url(callback=self._download_track)
else:
Logger.get().debug('Track %s in cache, playing', track.store_id)
self._play_ready(path, None, track)
else:
Logger.get().debug('Starting to stream %s', track.store_id)
track.get_url(callback=self._play_ready)
def _download_track(self, url, error, track):
if error:
NotificationArea.notify('Failed to request media URL: {}'.format(str(error)))
Logger.get().error(
'Failed to request media URL for track %s: %s',
track.store_id,
str(error)
)
return
response = urlopen(url)
path = Settings.save_file_to_cache(track.store_id + '.mp3', response.read())
self._play_ready(path, None, track)
def _play_ready(self, url, error, track): def _play_ready(self, url, error, track):
""" """
Called once track's media stream URL request completes. Called once track's media stream URL request completes.
@ -341,6 +371,11 @@ class Player(object):
self._is_loading = False self._is_loading = False
if error: if error:
NotificationArea.notify('Failed to request media URL: {}'.format(str(error))) NotificationArea.notify('Failed to request media URL: {}'.format(str(error)))
Logger.get().error(
'Failed to request media URL for track %s: %s',
track.store_id,
str(error)
)
return return
assert track assert track
media = vlc.Media(url) media = vlc.Media(url)

View file

@ -25,6 +25,12 @@ class Settings(object):
if error.errno != errno.EEXIST: if error.errno != errno.EEXIST:
raise raise
try:
os.makedirs(appdirs.user_cache_dir('clay', 'Clay'))
except OSError as error:
if error.errno != errno.EEXIST:
raise
path = os.path.join(filedir, 'config.yaml') path = os.path.join(filedir, 'config.yaml')
if not os.path.exists(path): if not os.path.exists(path):
with open(path, 'w') as settings: with open(path, 'w') as settings:
@ -48,3 +54,25 @@ class Settings(object):
config.update(new_config) config.update(new_config)
with open(Settings.get_config_filename(), 'w') as settings: with open(Settings.get_config_filename(), 'w') as settings:
settings.write(yaml.dump(config, default_flow_style=False)) settings.write(yaml.dump(config, default_flow_style=False))
@classmethod
def get_cached_file_path(cls, filename):
"""
Get full path to cached file.
"""
cache_dir = appdirs.user_cache_dir('clay', 'Clay')
path = os.path.join(cache_dir, filename)
if os.path.exists(path):
return path
return None
@classmethod
def save_file_to_cache(cls, filename, content):
"""
Save content into file in cache.
"""
cache_dir = appdirs.user_cache_dir('clay', 'Clay')
path = os.path.join(cache_dir, filename)
with open(path, 'wb') as cachefile:
cachefile.write(content)
return path