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)
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 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 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()