Device selection logic
This commit is contained in:
@@ -72,7 +72,7 @@ class LibremsonicApp(Gtk.Application):
|
||||
on_player_event,
|
||||
self.state.config,
|
||||
)
|
||||
self.player = self.chromecast_player
|
||||
self.player = self.mpv_player
|
||||
|
||||
# Handle command line option parsing.
|
||||
def do_command_line(self, command_line):
|
||||
@@ -142,6 +142,8 @@ class LibremsonicApp(Gtk.Application):
|
||||
self.on_stack_change)
|
||||
self.window.connect('song-clicked', self.on_song_clicked)
|
||||
self.window.player_controls.connect('song-scrub', self.on_song_scrub)
|
||||
self.window.player_controls.connect('device-update',
|
||||
self.on_device_update)
|
||||
self.window.player_controls.volume_slider.connect(
|
||||
'value-changed', self.on_volume_change)
|
||||
self.window.connect('key-press-event', self.on_window_key_press)
|
||||
@@ -166,12 +168,12 @@ class LibremsonicApp(Gtk.Application):
|
||||
self.show_configure_servers_dialog()
|
||||
|
||||
def on_play_pause(self, *args):
|
||||
if not self.player.song_loaded:
|
||||
# This is from a restart, start playing the file.
|
||||
self.play_song(self.state.current_song.id)
|
||||
else:
|
||||
if self.player.song_loaded:
|
||||
self.player.toggle_play()
|
||||
self.save_play_queue()
|
||||
else:
|
||||
# This is from a restart, start playing the file.
|
||||
self.play_song(self.state.current_song.id)
|
||||
|
||||
self.state.playing = not self.state.playing
|
||||
self.update_window()
|
||||
@@ -283,6 +285,22 @@ class LibremsonicApp(Gtk.Application):
|
||||
|
||||
self.save_play_queue()
|
||||
|
||||
def on_device_update(self, _, device_uuid):
|
||||
was_playing = self.state.playing
|
||||
self.player.pause()
|
||||
self.player._song_loaded = False
|
||||
self.state.playing = False
|
||||
self.update_window()
|
||||
|
||||
if device_uuid == 'this device':
|
||||
self.player = self.mpv_player
|
||||
else:
|
||||
self.chromecast_player.set_playing_chromecast(device_uuid)
|
||||
self.player = self.chromecast_player
|
||||
|
||||
if was_playing:
|
||||
self.on_play_pause()
|
||||
|
||||
def on_mute_toggle(self, action, _):
|
||||
if self.state.volume == 0:
|
||||
new_volume = self.state.old_volume
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import threading
|
||||
from uuid import UUID
|
||||
from urllib.parse import urlparse
|
||||
import io
|
||||
import socket
|
||||
@@ -190,7 +191,6 @@ class ChromecastPlayer(Player):
|
||||
|
||||
@self.app.route('/song/<id>')
|
||||
def stream_song(id):
|
||||
print(f'stream song {id}')
|
||||
song = CacheManager.get_song_details(id).result()
|
||||
filename = CacheManager.get_song_filename_or_stream(song)[0]
|
||||
with open(filename, 'rb') as fin:
|
||||
@@ -207,22 +207,23 @@ class ChromecastPlayer(Player):
|
||||
bottle.run(self.app, host=self.host, port=self.port)
|
||||
|
||||
@classmethod
|
||||
def get_chromecasts(self) -> Future:
|
||||
def get_chromecasts(cls) -> Future:
|
||||
def do_get_chromecasts():
|
||||
self.chromecasts = pychromecast.get_chromecasts()
|
||||
return self.chromecasts
|
||||
ChromecastPlayer.chromecasts = pychromecast.get_chromecasts()
|
||||
return ChromecastPlayer.chromecasts
|
||||
|
||||
return ChromecastPlayer.executor.submit(do_get_chromecasts)
|
||||
|
||||
@classmethod
|
||||
def set_playing_chromecast(self, chromecast):
|
||||
self.chromecast = chromecast
|
||||
def set_playing_chromecast(self, uuid):
|
||||
self.chromecast = next(cc for cc in ChromecastPlayer.chromecasts
|
||||
if cc.device.uuid == UUID(uuid))
|
||||
|
||||
self.chromecast.media_controller.register_status_listener(
|
||||
ChromecastPlayer.media_status_listener)
|
||||
self.chromecast.register_status_listener(
|
||||
ChromecastPlayer.cast_status_listener)
|
||||
self.chromecast.wait()
|
||||
print(f'Using: {chromecast.device.friendly_name}')
|
||||
print(f'Using: {self.chromecast.device.friendly_name}')
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
@@ -242,8 +243,6 @@ class ChromecastPlayer(Player):
|
||||
self.server_thread.start()
|
||||
|
||||
def on_new_cast_status(self, status):
|
||||
print('new cast status')
|
||||
print(status)
|
||||
self.on_player_event(
|
||||
PlayerEvent(
|
||||
'volume_change',
|
||||
@@ -261,9 +260,6 @@ class ChromecastPlayer(Player):
|
||||
|
||||
self._timepos = status.current_time
|
||||
|
||||
print('new status')
|
||||
print(status)
|
||||
|
||||
self.on_player_event(
|
||||
PlayerEvent(
|
||||
'play_state_change',
|
||||
@@ -302,6 +298,8 @@ class ChromecastPlayer(Player):
|
||||
ChromecastPlayer.executor.submit(do_wait_for_playing)
|
||||
|
||||
def _is_playing(self):
|
||||
if not self.chromecast or not self.chromecast.media_controller:
|
||||
return False
|
||||
return self.chromecast.media_controller.status.player_is_playing
|
||||
|
||||
def reset(self):
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import math
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Pango, GObject, Gio, GLib
|
||||
|
||||
from libremsonic.state_manager import ApplicationState, RepeatType
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.state_manager import ApplicationState, RepeatType
|
||||
from libremsonic.ui import util
|
||||
from libremsonic.ui.common import SpinnerImage
|
||||
from libremsonic.ui.common.players import ChromecastPlayer
|
||||
@@ -19,6 +18,8 @@ class PlayerControls(Gtk.ActionBar):
|
||||
__gsignals__ = {
|
||||
'song-scrub':
|
||||
(GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (float, )),
|
||||
'device-update':
|
||||
(GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str, )),
|
||||
}
|
||||
editing: bool = False
|
||||
|
||||
@@ -120,11 +121,11 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.popover_label.set_markup(f'<b>Up Next:</b> {song_label}')
|
||||
|
||||
# Remove everything from the play queue.
|
||||
for c in self.popover_list.get_children():
|
||||
self.popover_list.remove(c)
|
||||
for c in self.up_next_list.get_children():
|
||||
self.up_next_list.remove(c)
|
||||
|
||||
for s in state.play_queue:
|
||||
self.popover_list.add(
|
||||
self.up_next_list.add(
|
||||
Gtk.Label(
|
||||
label='\n',
|
||||
halign=Gtk.Align.START,
|
||||
@@ -132,7 +133,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
margin=5,
|
||||
))
|
||||
|
||||
self.popover_list.show_all()
|
||||
self.up_next_list.show_all()
|
||||
|
||||
# Create a function to capture the value of index for the inner
|
||||
# function. This outer function creates the actual callback
|
||||
@@ -141,7 +142,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
def do_update_label(result):
|
||||
title = util.esc(result.title)
|
||||
album = util.esc(result.album)
|
||||
row = self.popover_list.get_row_at_index(index)
|
||||
row = self.up_next_list.get_row_at_index(index)
|
||||
row.get_child().set_markup(f'<b>{title}</b>\n{album}')
|
||||
row.show_all()
|
||||
|
||||
@@ -183,14 +184,45 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.up_next_popover.popup()
|
||||
self.up_next_popover.show_all()
|
||||
|
||||
def on_device_click(self, button):
|
||||
def update_device_list(self, clear=False):
|
||||
self.device_list_loading.show()
|
||||
|
||||
def clear_list():
|
||||
for c in self.chromecast_device_list.get_children():
|
||||
self.chromecast_device_list.remove(c)
|
||||
|
||||
def chromecast_callback(f):
|
||||
cast = next(cc for cc in f.result()
|
||||
if cc.device.friendly_name == "Sumner's Bedroom")
|
||||
ChromecastPlayer.set_playing_chromecast(cast)
|
||||
clear_list()
|
||||
chromecasts = f.result()
|
||||
chromecasts.sort(key=lambda c: c.device.friendly_name)
|
||||
for cc in chromecasts:
|
||||
btn = Gtk.ModelButton(text=cc.device.friendly_name)
|
||||
btn.get_style_context().add_class('menu-button')
|
||||
btn.connect(
|
||||
'clicked',
|
||||
lambda _, uuid: self.emit('device-update', uuid),
|
||||
cc.device.uuid,
|
||||
)
|
||||
self.chromecast_device_list.add(btn)
|
||||
self.chromecast_device_list.show_all()
|
||||
|
||||
self.device_list_loading.hide()
|
||||
|
||||
if clear:
|
||||
clear_list()
|
||||
|
||||
future = ChromecastPlayer.get_chromecasts()
|
||||
future.add_done_callback(chromecast_callback)
|
||||
future.add_done_callback(
|
||||
lambda f: GLib.idle_add(chromecast_callback, f))
|
||||
|
||||
def on_device_click(self, button):
|
||||
self.update_device_list()
|
||||
self.device_popover.set_relative_to(button)
|
||||
self.device_popover.popup()
|
||||
self.device_popover.show_all()
|
||||
|
||||
def on_device_refresh_click(self, button):
|
||||
self.update_device_list(clear=True)
|
||||
|
||||
def create_song_display(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
@@ -302,6 +334,45 @@ class PlayerControls(Gtk.ActionBar):
|
||||
|
||||
self.device_popover = Gtk.PopoverMenu(name='device-popover')
|
||||
|
||||
device_popover_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
device_popover_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.popover_label = Gtk.Label(
|
||||
label='<b>Devices</b>',
|
||||
use_markup=True,
|
||||
halign=Gtk.Align.START,
|
||||
margin=5,
|
||||
)
|
||||
device_popover_header.add(self.popover_label)
|
||||
|
||||
refresh_devices = util.button_with_icon('view-refresh')
|
||||
refresh_devices.connect('clicked', self.on_device_refresh_click)
|
||||
device_popover_header.pack_end(refresh_devices, False, False, 0)
|
||||
|
||||
device_popover_box.add(device_popover_header)
|
||||
|
||||
device_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
this_device = Gtk.ModelButton(text='This Device')
|
||||
this_device.get_style_context().add_class('menu-button')
|
||||
this_device.connect(
|
||||
'clicked', lambda *a: self.emit('device-update', 'this device'))
|
||||
device_list.add(this_device)
|
||||
|
||||
device_list.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
|
||||
|
||||
self.device_list_loading = Gtk.Spinner(active=True)
|
||||
self.device_list_loading.get_style_context().add_class('menu-button')
|
||||
device_list.add(self.device_list_loading)
|
||||
|
||||
self.chromecast_device_list = Gtk.Box(
|
||||
orientation=Gtk.Orientation.VERTICAL)
|
||||
device_list.add(self.chromecast_device_list)
|
||||
|
||||
device_popover_box.pack_end(device_list, True, True, 0)
|
||||
|
||||
self.device_popover.add(device_popover_box)
|
||||
|
||||
# Up Next button
|
||||
up_next_button = util.button_with_icon(
|
||||
'view-list-symbolic', icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
@@ -310,8 +381,9 @@ class PlayerControls(Gtk.ActionBar):
|
||||
|
||||
self.up_next_popover = Gtk.PopoverMenu(name='up-next-popover')
|
||||
|
||||
popover_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
popover_box_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
up_next_popover_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
up_next_popover_header = Gtk.Box(
|
||||
orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
self.popover_label = Gtk.Label(
|
||||
label='<b>Up Next</b>',
|
||||
@@ -319,23 +391,23 @@ class PlayerControls(Gtk.ActionBar):
|
||||
halign=Gtk.Align.START,
|
||||
margin=10,
|
||||
)
|
||||
popover_box_header.add(self.popover_label)
|
||||
up_next_popover_header.add(self.popover_label)
|
||||
|
||||
load_up_next = Gtk.Button(label='Load Queue from Server', margin=5)
|
||||
load_up_next.set_action_name('app.update-play-queue-from-server')
|
||||
popover_box_header.pack_end(load_up_next, False, False, 0)
|
||||
up_next_popover_header.pack_end(load_up_next, False, False, 0)
|
||||
|
||||
popover_box.add(popover_box_header)
|
||||
up_next_popover_box.add(up_next_popover_header)
|
||||
|
||||
popover_scroll_box = Gtk.ScrolledWindow(
|
||||
up_next_scrollbox = Gtk.ScrolledWindow(
|
||||
min_content_height=600,
|
||||
min_content_width=400,
|
||||
)
|
||||
self.popover_list = Gtk.ListBox()
|
||||
popover_scroll_box.add(self.popover_list)
|
||||
popover_box.pack_end(popover_scroll_box, True, True, 0)
|
||||
self.up_next_list = Gtk.ListBox()
|
||||
up_next_scrollbox.add(self.up_next_list)
|
||||
up_next_popover_box.pack_end(up_next_scrollbox, True, True, 0)
|
||||
|
||||
self.up_next_popover.add(popover_box)
|
||||
self.up_next_popover.add(up_next_popover_box)
|
||||
|
||||
# Volume mute toggle
|
||||
self.volume_mute_toggle = util.button_with_icon('audio-volume-high')
|
||||
|
Reference in New Issue
Block a user