def test_server_different_port(): # server, will be tested: server_3000 = OSCThreadServer(encoding='utf8') sock_3000 = server_3000.listen(address='0.0.0.0', port=3000, default=True) server_3000.bind(b'/callback_3000', callback_3000) # clients sending to different ports, used to test the server: client_3000 = OSCClient(address='localhost', port=3000, encoding='utf8') # server sends message to himself, should work: server_3000.send_message(b'/callback_3000', ["a"], ip_address='localhost', port=3000) sleep(0.05) # client sends message to server, will be received properly: client_3000.send_message(b'/callback_3000', ["b"]) sleep(0.05) # sever sends message on different port, might crash the server on windows: server_3000.send_message(b'/callback_3000', ["nobody is going to receive this"], ip_address='localhost', port=3001) sleep(0.05) # client sends message to server again. if server is dead, message will not be received: client_3000.send_message(b'/callback_3000', ["c"]) sleep(0.1) # give time to finish transmissions # if 'c' is missing in the received checklist, the server thread crashed and could not recieve the last message from the client: assert checklist == ['a', 'b', 'c'] server_3000.stop() # clean up
def test_answer(): cont = [] osc_1 = OSCThreadServer() osc_1.listen(default=True) @osc_1.address(b'/ping') def ping(*values): if True in values: osc_1.answer(b'/zap', [True], port=osc_3.getaddress()[1]) else: osc_1.answer(bundle=[(b'/pong', [])]) osc_2 = OSCThreadServer() osc_2.listen(default=True) @osc_2.address(b'/pong') def pong(*values): osc_2.answer(b'/ping', [True]) osc_3 = OSCThreadServer() osc_3.listen(default=True) @osc_3.address(b'/zap') def zap(*values): if True in values: cont.append(True) osc_2.send_message(b'/ping', [], *osc_1.getaddress()) with pytest.raises(RuntimeError) as e_info: # noqa osc_1.answer(b'/bing', []) timeout = time() + 2 while not cont: if time() > timeout: raise OSError('timeout while waiting for success message.') sleep(10e-9)
def test_send_message_without_socket(): osc = OSCThreadServer() with pytest.raises(RuntimeError): osc.send_message(b'/test', [], 'localhost', 0)
class OBSRemote: def __init__(self, osc_port, osc_client_host, osc_client_port, host, port, password=''): self.client = obsws(host, port, password) self.register_callbacks() self.osc = OSCThreadServer( encoding='utf8', advanced_matching=True, ) self.socket = None self.osc_client_host = osc_client_host self.osc_client_port = osc_client_port self.osc_port = osc_port self.volume_changed = False self.levels = [0, 0] self.scenes = [] def start(self): self.client.connect() self.socket = self.osc.listen(address='0.0.0.0', port=self.osc_port, default=True) self.update_audio_sources() self.update_audio_levels() self.update_scenes() self.update_mute_status() self.register_osc_addresses() self.client.call(requests.SetHeartbeat(True)) def stop(self): self.client.disconnect() self.osc.stop() self.socket = None def register_callbacks(self): self.client.register(self.scene_changed, event=events.SwitchScenes) self.client.register(self.mute_changed, event=events.SourceMuteStateChanged) self.client.register(self.update_scenes, event=events.ScenesChanged) self.client.register(self.update_scenes, event=events.SceneCollectionChanged) self.client.register(self.update_audio_sources, event=events.SourceRenamed) self.client.register(self.update_audio_levels, event=events.SourceVolumeChanged) self.client.register(self.status_update, event=events.Heartbeat) def register_osc_addresses(self): @self.osc.address('/scene/?/?', get_address=True) def scene_cb(address, values): if values < 1.0: return scene_id = int(address.split(b'/')[-1]) - 1 if scene_id < len(self.scenes): self.osc.answer(address, [1.0]) self.client.call( requests.SetCurrentScene(self.scenes[scene_id])) @self.osc.address('/mic') def mic_cb(values): self.osc.answer('/mic', [values]) self.client.call( requests.SetMute(self.audio_sources['mic'], values < 1.0)) @self.osc.address('/audio') def audio_cb(values): self.osc.answer('/audio', [values]) self.client.call( requests.SetMute(self.audio_sources['desktop'], values < 1.0)) @self.osc.address('/cam') def cam_cb(values): self.osc.answer('/cam', [values]) self.client.call( requests.SetSceneItemProperties('Webcam', visible=values > 0.0)) self.client.call( requests.SetSceneItemProperties('Overlay: Webcam', visible=values > 0.0)) @self.osc.address('/audio_level/?', get_address=True) def audio_level_cb(address, values): slider_id = int(address.split(b'/')[-1]) - 1 self.levels[slider_id] = values self.volume_changed = True @self.osc.address('/rec') def rec_cb(values): if values > 0.0: self.client.call(requests.StartRecording()) else: self.client.call(requests.StopRecording()) self.osc.answer('/rec', [values]) @self.osc.address('/stream') def stream_cb(values): if values > 0.0: self.client.call(requests.StartStreaming()) else: self.client.call(requests.StopStreaming()) self.osc.answer('/stream', [values]) def mute_changed(self, event): name = event.getSourceName() muted = event.getMuted() if name == self.audio_sources['mic']: self._send_osc('/mic', 1.0 if muted is False else 0.0) if name == self.audio_sources['desktop']: self._send_osc('/audio', 1.0 if muted is False else 0.0) def scene_changed(self, event): name = event.getSceneName() for idx, scene_name in enumerate(self.scenes): if name == scene_name: self._send_osc('/scene/1/{}'.format(idx + 1), 1.0) self.update_mute_status(sources=event.getSources()) def status_update(self, event: events.Heartbeat): streaming = event.getStreaming() recording = event.getRecording() try: stream_time = timedelta(seconds=event.getTotalStreamTime()) except KeyError: stream_time = 0 try: rec_time = timedelta(seconds=event.getTotalRecordTime()) except KeyError: rec_time = 0 self._send_osc('/stream', 1.0 if streaming else 0.0) self._send_osc('/rec', 1.0 if recording else 0.0) self._send_osc('/stream_time', str(stream_time)) self._send_osc('/rec_time', str(rec_time)) def update_mute_status(self, sources=None): if sources is None: sources = self.client.call(requests.GetCurrentScene()).getSources() webcam_found = False for source in sources: if source['name'] in ['Webcam', 'Overlay: Webcam']: webcam_found = True self._send_osc('/cam', 1.0 if source['render'] else 0.0) if not webcam_found: self._send_osc('/cam', 0.0) def update_scenes(self, *args): if len(args) > 0: self.scenes = [ s['name'] for s in args[0].getScenes() if not s['name'].lower().startswith('overlay:') ] else: self.scenes = [ s['name'] for s in self.client.call(requests.GetSceneList()).getScenes() if not s['name'].lower().startswith('overlay:') ] for idx in range(0, 8): try: name = self.scenes[idx] except IndexError: name = '' self._send_osc('/scene_label_{}'.format(idx), name) self.update_mute_status() def update_audio_sources(self, *args): sources = self.client.call(requests.GetSpecialSources()) self.audio_sources = { "mic": sources.getMic1(), "desktop": sources.getDesktop1() } def update_audio_levels(self, *args): if len(args) > 0: event = args[0] name = event.getSourceName() volume = None if isinstance(event, events.SourceVolumeChanged): volume = event.getVolume() muted = None if isinstance(event, events.SourceMuteStateChanged): muted = event.getMuted() if name == self.audio_sources['mic']: if volume is not None: self._send_osc('/audio_level/1', volume) self.levels[0] = volume if muted is not None: self._send_osc('/mic', 1.0 if not muted else 0.0) elif name == self.audio_sources['desktop']: if volume is not None: self._send_osc('/audio_level/2', volume) if muted is not None: self._send_osc('/audio', 1.0 if not muted else 0.0) else: desktop = self.client.call( requests.GetVolume(self.audio_sources['desktop'])) mic = self.client.call( requests.GetVolume(self.audio_sources['mic'])) self._send_osc('/audio_level/2', desktop.getVolume()) self.levels[1] = desktop.getVolume() self._send_osc('/audio', 1.0 if not desktop.getMuted() else 0.0) self._send_osc('/audio_level/1', mic.getVolume()) self.levels[0] = mic.getVolume() self._send_osc('/mic', 1.0 if not mic.getMuted() else 0.0) def _send_osc(self, address, value): self.osc.send_message(address, [value], self.osc_client_host, self.osc_client_port) def tick(self): if self.volume_changed: self.volume_changed = False self.client.call( requests.SetVolume( list(self.audio_sources.values())[0], self.levels[0])) self.client.call( requests.SetVolume( list(self.audio_sources.values())[1], self.levels[1]))
class ServerSong(): def __init__(self, app, pos_callback, state_callback, port): from oscpy.server import OSCThreadServer self.app = app self.pos_callback = pos_callback self.state_callback = state_callback self.state = 'stop' self.length = 30 self.is_complete = False self.song_object = None self.ready = False self.osc = OSCThreadServer() self.osc.listen(port=port, default=True) self.osc.bind(b'/pos', self._get_pos) self.osc.bind(b'/set_state', self.set_state) self.osc.bind(b'/set_length', self.set_length) self.osc.bind(b'/set_complete', self.set_complete) self.osc.bind(b'/playing', self.playing) self.osc.bind(b'/update_playlist', self.update_playlist) self.osc.bind(b'/ready', self.ready_callback) def ready_callback(self): self.ready = True Logger.debug("ACTIVITY: Service is ready.") def playing(self, id, pos): Logger.debug('ACTIVITY: Playing.') self.state = 'play' self.last_pos = pos song = self.app.playlist.get_track(id=id) play_button = self.app.play_button if self.song_object != song: play_button.load_song(song, playing=True) play_button.update_track_current(current=pos) if play_button.event: play_button.event.cancel() play_button.event = Clock.schedule_interval( play_button.update_track_current, 1) def set_state(self, value): Logger.debug('ACTIVITY: State %s', value) self.state = value.decode() def set_length(self, value): Logger.debug('ACTIVITY: Length %s', value) self.length = value def set_complete(self, value): Logger.debug('ACTIVITY: Song is_complete=%s', value) self.is_complete = value def set_volume(self, value): Logger.debug('ACTIVITY -> Service: Set volume %s', value) self.osc.send_message(b'/set_volume', [value], *self.server_address) def getaddress(self): return self.osc.getaddress() def load(self, song): Logger.debug('ACTIVITY -> SERVER: /load') self.osc.send_message(b'/load', [song.id], *self.server_address) def unload(self): Logger.debug('ACTIVITY -> SERVER: /unload') self.osc.send_message(b'/unload', [], *self.server_address) def play(self, seek, volume): Logger.debug('ACTIVITY -> SERVER: /play pos %s vol %s', seek, volume) self.osc.send_message(b'/play', [seek, volume], *self.server_address) def play_new_playlist(self): Logger.debug('ACTIVITY -> SERVER: /play_new_playlist') self.osc.send_message(b'/play_new_playlist', [], *self.server_address) def load_play(self, song, volume): Logger.debug('ACTIVITY -> SERVER: /load_play %s vol %s', song.id, volume) self.osc.send_message(b'/load_play', [song.id, volume], *self.server_address) def stop(self): Logger.debug('ACTIVITY -> SERVER: /stop') self.osc.send_message(b'/stop', [], *self.server_address) def seek(self, position): Logger.debug('ACTIVITY -> SERVER: /seek %s', position) self.osc.send_message(b'/seek', [position], *self.server_address) def save_pos(self, callback): Logger.debug('ACTIVITY -> SERVER: /get_pos') self.pos_callback = callback self.osc.send_message(b'/get_pos', [], *self.server_address) def get_pos(self, callback): Logger.debug('ACTIVITY -> SERVER: get pos and call %s', callback) self.pos_callback = callback self.osc.send_message(b'/get_pos', [], *self.server_address) def _get_pos(self, value): Logger.debug('ACTIVITY: pos received %s', value) self.pos_callback(value) def update_playlist(self): self.app.playlist = self.app.db.get_playlist() Logger.debug('ACTIVITY: Updated playlist.')
class Console: def __init__(self, console_ip): self.console_ip = console_ip self.console_port = 10023 self.name_list = [] # Initialize name_list with 32 empty strings. for n in range(32): self.name_list.append("") # Setup socket to console connection and start listening hostname = gethostname() local_ip = gethostbyname(hostname) self.server = OSCThreadServer(advanced_matching=True) self.sock = self.server.listen(address=local_ip, port=0, default=True) self.server.bind(b"/ch/../config/name", self.callback, sock=self.sock, get_address=True) print("Listening to console at", self.console_ip) # Query for all channel names for ch in range(1, 33): ch = two_digits(ch) message = "/ch/" + ch + "/config/name" self.message(message, "") def callback(self, address, values): # Extract channel number from address string address = str(address) channel = int(address[6:8]) # Extract channel name string values = str(values) name = values[2:-1] # Find if there are battery bars in the name and remove. batt_indicator = name.find(" (") if batt_indicator > -1: name = name[:batt_indicator] value = bytes(name, 'utf-8') name_message = "/ch/" + two_digits(channel) + "/config/name" self.message(name_message, value) # Find if there are h:mm in the name and remove. batt_indicator = name.find(":") if batt_indicator > -1: name = name[:batt_indicator - 2] value = bytes(name, 'utf-8') name_message = "/ch/" + two_digits(channel) + "/config/name" self.message(name_message, value) # Add channel name into the name list at the appropriate index self.name_list[channel - 1] = name self.duplicate_names(channel) def subscribe(self): # Subscribe to console updates via OSC /xremote command. while True: self.server.send_message(b"/xremote", "", self.console_ip, self.console_port, sock=self.sock, safer=True) sleep(5) def message(self, message, value): # Send one OSC message to console. self.server.send_message(bytes(message, 'utf-8'), value, self.console_ip, self.console_port, sock=self.sock, safer=True) def duplicate_names(self, ch): dups = [] a = self.name_list[ch - 1] # Search through all values in self.name_list and compare with ch. # Do not compare to itself, ignore blank channels. for y in range(32): b = self.name_list[y] if (ch - 1) != y: if a == b and a != "": dups.append(ch) dups.append(y + 1) # Report duplicate channels from temporary duplicate list, sort, and print warning message. dups = list(set(dups)) dups.sort() if len(dups) > 1: print('Warning: Duplicate channel name! Channels ' + str(dups)[1:-1] + ' = ' + self.name_list[ch - 1])
class SocketP2P(object): ''' A peer to peer socket server base on OSCPy ''' server = None allow_finding = False is_host = False is_running = False config_data = '' _my_name = '' def get_my_name(self): return self._my_name def set_my_name(self, name): if name: self._my_name = name my_name = property(get_my_name, set_my_name) __events__ = ('on_create_server', 'on_scan_device', 'on_found_device', 'on_device_hided', 'on_connect_user', 'on_accepted_connection', 'on_request_config', 'on_got_config', 'on_new_device', 'on_delete_device') _callback_collec = {} def __init__(self, port, bind_collection={}, **kwargs): # super(SocketP2P, self).__init__(**kwargs) self._port = port self._bind_collection = bind_collection self._bind_collection.update({ '/probe': self._probe, '/found': self._found, '/hided': self._hided_host, '/get_conf': self._send_conf, '/conf': self._got_conf, '/new_device': self._new_device, '/delete_device': self._delete_device }) for event in SocketP2P.__events__: self._callback_collec[event] = [getattr(self, event)] sock = socket.socket(AF_INET, SOCK_DGRAM) sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) sock.settimeout(1) self._scan_client = OSCClient(address='255.255.255.255', port=self._port, sock=sock, encoding='utf8') self.create_server() @_catch_exception def _identify_me(self, time): global MY_IP if time == self._time: self.server.unbind('/identify_me', self._identify_me) _, ip_address, _ = self.server.get_sender() MY_IP = ip_address if self._my_name == '': self.my_name = ip_address Log_P2P(f'P2P: IP: {self.myip}') def stop(self): if self.server: try: self.server.stop_all() except Exception as e: Logger.exception('P2P: On close') self.is_running = False def bind(self, **kwargs): for event, cb in kwargs.items(): if event in SocketP2P.__events__: self._callback_collec[event].append(cb) def unbind(self, event, callback): if event in SocketP2P.__events__ and\ callback in self._callback_collec[event]: self._callback_collec[event].remove(callback) def bind_address(self, address, callback): self.server.bind(address, callback) self._bind_collection[address] = callback def unbind_address(self, address): self.server.unbind(address, self._bind_collection.pop(address, None)) def dispatch(self, event, *args, **kwargs): if event in SocketP2P.__events__: args = [self] + list(args) for cb in self._callback_collec[event]: try: cb(*args, **kwargs) except TypeError as tye: Logger.exception( f'TypeError: {cb.__qualname__} with {args} {kwargs}') except Exception as e: Logger.exception(f'P2P: Error in {cb.__qualname__}') @property def myip(self): return MY_IP @_catch_exception def create_server(self): self.stop() self.reset_data() # Fix [Errno 98] Address already in use sleep(0.01) self.server = OSCThreadServer(encoding='utf8') self.server.listen(address='0.0.0.0', port=self._port, default=True) for address, cb in self._bind_collection.items(): self.server.bind(address, cb) self.server.bind('/identify_me', self._identify_me) self._time = str(datetime.now()) try: self._scan_client.send_message('/identify_me', [self._time]) except OSError: Logger.exception('P2P: No network') self.server.unbind('/identify_me', self._identify_me) return self.dispatch('on_create_server', self.server) self.is_running = True def reset_data(self): self.list_device = [] self.list_name = {} self.waiting_list = [] @_catch_exception def scan_device(self): self.dispatch('on_scan_device') self._scan_client.send_message(b'/probe', []) @_catch_exception def _probe(self): if self.allow_finding: Log_P2P('P2P: /probe') self.server.answer(b'/found', values=[MY_IP, self.my_name], port=self._port) else: self.server.answer(b'/hided', values=[MY_IP], port=self._port) @_catch_exception def _found(self, ip, ip_name): if ip != MY_IP and ip not in self.list_device\ and ip not in self.waiting_list: Log_P2P(f'P2P: /found {ip}') self.waiting_list.append(ip) self.dispatch('on_found_device', ip, ip_name) @_catch_exception def _hided_host(self, ip): if ip in self.waiting_list: Log_P2P(f'P2P: /hided {ip}') self.waiting_list.remove(ip) self.dispatch('on_device_hided', ip) def connect_user(self, ip): if ip in self.waiting_list: self.dispatch('on_connect_user', ip) self.get_config(ip) @_catch_exception def send_to_all(self, address, data, list_ip=None, and_myip=False): if not isinstance(data, list): data - list(data) if not list_ip: list_ip = self.list_device.copy() if and_myip: list_ip += [self.myip] Log_P2P(f'P2P: send {address}\nData: {data}\nTo: {list_ip}') for other in list_ip: self.server.send_message(address, data, other, self._port) def generate_token(self, *args): return '1234' def handle_token(self, token): if token == '1234': return True else: return False def get_config(self, ip): self.server.send_message( b'/get_conf', [MY_IP, self.generate_token(), self.my_name], ip, self._port) @_catch_exception def _send_conf(self, ip, token, ip_name): if self.handle_token(token) and self.allow_finding: Log_P2P(f'P2P: /get_conf {ip}') if ip not in self.list_device: for other in self.list_device: self.send_to_all( '/new_device', [MY_IP, self.generate_token(), ip, ip_name]) self.list_device.append(ip) self.list_name[ip] = ip_name self.dispatch('on_accepted_connection', ip, ip_name) list_send = {MY_IP: self.my_name} list_send.update(self.list_name) self.server.answer(b'/conf', [ MY_IP, self.generate_token(), self.config_data, json.dumps(list_send) ], port=self._port) self.dispatch('on_request_config', ip) @_catch_exception def _got_conf(self, ip, token, config_data, list_device): if self.handle_token(token): Log_P2P(f'P2P: /conf {ip} with {list_device}') self.list_device = [] for i, name in json.loads(list_device).items(): if i != MY_IP: self.list_device.append(i) self.list_name[i] = name if ip in self.waiting_list: self.waiting_list.remove(ip) self.dispatch('on_got_config', ip, config_data) @_catch_exception def _new_device(self, ip, token, new_ip, ip_name): if self.handle_token(token) and new_ip not in self.list_device: Log_P2P(f'P2P: /new_device {new_ip} {ip_name}') if ip in self.waiting_list: self.waiting_list.remove(new_ip) self.list_device.append(new_ip) self.list_name[new_ip] = ip_name self.dispatch('on_new_device', new_ip, ip_name) def remove_device(self): self.send_to_all('/delete_device', [MY_IP, self.generate_token(), MY_IP], and_myip=True) self.reset_data() @_catch_exception def _delete_device(self, ip, token, del_ip): if self.handle_token(token): Log_P2P(f'P2P: /delete_device {del_ip}') if del_ip in self.waiting_list: self.waiting_list.remove(del_ip) if del_ip in self.list_device: self.list_device.remove(del_ip) self.list_name.pop(del_ip, None) self.dispatch('on_delete_device', ip, del_ip) def on_create_server(self, ins, server): pass def on_scan_device(self, ins): pass def on_found_device(self, ins, ip, ip_name): pass def on_device_hided(self, ins, ip): pass def on_connect_user(self, ins, ip): pass def on_accepted_connection(self, ins, ip, ip_name): pass def on_request_config(self, ins, ip): pass def on_got_config(self, ins, ip, config_data): pass def on_new_device(self, ins, new_ip, ip_name): pass def on_delete_device(self, ins, ip, del_ip): pass
class SendReceive(Widget): selected_store = ObjectProperty() device_ip = StringProperty() other_ip = StringProperty() lan_sock = None replay_chunk_upload_queue = Queue() replay_chunk_download_queue = Queue() accept_next_chunk = False chunk_size = 1024 * 10 chunks_total = NumericProperty() chunks_uploaded = NumericProperty() chunks_downloaded = NumericProperty() # data ratio is unsent:sent chunks_ratio = NumericProperty() status_text = StringProperty() do_polling = None polling_thread = None # ip = '192.168.0.7' if platform == 'android' else '192.168.0.22' # server_ip = StringProperty() port = 8002 successfully_downloaded = [] # def get_ips_store(self): # store_type = 'ips_store.pydict' # store_path = path.join(App.get_running_app().user_data_dir, store_type) # ips_store = DictStore(store_path) # return ips_store # # def set_ips(self): # self.ips_store = self.get_ips_store() # self.ips_store['device_ip'] = self.device_ip # self.ips_store['other_ip'] = self.other_ip def do_on_pre_enter(self, *args): # self.ips_store = self.get_ips_store() self.device_ip = '192.168.0.7' if platform == 'android' else '192.168.0.22' self.other_ip = '192.168.0.22' if platform == 'android' else '192.168.0.7' # try: # self.device_ip = self.ips_store.get('device_ip') # self.other_ip = self.ips_store.get('other_ip') # except: # self.device_ip = '192.168.0.' # self.other_ip = '192.168.0.' # fixme actually this should initialise as a client with the ips, and send the server it's details(?) if not self.lan_sock: # # Importing socket library # import socket # # # Function to display hostname and # # IP address # def get_Host_name_IP(): # try: # host_name = socket.gethostname() # host_ip_info = socket.gethostbyname_ex(host_name) # host_ip_list = host_ip_info[-1] # host_ip = host_ip_list[-1] # print("Hostname : ", host_name) # print("IP List : ", host_ip_list) # print("IP : ", host_ip) # return str(host_ip) # except: # print("Unable to get Hostname and IP") # # # Driver code # # self.device_ip = get_Host_name_IP() # Function call # if platform == 'android': # print('from AndroidGetIP import getIP') # from AndroidGetIP import getIP # print('self.device_ip = getIP()') # self.device_ip = getIP() # print('...................................') # else: # import socket # self.device_ip = [l for l in ( # [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1], [ # [(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in # [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][1][0] # print('**************', self.device_ip) self.lan_sock = self.osc.listen(self.device_ip, self.port, default=True) # server # @self.osc.address(b'/receive_data_information') def receive_data_information(number_of_items, number_of_chunks): # this is (intended) for the windows device to receive self.number_of_items_downloading = number_of_items self.chunks_total = number_of_chunks self.chunks_downloaded = 0 self.successfully_downloaded = [] self.status_text = 'Initialising download of {} item(s)'.format(number_of_items) self.last_data_received_time = time() while not self.replay_chunk_download_queue.empty(): self.replay_chunk_download_queue.get() self.polling_check_if_data_stopped = Clock.schedule_interval(self.check_if_data_stopped, 2) # server # @self.osc.address(b'/receive_replay_chunk') def receive_replay_chunk(chunk_index, chunk): # this is (intended) for the windows device to receive print('D {} / {} -> {}'.format(self.chunks_downloaded, self.chunks_total, chunk)) self.calculate_download_ratio() if chunk not in self.successfully_downloaded: # when there is an error sending a chunk i go back and send the previous chunk # this could lead to duplicates so i check to see if it's already added self.successfully_downloaded.append(chunk) self.replay_chunk_download_queue.put((chunk_index, chunk)) self.chunks_downloaded += 1 else: print('THIS CHUNK IS ALREADY DOWNLOADED') self.last_data_received_time = time() if self.chunks_downloaded < self.chunks_total: print('................', self.chunks_downloaded, self.chunks_total) try: self.osc.answer(b'/next_chunk', [], safer=True) except: self.osc.send_message(b'/next_chunk', [], self.other_ip, 8002, self.lan_sock, safer=True) else: self.reconstruct_replay_chunks() self.status_text = 'Downloading chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total) # client # @self.osc.address(b'/next_chunk') def next_chunk(): chunk_index, chunk = self.replay_chunk_upload_queue.get() self.chunks_uploaded += 1 print('U {} / {}'.format(chunk_index, self.chunks_total)) self.calculate_upload_ratio() self.send_replay_chunk(chunk_index, chunk) # client # @self.osc.address(b'/receive_resume_from_chunk') def receive_resume_from_chunk(chunk_start): print('received instruction to resume from chunk', chunk_start) self.status_text = 'Resuming from chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total) self.add_to_upload_queue(chunk_start) def check_if_data_stopped(self, *args): if time() - self.last_data_received_time > 0.125: print('RESUMING FROM CHUNK', self.chunks_downloaded) self.resume_from_chunk(self.chunks_downloaded) def do_on_pre_leave(self, *args): # self.osc.stop() self.do_polling = None def __init__(self, **kwargs): super(SendReceive, self).__init__(**kwargs) self.osc = OSCThreadServer(encoding=None) self.polling_check_if_data_stopped = None def resume_from_chunk(self, chunk_start=None): if chunk_start is None: chunk_start = self.chunks_downloaded # + 1#-1 # + 1 self.status_text = 'Resuming from chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total) self.osc.send_message(b'/receive_resume_from_chunk', [chunk_start], self.other_ip, 8002, self.lan_sock) def send_replay_chunk(self, chunk_index, chunk): # this is (intended) for the android device to send self.status_text = 'Uploading chunk {} out of {}'.format(self.chunks_downloaded, self.chunks_total) self.osc.send_message(b'/receive_replay_chunk', [chunk_index, chunk], self.other_ip, 8002, self.lan_sock, safer=True) def send_replay_chunks(self): print('going to genrate replay chunks') app = App.get_running_app() if hasattr(app, 'selected_items') and app.selected_items: items = app.selected_items print('{} item(s) selected -> {}'.format(len(items), items)) self.status_text = '{} item(s) selected -> {}'.format(len(items), items) else: items = dict(self.selected_store) print('No items selected. Sending all {} -> '.format(len(items), items)) self.status_text = 'No items selected. Sending all {} -> '.format(len(items), items) if not items: self.status_text = 'No items to be sent' return item_json = json.dumps(items) self.item_bytes = item_json.encode() number_of_items = len(items) unrounded_number_of_chunks = len(self.item_bytes) / self.chunk_size self.chunks_total = round(unrounded_number_of_chunks + .5) while not self.replay_chunk_upload_queue.empty(): self.replay_chunk_upload_queue.get() self.status_text = 'Initialising upload of {} item(s)'.format(number_of_items) self.status_text = 'Initialising upload of {} item(s)'.format(number_of_items) print('@@@@', type(self.other_ip), self.other_ip) self.osc.send_message(b'/receive_data_information', [number_of_items, self.chunks_total], self.other_ip, 8002, self.lan_sock) print('{{{{{{{{{{{{{{{ self.chunks_total', self.chunks_total) self.chunks_uploaded = 0 self.add_to_upload_queue_thread = Thread(target=self.add_to_upload_queue, args=[]) self.add_to_upload_queue_thread.start() def add_to_upload_queue(self, chunk_index=0): if not hasattr(self, 'item_bytes'): return x = chunk_index * self.chunk_size first_chunk = True while True: chunk = self.item_bytes[x:x + self.chunk_size] print('adding chunk', chunk_index) if chunk: # ie if it's the first chunk if first_chunk: first_chunk = False # the first time you dont need to put it in a queue. just send it. # this will keep off a send and receive parle between the two devices, # which will .get the subsequent chunks from the replay_chunk_upload_queue (see the else statement) self.send_replay_chunk(chunk_index, chunk) else: self.replay_chunk_upload_queue.put((chunk_index, chunk)) print(chunk) x += self.chunk_size chunk_index += 1 else: break print(chunk) print('{{{{{{{{{{{{{{{ chunk_index', chunk_index) def reconstruct_replay_chunks(self): if self.polling_check_if_data_stopped: self.polling_check_if_data_stopped.cancel() self.polling_check_if_data_stopped = None # chunk_list = [None for x in range(self.chunks_total)] chunk_list = [] while not self.replay_chunk_download_queue.empty(): chunk_index, chunk = self.replay_chunk_download_queue.get() print('recon:', chunk_index, chunk) chunk_list.append(chunk.decode()) # chunk_list[chunk_index] = chunk.decode() # chunk_list.append(chunk.decode()) # decode it here for i, x in enumerate(chunk_list): if x: print('>>>', i, x[-15:]) else: print('>>>', i, x) item_json = ''.join(chunk_list) # print('reconstructed item_json', item_json) item_dict = json.loads(item_json) added = 0 replaced = 0 selected_store = self.selected_store for item_id, item_info in item_dict.items(): item_id = float(item_id) if item_id in selected_store: replaced += 1 else: added += 1 selected_store[item_id] = item_info selected_store.store_load() selected_store.store_sync() def calculate_upload_ratio(self, *args): if self.chunks_total: self.chunks_ratio = self.chunks_uploaded / self.chunks_total else: self.chunks_ratio = 0 def calculate_download_ratio(self, *args): if self.chunks_total: self.chunks_ratio = self.chunks_downloaded / self.chunks_total else: self.chunks_ratio = 0
class OSCSever: def __init__(self, activity_server_address, port): self.song = SoundAndroidPlayer(self.on_complete) self.osc = OSCThreadServer() self.osc.listen(port=port, default=True) self.activity_server_address = activity_server_address self.osc.bind(b'/get_pos', self.get_pos) self.osc.bind(b'/load', self.load) self.osc.bind(b'/play', self.play) self.osc.bind(b'/load_play', self.load_play) self.osc.bind(b'/seek', self.seek) self.osc.bind(b'/play_new_playlist', self.play_next) self.osc.bind(b'/set_volume', self.set_volume) self.osc.bind(b'/stop', self.pause) self.osc.bind(b'/unload', self.unload) self.api = API() self.db = Database() user = self.db.get_user() self.genres = user['genres'] self.artists = user['artists'] self.volume = user['volume'] self.songs_path = user['songs_path'] self.playlist = self.db.get_playlist() self.waiting_for_load = False self.seek_pos = 0 self.downloading = None self.waiting_for_download = False self.downloads = [] def check_pos(self, *args): if self.song.state == "play" and self.song.length - self.song.get_pos( ) < 20: next_song = self.playlist.preview_next() if next_song is None or next_song.preview_file is None: if next_song is None: self.playlist = self.get_new_playlist() next_song = self.playlist.preview_next() Logger.debug('SERVICE: Preloading next song.') self.download_song(next_song) def thread_download_song(self, song): self.downloads.append(song.id) try: res = self.api.download_preview(song) except Exception as e: Logger.error("SERVICE: Download failed. Reason: %s", e) else: song.preview_file = save_song(self.songs_path, song, res) self.db.update_track(song, 'preview_file', song.preview_file) Logger.debug('SERVICE: Downloading song finished.') self.downloads.remove(song.id) def download_song(self, song): if song.id not in self.downloads and song.preview_file is None: Logger.debug('SERVICE: Downloading %s.', song.id) t = threading.Thread(target=self.thread_download_song, args=(song, )) t.daemon = True t.start() else: Logger.debug( 'SERVICE: Skipped downloading %s. Already in progress.', song.id) def get_new_playlist(self): Logger.debug('SERVICE: getting new playlist.') req = self.api.get_recommendations( self.genres, self.artists, song_type='preview', ) playlist = Playlist(req) self.osc.send_message(b'/update_playlist', [], *self.activity_server_address) # clean up playlist songs favorites = self.db.get_favorites() for song in self.playlist.tracks: if (song.preview_file and song not in favorites and song != self.song.song_object): Logger.debug("Service: Removed %s", song.id) os.remove(song.preview_file) return playlist def get_pos(self, *values): pos = self.song.get_pos() if self.song and self.song.is_prepared else 0 Logger.debug('SERVICE -> ACTIVITY: /pos %s', pos) self.osc.answer(b'/pos', [pos]) def getaddress(self): return self.osc.getaddress() def load(self, id): self.first_load = not getattr(self.song, "id", 0) self.song.id = id self.song.is_prepared = False Logger.debug('SERVICE: Loading %d.', id) song = self.db.get_track(id) if song.preview_file is None and self.downloading != song.id: Logger.debug('SERVICE: %d is not downloaded.', id) self.download_song(song) if song.id in self.downloads: Logger.debug('SERVICE: %d is downloading. Returning.', id) self.waiting_for_download = song.id return Logger.debug('SERVICE: %d file is available.', id) self.waiting_for_download = None if not self.first_load: self.song.reset() self.song.load(song.preview_file) self.song.song_object = song self.db.update_current_track(song) self.playlist = self.db.get_playlist() self.song.is_prepared = True self.update_notification() Logger.debug('SERVICE: Song loaded.') def load_play(self, id, volume=None): Logger.debug('SERVICE: Loading and playing %d.', id) self.pause() self.load(id) self.play(0, volume if volume is not None else self.volume) def play(self, seek, volume): if not self.song.is_prepared: song_id = getattr(self.song, "id", None) Logger.debug('SERVICE: %s is not prepared.', song_id if song_id else "Song") if not self.waiting_for_download: self.load(self.playlist.current_track.id) else: self.waiting_for_load = True self.seek_pos = seek self.volume = volume return else: self.waiting_for_load = False self.song.is_prepared = True self.song.play() self.song.seek(seek) self.song.volume = volume pos = self.song.get_pos() Logger.debug('SERVICE -> ACTIVITY: /playing %s.', pos) values = [self.song.song_object.id, pos] self.osc.send_message(b'/playing', values, *self.activity_server_address) Logger.debug('SERVICE: Playing %d.', self.song.song_object.id) self.update_notification() def stop(self, *values): Logger.debug('SERVICE: stopping %d.', self.song.song_object.id) self.waiting_for_load = False if self.song.is_prepared and self.song.state == 'play': self.song.is_prepared = False self.song.stop() self.update_notification() def pause(self, *values): Logger.debug('SERVICE: pausing Song.') self.waiting_for_load = False if self.song.is_prepared and self.song.state == 'play': self.song.pause() self.update_notification() def seek(self, value): Logger.debug('SERVICE: seeking %s.', value) if self.song.is_prepared: self.song.seek(value) def set_volume(self, value): Logger.debug('SERVICE: setting song volume %s.', value) self.volume = value if self.song.is_prepared: self.song.volume = value def on_complete(self, *values): Logger.debug('SERVICE -> ACTIVITY: /set_complete') self.osc.send_message(b'/set_complete', [True], *self.activity_server_address) self.play_next() def play_next(self): Logger.debug('SERVICE: Playing next.') self.stop() if self.playlist.is_last: self.playlist = self.get_new_playlist() song = self.playlist.next() self.db.update_playlist(self.playlist) self.load_play(song.id) def play_previous(self): Logger.debug('SERVICE: Playing previous.') if not self.playlist.is_first: self.stop() self.load_play(self.playlist.previous().id) def on_state(self, instance, value): Logger.debug('SERVICE -> ACTIVITY: /set_state %s', value) self.osc.send_message(b'/set_state', [value.encode()], *self.activity_server_address) if self.song.is_prepared and self.song.length - self.song.get_pos( ) < 0.2: self.on_complete() def unload(self, *values): self.song.unload() def update_notification(self): notification = self.create_notification() notification_manager = context.getSystemService( Context.NOTIFICATION_SERVICE) notification_manager.notify(1, notification) def create_notification(self): song = getattr(self.song, "song_object", None) if api_version >= 26: builder = NotificationCompatBuilder(context, "gtplayer") else: builder = NotificationCompatBuilder(context) (builder.setContentTitle(song.name if song else "GTPlayer"). setContentText(song.artist if song else "GTPlayer").setContentIntent( controller.getSessionActivity()).setDeleteIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP)).setVisibility( NotificationCompat.VISIBILITY_PUBLIC).setSmallIcon( icon)) style = MediaStyle().setShowActionsInCompactView(0) builder.setStyle(style) if song is not None and not self.playlist.is_first: previous_intent = MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) action = NotificationCompatAction(RDrawable.ic_media_previous, "Previous", previous_intent) builder.addAction(action) if self.song.state == "play": intent = MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PAUSE) action = NotificationCompatAction(RDrawable.ic_media_pause, "Pause", intent) else: intent = MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PLAY) action = NotificationCompatAction(RDrawable.ic_media_play, "Play", intent) builder.addAction(action) coverart = None if song is not None: next_intent = MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_SKIP_TO_NEXT) action = NotificationCompatAction(RDrawable.ic_media_next, "Next", next_intent) builder.addAction(action) path = os.path.join(images_path, f"{song.id}.png") if os.path.isfile(path): coverart = path coverart = coverart if coverart is not None else "images/empty_coverart.png" builder.setLargeIcon(BitmapFactory.decodeFile(coverart)) return builder.build()