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.
This commit is contained in:
parent
bec7071c48
commit
3cefa5d65c
@ -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…
x
Reference in New Issue
Block a user