This commit is contained in:
Andrew Dunai 2018-01-03 13:36:32 +02:00
parent 357ab3bd73
commit 556bf51d1f
6 changed files with 88 additions and 29 deletions

View file

@ -24,15 +24,15 @@ It's being actively developed, but is still in the early alpha version, so many
- Playback
- Music library
- Playlists
- Queue
- Radio stations
- Queue management
- Notifications
- Configuration
- Caching (partially)
- Basic error handling
# What is being developed
- Search
- Add to playlist
- Like/dislike
- Caching
- Other functionality that is supported by [gmusicapi]
@ -83,14 +83,31 @@ Also be aware that this app has not been tested with 2FA yet.
# Controls
## General
- `<UP|DOWN|LEFT|RIGHT>` - nagivate around
- `<ALT> + 1..9` - switch active tab
- `<ENTER>` - play selected track
## Songs
- `<ENTER>` - play highlighted track
- `<CTRL> w` - play/pause
- `<CTRL> e` - play next song
- `<SHIFT> <LEFT|RIGHT>` - seek backward/forward by 5% of the song duration
- `<CTRL> a` - append highlighted song to the queue
- `<CTRL> u` - remove highlighted song from the queue
- `<CTRL> p` - start station from highlighted song
## Playback
- `<CTRL> s` - toggle shuffle
- `<CTRL> r` - toggle song repeat
- `<SHIFT> <LEFT|RIGHT>` - seek backward/forward by 5% of the song duration
- `<CTRL> q` - seek to song beginning
## Misc
- `<ESC>` - close most recent notification (we should remap this to something else)
- `<CTRL> x` - exit app
# Credits

View file

@ -238,7 +238,7 @@ class AppWidget(urwid.Frame):
player.set_random(not player.get_is_random())
elif key == 'ctrl r':
player.set_repeat_one(not player.get_is_repeat_one())
elif key == 'ctrl q':
elif key == 'ctrl x':
sys.exit(0)
elif key == 'esc':
NotificationArea.close_newest()

View file

@ -36,27 +36,49 @@ def synchronized(fn):
class Track(object):
def __init__(self, id, title, artist, duration):
TYPE_UPLOADED = 'uploaded'
TYPE_STORE = 'store'
def __init__(self, id, title, artist, duration, type):
self.id = id
self.title = title
self.artist = artist
self.duration = duration
self.type = type
@classmethod
def from_data(cls, data, many=False):
if many:
return [cls.from_data(one) for one in data]
if 'id' in data:
track_id = data['id']
track_type = 'uploaded'
elif 'storeId' in data:
track_id = data['storeId']
track_type = 'store'
else:
raise Exception('Track is missing both "id" and "storeId"! Where does it come from?')
return Track(
id=data['id'],
id=track_id,
title=data['title'],
artist=data['artist'],
duration=int(data['durationMillis'])
duration=int(data['durationMillis']),
type=track_type
)
def get_url(self, callback):
gp.get_stream_url(self.id, callback=callback, extra=dict(track=self))
@async
@synchronized
def create_station(self):
station_id = gp.mc.create_station(name=u'Station - {}'.format(self.title), track_id=self.id)
station = Station(station_id)
station.load_tracks()
return station
class Playlist(object):
def __init__(self, id, name, tracks):
@ -91,13 +113,18 @@ class Playlist(object):
class Station(object):
def __init__(self):
pass
def __init__(self, id):
self.id = id
self.tracks = []
@classmethod
def from_data(cls, data):
print(data)
raise Exception(str(data))
def load_tracks(self):
data = gp.mc.get_station_tracks(self.id, 100)
# import json
# raise Exception(json.dumps(data, indent=4))
self.tracks = Track.from_data(data, many=True)
def get_tracks(self):
return self.tracks
class GP(object):
@ -149,14 +176,15 @@ class GP(object):
)
return self.cached_playlists
@async
@synchronized
def create_station(self, name, track_id=None, artist_id=None, album_id=None, genre_id=None):
kwargs = dict(track_id=track_id, artist_id=artist_id, album_id=album_id, genre_id=genre_id)
# kwargs = {k: v for k, v in kwargs.items() if v is not None}
# if len(kwargs) != 1:
# raise Exception('Must provide one of artist_id, album_id or genre_id')
return Station.from_data(self.mc.create_station(name, **kwargs))
# @async
# @synchronized
# def create_station(self, name, track_id=None, artist_id=None, album_id=None, genre_id=None):
# kwargs = dict(track_id=track_id, artist_id=artist_id, album_id=album_id, genre_id=genre_id)
# # kwargs = {k: v for k, v in kwargs.items() if v is not None}
# # if len(kwargs) != 1:
# # raise Exception('Must provide one of artist_id, album_id or genre_id')
# station_id = Station.from_data(self.mc.create_station(name, **kwargs))
# return station_id
def get_cached_tracks_map(self):
return {track.id: track for track in self.cached_tracks}

View file

@ -19,7 +19,7 @@ class Notification(urwid.Columns):
])
def update(self, message):
self.text.text = Notification.TEMPLATE.format(message)
self.text.set_text(Notification.TEMPLATE.format(message))
def close(self):
for notification, props in reversed(self.area.contents):

View file

@ -18,7 +18,7 @@ class Queue(object):
def load(self, tracks, current_track=None):
self.tracks = tracks[:]
if current_track is None and len(self.tracks):
current_track = self.tracks[0]
current_track = 0
self.current_track = current_track
def append(self, track):
@ -126,7 +126,7 @@ class Player(object):
self.get_play_progress()
)
def load_queue(self, data, current_index):
def load_queue(self, data, current_index=None):
self.queue.load(data, current_index)
self.queue_changed.fire()
self._play()
@ -141,6 +141,22 @@ class Player(object):
self.queue_changed.fire()
self.track_removed.fire(track)
def create_station_from_track(self, track):
self._create_station_notification = NotificationArea.notify('Creating station...')
track.create_station(callback=self._create_station_from_track_ready)
def _create_station_from_track_ready(self, station, error):
if error:
self._create_station_notification.update('Failed to create station: {}'.format(str(error)))
return
if not station.get_tracks():
self._create_station_notification.update('Newly created station is empty :(')
return
self.load_queue(station.get_tracks())
self._create_station_notification.update('Station ready!')
def get_is_random(self):
return self.queue.random

View file

@ -170,9 +170,7 @@ class SongListBox(urwid.ListBox):
player.remove_from_queue(songitem.track)
def item_station_requested(self, songitem):
# TODO: Implement me
# player.start_station()
pass
player.create_station_from_track(songitem.track)
def track_changed(self, track):
for i, songitem in enumerate(self.walker):