diff --git a/Client/__pycache__/appcomponent.cpython-310.pyc b/Client/__pycache__/appcomponent.cpython-310.pyc index 89146bb..bfe064c 100644 Binary files a/Client/__pycache__/appcomponent.cpython-310.pyc and b/Client/__pycache__/appcomponent.cpython-310.pyc differ diff --git a/Client/__pycache__/utils.cpython-310.pyc b/Client/__pycache__/utils.cpython-310.pyc index 23aa5d9..a8ee467 100644 Binary files a/Client/__pycache__/utils.cpython-310.pyc and b/Client/__pycache__/utils.cpython-310.pyc differ diff --git a/Client/appcomponent.py b/Client/appcomponent.py index 2fd8dcb..f26a6b6 100644 --- a/Client/appcomponent.py +++ b/Client/appcomponent.py @@ -1,3 +1,20 @@ +""" +This file is part of IDRB Project. + +IDRB Project is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IDRB Project is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IDRB Project. If not, see . +""" + import dearpygui.dearpygui as dpg from utils import * @@ -60,7 +77,7 @@ def window(self): dpg.add_spacer() dpg.add_text("IDRB (Internet Digital Radio Broadcasting System) Client") dpg.add_spacer() - dpg.add_text(f"IDRB Client v1.6.1 Beta") + dpg.add_text(f"IDRB Client v1.6.2 Beta") dpg.add_spacer() desc = "IDRB is a novel internet radio broadcasting alternative that uses HLS/DASH/HTTP streams, transferring over TCP/IP. This system supports images and RDS (Dynamic update) capabilities, enabling the transmission of station information. Additionally, it allows for setting station logos and images. IDRB offers multi-broadcasting functionalities and currently supports the Opus codec, with plans to incorporate PCM, MP2/3, AAC/AAC+, and more in the future, ensuring low delay. If you find this project intriguing, you can support it at damp11113.xyz/support." @@ -148,8 +165,10 @@ def window(self): dpg.add_text("Please restart software when configured") with dpg.tab_bar(): with dpg.tab(label="Audio"): - dpg.add_combo([], label="Output Device", tag="selectaudiooutputdevicecombo", - callback=self.changeaudiodevice) + dpg.add_combo([], label="Output Device", tag="selectaudiooutputdevicecombo", callback=self.changeaudiodevice) + with dpg.tab(label="Network"): + dpg.add_input_int(label="Buffer Size", tag="buffersizeintinput", callback=self.changebuffersize) + def menubar(self): with dpg.viewport_menu_bar(): diff --git a/Client/client.py b/Client/client.py index eeff736..74a63e5 100644 --- a/Client/client.py +++ b/Client/client.py @@ -40,8 +40,7 @@ class App: self.config = configparser.ConfigParser() self.config.read("config.ini") self.device_name_output = self.config["audio"]["device"] - self.buffersize = 64 # can configable - + self.buffersize = self.config["network"]["buffersize"] self.working = False self.readchannel = 1 @@ -60,8 +59,6 @@ class App: self.lsitem = None self.ccconwithpubselect = False self.buffer = queue.Queue(maxsize=self.buffersize) - self.okbuffer = False - self.firstrunbuffer = True def connecttoserverwithpubselect(self, sender, data): self.ccconwithpubselect = True @@ -125,8 +122,6 @@ class App: dpg.configure_item("station_logo_config", show=False) dpg.configure_item("RDSinfo", show=False) dpg.configure_item("disconnectbutton", show=False) - dpg.configure_item("connectservergroup", show=True) - dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0)) dpg.configure_item("logostatus", show=False) self.firstrun = True self.firststart = True @@ -136,9 +131,7 @@ class App: self.ccisdecryptpassword = None self.cciswaitlogoim = True self.ccthreadlogorecisworking = False - self.buffer = queue.Queue(maxsize=self.buffersize) - self.okbuffer = False - self.firstrunbuffer = True + # clear buffer def RDSshow(self): try: @@ -231,6 +224,11 @@ class App: self.config["audio"]["device"] = dpg.get_value(sender) self.config.write(open('config.ini', 'w')) + def changebuffersize(self, sender, data): + self.buffersize = int(dpg.get_value(sender)) + self.config["network"]["buffersize"] = str(dpg.get_value(sender)) + self.config.write(open('config.ini', 'w')) + def pubserverselectone(self, sender, data): if data == False: dpg.configure_item("connectbuttonpubserverselect", show=False) @@ -295,25 +293,33 @@ class App: self.pubserverselectsearch() def streambuffer(self, socket): - consecutive_above_threshold = 0 # Counter to track consecutive iterations above threshold - tolerance_iterations = 5 # Number of consecutive iterations required above threshold while self.working: - if self.cprotocol == "TCP": - tempdata = b'' - # data = socket.recv(1580152) - while True: - part = socket.recv(1024) - tempdata += part - if len(part) < 1024: - # either 0 or end of data - break - self.buffer.put(tempdata) - elif self.cprotocol == "ZeroMQ": - self.buffer.put(socket.recv()) - else: - self.buffer.put(b"") + try: + if self.cprotocol == "TCP": + tempdata = b'' + # data = socket.recv(1580152) + while True: + part = socket.recv(1024) + tempdata += part + if len(part) < 1024: + # either 0 or end of data + break + self.buffer.put(tempdata, timeout=0.1) + elif self.cprotocol == "ZeroMQ": + self.buffer.put(socket.recv(), timeout=0.1) + else: + self.buffer.put(b"") - dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}') + dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(0, 255, 0)) + except queue.Full: + dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(255, 0, 0)) + + while not self.buffer.empty(): + self.buffer.get() + + dpg.configure_item("bufferstatus", default_value=f'Buffer: {self.buffer.qsize()}/{self.buffersize}', color=(255, 255, 0)) + dpg.configure_item("serverstatus", default_value='disconnected', color=(255, 0, 0)) + dpg.configure_item("connectservergroup", show=True) def stream(self, socket): opus_decoder = None @@ -339,6 +345,7 @@ class App: if self.buffer.not_empty: data = self.buffer.get() else: + dpg.configure_item("serverstatus", default_value='Buffering...', color=(255, 255, 0)) continue bytesconunt += len(data) @@ -589,7 +596,7 @@ class App: ctypes.CDLL("opus.dll") dpg.create_context() - dpg.create_viewport(title=f'IDRB Client v1.6.1 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico", clear_color=(43, 45, 48)) # set viewport window + dpg.create_viewport(title=f'IDRB Client v1.6.2 Beta', width=1280, height=720, large_icon="IDRBfavicon.ico", clear_color=(43, 45, 48)) # set viewport window dpg.setup_dearpygui() # -------------- add code here -------------- noimage_texture_data = [] @@ -621,6 +628,7 @@ class App: output_devices.append(device_info['name']) dpg.configure_item("selectaudiooutputdevicecombo", items=output_devices, default_value=self.config["audio"]["device"]) + dpg.configure_item("buffersizeintinput", default_value=int(self.config["network"]["buffersize"])) # ------------------------------------------- dpg.show_viewport() diff --git a/Client/config.ini b/Client/config.ini index 06777f3..28de1f7 100644 --- a/Client/config.ini +++ b/Client/config.ini @@ -1,6 +1,9 @@ [audio] device = Speakers (2- USB Audio DAC ) +[network] +buffersize = 128 + [debug] hideconsole = true diff --git a/Client/utils.py b/Client/utils.py index 6bf6f06..ec012aa 100644 --- a/Client/utils.py +++ b/Client/utils.py @@ -1,3 +1,20 @@ +""" +This file is part of IDRB Project. + +IDRB Project is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IDRB Project is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IDRB Project. If not, see . +""" + from Crypto.Cipher import AES from Crypto.Protocol.KDF import scrypt import numpy as np diff --git a/Server/Settings.py b/Server/Settings.py index c2bd6de..4d1dac3 100644 --- a/Server/Settings.py +++ b/Server/Settings.py @@ -15,10 +15,17 @@ You should have received a copy of the GNU General Public License along with IDRB Project. If not, see . """ +# To config Muxer you need to goto server.py and find "Config Muxer" +# To config RDS you need to goto RDS.py +# To config Encoder you need to goto Encoder.py +# Do not goto ThaiSDRDir.py and utils.py + # Server Settings protocol = "ZMQ_WS" # TCP ZMQ ZMQ_WS server_port = ('*', 6980) # if use other protocol ZMQ please use 0.0.0.0 compression_level = 9 # 0-9 +buffersize = 32 # must be int (on working it use buffersize + (buffersize/2) to standby) +# low buffersize = low delay # Server Info ServerName = "DPCloudev" @@ -40,4 +47,4 @@ public = True ServerIP = "localhost" #ServerPort = server_port[1] ServerPort = 6980 -ThaiSDRkey = "1N5LURICLIN1U9QNYZ4MHJ6FNXISFXFELZAX135CFM0HSD17O2.63E60BE9EEA2339C113A15EB" \ No newline at end of file +ThaiSDRkey = "" \ No newline at end of file diff --git a/Server/__pycache__/Settings.cpython-310.pyc b/Server/__pycache__/Settings.cpython-310.pyc index 31dc15f..4533904 100644 Binary files a/Server/__pycache__/Settings.cpython-310.pyc and b/Server/__pycache__/Settings.cpython-310.pyc differ diff --git a/Server/server.py b/Server/server.py index 48de425..703d29b 100644 --- a/Server/server.py +++ b/Server/server.py @@ -22,6 +22,8 @@ import threading import zmq import logging import zlib +import queue +import math logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s') ServerLog = logging.getLogger("IDRBServer") @@ -61,7 +63,6 @@ _RDS.startRDSThread() ServerLog.info('starting audio encoding') Encoder.StartEncoder() - if protocol == "TCP": connected_users = 0 elif protocol == "ZMQ": @@ -74,78 +75,98 @@ timestart = time.time() connectionlist = [] first = True +Buffer = queue.Queue(maxsize=math.trunc(Settings.buffersize + (Settings.buffersize/2))) + +# ---------------------------------- Config Muxer --------------------------------------- + +def Muxer(): + while True: + # Get the encoded audio from the buffer + ENchannel1 = Encoder.channel1.get() + + # encrypt data + # ENC1encrypted, ENC1salt, ENC1iv = utils.encrypt_data(ENchannel1, "password") + + # ENchannel1 = ENC1encrypted + b'|||||' + ENC1salt + b'|||||' + ENC1iv + + ENchannel2 = Encoder.channel2.get() + content = { + "first": False, + "mainchannel": 1, + "channel": { + 1: { + "Station": "DPRadio+", + "StationDesc": "The best station in the world!", + "Encrypt": b'|||||' in ENchannel1, # check if encrypt + "ContentSize": len(ENchannel1), + "Content": ENchannel1, + "RDS": _RDS.RDS + }, + 2: { + "Station": "DPTest", + "StationDesc": "", + "Encrypt": b'|||||' in ENchannel2, + "ContentSize": len(ENchannel2), + "Content": ENchannel2, + "RDS": _RDS.RDS2 + } + }, + "serverinfo": { + "Listener": connected_users, + "Startat": timestart, + "RDS": _RDS.ServerRDS + } + } + ThaiSDRDir.content = content + + compressedcontent = zlib.compress(pickle.dumps(content), level=Settings.compression_level) + + Buffer.put(compressedcontent) + +# ----------------------------------------------------------------------------------------------- + def handle_client(): global connected_users, first try: while True: - # Get the encoded audio from the buffer - ENchannel1 = Encoder.channel1.get() - - # encrypt data - #ENC1encrypted, ENC1salt, ENC1iv = utils.encrypt_data(ENchannel1, "password") - - #ENchannel1 = ENC1encrypted + b'|||||' + ENC1salt + b'|||||' + ENC1iv - - ENchannel2 = Encoder.channel2.get() - content = { - "first": False, - "mainchannel": 1, - "channel": { - 1: { - "Station": "DPRadio+", - "StationDesc": "The best station in the world!", - "Encrypt": b'|||||' in ENchannel1, # check if encrypt - "ContentSize": len(ENchannel1), - "Content": ENchannel1, - "RDS": _RDS.RDS - }, - 2: { - "Station": "DPTest", - "StationDesc": "", - "Encrypt": b'|||||' in ENchannel2, - "ContentSize": len(ENchannel2), - "Content": ENchannel2, - "RDS": _RDS.RDS2 - } - }, - "serverinfo": { - "Listener": connected_users, - "Startat": timestart, - "RDS": _RDS.ServerRDS - } - } - ThaiSDRDir.content = content - - compressedcontent = zlib.compress(pickle.dumps(content), level=Settings.compression_level) - - #connection.sendall(pickle.dumps(content)) - if protocol == "TCP": - for i in connectionlist: - try: - i.sendall(compressedcontent) - except Exception as e: - #print(f'Error sending data to {i.getpeername()}: {e}') - # Remove disconnected client from the list - if i in connectionlist: - i.close() - connectionlist.remove(i) - connected_users -= 1 - # check if no user - if not connectionlist: - first = True - ServerLog.info('server is standby now') - break - elif protocol == "ZMQ": - s.send(compressedcontent) + # Check if the buffer queue has enough data to send + if Buffer.qsize() >= Settings.buffersize: # Adjust the threshold as needed + if protocol == "TCP": + for i in connectionlist: + try: + # Send data from the buffer queue to connected clients + for _ in range(Settings.buffersize): + i.sendall(Buffer.get()) + except Exception as e: + if i in connectionlist: + i.close() + connectionlist.remove(i) + connected_users -= 1 + if not connectionlist: + first = True + ServerLog.info('server is standby now') + break + elif protocol == "ZMQ": + # Send data from the buffer queue to ZMQ socket + for _ in range(Settings.buffersize): + s.send(Buffer.get()) except Exception as e: print(f'Error: {e}') + # Your main server logic using threading for handling connections if __name__ == "__main__": if public: ServerLog.info('starting ThaiSDR Directory') ThaiSDRDir.run() - ServerLog.info('server is running') + + ServerLog.info('starting Muxer') + + muxerthread = threading.Thread(target=Muxer) + muxerthread.start() + + ServerLog.info('starting server') + if protocol == "TCP": while True: connection, client_address = s.accept() @@ -162,4 +183,6 @@ if __name__ == "__main__": elif protocol == "ZMQ": client_thread = threading.Thread(target=handle_client) # client_thread.daemon = True # Set the thread as a daemon so it exits when the main thread exits - client_thread.start() \ No newline at end of file + client_thread.start() + + ServerLog.info('server is running') \ No newline at end of file