aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2017-04-19 17:20:10 -0400
committerRapptz <[email protected]>2017-04-19 17:23:39 -0400
commitf5cfc96aafb14349a942fe392ee062773cf4d2a5 (patch)
tree4a0eea7622e4e75183c25cd9005453c585016e2f
parentAdd VoiceClient.is_paused to query pause state. (diff)
downloaddiscord.py-f5cfc96aafb14349a942fe392ee062773cf4d2a5.tar.xz
discord.py-f5cfc96aafb14349a942fe392ee062773cf4d2a5.zip
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.
-rw-r--r--discord/player.py58
-rw-r--r--discord/voice_client.py15
-rw-r--r--docs/api.rst3
3 files changed, 72 insertions, 4 deletions
diff --git a/discord/player.py b/discord/player.py
index 4385a6df..96f52550 100644
--- a/discord/player.py
+++ b/discord/player.py
@@ -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()
diff --git a/discord/voice_client.py b/discord/voice_client.py
index 64dd6538..e1e6fce7 100644
--- a/discord/voice_client.py
+++ b/discord/voice_client.py
@@ -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.
diff --git a/docs/api.rst b/docs/api.rst
index 2177a270..eb287121 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -55,6 +55,9 @@ Voice
.. autoclass:: FFmpegPCMAudio
:members:
+.. autoclass:: PCMVolumeTransformer
+ :members:
+
Opus Library
~~~~~~~~~~~~~