# video from server to client if self.server_video_stream_offer is None: self.server_video_stream_offer = self.create_local_tracks() self.pcs[call_number]["pc"].addTrack(self.server_video_stream_offer) # Attach video from server to QLabel if self.server_video_track is None: self.server_video_track = WebCamera(self.server_video_stream_offer,self.to_emitter) if self.server_video_blackholde is None: self.server_video_blackholde = MediaBlackhole() self.server_video_blackholde.addTrack(self.server_video_track) await self.server_video_blackholde.start() @self.pcs[call_number]["pc"].on("track") async def on_track(track): if track.kind == "audio": self.pcs[call_number]["audio_track"] = track # audio from client (server use) if call_number == 1: correct_queue = self.ip_call_1_packet_queue elif call_number == 2: correct_queue = self.ip_call_2_packet_queue else: correct_queue = self.ip_call_3_packet_queue self.put_to_q = True self.pcs[call_number]["audio_track_for_local_use"] = ClientTrack(track, self, self.to_emitter,call_number,self.put_to_q,correct_queue) self.pcs[call_number]["audio_blackhole"] = MediaBlackhole() self.pcs[call_number]["audio_blackhole"].addTrack( self.pcs[call_number]["audio_track_for_local_use"]) await self.pcs[call_number]["audio_blackhole"].start() else: self.pcs[call_number]["video_track"] = track # video from client (server use) self.pcs[call_number]["video_track_for_local_use"] = ClientWebCamera(track, self.to_emitter, call_number, self) self.pcs[call_number]["video_blackhole"] = MediaBlackhole() self.pcs[call_number]["video_blackhole"].addTrack( self.pcs[call_number]["video_track_for_local_use"]) await self.pcs[call_number]["video_blackhole"].start() def get_available_cameras(self): try: devices = FilterGraph().get_input_devices() available_cameras = {} for device_index, device_name in enumerate(devices): available_cameras[device_index] = device_name return available_cameras except: error = traceback.format_exc() self.to_emitter.send({"type": "error", "error_message": error}) def create_local_tracks(self): try: options = {"framerate": "30", "video_size": "640x480"} # Reduced frame rate and resolution camera_name = "video=" + self.get_available_cameras()[0] self.webcam = MediaPlayer(camera_name, format='dshow', options=options) self.relay = MediaRelay() video_track = self.relay.subscribe(self.webcam.video) # Relay the video stream return video_track except Exception as e: error = traceback.format_exc() self.to_emitter.send({"type": "error", "error_message": error}) async def stop_peer_connection(self, call_number, uid): try: if call_number not in self.pcs: # Peer connection does not exist return None if self.pcs[call_number]["stop_in_progress"]: # Stop process is already in progress return None self.pcs[call_number]["stop_in_progress"] = True # 1. Notify PyQt5 about the stop self.to_emitter.send({"type": "stop-peer-connection", "call_number": call_number}) # 2. Empty the correct queue queue = self.call_queues[call_number - 1] while not queue.empty(): queue.get() # 3. Close peer connection try: await self.pcs[call_number]["pc"].close() except Exception: pass # 4. Close data channel try: self.pcs[call_number]["dc"].close() except Exception: pass # 5. Stop client audio track try: await self.pcs[call_number]["audio_blackhole"].stop() except Exception: pass # 6. Notify PyQt5 about call status self.to_emitter.send({"type": f"call-{call_number}-status", "status": "closed-by-client"}) # 7. Stop client video track try: self.pcs[call_number]["video_track_for_local_use"].stop() await self.pcs[call_number]["video_blackhole"].stop() except Exception: pass # 8. Cleanup try: del self.pcs[call_number] except Exception: pass # 9. Release server resources if there are no remaining connections if not self.pcs: # No active peer connections # Stop video relay and related blackholes try: if self.server_video_blackholde: await self.server_video_blackholde.stop() self.server_video_blackholde = None if self.server_video_track: await self.server_video_track.stop() self.server_video_track = None except Exception: pass # Stop server audio stream try: if self.server_audio_stream_offer: self.server_audio_stream_offer.stop() self.server_audio_stream_offer = None except Exception: pass # Stop and release the webcam try: self.relay._stop(self.webcam.video) if self.webcam: self.to_emitter.send({"type": "log", "message": "Stopping webcam."}) if self.webcam.audio: self.webcam.audio.stop() if self.webcam.video: self.webcam._stop(self.webcam.video) self.webcam.video.stop() await asyncio.sleep(0.1) # Give time for cleanup self.webcam = None except Exception as e: self.to_emitter.send({"type": "error", "error_message": str(e)}) # Attempt final resource cleanup gc.collect() # Force garbage collection to release resources self.to_emitter.send({"type": "log", "message": "Garbage collection triggered for cleanup."}) except Exception: error = traceback.format_exc() self.to_emitter.send({"type": "error", "error_message": error}) class WebCamera(MediaStreamTrack): kind = "video" def __init__(self, track, to_emitter): super().__init__() # don't forget this! self.track = track self.to_emitter = to_emitter async def recv(self): try: frame = await self.track.recv() pil_image = frame.to_image() self.to_emitter.send({"type": "server-web-camera-frame", "pil_image": [pil_image]}) return frame.reformat(format="yuv420p") except: error_message = traceback.format_exc() I can't close the camera led after aiortc p2p offer close. The problem is that: # 3. Close peer connection try: await self.pcs[call_number]["pc"].close() except Exception: pass never run (with return)... Edit: There is no error, but this close method pause at: for transceiver in self.__transceivers: await transceiver.stop() --> await self.__sender.stop() --> await asyncio.gather(self.__rtp_started.wait(), self.__rtcp_started.wait()) and that's because the sender is already stoped (client stop the connection in this case). Any thoughts? Continue reading...