mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-03 10:32:42 +00:00
Add PCMVolumeTransformer to augment volume of a PCM stream.
This also introduces the idea of replacing the VoiceClient.source on the fly. Note that this internally pauses and resumes the audio stream.
This commit is contained in:
parent
07d5328873
commit
f5cfc96aaf
@ -26,13 +26,14 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import threading
|
||||
import subprocess
|
||||
import audioop
|
||||
import shlex
|
||||
import time
|
||||
|
||||
from .errors import ClientException
|
||||
from .opus import Encoder as OpusEncoder
|
||||
|
||||
__all__ = [ 'AudioSource', 'PCMAudio', 'FFmpegPCMAudio' ]
|
||||
__all__ = [ 'AudioSource', 'PCMAudio', 'FFmpegPCMAudio', 'PCMVolumeTransformer' ]
|
||||
|
||||
class AudioSource:
|
||||
"""Represents an audio stream.
|
||||
@ -169,6 +170,51 @@ class FFmpegPCMAudio(AudioSource):
|
||||
if proc.poll() is None:
|
||||
proc.communicate()
|
||||
|
||||
class PCMVolumeTransformer(AudioSource):
|
||||
"""Transforms a previous :class:`AudioSource` to have volume controls.
|
||||
|
||||
This does not work on audio sources that have :meth:`AudioSource.is_opus`
|
||||
set to ``True``.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
original: :class:`AudioSource`
|
||||
The original AudioSource to transform.
|
||||
|
||||
Raises
|
||||
-------
|
||||
TypeError
|
||||
Not an audio source.
|
||||
ClientException
|
||||
The audio source is opus encoded.
|
||||
"""
|
||||
|
||||
def __init__(self, original):
|
||||
if not isinstance(original, AudioSource):
|
||||
raise TypeError('expected AudioSource not {0.__class__.__name__}.'.format(original))
|
||||
|
||||
if original.is_opus():
|
||||
raise ClientException('AudioSource must not be Opus encoded.')
|
||||
|
||||
self.original = original
|
||||
self._volume = 1.0
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
"""Retrieves or sets the volume as a floating point percentage (e.g. 1.0 for 100%)."""
|
||||
return self._volume
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value):
|
||||
self._volume = max(value, 0.0)
|
||||
|
||||
def cleanup(self):
|
||||
self.original.cleanup()
|
||||
|
||||
def read(self):
|
||||
ret = self.original.read()
|
||||
return audioop.mul(ret, 2, min(self._volume, 2.0))
|
||||
|
||||
class AudioPlayer(threading.Thread):
|
||||
DELAY = OpusEncoder.FRAME_LENGTH / 1000.0
|
||||
|
||||
@ -184,6 +230,7 @@ class AudioPlayer(threading.Thread):
|
||||
self._resumed.set() # we are not paused
|
||||
self._current_error = None
|
||||
self._connected = client._connected
|
||||
self._lock = threading.Lock()
|
||||
|
||||
if after is not None and not callable(after):
|
||||
raise TypeError('Expected a callable for the "after" parameter.')
|
||||
@ -191,7 +238,6 @@ class AudioPlayer(threading.Thread):
|
||||
def _do_run(self):
|
||||
self.loops = 0
|
||||
self._start = time.time()
|
||||
is_opus = self.source.is_opus()
|
||||
|
||||
# getattr lookup speed ups
|
||||
play_audio = self.client.send_audio_packet
|
||||
@ -217,7 +263,7 @@ class AudioPlayer(threading.Thread):
|
||||
self.stop()
|
||||
break
|
||||
|
||||
play_audio(data, encode=not is_opus)
|
||||
play_audio(data, encode=not self.source.is_opus())
|
||||
next_time = self._start + self.DELAY * self.loops
|
||||
delay = max(0, self.DELAY + (next_time - time.time()))
|
||||
time.sleep(delay)
|
||||
@ -255,3 +301,9 @@ class AudioPlayer(threading.Thread):
|
||||
|
||||
def is_paused(self):
|
||||
return not self._end.is_set() and not self._resumed.is_set()
|
||||
|
||||
def _set_source(self, source):
|
||||
with self._lock:
|
||||
self.pause()
|
||||
self.source = source
|
||||
self.resume()
|
||||
|
@ -359,9 +359,22 @@ class VoiceClient:
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
"""Optional[:class:`AudioSource`]: The audio source being played, if playing."""
|
||||
"""Optional[:class:`AudioSource`]: The audio source being played, if playing.
|
||||
|
||||
This property can also be used to change the audio source currently being played.
|
||||
"""
|
||||
return self._player.source if self._player else None
|
||||
|
||||
@source.setter
|
||||
def source(self, value):
|
||||
if not isinstance(value, AudioSource):
|
||||
raise TypeError('expected AudioSource not {0.__class__.__name__}.'.format(value))
|
||||
|
||||
if self._player is None:
|
||||
raise ValueError('Not playing anything.')
|
||||
|
||||
self._player._set_source(value)
|
||||
|
||||
def send_audio_packet(self, data, *, encode=True):
|
||||
"""Sends an audio packet composed of the data.
|
||||
|
||||
|
@ -55,6 +55,9 @@ Voice
|
||||
.. autoclass:: FFmpegPCMAudio
|
||||
:members:
|
||||
|
||||
.. autoclass:: PCMVolumeTransformer
|
||||
:members:
|
||||
|
||||
Opus Library
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user