From 3cefa5d65c464c0abe7486b0689397614ec42c7b Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 11 Dec 2015 01:36:41 -0500 Subject: [PATCH] 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. --- discord/voice_client.py | 48 ++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/discord/voice_client.py b/discord/voice_client.py index cf8a510dc..442b359df 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -70,10 +70,14 @@ class StreamPlayer(threading.Thread): def run(self): self.loops = 0 self._start = time.time() - while not self.is_done(): + while not self._end.is_set(): if self._paused.is_set(): continue + if not self._connected.is_set(): + self.stop() + break + self.loops += 1 data = self.buff.read(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): 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: """Represents a Discord voice connection. @@ -318,7 +332,7 @@ class VoiceClient: struct.pack_into('>I', buff, 8, self.ssrc) 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 audio. @@ -342,11 +356,17 @@ class VoiceClient: Parameters ----------- - filename : str + filename 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`` 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 The finalizer that is called after the stream is done being played. All exceptions the finalizer throws are silently discarded. @@ -363,17 +383,21 @@ class VoiceClient: See :meth:`create_stream_player`. """ command = 'ffmpeg' if not use_avconv else 'avconv' - cmd = '{} -i "{}" -f s16le -ar {} -ac {} -loglevel warning pipe:1' - cmd = cmd.format(command, filename, self.encoder.sampling_rate, self.encoder.channels) + input_name = '-' if pipe else shlex.quote(filename) + 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: - process = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE) - except Exception as e: + p = subprocess.Popen(args, stdin=stdin, stdout=subprocess.PIPE) + return ProcessPlayer(p, self, after) + except subprocess.SubprocessError as 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)