IDRB/client.py
2023-12-16 13:21:42 +07:00

185 lines
8.0 KiB
Python

import asyncio
from datetime import datetime
import dearpygui.dearpygui as dpg
import threading
import socket
import numpy as np
import pickle
import pyaudio
import websockets
from pyogg import OpusDecoder
class App:
def __init__(self):
self.RDS = None
self.device_name_output = "Speakers (4- USB Audio DAC )"
self.working = False
def connecttoserver(self, sender, data):
dpg.configure_item("connectbutton", show=False)
protocol = dpg.get_value("serverprotocol")
dpg.configure_item("serverstatus", default_value='connecting...', color=(255, 255, 0))
if protocol == "Websocket":
asyncio.create_task(self.WSstream())
return
ip = dpg.get_value("serverip")
port = dpg.get_value("serverport")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
except:
dpg.configure_item("connectbutton", show=True)
self.working = True
p = pyaudio.PyAudio()
device_index_output = 0
for i in range(p.get_device_count()):
dev = p.get_device_info_by_index(i)
if dev['name'] == self.device_name_output:
device_index_output = dev['index']
break
thread = threading.Thread(target=self.stream, args=(s, device_index_output))
thread.start()
def disconnectserver(self, sender, data):
dpg.configure_item("disconnectbutton", show=False)
dpg.configure_item("serverstatus", default_value='disconnecting...', color=(255, 255, 0))
self.working = False
def RDSshow(self):
try:
dpg.configure_item("RDSinfo",
default_value=f'{self.RDS["PS"]} ({self.RDS["ContentInfo"]["Codec"]} {self.RDS["ContentInfo"]["bitrate"] / 1000}Kbps {self.RDS["AudioMode"]})',
show=True)
dpg.configure_item("RDSPS", default_value="PS: " + self.RDS["PS"])
dpg.configure_item("RDSRT", default_value="RT: " + self.RDS["RT"])
dpg.configure_item("RDSCTlocal", default_value="Time Local: " + datetime.fromtimestamp(self.RDS["CT"]["Local"]).strftime('%H:%M:%S'))
dpg.configure_item("RDSCTUTC", default_value="Time UTC: " + datetime.fromtimestamp(self.RDS["CT"]["UTC"]).strftime('%H:%M:%S'))
dpg.configure_item("RDSListener", default_value="Listener: " + str(self.RDS["Listener"]) + " Users")
except Exception as e:
pass
def stream(self, socket, deviceindex):
opus_decoder = OpusDecoder()
streamoutput = None
firstrun = True
while True:
try:
if self.working:
data = socket.recv(650000)
if len(data) == 0:
dpg.configure_item("serverstatus", default_value='lost connected', color=(255, 0, 0))
socket.close()
dpg.configure_item("showRDS", show=False)
dpg.configure_item("RDSinfo", show=False)
dpg.configure_item("disconnectbutton", show=False)
dpg.configure_item("connectbutton", show=True)
break
try:
datadecoded = pickle.loads(data)
except:
pass
if datadecoded["RDS"] != self.RDS:
self.RDS = datadecoded["RDS"]
rdshow = threading.Thread(target=self.RDSshow)
rdshow.start()
if firstrun:
p = pyaudio.PyAudio()
opus_decoder.set_channels(self.RDS["ContentInfo"]["channel"])
opus_decoder.set_sampling_frequency(self.RDS["ContentInfo"]["samplerates"])
streamoutput = p.open(format=pyaudio.paInt16, channels=self.RDS["ContentInfo"]["channel"], rate=self.RDS["ContentInfo"]["samplerates"], output=True, output_device_index=deviceindex)
dpg.configure_item("showRDS", show=True)
dpg.configure_item("serverstatus", default_value='connected', color=(0, 255, 0))
dpg.configure_item("disconnectbutton", show=True)
firstrun = False
decoded_pcm = opus_decoder.decode(memoryview(bytearray(datadecoded["Content"])))
# Check if the decoded PCM is empty or not
if len(decoded_pcm) > 0:
pcm_to_write = np.frombuffer(decoded_pcm, dtype=np.int16)
streamoutput.write(pcm_to_write.tobytes())
else:
print("Decoded PCM is empty")
else:
streamoutput.close()
socket.close()
self.working = False
dpg.configure_item("showRDS", show=False)
dpg.configure_item("RDSinfo", show=False)
dpg.configure_item("connectbutton", show=True)
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
break
except Exception as e:
if str(e) == "An error occurred while decoding an Opus-encoded packet: corrupted stream":
dpg.configure_item("serverstatus", default_value="Unable to decode audio data", color=(255, 0, 0))
else:
print("connection lost", e)
self.working = False
streamoutput.close()
socket.close()
dpg.configure_item("showRDS", show=False)
dpg.configure_item("RDSinfo", show=False)
dpg.configure_item("connectbutton", show=True)
dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0))
break
def window(self):
with dpg.window(label="IDRB", width=320):
dpg.add_text("", tag="RDSinfo", show=False)
dpg.add_input_text(label="server ip", tag="serverip", default_value="localhost")
dpg.add_input_int(label="port", tag="serverport", max_value=65535, default_value=6980)
dpg.add_combo(["TCP", "Websocket"], label="protocol", tag="serverprotocol", default_value="TCP")
dpg.add_button(label="connect", callback=self.connecttoserver, tag="connectbutton")
dpg.add_button(label="disconnect", callback=self.disconnectserver, tag="disconnectbutton", show=False)
dpg.add_spacer()
dpg.add_text("not connect", tag="serverstatus", color=(255, 0, 0))
dpg.add_spacer()
with dpg.collapsing_header(label="RDS", show=False, tag="showRDS"):
dpg.add_text("PS: ...", tag="RDSPS")
dpg.add_text("RT: ...", tag="RDSRT")
dpg.add_text("Listener: ...", tag="RDSListener")
dpg.add_text("Time Local: ...", tag="RDSCTlocal")
dpg.add_text("Time UTC: ...", tag="RDSCTUTC")
def init(self):
dpg.create_context()
dpg.create_viewport(title='[ThaiSDR] IDRB (Internet Digital Radio Broadcasting) V1 Beta', width=1280, height=720, small_icon="favicon.ico") # set viewport window
dpg.setup_dearpygui()
# -------------- add code here --------------
self.window()
# -------------------------------------------
dpg.show_viewport()
# Start a separate thread for a task
self.thread_stop_event = threading.Event()
while dpg.is_dearpygui_running():
self.render()
dpg.render_dearpygui_frame()
# Signal the thread to stop and wait for it to finish
self.thread_stop_event.set()
dpg.destroy_context()
def render(self):
# insert here any code you would like to run in the render loop
# you can manually stop by using stop_dearpygui() or self.exit()
pass
def exit(self):
dpg.destroy_context()
app = App()
app.init()