Browse Source

ffmpeg process is now properly killed.

Two new options are added to the ffmpeg player. `options` and `pipe`.
If `pipe` is True then we can pass in a file-like object to be the
stdin of ffmpeg. `options` allows you to pass extra things to the
ffmpeg command line.
pull/57/head
Rapptz 9 years ago
parent
commit
3cefa5d65c
  1. 48
      discord/voice_client.py

48
discord/voice_client.py

@ -70,10 +70,14 @@ class StreamPlayer(threading.Thread):
def run(self): def run(self):
self.loops = 0 self.loops = 0
self._start = time.time() self._start = time.time()
while not self.is_done(): while not self._end.is_set():
if self._paused.is_set(): if self._paused.is_set():
continue continue
if not self._connected.is_set():
self.stop()
break
self.loops += 1 self.loops += 1
data = self.buff.read(self.frame_size) data = self.buff.read(self.frame_size)
log.info('received {} bytes (out of {})'.format(len(data), self.frame_size)) log.info('received {} bytes (out of {})'.format(len(data), self.frame_size))
@ -108,6 +112,16 @@ class StreamPlayer(threading.Thread):
def is_done(self): def is_done(self):
return not self._connected.is_set() or self._end.is_set() return not self._connected.is_set() or self._end.is_set()
class ProcessPlayer(StreamPlayer):
def __init__(self, process, client, after, **kwargs):
super().__init__(process.stdout, client.encoder,
client._connected, client.play_audio, after, **kwargs)
self.process = process
def stop(self):
self.process.kill()
super().stop()
class VoiceClient: class VoiceClient:
"""Represents a Discord voice connection. """Represents a Discord voice connection.
@ -318,7 +332,7 @@ class VoiceClient:
struct.pack_into('>I', buff, 8, self.ssrc) struct.pack_into('>I', buff, 8, self.ssrc)
return buff return buff
def create_ffmpeg_player(self, filename, *, use_avconv=False, after=None): def create_ffmpeg_player(self, filename, *, use_avconv=False, pipe=False, options=None, after=None):
"""Creates a stream player for ffmpeg that launches in a separate thread to play """Creates a stream player for ffmpeg that launches in a separate thread to play
audio. audio.
@ -342,11 +356,17 @@ class VoiceClient:
Parameters Parameters
----------- -----------
filename : str filename
The filename that ffmpeg will take and convert to PCM bytes. The filename that ffmpeg will take and convert to PCM bytes.
This is passed to the ``-i`` flag that ffmpeg takes. If ``pipe`` is True then this is a file-like object that is
passed to the stdin of ``ffmpeg``.
use_avconv: bool use_avconv: bool
Use ``avconv`` instead of ``ffmpeg``. Use ``avconv`` instead of ``ffmpeg``.
pipe : bool
If true, denotes that ``filename`` parameter will be passed
to the stdin of ffmpeg.
options: str
Extra command line flags to pass to ``ffmpeg``.
after : callable after : callable
The finalizer that is called after the stream is done being The finalizer that is called after the stream is done being
played. All exceptions the finalizer throws are silently discarded. played. All exceptions the finalizer throws are silently discarded.
@ -363,17 +383,21 @@ class VoiceClient:
See :meth:`create_stream_player`. See :meth:`create_stream_player`.
""" """
command = 'ffmpeg' if not use_avconv else 'avconv' command = 'ffmpeg' if not use_avconv else 'avconv'
cmd = '{} -i "{}" -f s16le -ar {} -ac {} -loglevel warning pipe:1' input_name = '-' if pipe else shlex.quote(filename)
cmd = cmd.format(command, filename, self.encoder.sampling_rate, self.encoder.channels) cmd = command + ' -i {} -f s16le -ar {} -ac {} -loglevel warning pipe:1'
cmd = cmd.format(input_name, self.encoder.sampling_rate, self.encoder.channels)
if isinstance(options, str):
cmd = cmd + ' ' + options
stdin = None if not pipe else filename
args = shlex.split(cmd)
try: try:
process = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE) p = subprocess.Popen(args, stdin=stdin, stdout=subprocess.PIPE)
except Exception as e: return ProcessPlayer(p, self, after)
except subprocess.SubprocessError as e:
raise ClientException('Popen failed: {0.__name__} {1}'.format(type(e), str(e))) raise ClientException('Popen failed: {0.__name__} {1}'.format(type(e), str(e)))
def killer():
process.kill()
if callable(after):
after()
return StreamPlayer(process.stdout, self.encoder, self._connected, self.play_audio, killer) return StreamPlayer(process.stdout, self.encoder, self._connected, self.play_audio, killer)

Loading…
Cancel
Save