Audio fixes and exception handling
This commit is contained in:
parent
b5a0820ac2
commit
75a9fea723
|
|
@ -1,5 +1,6 @@
|
|||
import vlc
|
||||
import time
|
||||
import threading
|
||||
|
||||
#instance = vlc.Instance("-vvv", "--no-video", "--repeat")
|
||||
#player = instance.media_player_new()
|
||||
|
|
@ -13,40 +14,90 @@ class AudioManager:
|
|||
vlc = None
|
||||
player = None
|
||||
media = None
|
||||
do_play = False
|
||||
monitor_thread = None
|
||||
volume = 50
|
||||
mute = False
|
||||
|
||||
def __init__(self):
|
||||
self.vlc = vlc.Instance('--no-video', '--repeat', '-v')
|
||||
self.vlc = vlc.Instance('--no-video', '--repeat')#, '-v')
|
||||
self.vlc_lock = threading.Lock()
|
||||
|
||||
def close(self):
|
||||
if do_play:
|
||||
self.stopRtspAudioStream()
|
||||
|
||||
if self.player:
|
||||
self.player.stop()
|
||||
self.player.release()
|
||||
|
||||
self.vlc.release()
|
||||
|
||||
def startRtspAudioStream(self, host, port):
|
||||
print('create media')
|
||||
self.media = self.vlc.media_new('rtsp://' + host + ':' + port + '/audio', 'network-caching=50')
|
||||
print('create new player')
|
||||
self.player = self.media.player_new_from_media()
|
||||
print('start player')
|
||||
self.player.play()
|
||||
def startRtspAudioStream(self, host, port) -> bool :
|
||||
with self.vlc_lock:
|
||||
try:
|
||||
print('create media')
|
||||
self.media = self.vlc.media_new('rtsp://' + host + ':' + str(port) + '/audio', 'network-caching=50')
|
||||
#media.add_options('input-repeat=65535') # NOTE: repeat leads to the error: 'main input error: INPUT_CONTROL_SET_POSITION 0.0% failed'
|
||||
print('create new player')
|
||||
self.player = self.media.player_new_from_media()
|
||||
print('start player')
|
||||
self.player.play()
|
||||
|
||||
self.player.audio_set_volume(self.volume)
|
||||
self.player.audio_set_mute(self.mute)
|
||||
|
||||
self.do_play = True
|
||||
self.monitor_thread = threading.Thread(target=self.monitor, name='audio monitor')
|
||||
self.monitor_thread.start()
|
||||
return True
|
||||
except:
|
||||
print('error starting stream')
|
||||
return False
|
||||
|
||||
def stopRtspAudioStream(self):
|
||||
print('stopping audio')
|
||||
if self.player:
|
||||
self.player.stop()
|
||||
self.player.release()
|
||||
self.player = None
|
||||
self.media.release()
|
||||
self.media = None
|
||||
|
||||
with self.vlc_lock:
|
||||
if self.do_play and self.player:
|
||||
self.do_play = False
|
||||
self.player.stop()
|
||||
self.player.release()
|
||||
self.player = None
|
||||
self.media.release()
|
||||
self.media = None
|
||||
|
||||
self.monitor_thread.join()
|
||||
self.monitor_thread = None
|
||||
print('stopped')
|
||||
|
||||
def setAudioVolume(self, level : int):
|
||||
self.player.audio_set_volume(level)
|
||||
with self.vlc_lock:
|
||||
self.volume = level
|
||||
self.player.audio_set_volume(self.volume)
|
||||
|
||||
def setAudioMute(self, mute : bool):
|
||||
self.player.audio_set_mute(mute)
|
||||
with self.vlc_lock:
|
||||
self.mute = mute
|
||||
self.player.audio_set_mute(self.mute)
|
||||
|
||||
# NOTE: this is a hacky way to maintain stream playback; for some reason using vlc's repeat parameter doesn't work
|
||||
def monitor(self):
|
||||
print('start stream monitor thread')
|
||||
while True:
|
||||
with self.vlc_lock:
|
||||
if not self.do_play:
|
||||
break
|
||||
state = self.player.get_state()
|
||||
if state == vlc.State.Ended or state == vlc.State.Error:
|
||||
print('attempting to restart stream')
|
||||
self.player.stop()
|
||||
self.player.release()
|
||||
self.player = self.media.player_new_from_media()
|
||||
self.player.play()
|
||||
self.player.audio_set_volume(self.volume)
|
||||
self.player.audio_set_mute(self.mute)
|
||||
time.sleep(0.01) # sleep 10ms
|
||||
print('exiting stream monitor')
|
||||
|
||||
if __name__ == '__main__':
|
||||
audio = AudioManager()
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ import google.protobuf.message as proto
|
|||
class PiScanClient(QWidget, common.AppInterface):
|
||||
dataReceived = Signal(bytes)
|
||||
manualDisconnectInitiated = False
|
||||
manualDisconnectMessage = None
|
||||
|
||||
def __init__(self, parent=None, address=None, port=None):
|
||||
def __init__(self, parent=None, address=None, port=None, use_audio=False, rtsp_port=None):
|
||||
super(PiScanClient, self).__init__(parent)
|
||||
common.setInstance(self)
|
||||
ui_file = 'scan_client.ui'
|
||||
|
|
@ -61,6 +62,7 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
|
||||
self.inmsg = messages_pb2.ServerToClient()
|
||||
|
||||
self.use_audio = use_audio
|
||||
self.audio = audio_manager.AudioManager()
|
||||
|
||||
def mainWidget(self):
|
||||
|
|
@ -76,7 +78,7 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
self.setWindowMode(common.WindowMode.DIALOG)
|
||||
|
||||
def receive(self):
|
||||
disconnectMessage = ''
|
||||
disconnectMessage = None
|
||||
|
||||
while True:
|
||||
try:
|
||||
|
|
@ -89,13 +91,25 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
if not self.manualDisconnectInitiated:
|
||||
disconnectMessage = 'Connection aborted'
|
||||
break
|
||||
except OSError as err:
|
||||
if not self.manualDisconnectInitiated:
|
||||
disconnectMessage = str(err)
|
||||
break
|
||||
except:
|
||||
e = sys.exc_info()[0]
|
||||
disconnectMessage = 'Unhandled exception: ' + str(e)
|
||||
break
|
||||
|
||||
print("Closing connection. Reason: " + disconnectMessage)
|
||||
self.audio.stopRtspAudioStream()
|
||||
if not disconnectMessage:
|
||||
disconnectMessage = self.manualDisconnectMessage
|
||||
|
||||
if disconnectMessage:
|
||||
print('Closing connection. Reason: ' + disconnectMessage)
|
||||
else:
|
||||
print('Disconnecting...')
|
||||
|
||||
if self.use_audio:
|
||||
self.audio.stopRtspAudioStream()
|
||||
self.manualDisconnectInitiated = False
|
||||
self.showConnectDialog(disconnectMessage)
|
||||
|
||||
|
|
@ -106,18 +120,18 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
def decodeMessage(self, message):
|
||||
#print(message)
|
||||
if message.WhichOneof('content') == 'systemInfo':
|
||||
print('system info')
|
||||
#print('system info')
|
||||
self.scanner.setSquelchRange(message.systemInfo.squelchScaleMin, message.systemInfo.squelchScaleMax)
|
||||
for mode in message.systemInfo.supportedModulations:
|
||||
self.dialogs.manualEntry.addModulation(mode)
|
||||
elif message.type == messages_pb2.ServerToClient.Type.Value('SCANNER_CONTEXT'):
|
||||
print('scanner context')
|
||||
#print('scanner context')
|
||||
self.scanner.updateScanContext(message.scannerContext)
|
||||
if self.contextWait:
|
||||
self.setWindowMode(common.WindowMode.SCANNER)
|
||||
self.contextWait = False
|
||||
elif message.type == messages_pb2.ServerToClient.Type.Value('DEMOD_CONTEXT'):
|
||||
print('demod context')
|
||||
#print('demod context')
|
||||
self.scanner.updateDemodContext(message.demodContext)
|
||||
#elif message.type == messages_pb2.ServerToClient.Type.Value('GENERAL_MESSAGE'):
|
||||
elif message.WhichOneof('content') == 'signalLevel':
|
||||
|
|
@ -134,7 +148,7 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
|
||||
def completeConnection(self, sock, host, use_audio, audio_port):
|
||||
self.sock = sock
|
||||
self.rcv_thread = Thread(target=self.receive)
|
||||
self.rcv_thread = Thread(target=self.receive, name='socket monitor')
|
||||
self.rcv_thread.start()
|
||||
self.contextWait = True
|
||||
self.connectDialog.contextWait()
|
||||
|
|
@ -142,34 +156,34 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
message1 = messages_pb2.ClientToServer()
|
||||
message1.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST')
|
||||
message1.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('SCANNER_CONTEXT')
|
||||
self.sock.send(message1.SerializeToString())
|
||||
self.sendData(message1.SerializeToString())
|
||||
sleep(0.25)
|
||||
message2 = messages_pb2.ClientToServer()
|
||||
message2.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST')
|
||||
message2.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('DEMOD_CONTEXT')
|
||||
self.sock.send(message2.SerializeToString())
|
||||
self.sendData(message2.SerializeToString())
|
||||
sleep(0.25)
|
||||
message3 = messages_pb2.ClientToServer()
|
||||
message3.type = messages_pb2.ClientToServer.Type.Value('GENERAL_REQUEST')
|
||||
message3.generalRequest.type = request_pb2.GeneralRequest.RequestType.Value('SYSTEM_INFO')
|
||||
self.sock.send(message3.SerializeToString())
|
||||
self.sendData(message3.SerializeToString())
|
||||
|
||||
if use_audio:
|
||||
self.audio.startRtspAudioStream(host, audio_port)
|
||||
self.setAudioVolume(50)
|
||||
self.scanner.setVolumeVisible(use_audio)
|
||||
self.use_audio = self.audio.startRtspAudioStream(host, audio_port)
|
||||
#self.setAudioVolume(50)
|
||||
self.scanner.setVolumeVisible(self.use_audio)
|
||||
|
||||
def scan(self):
|
||||
message = messages_pb2.ClientToServer()
|
||||
message.type = messages_pb2.ClientToServer.Type.Value('SCANNER_STATE_REQUEST')
|
||||
message.scanStateRequest.state = request_pb2.ScannerStateRequest.NewState.Value('SCAN')
|
||||
self.sock.send(message.SerializeToString())
|
||||
self.sendData(message.SerializeToString())
|
||||
|
||||
def hold(self):
|
||||
message = messages_pb2.ClientToServer()
|
||||
message.type = messages_pb2.ClientToServer.Type.Value('SCANNER_STATE_REQUEST')
|
||||
message.scanStateRequest.state = request_pb2.ScannerStateRequest.NewState.Value('HOLD')
|
||||
self.sock.send(message.SerializeToString())
|
||||
self.sendData(message.SerializeToString())
|
||||
|
||||
def manualEntry(self, frequency, modulation):
|
||||
message = messages_pb2.ClientToServer()
|
||||
|
|
@ -177,7 +191,7 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
message.scanStateRequest.state = request_pb2.ScannerStateRequest.NewState.Value('MANUAL')
|
||||
message.scanStateRequest.manFreq = frequency
|
||||
message.scanStateRequest.manModulation = modulation
|
||||
self.sock.send(message.SerializeToString())
|
||||
self.sendData(message.SerializeToString())
|
||||
|
||||
def showConnectDialog(self, errorMessage = ''):
|
||||
self.clearWindowTitleInfo()
|
||||
|
|
@ -205,20 +219,20 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
message.type = messages_pb2.ClientToServer.Type.Value('DEMOD_REQUEST')
|
||||
message.demodRequest.type = request_pb2.DemodRequest.DemodFunc.Value('SET_GAIN')
|
||||
message.demodRequest.level = value
|
||||
self.sock.send(message.SerializeToString())
|
||||
self.sendData(message.SerializeToString())
|
||||
|
||||
def setSquelch(self, value):
|
||||
message = messages_pb2.ClientToServer()
|
||||
message.type = messages_pb2.ClientToServer.Type.Value('DEMOD_REQUEST')
|
||||
message.demodRequest.type = request_pb2.DemodRequest.DemodFunc.Value('SET_SQUELCH')
|
||||
message.demodRequest.level = value
|
||||
self.sock.send(message.SerializeToString())
|
||||
self.sendData(message.SerializeToString())
|
||||
|
||||
def dialogClosed(self):
|
||||
self.setWindowMode(self.lastMode)
|
||||
|
||||
def tryConnect(self, address, port):
|
||||
self.connectDialog.tryConnect(address, port)
|
||||
def tryConnect(self, address, port, use_audio, rtsp_port):
|
||||
self.connectDialog.tryConnect(address, port, use_audio, rtsp_port)
|
||||
|
||||
def setWindowTitleInfo(self, message):
|
||||
if isinstance(self.parentWindow, QtWidgets.QMainWindow):
|
||||
|
|
@ -232,26 +246,37 @@ class PiScanClient(QWidget, common.AppInterface):
|
|||
else:
|
||||
self.setWindowTitle('PiScan')
|
||||
|
||||
def disconnect(self):
|
||||
def disconnect(self, message=''):
|
||||
self.manualDisconnectInitiated = True
|
||||
self.manualDisconnectMessage = message
|
||||
try:
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
#self.rcv_thread.join()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def setAudioVolume(self, level):
|
||||
self.audio.setAudioVolume(level)
|
||||
if self.use_audio:
|
||||
self.audio.setAudioVolume(level)
|
||||
|
||||
def setAudioMute(self, mute):
|
||||
self.audio.setAudioMute(mute)
|
||||
if self.use_audio:
|
||||
self.audio.setAudioMute(mute)
|
||||
|
||||
def sendData(self, message):
|
||||
try:
|
||||
self.sock.send(message)
|
||||
except OSError as err:
|
||||
self.disconnect('Connection error: ' + str(err))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
class HostWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self, parent=None, address=None, port=None):
|
||||
def __init__(self, parent=None, address=None, port=None, use_audio=False, rtsp_port=None):
|
||||
super(HostWindow, self).__init__(parent)
|
||||
|
||||
form = PiScanClient(self, address, port)
|
||||
form = PiScanClient(self, address, port, use_audio, rtsp_port)
|
||||
|
||||
mainWidget = form.mainWidget()
|
||||
self.setPalette(mainWidget.palette())
|
||||
|
|
@ -266,22 +291,24 @@ class HostWindow(QtWidgets.QMainWindow):
|
|||
if address:
|
||||
if not port:
|
||||
port = constants.DEFAULT_TCP_PORT
|
||||
form.tryConnect(address, port)
|
||||
form.tryConnect(address, port, use_audio, rtsp_port)
|
||||
|
||||
def closeEvent(self, event):
|
||||
print('exit')
|
||||
#print('exit')
|
||||
common.getApp().closeEvent(event)
|
||||
event.accept()
|
||||
|
||||
if __name__ == '__main__':
|
||||
shortOpts = 'la:p:w'
|
||||
longOpts = ['--local', '--address', '--port', '--pi_mode']
|
||||
shortOpts = 'la:p:wsr:'
|
||||
longOpts = ['--local', '--address', '--port', '--pi_mode', '--audio', '--rtsp_port']
|
||||
|
||||
options, remainder = getopt.getopt(sys.argv[1:], shortOpts, longOpts)
|
||||
|
||||
address = None
|
||||
port = None
|
||||
piMode = False
|
||||
use_audio = False
|
||||
rtsp_port = 8554
|
||||
|
||||
for opt, arg in options:
|
||||
if opt in ('-l', '--local'):
|
||||
|
|
@ -292,9 +319,13 @@ if __name__ == '__main__':
|
|||
port = int(arg)
|
||||
elif opt in ('-w', '--pi_mode'):
|
||||
piMode = True
|
||||
elif opt in ('-s', '--audio'):
|
||||
use_audio = True
|
||||
elif opt in ('-r', '--rtsp_port'):
|
||||
rtsp_port = int(arg)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
window = HostWindow(address=address, port=port)
|
||||
window = HostWindow(address=address, port=port, use_audio=use_audio, rtsp_port=rtsp_port)
|
||||
if piMode:
|
||||
flags = Qt.WindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint | Qt.Tool)
|
||||
window.setWindowFlags(flags)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class AppInterface:
|
|||
def closeEvent(self, event):
|
||||
pass
|
||||
|
||||
def tryConnect(self, address, port):
|
||||
def tryConnect(self, address, port, use_audio, rtsp_port):
|
||||
pass
|
||||
|
||||
def setWindowTitleInfo(self, message):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import common
|
|||
import constants
|
||||
|
||||
class ConnectDialog:
|
||||
def __init__(self, parentWindow, address=None, port=None):
|
||||
def __init__(self, parentWindow, address=None, port=None, use_audio=False, rtsp_port=None):
|
||||
self.widget = parentWindow.findChild(QWidget, 'connectPage')
|
||||
self.errorLabel = parentWindow.findChild(QLabel, 'connect_errorLabel')
|
||||
self.confirmButton = parentWindow.findChild(QPushButton, 'connect_confirmButton')
|
||||
|
|
@ -47,15 +47,19 @@ class ConnectDialog:
|
|||
def onConfirm(self):
|
||||
host = self.hostLineEdit.text()
|
||||
port = int(self.portLineEdit.text())
|
||||
self.tryConnect(host, port)
|
||||
audio = self.audioCheckBox.isChecked()
|
||||
rtsp_port = int(self.rtspPortLineEdit.text())
|
||||
self.tryConnect(host, port, audio, rtsp_port)
|
||||
|
||||
def tryConnect(self, address, port):
|
||||
def tryConnect(self, address, port, use_audio=False, rtsp_port=8554):
|
||||
print('connect confirm')
|
||||
try:
|
||||
self.connectIndicator.setVisible(True)
|
||||
self.errorLabel.setVisible(False)
|
||||
self.hostLineEdit.setText(address)
|
||||
self.portLineEdit.setText(str(port))
|
||||
self.audioCheckBox.setChecked(use_audio)
|
||||
self.rtspPortLineEdit.setText(str(rtsp_port))
|
||||
self.widget.repaint()
|
||||
|
||||
print('Connecting to ', address, ':', port)
|
||||
|
|
@ -67,10 +71,7 @@ class ConnectDialog:
|
|||
|
||||
self.connectIndicator.setVisible(False)
|
||||
|
||||
use_audio = self.audioCheckBox.isChecked()
|
||||
audio_port = self.rtspPortLineEdit.text()
|
||||
|
||||
common.getApp().completeConnection(sock, address, use_audio, audio_port)
|
||||
common.getApp().completeConnection(sock, address, use_audio, rtsp_port)
|
||||
except ConnectionRefusedError:
|
||||
self.connectFailed('Connect failed - Connection refused')
|
||||
except gaierror as err:
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@
|
|||
<item row="0" column="0">
|
||||
<widget class="QStackedWidget" name="contextStack">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="connectPage">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
|
|
@ -2324,7 +2324,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="scanner_scanModeLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ class Scanner:
|
|||
self.modulationLabel = parentWindow.findChild(QLabel, 'scanner_modulationLabel')
|
||||
self.systemTagLabel = parentWindow.findChild(QLabel, 'scanner_systemTagLabel')
|
||||
self.entryNumLabel = parentWindow.findChild(QLabel, 'scanner_entryNumLabel')
|
||||
self.delayLabel = parentWindow.findChild(QLabel, 'scanner_delayLabel')
|
||||
self.lockoutDurationLabel = parentWindow.findChild(QLabel, 'scanner_lockoutDurationLabel')
|
||||
self.scanModeLabel = parentWindow.findChild(QLabel, 'scanner_scanModeLabel')
|
||||
self.lockoutDurationButton = parentWindow.findChild(QPushButton, 'scanner_lockoutDurationButton')
|
||||
self.scanIndicator = parentWindow.findChild(QLabel, 'scanner_scanIndicator')
|
||||
self.gainSlider = parentWindow.findChild(QSlider, 'scanner_gainSlider')
|
||||
|
|
@ -69,7 +71,7 @@ class Scanner:
|
|||
self.volumeSlider.valueChanged.connect(self.onVolumeSlider)
|
||||
self.muteButton.toggled.connect(self.onMuteButton)
|
||||
|
||||
#temporary since settins dialog is not yet implemented
|
||||
# TODO TEMPORARY - hiding UI elements for features not added yet
|
||||
self.volumeControlPanel.setVisible(False)
|
||||
self.lockoutDurationButton.setVisible(False)
|
||||
self.lockoutDurationLabel.setVisible(False)
|
||||
|
|
@ -77,6 +79,9 @@ class Scanner:
|
|||
self.settingsButton.setVisible(False)
|
||||
self.connectInfoButton.setVisible(False)
|
||||
self.entryEditButton.setVisible(False)
|
||||
self.delayLabel.setVisible(False)
|
||||
self.scanModeLabel.setVisible(False)
|
||||
parentWindow.findChild(QWidget, 'line_4').setVisible(False)
|
||||
|
||||
movie = QMovie("resources/bar-scan.gif")
|
||||
movie.start()
|
||||
|
|
|
|||
Loading…
Reference in New Issue