mirror of
https://github.com/markqvist/Sideband.git
synced 2025-12-22 06:07:06 +00:00
Use LXST to play audio message contents. Adds support for audio message playback on Windows and macOS.
This commit is contained in:
153
sbapp/main.py
153
sbapp/main.py
@@ -2422,116 +2422,66 @@ class SidebandApp(MDApp):
|
|||||||
self.compat_error_dialog.open()
|
self.compat_error_dialog.open()
|
||||||
|
|
||||||
def play_audio_field(self, audio_field):
|
def play_audio_field(self, audio_field):
|
||||||
if RNS.vendor.platformutils.is_darwin():
|
try:
|
||||||
if self.compat_error_dialog == None:
|
if self.msg_sound != None and self.msg_sound.playing:
|
||||||
def cb(sender):
|
RNS.log("Stopping playback", RNS.LOG_DEBUG)
|
||||||
self.compat_error_dialog.dismiss()
|
self.msg_sound.stop()
|
||||||
self.compat_error_dialog = MDDialog(
|
return
|
||||||
title="Unsupported Feature on macOS",
|
|
||||||
text="Audio message functionality is currently only implemented on Linux and Android. Please support the development if you need this feature on macOS.",
|
temp_path = None
|
||||||
buttons=[
|
if self.last_msg_audio != audio_field[1]:
|
||||||
MDRectangleFlatButton(
|
RNS.log("Reloading audio source", RNS.LOG_DEBUG)
|
||||||
text="OK",
|
if len(audio_field[1]) > 10: self.last_msg_audio = audio_field[1]
|
||||||
font_size=dp(18),
|
else:
|
||||||
on_release=cb
|
self.last_msg_audio = None
|
||||||
)
|
return
|
||||||
],
|
|
||||||
)
|
if audio_field[0] == LXMF.AM_OPUS_OGG:
|
||||||
self.compat_error_dialog.open()
|
temp_path = self.sideband.rec_cache+"/msg.ogg"
|
||||||
return
|
with open(temp_path, "wb") as af: af.write(self.last_msg_audio)
|
||||||
elif RNS.vendor.platformutils.is_windows():
|
|
||||||
if self.compat_error_dialog == None:
|
elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200:
|
||||||
def cb(sender):
|
temp_path = self.sideband.rec_cache+"/msg.ogg"
|
||||||
self.compat_error_dialog.dismiss()
|
from sideband.audioproc import samples_to_ogg, decode_codec2, detect_codec2
|
||||||
self.compat_error_dialog = MDDialog(
|
|
||||||
title="Unsupported Feature on Windows",
|
target_rate = 8000
|
||||||
text="Audio message functionality is currently only implemented on Linux and Android. Please support the development if you need this feature on Windows.",
|
if RNS.vendor.platformutils.is_linux(): target_rate = 48000
|
||||||
buttons=[
|
|
||||||
MDRectangleFlatButton(
|
if detect_codec2():
|
||||||
text="OK",
|
if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path, input_rate=8000, output_rate=target_rate): RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG)
|
||||||
font_size=dp(18),
|
else: RNS.log("OGG write failed", RNS.LOG_DEBUG)
|
||||||
on_release=cb
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
self.compat_error_dialog.open()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
temp_path = None
|
|
||||||
if self.last_msg_audio != audio_field[1]:
|
|
||||||
RNS.log("Reloading audio source", RNS.LOG_DEBUG)
|
|
||||||
if len(audio_field[1]) > 10:
|
|
||||||
self.last_msg_audio = audio_field[1]
|
|
||||||
else:
|
else:
|
||||||
self.last_msg_audio = None
|
self.last_msg_audio = None
|
||||||
|
self.display_codec2_error()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
else: raise NotImplementedError(audio_field[0])
|
||||||
|
|
||||||
if audio_field[0] == LXMF.AM_OPUS_OGG:
|
if self.msg_sound == None:
|
||||||
temp_path = self.sideband.rec_cache+"/msg.ogg"
|
from LXST.Primitives.Players import FilePlayer
|
||||||
with open(temp_path, "wb") as af:
|
self.msg_sound = FilePlayer()
|
||||||
af.write(self.last_msg_audio)
|
|
||||||
|
|
||||||
elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200:
|
self.msg_sound.set_source(temp_path)
|
||||||
temp_path = self.sideband.rec_cache+"/msg.ogg"
|
|
||||||
from sideband.audioproc import samples_to_ogg, decode_codec2, detect_codec2
|
|
||||||
|
|
||||||
target_rate = 8000
|
|
||||||
if RNS.vendor.platformutils.is_linux():
|
|
||||||
target_rate = 48000
|
|
||||||
|
|
||||||
if detect_codec2():
|
if self.msg_sound != None:
|
||||||
if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path, input_rate=8000, output_rate=target_rate):
|
RNS.log("Starting playback", RNS.LOG_DEBUG)
|
||||||
RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG)
|
self.msg_sound.play()
|
||||||
else:
|
else:
|
||||||
RNS.log("OGG write failed", RNS.LOG_DEBUG)
|
RNS.log("Playback was requested, but no audio data was loaded for playback", RNS.LOG_ERROR)
|
||||||
else:
|
|
||||||
self.last_msg_audio = None
|
|
||||||
self.display_codec2_error()
|
|
||||||
return
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(audio_field[0])
|
|
||||||
|
|
||||||
if self.msg_sound == None:
|
except Exception as e:
|
||||||
if RNS.vendor.platformutils.is_android():
|
RNS.log("Error while playing message audio:"+str(e))
|
||||||
from plyer import audio
|
RNS.trace_exception(e)
|
||||||
self.request_microphone_permission()
|
|
||||||
else:
|
|
||||||
from sbapp.plyer import audio
|
|
||||||
|
|
||||||
self.msg_sound = audio
|
|
||||||
|
|
||||||
self.msg_sound._file_path = temp_path
|
|
||||||
self.msg_sound.reload()
|
|
||||||
|
|
||||||
if self.msg_sound != None and self.msg_sound.playing():
|
|
||||||
RNS.log("Stopping playback", RNS.LOG_DEBUG)
|
|
||||||
self.msg_sound.stop()
|
|
||||||
else:
|
|
||||||
if self.msg_sound != None:
|
|
||||||
RNS.log("Starting playback", RNS.LOG_DEBUG)
|
|
||||||
self.msg_sound.play()
|
|
||||||
else:
|
|
||||||
RNS.log("Playback was requested, but no audio data was loaded for playback", RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
RNS.log("Error while playing message audio:"+str(e))
|
|
||||||
RNS.trace_exception(e)
|
|
||||||
|
|
||||||
def message_ptt_down_action(self, sender=None):
|
def message_ptt_down_action(self, sender=None):
|
||||||
if self.sideband.ui_recording:
|
if self.sideband.ui_recording: return
|
||||||
return
|
|
||||||
|
|
||||||
self.sideband.ui_started_recording()
|
self.sideband.ui_started_recording()
|
||||||
if self.sideband.config["hq_ptt"]:
|
if self.sideband.config["hq_ptt"]: self.audio_msg_mode = LXMF.AM_OPUS_OGG
|
||||||
self.audio_msg_mode = LXMF.AM_OPUS_OGG
|
else: self.audio_msg_mode = LXMF.AM_CODEC2_2400
|
||||||
else:
|
|
||||||
self.audio_msg_mode = LXMF.AM_CODEC2_2400
|
|
||||||
|
|
||||||
self.message_attach_action(attach_type="audio", nodialog=True)
|
self.message_attach_action(attach_type="audio", nodialog=True)
|
||||||
if self.rec_dialog == None:
|
if self.rec_dialog == None: self.message_init_rec_dialog()
|
||||||
self.message_init_rec_dialog()
|
|
||||||
self.rec_dialog.recording = True
|
self.rec_dialog.recording = True
|
||||||
el_button = self.messages_view.ids.message_ptt_button
|
el_button = self.messages_view.ids.message_ptt_button
|
||||||
el_icon = self.messages_view.ids.message_ptt_button.children[0].children[1]
|
el_icon = self.messages_view.ids.message_ptt_button.children[0].children[1]
|
||||||
@@ -2540,14 +2490,12 @@ class SidebandApp(MDApp):
|
|||||||
el_button.line_color=mdc("Orange","400")
|
el_button.line_color=mdc("Orange","400")
|
||||||
el_icon.theme_text_color="Custom"
|
el_icon.theme_text_color="Custom"
|
||||||
el_icon.text_color=mdc("Orange","400")
|
el_icon.text_color=mdc("Orange","400")
|
||||||
def cb(dt):
|
def cb(dt): self.msg_audio.start()
|
||||||
self.msg_audio.start()
|
|
||||||
Clock.schedule_once(cb, 0.15)
|
Clock.schedule_once(cb, 0.15)
|
||||||
|
|
||||||
|
|
||||||
def message_ptt_up_action(self, sender=None):
|
def message_ptt_up_action(self, sender=None):
|
||||||
if not self.sideband.ui_recording:
|
if not self.sideband.ui_recording: return
|
||||||
return
|
|
||||||
|
|
||||||
self.rec_dialog.recording = False
|
self.rec_dialog.recording = False
|
||||||
el_button = self.messages_view.ids.message_ptt_button
|
el_button = self.messages_view.ids.message_ptt_button
|
||||||
@@ -2558,8 +2506,7 @@ class SidebandApp(MDApp):
|
|||||||
el_icon.theme_text_color="Custom"
|
el_icon.theme_text_color="Custom"
|
||||||
el_icon.text_color=mdc("BlueGray","500")
|
el_icon.text_color=mdc("BlueGray","500")
|
||||||
def cb_s(dt):
|
def cb_s(dt):
|
||||||
try:
|
try: self.msg_audio.stop()
|
||||||
self.msg_audio.stop()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("An error occurred while stopping recording: "+str(e), RNS.LOG_ERROR)
|
RNS.log("An error occurred while stopping recording: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.trace_exception(e)
|
RNS.trace_exception(e)
|
||||||
|
|||||||
@@ -152,9 +152,9 @@ class SidebandCore():
|
|||||||
self.is_service = is_service
|
self.is_service = is_service
|
||||||
self.is_client = is_client
|
self.is_client = is_client
|
||||||
self.is_daemon = is_daemon
|
self.is_daemon = is_daemon
|
||||||
self.msg_audio = None
|
self.ptt_player = None
|
||||||
|
self.ptt_player_lock = threading.Lock()
|
||||||
self.last_msg_audio = None
|
self.last_msg_audio = None
|
||||||
self.ptt_playback_lock = threading.Lock()
|
|
||||||
self.ui_recording = False
|
self.ui_recording = False
|
||||||
self.db = None
|
self.db = None
|
||||||
self.db_lock = threading.Lock()
|
self.db_lock = threading.Lock()
|
||||||
@@ -4989,80 +4989,65 @@ class SidebandCore():
|
|||||||
def ptt_playback(self, message):
|
def ptt_playback(self, message):
|
||||||
ptt_timeout = 60
|
ptt_timeout = 60
|
||||||
event_time = time.time()
|
event_time = time.time()
|
||||||
while hasattr(self, "msg_sound") and self.msg_sound != None and self.msg_sound.playing() and time.time() < event_time+ptt_timeout:
|
with self.ptt_player_lock:
|
||||||
time.sleep(0.1)
|
time.sleep(0.2)
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
if self.msg_audio == None:
|
try:
|
||||||
if RNS.vendor.platformutils.is_android():
|
temp_path = None
|
||||||
from plyer import audio
|
audio_field = message.fields[LXMF.FIELD_AUDIO]
|
||||||
else:
|
if self.last_msg_audio != audio_field[1]:
|
||||||
from sbapp.plyer import audio
|
RNS.log("Reloading audio source", RNS.LOG_DEBUG)
|
||||||
|
if len(audio_field[1]) > 10: self.last_msg_audio = audio_field[1]
|
||||||
RNS.log("Audio init done")
|
|
||||||
self.msg_audio = audio
|
|
||||||
try:
|
|
||||||
temp_path = None
|
|
||||||
audio_field = message.fields[LXMF.FIELD_AUDIO]
|
|
||||||
if self.last_msg_audio != audio_field[1]:
|
|
||||||
RNS.log("Reloading audio source", RNS.LOG_DEBUG)
|
|
||||||
if len(audio_field[1]) > 10:
|
|
||||||
self.last_msg_audio = audio_field[1]
|
|
||||||
else:
|
|
||||||
self.last_msg_audio = None
|
|
||||||
return
|
|
||||||
|
|
||||||
if audio_field[0] == LXMF.AM_OPUS_OGG:
|
|
||||||
temp_path = self.rec_cache+"/msg.ogg"
|
|
||||||
with open(temp_path, "wb") as af:
|
|
||||||
af.write(self.last_msg_audio)
|
|
||||||
|
|
||||||
elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200:
|
|
||||||
temp_path = self.rec_cache+"/msg.ogg"
|
|
||||||
from sideband.audioproc import samples_to_ogg, decode_codec2, detect_codec2
|
|
||||||
|
|
||||||
target_rate = 8000
|
|
||||||
if RNS.vendor.platformutils.is_linux():
|
|
||||||
target_rate = 48000
|
|
||||||
|
|
||||||
if detect_codec2():
|
|
||||||
if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path, input_rate=8000, output_rate=target_rate):
|
|
||||||
RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG)
|
|
||||||
else:
|
|
||||||
RNS.log("OGG write failed", RNS.LOG_DEBUG)
|
|
||||||
else:
|
else:
|
||||||
self.last_msg_audio = None
|
self.last_msg_audio = None
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
if audio_field[0] == LXMF.AM_OPUS_OGG:
|
||||||
|
temp_path = self.rec_cache+"/ptt_msg.ogg"
|
||||||
|
with open(temp_path, "wb") as af: af.write(self.last_msg_audio)
|
||||||
|
|
||||||
|
elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200:
|
||||||
|
temp_path = self.rec_cache+"/ptt_msg.ogg"
|
||||||
|
from sideband.audioproc import samples_to_ogg, decode_codec2, detect_codec2
|
||||||
|
|
||||||
|
target_rate = 8000
|
||||||
|
if RNS.vendor.platformutils.is_linux(): target_rate = 48000
|
||||||
|
|
||||||
|
if detect_codec2():
|
||||||
|
if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path, input_rate=8000, output_rate=target_rate): RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG)
|
||||||
|
else: RNS.log("OGG write failed", RNS.LOG_DEBUG)
|
||||||
|
else:
|
||||||
|
self.last_msg_audio = None
|
||||||
|
return
|
||||||
|
|
||||||
# Unimplemented audio type
|
# Unimplemented audio type
|
||||||
pass
|
else: pass
|
||||||
|
|
||||||
self.msg_sound = self.msg_audio
|
if self.ptt_player == None:
|
||||||
self.msg_sound._file_path = temp_path
|
from LXST.Primitives.Players import FilePlayer
|
||||||
self.msg_sound.reload()
|
self.ptt_player = FilePlayer()
|
||||||
|
RNS.log("LXMF PTT audio init done", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
if self.msg_sound != None:
|
self.ptt_player.set_source(temp_path)
|
||||||
RNS.log("Starting playback", RNS.LOG_DEBUG)
|
|
||||||
self.msg_sound.play()
|
|
||||||
else:
|
|
||||||
RNS.log("Playback was requested, but no audio data was loaded for playback", RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
except Exception as e:
|
if self.ptt_player != None:
|
||||||
RNS.log("Error while playing message audio:"+str(e))
|
RNS.log("Starting playback", RNS.LOG_DEBUG)
|
||||||
RNS.trace_exception(e)
|
self.ptt_player.play()
|
||||||
|
while self.ptt_player.playing and time.time() < event_time+ptt_timeout: time.sleep(0.25)
|
||||||
|
if self.ptt_player.playing: self.ptt_player.stop()
|
||||||
|
time.sleep(0.2)
|
||||||
|
else:
|
||||||
|
RNS.log("Playback was requested, but no audio data was loaded for playback", RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while playing message audio:"+str(e))
|
||||||
|
RNS.trace_exception(e)
|
||||||
|
|
||||||
def ptt_event(self, message):
|
def ptt_event(self, message):
|
||||||
def ptt_job():
|
def ptt_job():
|
||||||
try:
|
while self.ui_recording: time.sleep(0.5)
|
||||||
self.ptt_playback_lock.acquire()
|
try: self.ptt_playback(message)
|
||||||
while self.ui_recording:
|
except Exception as e: RNS.log("Error while starting playback for PTT-enabled conversation: "+str(e), RNS.LOG_ERROR)
|
||||||
time.sleep(0.5)
|
|
||||||
self.ptt_playback(message)
|
|
||||||
except Exception as e:
|
|
||||||
RNS.log("Error while starting playback for PTT-enabled conversation: "+str(e), RNS.LOG_ERROR)
|
|
||||||
finally:
|
|
||||||
self.ptt_playback_lock.release()
|
|
||||||
|
|
||||||
threading.Thread(target=ptt_job, daemon=True).start()
|
threading.Thread(target=ptt_job, daemon=True).start()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user