Player refactor works with MPV player
This commit is contained in:
@@ -651,6 +651,7 @@ class SubsonicAdapter(Adapter):
|
||||
self._get(
|
||||
self._make_url("savePlayQueue"),
|
||||
id=song_ids,
|
||||
timeout=2,
|
||||
current=song_ids[current_song_index]
|
||||
if current_song_index is not None
|
||||
else None,
|
||||
|
@@ -265,7 +265,39 @@ class SublimeMusicApp(Gtk.Application):
|
||||
)
|
||||
|
||||
def player_device_change_callback(event: PlayerDeviceEvent):
|
||||
pass
|
||||
assert self.player_manager
|
||||
state_device = self.app_config.state.current_device
|
||||
|
||||
if event.delta == PlayerDeviceEvent.Delta.ADD:
|
||||
# If the device added is the one that's supposed to be active, activate
|
||||
# it and set the volume.
|
||||
if event.id == state_device:
|
||||
self.player_manager.set_current_device_id(
|
||||
self.app_config.state.current_device
|
||||
)
|
||||
self.player_manager.set_volume(self.app_config.state.volume)
|
||||
self.app_config.state.connecting_to_device = False
|
||||
# TODO actually add it
|
||||
print("ADDED", event)
|
||||
|
||||
elif event.delta == PlayerDeviceEvent.Delta.REMOVE:
|
||||
print("REMOVED", event)
|
||||
|
||||
self.update_window()
|
||||
|
||||
self.app_config.state.connecting_to_device = True
|
||||
|
||||
def check_if_connected():
|
||||
if not self.app_config.state.connecting_to_device:
|
||||
return
|
||||
print(
|
||||
f"Couldn't find device {self.app_config.state.current_device} in time."
|
||||
)
|
||||
self.app_config.state.current_device = "this device"
|
||||
self.player_manager.set_current_device_id(
|
||||
self.app_config.state.current_device
|
||||
)
|
||||
self.update_window()
|
||||
|
||||
self.player_manager = PlayerManager(
|
||||
on_timepos_change,
|
||||
@@ -274,15 +306,8 @@ class SublimeMusicApp(Gtk.Application):
|
||||
player_device_change_callback,
|
||||
self.app_config.player_config,
|
||||
)
|
||||
|
||||
if self.app_config.state.current_device != "this device":
|
||||
# TODO (#120) attempt to connect to the previously connected device
|
||||
pass
|
||||
|
||||
self.app_config.state.current_device = "this device"
|
||||
|
||||
# Need to do this after we set the current device.
|
||||
self.player.volume = self.app_config.state.volume
|
||||
self.player_manager.init_players()
|
||||
GLib.timeout_add(10000, check_if_connected)
|
||||
|
||||
# Update after Adapter Initial Sync
|
||||
inital_sync_result = AdapterManager.initial_sync()
|
||||
@@ -311,7 +336,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
connection,
|
||||
self.on_dbus_method_call,
|
||||
self.on_dbus_set_property,
|
||||
lambda: (self.app_config, self.player),
|
||||
lambda: (self.app_config, self.player_manager),
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -604,8 +629,8 @@ class SublimeMusicApp(Gtk.Application):
|
||||
|
||||
self.app_config.state.playing = not self.app_config.state.playing
|
||||
|
||||
if self.player.song_loaded:
|
||||
self.player.toggle_play()
|
||||
if self.player_manager.song_loaded:
|
||||
self.player_manager.toggle_play()
|
||||
self.save_play_queue()
|
||||
else:
|
||||
# This is from a restart, start playing the file.
|
||||
@@ -743,7 +768,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
if self.app_config.state.playing:
|
||||
self.on_play_pause()
|
||||
self.loading_state = True
|
||||
self.player.reset()
|
||||
self.player_manager.reset()
|
||||
AdapterManager.reset(self.app_config, self.on_song_download_progress)
|
||||
self.loading_state = False
|
||||
|
||||
@@ -838,20 +863,21 @@ class SublimeMusicApp(Gtk.Application):
|
||||
)
|
||||
|
||||
# If already playing, then make the player itself seek.
|
||||
if self.player and self.player.song_loaded:
|
||||
self.player.seek(new_time)
|
||||
if self.player_manager and self.player_manager.song_loaded:
|
||||
self.player_manager.seek(new_time)
|
||||
|
||||
self.save_play_queue()
|
||||
|
||||
def on_device_update(self, win: Any, device_uuid: str):
|
||||
assert self.player
|
||||
# TODO
|
||||
assert self.player_manager
|
||||
if device_uuid == self.app_config.state.current_device:
|
||||
return
|
||||
self.app_config.state.current_device = device_uuid
|
||||
|
||||
was_playing = self.app_config.state.playing
|
||||
self.player.pause()
|
||||
self.player._song_loaded = False
|
||||
self.player_manager.pause()
|
||||
self.player_manager._song_loaded = False
|
||||
self.app_config.state.playing = False
|
||||
|
||||
if self.dbus_manager:
|
||||
@@ -873,14 +899,14 @@ class SublimeMusicApp(Gtk.Application):
|
||||
@dbus_propagate()
|
||||
def on_mute_toggle(self, *args):
|
||||
self.app_config.state.is_muted = not self.app_config.state.is_muted
|
||||
self.player.is_muted = self.app_config.state.is_muted
|
||||
self.player_manager.set_muted(self.app_config.state.is_muted)
|
||||
self.update_window()
|
||||
|
||||
@dbus_propagate()
|
||||
def on_volume_change(self, _, value: float):
|
||||
assert self.player
|
||||
assert self.player_manager
|
||||
self.app_config.state.volume = value
|
||||
self.player.volume = self.app_config.state.volume
|
||||
self.player_manager.set_volume(self.app_config.state.volume)
|
||||
self.update_window()
|
||||
|
||||
def on_window_key_press(self, window: Gtk.Window, event: Gdk.EventKey) -> bool:
|
||||
@@ -926,13 +952,11 @@ class SublimeMusicApp(Gtk.Application):
|
||||
if self.app_config.provider is None:
|
||||
return
|
||||
|
||||
if self.player:
|
||||
self.player.pause()
|
||||
self.chromecast_player.shutdown()
|
||||
self.mpv_player.shutdown()
|
||||
if self.player_manager:
|
||||
self.player_manager.pause()
|
||||
self.player_manager.shutdown()
|
||||
|
||||
self.app_config.save()
|
||||
self.save_play_queue()
|
||||
if self.dbus_manager:
|
||||
self.dbus_manager.shutdown()
|
||||
AdapterManager.shutdown()
|
||||
@@ -973,7 +997,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
# prompt_confirm is False.
|
||||
if not prompt_confirm and self.app_config.state.playing:
|
||||
assert self.player
|
||||
self.player.pause()
|
||||
self.player_manager.pause()
|
||||
self.app_config.state.playing = False
|
||||
self.update_window()
|
||||
|
||||
@@ -1025,7 +1049,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
self.app_config.state.current_song_index = (
|
||||
play_queue.current_index or 0
|
||||
)
|
||||
self.player.reset()
|
||||
self.player_manager.reset()
|
||||
self.app_config.state.current_notification = None
|
||||
self.update_window()
|
||||
|
||||
@@ -1053,7 +1077,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
playable_song_search_direction: int = 1,
|
||||
):
|
||||
def do_reset():
|
||||
self.player.reset()
|
||||
self.player_manager.reset()
|
||||
self.app_config.state.song_progress = timedelta(0)
|
||||
self.should_scrobble_song = True
|
||||
|
||||
@@ -1061,7 +1085,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
# in the callback.
|
||||
@dbus_propagate(self)
|
||||
def do_play_song(order_token: int, song: Song):
|
||||
assert self.player
|
||||
assert self.player_manager
|
||||
if order_token != self.song_playing_order_token:
|
||||
return
|
||||
|
||||
@@ -1076,7 +1100,7 @@ class SublimeMusicApp(Gtk.Application):
|
||||
if order_token != self.song_playing_order_token:
|
||||
return
|
||||
|
||||
self.player.play_media(
|
||||
self.player_manager.play_media(
|
||||
uri,
|
||||
timedelta(0) if reset else self.app_config.state.song_progress,
|
||||
song,
|
||||
@@ -1163,9 +1187,10 @@ class SublimeMusicApp(Gtk.Application):
|
||||
# Switch to the local media if the player can hotswap without lag.
|
||||
# For example, MPV can is barely noticable whereas there's quite a
|
||||
# delay with Chromecast.
|
||||
assert self.player
|
||||
if self.player.can_hotswap_source:
|
||||
self.player.play_media(
|
||||
assert self.player_manager
|
||||
if self.player_manager.can_start_playing_with_no_latency:
|
||||
print(self.player_manager)
|
||||
self.player_manager.play_media(
|
||||
AdapterManager.get_song_filename_or_stream(song),
|
||||
self.app_config.state.song_progress,
|
||||
song,
|
||||
|
@@ -190,7 +190,7 @@ class DBusManager:
|
||||
|
||||
def property_dict(self) -> Dict[str, Any]:
|
||||
config, player_manager = self.get_config_and_player_manager()
|
||||
if config is None or player is None:
|
||||
if config is None or player_manager is None:
|
||||
return {}
|
||||
|
||||
state = config.state
|
||||
@@ -227,7 +227,7 @@ class DBusManager:
|
||||
(False, True): "Stopped",
|
||||
(True, False): "Paused",
|
||||
(True, True): "Playing",
|
||||
}[player is not None and player.song_loaded, state.playing],
|
||||
}[player_manager.song_loaded, state.playing],
|
||||
"LoopStatus": state.repeat_type.as_mpris_loop_status(),
|
||||
"Rate": 1.0,
|
||||
"Shuffle": state.shuffle_on,
|
||||
|
@@ -66,6 +66,18 @@ class PlayerEvent:
|
||||
device_id: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlayerDeviceEvent:
|
||||
class Delta(Enum):
|
||||
ADD = 0
|
||||
REMOVE = 1
|
||||
|
||||
delta: Delta
|
||||
player_type: Type
|
||||
id: str
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class Player(abc.ABC):
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
@@ -107,6 +119,7 @@ class Player(abc.ABC):
|
||||
on_timepos_change: Callable[[Optional[float]], None],
|
||||
on_track_end: Callable[[], None],
|
||||
on_player_event: Callable[[PlayerEvent], None],
|
||||
player_device_change_callback: Callable[[PlayerDeviceEvent], None],
|
||||
config: Dict[str, Union[str, int, bool]],
|
||||
):
|
||||
"""
|
||||
@@ -126,12 +139,6 @@ class Player(abc.ABC):
|
||||
Do any cleanup of the player.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_available_player_devices(self) -> Iterator[Tuple[str, str]]:
|
||||
"""
|
||||
:returns: an iterator of tuples containing the device ID and device name.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def playing(self) -> bool:
|
||||
|
@@ -23,7 +23,7 @@ from urllib.parse import urlparse
|
||||
from sublime.adapters import AdapterManager
|
||||
from sublime.adapters.api_objects import Song
|
||||
|
||||
from .base import Player, PlayerEvent
|
||||
from .base import Player, PlayerDeviceEvent, PlayerEvent
|
||||
|
||||
try:
|
||||
import pychromecast
|
||||
@@ -68,6 +68,7 @@ class ChromecastPlayer(Player):
|
||||
on_timepos_change: Callable[[Optional[float]], None],
|
||||
on_track_end: Callable[[], None],
|
||||
on_player_event: Callable[[PlayerEvent], None],
|
||||
player_device_change_callback: Callable[[PlayerDeviceEvent], None],
|
||||
config: Dict[str, Union[str, int, bool]],
|
||||
):
|
||||
self.server_process = None
|
||||
@@ -78,15 +79,33 @@ class ChromecastPlayer(Player):
|
||||
args=("0.0.0.0", self.config.get(LAN_PORT_KEY)),
|
||||
)
|
||||
|
||||
self._stop_retrieve_chromecasts = None
|
||||
if chromecast_imported:
|
||||
self._chromecasts: List[Any] = []
|
||||
self._chromecasts: Dict[str, pychromecast.Chromecast] = {}
|
||||
self._current_chromecast = pychromecast.Chromecast
|
||||
|
||||
def discovered_callback(chromecast: pychromecast.Chromecast):
|
||||
self._chromecasts[chromecast.device.uuid] = chromecast
|
||||
player_device_change_callback(
|
||||
PlayerDeviceEvent(
|
||||
PlayerDeviceEvent.Delta.ADD,
|
||||
type(self),
|
||||
str(chromecast.device.uuid),
|
||||
chromecast.device.friendly_name,
|
||||
)
|
||||
)
|
||||
|
||||
self._stop_retrieve_chromecasts = pychromecast.get_chromecasts(
|
||||
blocking=False, callback=discovered_callback
|
||||
)
|
||||
|
||||
def shutdown(self):
|
||||
if self._current_chromecast:
|
||||
self._current_chromecast.disconnect()
|
||||
if self.server_process:
|
||||
self.server_process.terminate()
|
||||
if self._stop_retrieve_chromecasts:
|
||||
self._stop_retrieve_chromecasts()
|
||||
|
||||
_serving_song_id = multiprocessing.Array("c", 1024) # huge buffer, just in case
|
||||
_serving_token = multiprocessing.Array("c", 12)
|
||||
@@ -121,14 +140,6 @@ class ChromecastPlayer(Player):
|
||||
|
||||
bottle.run(app, host=host, port=port)
|
||||
|
||||
def get_available_player_devices(self) -> Iterator[Tuple[str, str]]:
|
||||
if not chromecast_imported:
|
||||
return
|
||||
|
||||
self._chromecasts = pychromecast.get_chromecasts()
|
||||
for chromecast in self._chromecasts:
|
||||
yield (str(chromecast.device.uuid), chromecast.device.friendly_name)
|
||||
|
||||
@property
|
||||
def playing(self) -> bool:
|
||||
if (
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import multiprocessing
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from time import sleep
|
||||
@@ -17,27 +18,18 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from sublime.adapters.api_objects import Song
|
||||
|
||||
from .base import PlayerEvent
|
||||
from .base import PlayerDeviceEvent, PlayerEvent
|
||||
from .chromecast import ChromecastPlayer # noqa: F401
|
||||
from .mpv import MPVPlayer # noqa: F401
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlayerDeviceEvent:
|
||||
class Delta(Enum):
|
||||
ADD = 0
|
||||
REMOVE = 1
|
||||
|
||||
delta: Delta
|
||||
player_type: Type
|
||||
id: str
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class PlayerManager:
|
||||
# Available Players. Order matters for UI display.
|
||||
available_player_types: List[Type] = [MPVPlayer, ChromecastPlayer]
|
||||
# TODO
|
||||
# player_device_retrieval_process: Optional[multiprocessing.Process] = None
|
||||
|
||||
@staticmethod
|
||||
def get_configuration_options() -> Dict[
|
||||
@@ -64,62 +56,105 @@ class PlayerManager:
|
||||
self.on_timepos_change = on_timepos_change
|
||||
self.on_track_end = on_track_end
|
||||
self.on_player_event = on_player_event
|
||||
self.config = config
|
||||
self.players: Dict[Type, Any] = {}
|
||||
self.device_id_type_map: Dict[str, Type] = {}
|
||||
self._current_device_id = None
|
||||
|
||||
self.players = [
|
||||
player_type(
|
||||
on_timepos_change,
|
||||
on_track_end,
|
||||
on_player_event,
|
||||
config.get(player_type.name),
|
||||
def callback_wrapper(pde: PlayerDeviceEvent):
|
||||
self.device_id_type_map[pde.id] = pde.player_type
|
||||
player_device_change_callback(pde)
|
||||
|
||||
self.player_device_change_callback = callback_wrapper
|
||||
|
||||
# We have to have both init and and init_players so that by the time that any of the
|
||||
# players start calling the callback, the player manager exists on the app.
|
||||
def init_players(self):
|
||||
self.players = {
|
||||
player_type: player_type(
|
||||
self.on_timepos_change,
|
||||
self.on_track_end,
|
||||
self.on_player_event,
|
||||
self.player_device_change_callback,
|
||||
self.config.get(player_type.name),
|
||||
)
|
||||
for player_type in PlayerManager.available_player_types
|
||||
]
|
||||
}
|
||||
|
||||
self.device_id_type_map: Dict[str, Type] = {}
|
||||
self.player_device_change_callback = player_device_change_callback
|
||||
self.player_device_retrieval_process = multiprocessing.Process(
|
||||
target=self._retrieve_available_player_devices
|
||||
)
|
||||
has_done_one_retrieval = multiprocessing.Value("b", False)
|
||||
|
||||
def shutdown(self):
|
||||
print("SHUTDOWN PLAYER MANAGER")
|
||||
self.player_device_retrieval_process.terminate()
|
||||
pass
|
||||
|
||||
def _retrieve_available_player_devices(self):
|
||||
seen_ids = set()
|
||||
while True:
|
||||
new_ids = set()
|
||||
for t in PlayerManager.available_player_types:
|
||||
if not t.enabled:
|
||||
continue
|
||||
for device_id, device_name in t.get_available_player_devices():
|
||||
self.player_device_change_callback(
|
||||
PlayerDeviceEvent(
|
||||
PlayerDeviceEvent.Delta.ADD, t, device_id, device_name,
|
||||
)
|
||||
)
|
||||
new_ids.add((t, device_id))
|
||||
self.device_id_type_map[device_id] = t
|
||||
def _get_current_player_type(self) -> Any:
|
||||
if device_id := self._current_device_id:
|
||||
return self.device_id_type_map.get(device_id)
|
||||
|
||||
for t, device_id in seen_ids.difference(new_ids):
|
||||
self.player_device_change_callback(
|
||||
PlayerDeviceEvent(PlayerDeviceEvent.Delta.REMOVE, t, device_id)
|
||||
)
|
||||
del self.device_id_type_map[device_id]
|
||||
def _get_current_player(self) -> Any:
|
||||
if current_player_type := self._get_current_player_type():
|
||||
return self.players.get(current_player_type)
|
||||
|
||||
seen_ids = new_ids
|
||||
sleep(15)
|
||||
|
||||
def can_start_playing_with_no_latency(self, device_id: str) -> bool:
|
||||
return self.device_id_type_map[device_id].can_start_playing_with_no_latency
|
||||
|
||||
_current_device_id = None
|
||||
@property
|
||||
def can_start_playing_with_no_latency(self) -> bool:
|
||||
if self._current_device_id:
|
||||
return self._get_current_player_type().can_start_playing_with_no_latency
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def current_device_id(self) -> Optional[str]:
|
||||
return self._current_device_id
|
||||
|
||||
@current_device_id.setter
|
||||
def current_device_id(self, value: str):
|
||||
print("SET CURRENT DEVICE")
|
||||
self._current_device_id = value
|
||||
def set_current_device_id(self, device_id: str):
|
||||
logging.info(f"Setting current device id to '{device_id}'")
|
||||
self._current_device_id = device_id
|
||||
|
||||
def reset(self):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.reset()
|
||||
|
||||
@property
|
||||
def song_loaded(self) -> bool:
|
||||
if current_player := self._get_current_player():
|
||||
return current_player.song_loaded
|
||||
return False
|
||||
|
||||
@property
|
||||
def playing(self) -> bool:
|
||||
if current_player := self._get_current_player():
|
||||
return current_player.playing
|
||||
return False
|
||||
|
||||
def get_volume(self) -> float:
|
||||
if current_player := self._get_current_player():
|
||||
return current_player.get_volume()
|
||||
return 100
|
||||
|
||||
def set_volume(self, volume: float):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.set_volume(volume)
|
||||
|
||||
def get_is_muted(self) -> bool:
|
||||
if current_player := self._get_current_player():
|
||||
return current_player.get_is_muted()
|
||||
return False
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.set_muted(muted)
|
||||
|
||||
def play_media(self, uri: str, progress: timedelta, song: Song):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.play_media(uri, progress, song)
|
||||
|
||||
def pause(self):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.pause()
|
||||
|
||||
def toggle_play(self):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.toggle_play()
|
||||
|
||||
def seek(self, position: timedelta):
|
||||
if current_player := self._get_current_player():
|
||||
current_player.seek(position)
|
||||
|
@@ -17,7 +17,7 @@ import mpv
|
||||
|
||||
from sublime.adapters.api_objects import Song
|
||||
|
||||
from .base import Player, PlayerEvent
|
||||
from .base import Player, PlayerDeviceEvent, PlayerEvent
|
||||
|
||||
REPLAY_GAIN_KEY = "Replay Gain"
|
||||
|
||||
@@ -44,6 +44,7 @@ class MPVPlayer(Player):
|
||||
on_timepos_change: Callable[[Optional[float]], None],
|
||||
on_track_end: Callable[[], None],
|
||||
on_player_event: Callable[[PlayerEvent], None],
|
||||
player_device_change_callback: Callable[[PlayerDeviceEvent], None],
|
||||
config: Dict[str, Union[str, int, bool]],
|
||||
):
|
||||
self.mpv = mpv.MPV()
|
||||
@@ -75,6 +76,13 @@ class MPVPlayer(Player):
|
||||
)
|
||||
)
|
||||
|
||||
# Indicate to the UI that we exist.
|
||||
player_device_change_callback(
|
||||
PlayerDeviceEvent(
|
||||
PlayerDeviceEvent.Delta.ADD, type(self), "this device", "This Device"
|
||||
)
|
||||
)
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
@@ -83,9 +91,6 @@ class MPVPlayer(Player):
|
||||
with self._progress_value_lock:
|
||||
self._progress_value_count = 0
|
||||
|
||||
def get_available_player_devices(self) -> Iterator[Tuple[str, str]]:
|
||||
yield ("this device", "This Device")
|
||||
|
||||
@property
|
||||
def playing(self) -> bool:
|
||||
return not self.mpv.pause
|
||||
|
@@ -196,13 +196,14 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
# Main Settings
|
||||
self.notification_switch.set_active(app_config.song_play_notification)
|
||||
|
||||
# MPV Settings
|
||||
self.replay_gain_options.set_active_id(app_config.replay_gain.as_string())
|
||||
# TODO
|
||||
# # MPV Settings
|
||||
# self.replay_gain_options.set_active_id(app_config.replay_gain.as_string())
|
||||
|
||||
# Chromecast Settings
|
||||
self.serve_over_lan_switch.set_active(app_config.serve_over_lan)
|
||||
self.port_number_entry.set_value(app_config.port_number)
|
||||
self.port_number_entry.set_sensitive(app_config.serve_over_lan)
|
||||
# # Chromecast Settings
|
||||
# self.serve_over_lan_switch.set_active(app_config.serve_over_lan)
|
||||
# self.port_number_entry.set_value(app_config.port_number)
|
||||
# self.port_number_entry.set_sensitive(app_config.serve_over_lan)
|
||||
|
||||
# Download Settings
|
||||
allow_song_downloads = app_config.allow_song_downloads
|
||||
|
@@ -63,6 +63,7 @@ class UIState:
|
||||
song_progress: timedelta = timedelta()
|
||||
song_stream_cache_progress: Optional[timedelta] = timedelta()
|
||||
current_device: str = "this device"
|
||||
connecting_to_device: bool = False
|
||||
|
||||
# UI state
|
||||
current_tab: str = "albums"
|
||||
|
@@ -7,6 +7,7 @@ def test_init():
|
||||
empty_fn,
|
||||
empty_fn,
|
||||
empty_fn,
|
||||
empty_fn,
|
||||
{
|
||||
"Serve Local Files to Chromecasts on the LAN": True,
|
||||
"LAN Server Port Number": 6969,
|
||||
|
@@ -7,7 +7,7 @@ from sublime.players.mpv import MPVPlayer
|
||||
|
||||
def test_init():
|
||||
empty_fn = lambda *a, **k: None
|
||||
MPVPlayer(empty_fn, empty_fn, empty_fn, {"Replay Gain": "Disabled"})
|
||||
MPVPlayer(empty_fn, empty_fn, empty_fn, empty_fn, {"Replay Gain": "Disabled"})
|
||||
|
||||
|
||||
def is_close(expected: float, value: float, delta: float = 0.5) -> bool:
|
||||
@@ -16,7 +16,9 @@ def is_close(expected: float, value: float, delta: float = 0.5) -> bool:
|
||||
|
||||
def test_play():
|
||||
empty_fn = lambda *a, **k: None
|
||||
mpv_player = MPVPlayer(empty_fn, empty_fn, empty_fn, {"Replay Gain": "Disabled"})
|
||||
mpv_player = MPVPlayer(
|
||||
empty_fn, empty_fn, empty_fn, empty_fn, {"Replay Gain": "Disabled"}
|
||||
)
|
||||
|
||||
song_path = Path(__file__).parent.joinpath("mock_data/test-song.mp3")
|
||||
mpv_player.play_media(str(song_path), timedelta(seconds=10), None)
|
||||
|
Reference in New Issue
Block a user