Repeat working

This commit is contained in:
Sumner Evans
2019-06-28 21:06:27 -06:00
parent 2168b47585
commit b86a4ee5ee
6 changed files with 96 additions and 27 deletions

View File

@@ -10,7 +10,7 @@ from .ui.main import MainWindow
from .ui.configure_servers import ConfigureServersDialog
from .ui import util
from .state_manager import ApplicationState
from .state_manager import ApplicationState, RepeatType
from .cache_manager import CacheManager
from .server.api_objects import Child
@@ -122,7 +122,8 @@ class LibremsonicApp(Gtk.Application):
self.state.load()
# If there is no current server, show the dialog to select a server.
if self.state.config.current_server is None:
if (self.state.config.current_server is None
or self.state.config.current_server < 0):
self.show_configure_servers_dialog()
else:
self.on_connected_server_changed(
@@ -142,24 +143,42 @@ class LibremsonicApp(Gtk.Application):
def on_next_track(self, action, params):
current_idx = self.state.play_queue.index(self.state.current_song.id)
# Handle song repeating
if self.state.repeat_type == RepeatType.REPEAT_SONG:
current_idx = current_idx - 1
# Wrap around the play queue if at the end.
elif current_idx == len(self.state.play_queue) - 1:
current_idx = -1
self.play_song(self.state.play_queue[current_idx + 1])
def on_prev_track(self, action, params):
current_idx = self.state.play_queue.index(self.state.current_song.id)
# Go back to the beginning of the song if we are past 5 seconds.
# Otherwise, go to the previous song.
if self.player.time_pos < 5:
song_to_play = current_idx - 1
if self.state.repeat_type == RepeatType.REPEAT_SONG:
song_to_play = current_idx
elif self.player.time_pos < 5:
if (current_idx == 0
and self.state.repeat_type == RepeatType.NO_REPEAT):
song_to_play = 0
else:
song_to_play = current_idx - 1
else:
song_to_play = current_idx
self.play_song(self.state.play_queue[song_to_play])
def on_repeat_press(self, action, params):
print('repeat press')
# Cycle through the repeat types.
new_repeat_type = RepeatType((self.state.repeat_type.value + 1) % 3)
self.state.repeat_type = new_repeat_type
self.update_window()
def on_shuffle_press(self, action, params):
print('shuffle press')
self.state.shuffle_on = not self.state.shuffle_on
self.update_window()
def on_server_list_changed(self, action, servers):
self.state.config.servers = servers

View File

@@ -36,15 +36,15 @@ class ServerConfiguration:
class AppConfiguration:
servers: List[ServerConfiguration]
current_server: int
_cache_location: str
max_cache_size_mb: int # -1 means unlimited
current_server: int = -1
_cache_location: str = ''
max_cache_size_mb: int # -1 means unlimited
def to_json(self):
return {
'servers': [s.__dict__ for s in self.servers],
'current_server': self.current_server,
'_cache_location': self._cache_location,
'_cache_location': getattr(self, '_cache_location', None),
'max_cache_size_mb': self.max_cache_size_mb,
}

View File

@@ -1,5 +1,5 @@
import os
from pathlib import Path
from enum import Enum
import json
from typing import List
@@ -8,6 +8,21 @@ from .config import AppConfiguration
from .server.api_objects import Child
class RepeatType(Enum):
NO_REPEAT = 0
REPEAT_QUEUE = 1
REPEAT_SONG = 2
@property
def icon(self):
icon_name = [
'repeat',
'repeat-symbolic',
'repeat-song-symbolic',
][self.value]
return 'media-playlist-' + icon_name
class ApplicationState:
"""
Represents the state of the application. In general, there are two things
@@ -29,25 +44,37 @@ class ApplicationState:
play_queue: List[str]
volume: int = 100
old_volume: int = 100
repeat_type: RepeatType = RepeatType.NO_REPEAT
shuffle_on: bool = False
def to_json(self):
return {
'current_song': getattr(self, 'current_song', None),
# 'current_song': getattr(self, 'current_song', None),
'play_queue': getattr(self, 'play_queue', None),
'volume': getattr(self, 'volume', None),
'repeat_type': getattr(self, 'repeat_type',
RepeatType.NO_REPEAT).value,
'shuffle_on': getattr(self, 'shuffle_on', None),
}
def load_from_json(self, json_object):
self.current_song = json_object.get('current_song') or None
# self.current_song = json_object.get('current_song') or None
self.play_queue = json_object.get('play_queue') or []
self.volume = json_object.get('volume') or 100
self.repeat_type = (RepeatType(json_object.get('repeat_type'))
or RepeatType.NO_REPEAT)
self.shuffle_on = json_object.get('shuffle_on', False)
def load(self):
self.config = self.get_config(self.config_file)
if os.path.exists(self.state_filename):
with open(self.state_filename, 'r') as f:
self.load_from_json(json.load(f))
try:
self.load_from_json(json.load(f))
except json.decoder.JSONDecodeError:
# Who cares, it's just state.
pass
def save(self):
# Make the necessary directories before writing the config and state.

View File

@@ -84,6 +84,7 @@
#player-controls-bar #song-title {
margin-bottom: 3px;
font-weight: bold;
}
#player-controls-bar #album-name {

View File

@@ -5,7 +5,7 @@ import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango, GObject, Gio
from libremsonic.state_manager import ApplicationState
from libremsonic.state_manager import ApplicationState, RepeatType
from libremsonic.cache_manager import CacheManager
from libremsonic.ui import util
@@ -42,15 +42,37 @@ class PlayerControls(Gtk.ActionBar):
has_current_song = (hasattr(state, 'current_song')
and state.current_song is not None)
has_prev_song, has_next_song = False, False
if has_current_song and state.current_song.id in state.play_queue:
# TODO will need to change when repeat is implemented
has_next_song = False
if state.repeat_type in (RepeatType.REPEAT_QUEUE,
RepeatType.REPEAT_SONG):
has_next_song = True
elif has_current_song and state.current_song.id in state.play_queue:
current = state.play_queue.index(state.current_song.id)
has_prev_song = current > 0
has_next_song = current < len(state.play_queue) - 1
# Repeat button state
# TODO: it's not correct to use symboloc vs. not symbolic icons for
# lighter/darker versions of the icon. Fix this by using FG color I
# think? But then we have to deal with styling, which sucks.
icon = Gio.ThemedIcon(name=state.repeat_type.icon)
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
self.repeat_button.remove(self.repeat_button.get_child())
self.repeat_button.add(image)
self.repeat_button.show_all()
# Shuffle button state
# TODO: it's not correct to use symboloc vs. not symbolic icons for
# lighter/darker versions of the icon. Fix this by using FG color I
# think? But then we have to deal with styling, which sucks.
icon = Gio.ThemedIcon(name='media-playlist-shuffle' +
('-symbolic' if state.shuffle_on else ''))
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
self.shuffle_button.remove(self.shuffle_button.get_child())
self.shuffle_button.add(image)
self.shuffle_button.show_all()
self.song_scrubber.set_sensitive(has_current_song)
self.prev_button.set_sensitive(has_current_song and has_prev_song)
self.prev_button.set_sensitive(has_current_song)
self.play_button.set_sensitive(has_current_song)
self.next_button.set_sensitive(has_current_song and has_next_song)
@@ -81,9 +103,9 @@ class PlayerControls(Gtk.ActionBar):
def esc(string):
return string.replace('&', '&amp;')
self.song_title.set_markup(f'<b>{esc(state.current_song.title)}</b>')
self.album_name.set_markup(f'<i>{esc(state.current_song.album)}</i>')
self.artist_name.set_markup(f'{esc(state.current_song.artist)}')
self.song_title.set_text(esc(state.current_song.title))
self.album_name.set_text(esc(state.current_song.album))
self.artist_name.set_text(esc(state.current_song.artist))
@util.async_callback(
lambda *k, **v: CacheManager.get_cover_art_filename(*k, **v),
@@ -166,8 +188,7 @@ class PlayerControls(Gtk.ActionBar):
buttons.pack_start(Gtk.Box(), True, True, 0)
# Repeat button
self.repeat_button = util.button_with_icon(
'media-playlist-repeat-symbolic')
self.repeat_button = util.button_with_icon('media-playlist-repeat')
self.repeat_button.set_action_name('app.repeat-press')
buttons.pack_start(self.repeat_button, False, False, 5)
@@ -195,8 +216,7 @@ class PlayerControls(Gtk.ActionBar):
buttons.pack_start(self.next_button, False, False, 5)
# Shuffle button
self.shuffle_button = util.button_with_icon(
'media-playlist-shuffle-symbolic')
self.shuffle_button = util.button_with_icon('media-playlist-shuffle')
self.shuffle_button.set_action_name('app.shuffle-press')
buttons.pack_start(self.shuffle_button, False, False, 5)

View File

@@ -337,6 +337,8 @@ class PlaylistsPanel(Gtk.Paned):
self.playlist_stats.set_markup(self.format_stats(playlist))
# Update the song list model
# TODO don't do this. it clears out the list an refreshes it which is
# not what we want in most cases. Need to do some diffing.
self.playlist_song_model.clear()
for song in (playlist.entry or []):
cache_icon = {