From 5aead6aec6e19eda3e46bfbb6d233fffb435d025 Mon Sep 17 00:00:00 2001 From: ooliver1 Date: Sun, 23 Oct 2022 00:12:31 +0100 Subject: [PATCH] feat: add player controls --- mafic/errors.py | 6 ++++ mafic/filter.py | 4 +++ mafic/node.py | 14 ++++---- mafic/player.py | 95 ++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/mafic/errors.py b/mafic/errors.py index 5a4e68d..cf744c0 100644 --- a/mafic/errors.py +++ b/mafic/errors.py @@ -7,6 +7,7 @@ "MaficException", "MultipleCompatibleLibraries", "NoCompatibleLibraries", + "PlayerNotConnected", "TrackLoadException", ) @@ -38,3 +39,8 @@ def __init__(self, libraries: list[str]) -> None: class TrackLoadException(MaficException): def __init__(self, *, message: str, severity: str) -> None: super().__init__(f"The track could not be loaded: {message} ({severity} error)") + + +class PlayerNotConnected(MaficException): + def __init__(self) -> None: + super().__init__("The player is not connected to a voice channel.") diff --git a/mafic/filter.py b/mafic/filter.py index 3560b8b..5b11a2e 100644 --- a/mafic/filter.py +++ b/mafic/filter.py @@ -26,6 +26,7 @@ "ChannelMix", "Distortion", "EQBand", + "Filter", "Karaoke", "LowPass", "Rotation", @@ -284,3 +285,6 @@ def __iand__(self, other: Any) -> None: self.channel_mix = self.channel_mix or other.channel_mix self.low_pass = self.low_pass or other.low_pass self.volume = self.volume or other.volume + + +# TODO: people like easy default filters, add some default EQ and combo filters diff --git a/mafic/node.py b/mafic/node.py index 8441fe3..67a0345 100644 --- a/mafic/node.py +++ b/mafic/node.py @@ -350,17 +350,17 @@ def play( self, *, guild_id: int, - track: str, - start_time: int | None, - end_time: int | None, - volume: int | None, - no_replace: bool | None, - pause: bool | None, + track: Track, + start_time: int | None = None, + end_time: int | None = None, + volume: int | None = None, + no_replace: bool | None = None, + pause: bool | None = None, ) -> Coro[None]: data: PlayPayload = { "op": "play", "guildId": str(guild_id), - "track": track, + "track": track.id, } if start_time is not None: diff --git a/mafic/player.py b/mafic/player.py index a3ebcf5..0b22246 100644 --- a/mafic/player.py +++ b/mafic/player.py @@ -2,11 +2,16 @@ from __future__ import annotations +from collections import OrderedDict +from functools import reduce from logging import getLogger +from operator import or_ from time import time from typing import TYPE_CHECKING from .__libraries import GuildChannel, StageChannel, VoiceChannel, VoiceProtocol +from .errors import PlayerNotConnected +from .filter import Filter from .playlist import Playlist from .pool import NodePool from .search_type import SearchType @@ -67,6 +72,7 @@ def __init__( self._last_update: int = 0 self._ping = 0 self._current: Track | None = None + self._filters: OrderedDict[str, Filter] = OrderedDict() @property def connected(self) -> bool: @@ -215,11 +221,84 @@ async def fetch_tracks( return await node.fetch_tracks(query, search_type=raw_type) - # TODO: controls: - # TODO: play - # TODO: pause - # TODO: stop - # TODO: filter - # TODO: volume - # TODO: seek - # TODO: pause + async def play( + self, + track: Track, + /, + *, + start_time: int | None = None, + end_time: int | None = None, + volume: int | None = None, + replace: bool = True, + pause: bool | None = None, + ) -> None: + if self._node is None or not self._connected: + raise PlayerNotConnected + + await self._node.play( + guild_id=self._guild_id, + track=track, + start_time=start_time, + end_time=end_time, + volume=volume, + no_replace=not replace, + pause=pause, + ) + + self._current = track + + async def pause(self, pause: bool = True) -> None: + if self._node is None or not self._connected: + raise PlayerNotConnected + + await self._node.pause(guild_id=self._guild_id, pause=pause) + + async def resume(self) -> None: + await self.pause(False) + + async def stop(self) -> None: + if self._node is None or not self._connected: + raise PlayerNotConnected + + await self._node.stop(guild_id=self._guild_id) + + async def _update_filters(self, *, fast_apply: bool) -> None: + if self._node is None or not self._connected: + raise PlayerNotConnected + + await self._node.filter( + guild_id=self._guild_id, filter=reduce(or_, self._filters.values()) + ) + + if fast_apply: + await self.seek(self.position) + + async def add_filter( + self, filter: Filter, /, *, label: str, fast_apply: bool = False + ) -> None: + self._filters[label] = filter + + await self._update_filters(fast_apply=True) + + async def remove_filter(self, label: str, *, fast_apply: bool = False) -> None: + self._filters.pop(label, None) + + await self._update_filters(fast_apply=True) + + async def clear_filters(self, *, fast_apply: bool = False) -> None: + self._filters.clear() + + await self._update_filters(fast_apply=True) + + async def set_volume(self, volume: int, /) -> None: + if self._node is None or not self._connected: + raise PlayerNotConnected + + await self._node.volume(guild_id=self._guild_id, volume=volume) + + async def seek(self, position: int, /) -> None: + if self._node is None or not self._connected: + raise PlayerNotConnected + + await self._node.seek(guild_id=self._guild_id, position=position) + self._position = position