Repeat working
This commit is contained in:
@@ -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
|
||||
|
@@ -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,
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -84,6 +84,7 @@
|
||||
|
||||
#player-controls-bar #song-title {
|
||||
margin-bottom: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#player-controls-bar #album-name {
|
||||
|
@@ -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('&', '&')
|
||||
|
||||
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)
|
||||
|
||||
|
@@ -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 = {
|
||||
|
Reference in New Issue
Block a user