mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-07 12:18:59 +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 threading
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import audioop
|
||||||
import shlex
|
import shlex
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .errors import ClientException
|
from .errors import ClientException
|
||||||
from .opus import Encoder as OpusEncoder
|
from .opus import Encoder as OpusEncoder
|
||||||
|
|
||||||
__all__ = [ 'AudioSource', 'PCMAudio', 'FFmpegPCMAudio' ]
|
__all__ = [ 'AudioSource', 'PCMAudio', 'FFmpegPCMAudio', 'PCMVolumeTransformer' ]
|
||||||
|
|
||||||
class AudioSource:
|
class AudioSource:
|
||||||
"""Represents an audio stream.
|
"""Represents an audio stream.
|
||||||
@ -169,6 +170,51 @@ class FFmpegPCMAudio(AudioSource):
|
|||||||
if proc.poll() is None:
|
if proc.poll() is None:
|
||||||
proc.communicate()
|
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):
|
class AudioPlayer(threading.Thread):
|
||||||
DELAY = OpusEncoder.FRAME_LENGTH / 1000.0
|
DELAY = OpusEncoder.FRAME_LENGTH / 1000.0
|
||||||
|
|
||||||
@ -184,6 +230,7 @@ class AudioPlayer(threading.Thread):
|
|||||||
self._resumed.set() # we are not paused
|
self._resumed.set() # we are not paused
|
||||||
self._current_error = None
|
self._current_error = None
|
||||||
self._connected = client._connected
|
self._connected = client._connected
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
if after is not None and not callable(after):
|
if after is not None and not callable(after):
|
||||||
raise TypeError('Expected a callable for the "after" parameter.')
|
raise TypeError('Expected a callable for the "after" parameter.')
|
||||||
@ -191,7 +238,6 @@ class AudioPlayer(threading.Thread):
|
|||||||
def _do_run(self):
|
def _do_run(self):
|
||||||
self.loops = 0
|
self.loops = 0
|
||||||
self._start = time.time()
|
self._start = time.time()
|
||||||
is_opus = self.source.is_opus()
|
|
||||||
|
|
||||||
# getattr lookup speed ups
|
# getattr lookup speed ups
|
||||||
play_audio = self.client.send_audio_packet
|
play_audio = self.client.send_audio_packet
|
||||||
@ -217,7 +263,7 @@ class AudioPlayer(threading.Thread):
|
|||||||
self.stop()
|
self.stop()
|
||||||
break
|
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
|
next_time = self._start + self.DELAY * self.loops
|
||||||
delay = max(0, self.DELAY + (next_time - time.time()))
|
delay = max(0, self.DELAY + (next_time - time.time()))
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
@ -255,3 +301,9 @@ class AudioPlayer(threading.Thread):
|
|||||||
|
|
||||||
def is_paused(self):
|
def is_paused(self):
|
||||||
return not self._end.is_set() and not self._resumed.is_set()
|
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
|
@property
|
||||||
def source(self):
|
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
|
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):
|
def send_audio_packet(self, data, *, encode=True):
|
||||||
"""Sends an audio packet composed of the data.
|
"""Sends an audio packet composed of the data.
|
||||||
|
|
||||||
|
@ -55,6 +55,9 @@ Voice
|
|||||||
.. autoclass:: FFmpegPCMAudio
|
.. autoclass:: FFmpegPCMAudio
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: PCMVolumeTransformer
|
||||||
|
:members:
|
||||||
|
|
||||||
Opus Library
|
Opus Library
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user