Files
sublime-music/sublime_music/players/mpv.py
t11230 f13007587d Addressing MR comments
- Inverted conditional in PlayerManager.play_media to flatten code some
- Simplified check for gapless_playback in MPVPlayer
- Removed left-over code in SublimeMusicApp.on_track_end
- Changed song-details lookups for gapless playback to always defer
- Updated on_song_download_complete to use new next_song_index property
2022-01-10 21:41:29 -05:00

160 lines
4.7 KiB
Python

import threading
from datetime import timedelta
from typing import Callable, cast, Dict, Optional, Tuple, Type, Union
import mpv
from .base import Player, PlayerDeviceEvent, PlayerEvent
from ..adapters.api_objects import Song
REPLAY_GAIN_KEY = "Replay Gain"
GAPLESS_PLAYBACK_KEY = "Gapless Playback"
class MPVPlayer(Player):
enabled = True
name = "Local Playback"
can_start_playing_with_no_latency = True
supported_schemes = {"http", "https", "file"}
song_loaded = False
_progress_value_lock = threading.Lock()
_progress_value_count = 0
_volume = 100.0
_muted = False
_is_mock = False
@staticmethod
def get_configuration_options() -> Dict[str, Union[Type, Tuple[str, ...]]]:
return {
REPLAY_GAIN_KEY: ("Disabled", "Track", "Album"),
GAPLESS_PLAYBACK_KEY: ("Disabled", "Enabled"),
}
def __init__(
self,
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()
if MPVPlayer._is_mock:
self.mpv.audio_device = "null"
self.mpv.audio_client_name = "sublime-music"
self.change_settings(config)
@self.mpv.property_observer("time-pos")
def time_observer(_, value: Optional[float]):
on_timepos_change(value)
if value is None and self._progress_value_count > 1:
on_track_end()
with self._progress_value_lock:
self._progress_value_count = 0
if value:
with self._progress_value_lock:
self._progress_value_count += 1
@self.mpv.property_observer("demuxer-cache-time")
def cache_size_observer(_, value: Optional[float]):
on_player_event(
PlayerEvent(
PlayerEvent.EventType.STREAM_CACHE_PROGRESS_CHANGE,
"this device",
stream_cache_duration=value,
)
)
# Indicate to the UI that we exist.
player_device_change_callback(
PlayerDeviceEvent(
PlayerDeviceEvent.Delta.ADD, type(self), "this device", "This Device"
)
)
def change_settings(self, config: Dict[str, Union[str, int, bool]]):
self.config = config
self.mpv.replaygain = {
"Disabled": "no",
"Track": "track",
"Album": "album",
}.get(cast(str, config.get(REPLAY_GAIN_KEY, "Disabled")), "no")
def refresh_players(self):
# Don't do anything
pass
def set_current_device_id(self, device_id: str):
# Don't do anything beacuse it should always be the "this device" ID.
pass
def shutdown(self):
pass
def reset(self):
self.song_loaded = False
with self._progress_value_lock:
self._progress_value_count = 0
@property
def playing(self) -> bool:
return not self.mpv.pause
@property
def gapless_playback(self) -> bool:
return self.config.get(GAPLESS_PLAYBACK_KEY) == "Enabled"
def get_volume(self) -> float:
return self._volume
def set_volume(self, volume: float):
if not self._muted:
self.mpv.volume = volume
self._volume = volume
def get_is_muted(self) -> bool:
return self._muted
def set_muted(self, muted: bool):
self.mpv.volume = 0 if muted else self._volume
self._muted = muted
def play_media(self, uri: str, progress: timedelta, song: Song):
with self._progress_value_lock:
self._progress_value_count = 0
# Clears everything except the currently-playing song
self.mpv.command("playlist-clear")
options = {
"force-seekable": "yes",
"start": str(progress.total_seconds()),
}
self.mpv.command(
"loadfile", uri, "replace", ",".join(f"{k}={v}" for k, v in options.items())
)
self.mpv.pause = False
self.song_loaded = True
def pause(self):
self.mpv.pause = True
def play(self):
self.mpv.pause = False
def seek(self, position: timedelta):
self.mpv.seek(str(position.total_seconds()), "absolute")
def next_media_cached(self, uri: str, song: Song):
if not self.gapless_playback:
return
# Ensure the only 2 things in the playlist are the current song
# and the next song for gapless playback
self.mpv.command("playlist-clear")
self.mpv.command("loadfile", uri, "append")