diff --git a/screenstreamer/gui.py b/screenstreamer/gui.py index aee271d..3b33c26 100644 --- a/screenstreamer/gui.py +++ b/screenstreamer/gui.py @@ -75,9 +75,8 @@ class ScreenStreamerGUI(QtWidgets.QMainWindow): def stream_pressed(self): if self.streamer: # stream already running -> stopping - self.streamer = None - self.ui.statusbar.showMessage('stream stopped') - self.ui.streamBtn.setText('Stream') + self.streamer.stop() + self.ui.streamBtn.setEnabled(False) return displaydata = self.ui.displaySelect.currentData() @@ -88,18 +87,17 @@ class ScreenStreamerGUI(QtWidgets.QMainWindow): self.ui.protocolSelect.currentText().lower(), "{}:{}".format(self.ui.sendto.text(), self.ui.streamPort.value()) ) + self.streamer.process_manager.process_terminated.connect(self.stream_terminated) self.streamer.start() - self.ui.streamBtn.setText('Stop Stream') - # TODO: add feedback on error + self.set_stream_status(True) self.ui.statusbar.showMessage('stream started') @QtCore.Slot() def recieve_pressed(self): if self.receiver: - # stream already running -> stopping - self.receiver = None - self.ui.statusbar.showMessage('recieve stopped') - self.ui.recieveBtn.setText('Recieve') + # recieve already running -> stopping + self.receiver.stop() + self.ui.recieveBtn.setEnabled(False) return self.receiver = Reciever( @@ -107,7 +105,44 @@ class ScreenStreamerGUI(QtWidgets.QMainWindow): "{}:{}".format(self.ui.listenAddr.text(), self.ui.listenPort.value()), self.ui.disableAudio.checkState() == 'Checked' ) + self.receiver.process_manager.process_terminated.connect(self.recieve_terminated) self.receiver.start() - self.ui.recieveBtn.setText('Stop Recieve') - # TODO: add feedback on error + self.set_recieve_status(True) self.ui.statusbar.showMessage('recieve started') + + def set_recieve_status(self, status: bool): + """status = True - stream is running""" + self.ui.recieveBtn.setEnabled(True) + if status: + self.ui.recieveBtn.setText('Stop Recieve') + else: + self.ui.recieveBtn.setText('Recieve') + + def set_stream_status(self, status: bool): + """status = True - stream is running""" + self.ui.streamBtn.setEnabled(True) + if status: + self.ui.streamBtn.setText('Stop Stream') + else: + self.ui.streamBtn.setText('Stream') + + def show_error(self, tag: str, returncode: int, errormsg: str): + if errormsg: + error = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, tag + ' Error', tag + ' Error: ' + errormsg, QtWidgets.QMessageBox.Ok, self) + error.show() + error.exec() + + @QtCore.Slot(int, str) + def recieve_terminated(self, returncode, errormsg): + self.receiver = None + self.ui.statusbar.showMessage('recieve stopped') + self.show_error('Recieve', returncode, errormsg) + self.set_recieve_status(False) + + @QtCore.Slot(int, str) + def stream_terminated(self, returncode, errormsg): + self.streamer = None + self.ui.statusbar.showMessage('stream stopped') + self.show_error('Stream', returncode, errormsg) + self.set_stream_status(False) + diff --git a/screenstreamer/procmanager.py b/screenstreamer/procmanager.py new file mode 100644 index 0000000..7edf34b --- /dev/null +++ b/screenstreamer/procmanager.py @@ -0,0 +1,54 @@ +from PySide6.QtCore import QObject, Signal +from threading import Thread +import subprocess + +class ProcManager(QObject): + + process_terminated = Signal(int, str) + + def __init__(self) -> None: + super(ProcManager, self).__init__() + + self.thre = None + self.proc = None + + def start(self, proc): + # this method might override the old process and thread + self.proc = proc + + self.thre = Thread(target=self.detect_end) + self.thre.start() + + def stop(self): + if self.proc: + self.proc.terminate() + + try: + self.proc.wait(1) + except subprocess.TimeoutExpired as e: + self.proc.kill() + + if self.thre: + self.thre.join() + self.thre = None + + def detect_end(self): + # wait for the process to terminate + while True: + try: + self.proc.wait(1) + break + except subprocess.TimeoutExpired as e: + continue + + returncode = self.proc.returncode + errorstream = self.proc.stderr.read() + if isinstance(errorstream, bytes): + errorstream = errorstream.decode('utf-8') + + self.proc = None + + print('Process Terminated:', returncode, errorstream) + + self.process_terminated.emit(returncode, errorstream) + diff --git a/screenstreamer/reciever.py b/screenstreamer/reciever.py index 9c6d2fc..f8caef5 100644 --- a/screenstreamer/reciever.py +++ b/screenstreamer/reciever.py @@ -1,4 +1,5 @@ import subprocess +from procmanager import ProcManager class Reciever: @@ -6,7 +7,7 @@ class Reciever: self.protocol = protocol # should be 'tcp' or 'udp' self.target = target self.disable_audio = disable_audio - self.proc = None + self.process_manager = ProcManager() def __del__(self) -> None: self.stop() @@ -14,7 +15,7 @@ class Reciever: def start(self): # currently there is no direct ffplay support in the ffmpeg package - args = ['ffplay', '-fflags', 'nobuffer', '-flags', 'low_delay', '-f', 'mpegts'] + args = ['ffplay', '-hide_banner', '-loglevel', 'error', '-fflags', 'nobuffer', '-flags', 'low_delay', '-f', 'mpegts'] if self.disable_audio: args.append('-an') @@ -22,12 +23,9 @@ class Reciever: # the address args.append(self.protocol+ '://' + self.target + '?listen') - self.proc = subprocess.Popen(args) + proc = subprocess.Popen(args, encoding='utf-8', shell=False, text=True, stderr=subprocess.PIPE) + self.process_manager.start(proc) def stop(self): - if self.proc: - self.proc.terminate() - self.proc.wait(1) - self.proc.kill() - self.proc = None + self.process_manager.stop() diff --git a/screenstreamer/streamer.py b/screenstreamer/streamer.py index f57fe47..3bfca23 100644 --- a/screenstreamer/streamer.py +++ b/screenstreamer/streamer.py @@ -1,4 +1,5 @@ import ffmpeg +from procmanager import ProcManager class Streamer: @@ -8,14 +9,14 @@ class Streamer: self.audioindex = audioindex self.protocol = protocol # should be 'tcp' or 'udp' self.target = target - self.proc = None + self.process_manager = ProcManager() def __del__(self) -> None: self.stop() def start(self): streams = [] - self.input = ffmpeg.input(self.screendef, f='x11grab', r=60, s=self.screensize) + self.input = ffmpeg.input(self.screendef, f='x11grab', r=60, s=self.screensize, hide_banner='-y', loglevel='error') streams.append(self.input) if self.audioindex != -1: @@ -23,13 +24,10 @@ class Streamer: streams.append(self.audio) self.output = ffmpeg.output(*streams, self.protocol + '://' + self.target, f='mpegts', vcodec='nvenc_hevc', tune='zerolatency') - self.proc = ffmpeg.run_async(self.output) + proc = ffmpeg.run_async(self.output, pipe_stderr=True) + self.process_manager.start(proc) def stop(self): - if self.proc: - self.proc.terminate() - self.proc.wait(1) - self.proc.kill() - self.proc = None + self.process_manager.stop()