Play queue enabled/disabled depending on cache state

This commit is contained in:
Sumner Evans
2020-05-24 21:28:27 -06:00
parent 654b0902e7
commit 209491204f
7 changed files with 63 additions and 27 deletions

6
pyproject.toml Normal file
View File

@@ -0,0 +1,6 @@
[tool.black]
exclude = '''
(
/flatpak/
)
'''

View File

@@ -181,6 +181,10 @@
min-width: 50px;
}
#play-queue-image-disabled {
opacity: 0.5;
}
/* ********** General ********** */
.menu-button {
padding: 5px;
@@ -258,15 +262,18 @@
margin: 15px;
}
@define-color box_shadow_color rgba(0, 0, 0, 0.2);
#artist-info-panel {
box-shadow: 0 5px 5px @box_shadow_color;
margin-bottom: 10px;
padding-bottom: 10px;
}
@define-color detail_color rgba(0, 0, 0, 0.2);
#artist-detail-box {
padding-top: 10px;
padding-bottom: 10px;
box-shadow: inset 0 5px 5px @detail_color,
inset 0 -5px 5px @detail_color;
background-color: @detail_color;
box-shadow: inset 0 5px 5px @box_shadow_color,
inset 0 -5px 5px @box_shadow_color;
background-color: @box_shadow_color;
}

View File

@@ -234,13 +234,13 @@ class ArtistDetailPanel(Gtk.Box):
# TODO: make these disabled if there are no songs that can be played.
play_button = IconButton(
"media-playback-start-symbolic", label="Play All", relief=True,
"media-playback-start-symbolic", label="Play All", relief=True
)
play_button.connect("clicked", self.on_play_all_clicked)
self.play_shuffle_buttons.pack_start(play_button, False, False, 0)
shuffle_button = IconButton(
"media-playlist-shuffle-symbolic", label="Shuffle All", relief=True,
"media-playlist-shuffle-symbolic", label="Shuffle All", relief=True
)
shuffle_button.connect("clicked", self.on_shuffle_all_button)
self.play_shuffle_buttons.pack_start(shuffle_button, False, False, 5)

View File

@@ -7,7 +7,7 @@ from typing import Any, Callable, List, Optional, Tuple
from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk, Pango
from pychromecast import Chromecast
from sublime.adapters import AdapterManager, Result
from sublime.adapters import AdapterManager, Result, SongCacheStatus
from sublime.adapters.api_objects import Song
from sublime.config import AppConfiguration
from sublime.players import ChromecastPlayer
@@ -177,6 +177,7 @@ class PlayerControls(Gtk.ActionBar):
self.update_device_list()
# Short circuit if no changes to the play queue
force |= self.offline_mode != app_config.offline_mode
self.offline_mode = app_config.offline_mode
self.load_play_queue_button.set_sensitive(not self.offline_mode)
@@ -224,7 +225,7 @@ class PlayerControls(Gtk.ActionBar):
if order_token != self.play_queue_update_order_token:
return
self.play_queue_store[idx][0] = cover_art_filename
self.play_queue_store[idx][1] = cover_art_filename
def get_cover_art_filename_or_create_future(
cover_art_id: Optional[str], idx: int, order_token: int
@@ -247,21 +248,26 @@ class PlayerControls(Gtk.ActionBar):
if order_token != self.play_queue_update_order_token:
return
self.play_queue_store[idx][1] = calculate_label(song_details)
self.play_queue_store[idx][2] = calculate_label(song_details)
# Cover Art
filename = get_cover_art_filename_or_create_future(
song_details.cover_art, idx, order_token
)
if filename:
self.play_queue_store[idx][0] = filename
self.play_queue_store[idx][1] = filename
current_play_queue = [x[-1] for x in self.play_queue_store]
if app_config.state.play_queue != current_play_queue:
self.play_queue_update_order_token += 1
song_details_results = []
for i, song_id in enumerate(app_config.state.play_queue):
for i, (song_id, cached_status) in enumerate(
zip(
app_config.state.play_queue,
AdapterManager.get_cached_statuses(app_config.state.play_queue),
)
):
song_details_result = AdapterManager.get_song_details(song_id)
cover_art_filename = ""
@@ -282,6 +288,11 @@ class PlayerControls(Gtk.ActionBar):
new_store.append(
[
(
not self.offline_mode
or cached_status
in (SongCacheStatus.CACHED, SongCacheStatus.PERMANENTLY_CACHED)
),
cover_art_filename,
label,
i == app_config.state.current_song_index,
@@ -361,6 +372,8 @@ class PlayerControls(Gtk.ActionBar):
self.play_queue_popover.show_all()
def on_song_activated(self, t: Any, idx: Gtk.TreePath, c: Any):
if not self.play_queue_store[idx[0]][0]:
return
# The song ID is in the last column of the model.
self.emit(
"song-clicked",
@@ -481,7 +494,7 @@ class PlayerControls(Gtk.ActionBar):
# reordering_play_queue_song_list flag.
if self.reordering_play_queue_song_list:
currently_playing_index = [
i for i, s in enumerate(self.play_queue_store) if s[2]
i for i, s in enumerate(self.play_queue_store) if s[3] # playing
][0]
self.emit(
"refresh-window",
@@ -706,6 +719,7 @@ class PlayerControls(Gtk.ActionBar):
)
self.play_queue_store = Gtk.ListStore(
bool, # playable
str, # image filename
str, # title, album, artist
bool, # playing
@@ -714,30 +728,35 @@ class PlayerControls(Gtk.ActionBar):
self.play_queue_list = Gtk.TreeView(
model=self.play_queue_store, reorderable=True, headers_visible=False,
)
self.play_queue_list.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
selection = self.play_queue_list.get_selection()
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
selection.set_select_function(lambda _, model, path, current: model[path[0]][0])
# Album Art column.
# Album Art column. This function defines what image to use for the play queue
# song icon.
def filename_to_pixbuf(
column: Any,
cell: Gtk.CellRendererPixbuf,
model: Gtk.ListStore,
iter: Gtk.TreeIter,
tree_iter: Gtk.TreeIter,
flags: Any,
):
filename = model.get_value(iter, 0)
cell.set_property("sensitive", model.get_value(tree_iter, 0))
filename = model.get_value(tree_iter, 1)
if not filename:
cell.set_property("icon_name", "")
return
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, 50, 50, True)
# If this is the playing song, then overlay the play icon.
if model.get_value(iter, 2):
if model.get_value(tree_iter, 3):
play_overlay_pixbuf = GdkPixbuf.Pixbuf.new_from_file(
str(Path(__file__).parent.joinpath("images/play-queue-play.png"))
)
play_overlay_pixbuf.composite(
pixbuf, 0, 0, 50, 50, 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 255
pixbuf, 0, 0, 50, 50, 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 200
)
cell.set_property("pixbuf", pixbuf)
@@ -749,8 +768,8 @@ class PlayerControls(Gtk.ActionBar):
column.set_resizable(True)
self.play_queue_list.append_column(column)
renderer = Gtk.CellRendererText(markup=True, ellipsize=Pango.EllipsizeMode.END,)
column = Gtk.TreeViewColumn("", renderer, markup=1)
renderer = Gtk.CellRendererText(markup=True, ellipsize=Pango.EllipsizeMode.END)
column = Gtk.TreeViewColumn("", renderer, markup=2, sensitive=0)
self.play_queue_list.append_column(column)
self.play_queue_list.connect("row-activated", self.on_song_activated)

View File

@@ -713,6 +713,8 @@ class PlaylistDetailPanel(Gtk.Overlay):
)
def on_song_activated(self, _, idx: Gtk.TreePath, col: Any):
if not self.playlist_song_store[idx[0]][0]:
return
# The song ID is in the last column of the model.
self.emit(
"song-clicked",

View File

@@ -6,6 +6,7 @@ from pathlib import Path
from typing import Any, Generator, List, Tuple
import pytest
from dateutil.tz import tzutc
from sublime.adapters.subsonic import (
api_objects as SubsonicAPI,
@@ -111,8 +112,8 @@ def test_get_playlists(adapter: SubsonicAdapter):
name="Test",
song_count=132,
duration=timedelta(seconds=33072),
created=datetime(2020, 3, 27, 5, 38, 45, 0, tzinfo=timezone.utc),
changed=datetime(2020, 4, 9, 16, 3, 26, 0, tzinfo=timezone.utc),
created=datetime(2020, 3, 27, 5, 38, 45, 0, tzinfo=tzutc()),
changed=datetime(2020, 4, 9, 16, 3, 26, 0, tzinfo=tzutc()),
comment="Foo",
owner="foo",
public=True,
@@ -123,8 +124,8 @@ def test_get_playlists(adapter: SubsonicAdapter):
name="Bar",
song_count=23,
duration=timedelta(seconds=847),
created=datetime(2020, 3, 27, 5, 39, 4, 0, tzinfo=timezone.utc),
changed=datetime(2020, 3, 27, 5, 45, 23, 0, tzinfo=timezone.utc),
created=datetime(2020, 3, 27, 5, 39, 4, 0, tzinfo=tzutc()),
changed=datetime(2020, 3, 27, 5, 45, 23, 0, tzinfo=tzutc()),
comment="",
owner="foo",
public=False,
@@ -136,7 +137,7 @@ def test_get_playlists(adapter: SubsonicAdapter):
logging.info(filename)
logging.debug(data)
adapter._set_mock_data(data)
assert adapter.get_playlists() == expected
assert adapter.get_playlists() == sorted(expected, key=lambda e: e.name)
# When playlists is null, expect an empty list.
adapter._set_mock_data(mock_json())

View File

@@ -46,14 +46,15 @@ def test_yaml_load_unload():
def test_config_migrate():
config = AppConfiguration()
config = AppConfiguration(always_stream=True)
server = ServerConfiguration(
name="Test", server_address="https://test.host", username="test"
)
config.servers.append(server)
config.migrate()
assert config.version == 3
assert config.version == 4
assert config.allow_song_downloads is False
for server in config.servers:
server.version == 0