mirror of
https://github.com/vale981/clay
synced 2025-03-05 17:41:42 -05:00
Added track caching option & more debugging.
This commit is contained in:
parent
75e62e5fe3
commit
c4cb66a759
7 changed files with 108 additions and 3 deletions
|
@ -63,6 +63,7 @@ Documentation is .
|
||||||
- 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.)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue