Updated player.py

cleaned some code ig? made a BasePlayer so you can inherit it and build a completely custom one.
This commit is contained in:
Crussader 2021-10-20 20:54:36 +04:00 committed by GitHub
parent 7b83a9fc69
commit d81f81e0d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 109 additions and 90 deletions

View File

@ -1,46 +1,36 @@
import time import time
from typing import ( from typing import Any, Dict, Optional, Type, Union
Any,
Dict,
Optional
)
from discord import ( import discord
Guild, from discord import Client, Guild, VoiceChannel, VoiceProtocol
VoiceChannel,
VoiceProtocol
)
from discord.ext import commands from discord.ext import commands
from . import events from . import events
from .enums import SearchType from .enums import SearchType
from .events import PomiceEvent, TrackStartEvent
from .exceptions import TrackInvalidPosition from .exceptions import TrackInvalidPosition
from .filters import Filter from .filters import Filter
from .objects import Track
from .pool import Node, NodePool from .pool import Node, NodePool
from .utils import ClientType from .objects import Track
from .utils import MISSING
class BasePlayer(VoiceProtocol):
class Player(VoiceProtocol): """Base Player Class For Pomice This Class Has to Be Inherited if your are Building
"""The base player class for Pomice. your own Player Class, Unless you know what you are doing.
In order to initiate a player, you must pass it in as a cls when you connect to a channel.
i.e: ```py
await ctx.author.voice.channel.connect(cls=pomice.Player)
```
""" """
def __init__(self, client: ClientType, channel: VoiceChannel): def __call__(self, client: discord.Client, channel: VoiceChannel):
super().__init__(client=client, channel=channel) self.client: discord.Client = client
self.channel : VoiceChannel = channel
def __init__(self, client : Type[Client] = MISSING, channel: VoiceChannel = MISSING):
self.client = client self.client = client
self._bot = client self._bot = client
self.channel = channel self.channel = channel
self._guild: Guild = self.channel.guild self._guild : Guild = channel.guild
self._node = NodePool.get_node() self._node = NodePool.get_node()
self._current: Track = None
self._filter: Filter = None
self._volume = 100 self._volume = 100
self._paused = False self._paused = False
self._is_connected = False self._is_connected = False
@ -48,38 +38,11 @@ class Player(VoiceProtocol):
self._position = 0 self._position = 0
self._last_position = 0 self._last_position = 0
self._last_update = 0 self._last_update = 0
self._ending_track: Optional[Track] = None
self._voice_state = {} self._voice_state = {}
def __repr__(self): def __repr__(self) -> str:
return ( return f"<{self.__class__.__name__}(bot={self._bot}, guildId={self._guild.id})>"
f"<Pomice.player bot={self.bot} guildId={self.guild.id} "
f"is_connected={self.is_connected} is_playing={self.is_playing}>"
)
@property
def position(self) -> float:
"""Property which returns the player's position in a track in milliseconds"""
if not self.is_playing or not self.current:
return 0
if self.is_paused:
return min(self._last_position, self.current.length)
difference = (time.time() * 1000) - self._last_update
position = self._last_position + difference
if position > self.current.length:
return 0
return min(position, self.current.length)
@property
def is_playing(self) -> bool:
"""Property which returns whether or not the player is actively playing a track."""
return self._is_connected and self.current is not None
@property @property
def is_connected(self) -> bool: def is_connected(self) -> bool:
@ -90,12 +53,7 @@ class Player(VoiceProtocol):
def is_paused(self) -> bool: def is_paused(self) -> bool:
"""Property which returns whether or not the player has a track which is paused or not.""" """Property which returns whether or not the player has a track which is paused or not."""
return self._is_connected and self._paused return self._is_connected and self._paused
@property
def current(self) -> Track:
"""Property which returns the currently playing track"""
return self._current
@property @property
def node(self) -> Node: def node(self) -> Node:
"""Property which returns the node the player is connected to""" """Property which returns the node the player is connected to"""
@ -112,12 +70,7 @@ class Player(VoiceProtocol):
return self._volume return self._volume
@property @property
def filter(self) -> Filter: def bot(self) -> Type[Client]:
"""Property which returns the currently applied filter, if one is applied"""
return self._filter
@property
def bot(self) -> ClientType:
"""Property which returns the bot associated with this player instance""" """Property which returns the bot associated with this player instance"""
return self._bot return self._bot
@ -158,29 +111,20 @@ class Player(VoiceProtocol):
async def _dispatch_event(self, data: dict): async def _dispatch_event(self, data: dict):
event_type = data.get("type") event_type = data.get("type")
event: PomiceEvent = getattr(events, event_type)(data) if _track := data.get("track", None):
event.dispatch(self._bot) track = await self._node.build_track(_track)
if isinstance(event, TrackStartEvent): _events = {
self._ending_track = self._current "TrackStartEvent" : (self, track),
"TrackEndEvent" : (self, track, data.get("reason", None)),
async def get_tracks( "TrackExceptionEvent" : (self, track, data.get("error", None)),
self, "TrackStuckEvent" : (self, track, data.get("thresholdMs", None)),
query: str, "WebSocketOpenEvent": (data.get("target", None), data.get("ssrc", None)),
*, "WebSocketClosedEvent" : (self._guild, data.get("reason", None), data.get("code", None))
ctx: Optional[commands.Context] = None, }
search_type: SearchType = SearchType.ytsearch if (event := getattr(events, event_type, None)) and (args := _events.get(event_type, None)):
):
"""Fetches tracks from the node's REST api to parse into Lavalink. self.bot.dispatch(f"pomice_{event.name}", event, *args)
If you passed in Spotify API credentials when you created the node,
you can also pass in a Spotify URL of a playlist, album or track and it will be parsed
accordingly.
You can also pass in a discord.py Context object to get a
Context object on any track you search.
"""
return await self._node.get_tracks(query, ctx=ctx, search_type=search_type)
async def connect(self, *, timeout: float, reconnect: bool): async def connect(self, *, timeout: float, reconnect: bool):
await self.guild.change_voice_state(channel=self.channel) await self.guild.change_voice_state(channel=self.channel)
@ -205,6 +149,81 @@ class Player(VoiceProtocol):
await self.disconnect() await self.disconnect()
await self._node.send(op="destroy", guildId=str(self.guild.id)) await self._node.send(op="destroy", guildId=str(self.guild.id))
class Player(BasePlayer):
"""The Default Basic Player class for Pomice.
In order to initiate a player, you must pass it in as a cls when you connect to a channel.
i.e: ```py
await ctx.author.voice.channel.connect(cls=pomice.Player)
```
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._current: Track = None
self._filter: Filter = None
def __repr__(self):
return (
f"<Pomice.player bot={self.bot} guildId={self.guild.id} "
f"is_connected={self.is_connected} is_playing={self.is_playing}>"
)
@property
def position(self) -> float:
"""Property which returns the player's position in a track in milliseconds"""
if not self.is_playing or not self.current:
return 0
if self.is_paused:
return min(self._last_position, self.current.length)
difference = (time.time() * 1000) - self._last_update
position = self._last_position + difference
if position > self.current.length:
return 0
return min(position, self.current.length)
@property
def is_playing(self) -> bool:
"""Property which returns whether or not the player is actively playing a track."""
return self._is_connected and self.current is not None
@property
def current(self) -> Track:
"""Property which returns the currently playing track"""
return self._current
@property
def filter(self) -> Filter:
"""Property which returns the currently applied filter, if one is applied"""
return self._filter
async def get_tracks(
self,
query: str,
*,
ctx: Optional[commands.Context] = None,
search_type: SearchType = SearchType.ytsearch
):
"""Fetches tracks from the node's REST api to parse into Lavalink.
If you passed in Spotify API credentials when you created the node,
you can also pass in a Spotify URL of a playlist, album or track and it will be parsed
accordingly.
You can also pass in a discord.py Context object to get a
Context object on any track you search.
"""
return await self._node.get_tracks(query, ctx=ctx, search_type=search_type)
async def play(self, track: Track, *, start_position: int = 0) -> Track: async def play(self, track: Track, *, start_position: int = 0) -> Track:
"""Plays a track. If a Spotify track is passed in, it will be handled accordingly.""" """Plays a track. If a Spotify track is passed in, it will be handled accordingly."""
if track.spotify: if track.spotify: