class WebRTCServer: def __init__(self): self.pc = None self.signaling = None self.recorder = None self.__video = None async def accept(self, port, segment_time, server=None, turn=None): ice_servers = [RTCIceServer('stun:stun.l.google.com:19302')] if turn: ice_servers.append(turn) config = RTCConfiguration(ice_servers) self.pc = RTCPeerConnection(config) if server: self.signaling = WebSocketClient(server, port) else: self.signaling = WebSocketServer(port) recorder_options = { "segment_time": segment_time, "reset_timestamps": "1", "strftime": "1", } self.recorder = MediaRecorder('video/%Y-%m-%d_%H-%M-%S.mkv', format="segment", options=recorder_options) async def send_answer(): logger.debug(f"Ice Gathering State: {self.pc.iceGatheringState}") # отправка происходит если были собраны все IceCandidate if self.pc.iceGatheringState == 'complete': logger.debug("Answer sent") await self.signaling.send_data({ "sdp": self.pc.localDescription.sdp, "type": self.pc.localDescription.type }) else: # если IceCandidate не собраны, то ожидается их сбор self.pc.once("icegatheringstatechange", send_answer) @self.signaling.on_message async def on_message(message): logger.debug(f"{message.get('type')} received") if message.get("type") == "offer": offer = RTCSessionDescription(sdp=message["sdp"], type=message["type"]) await self.pc.setRemoteDescription(offer) answer = await self.pc.createAnswer() await self.pc.setLocalDescription(answer) await send_answer() @self.pc.on("connectionstatechange") async def on_connectionstatechange(): if self.pc.connectionState == "failed": await self.pc.close() elif self.pc.connectionState == "connected": await self.recorder.start() elif self.pc.connectionState == "closed": await self.recorder.stop() logger.info("Recorder closed") logger.info(f"Connection state: {self.pc.connectionState}") @self.pc.on("track") async def on_track(track): if track.kind == "audio": self.recorder.addTrack(track) elif track.kind == "video": self.__video = track self.recorder.addTrack( FixedPtsTrack(MediaRelay().subscribe(track))) logger.info(f"Track {track.kind} added") @track.on("ended") async def on_ended(): await self.recorder.stop() logger.info(f"Track {track.kind} ended") async def close_connection(self): await self.recorder.stop() await self.signaling.close() await self.pc.close() async def video_track(self): if self.__video: return MediaRelay().subscribe(self.__video) else: return None
class WebRTCClient: def __init__(self, resolution="640x480", bitrate=None): self.pc = None self.signaling = None self.__video = None self.resolution = resolution self.bitrate = bitrate async def connect(self, host, port, turn=None): ice_servers = [RTCIceServer('stun:stun.l.google.com:19302')] if turn: ice_servers.append(turn) config = RTCConfiguration(ice_servers) self.pc = RTCPeerConnection(config) if not self.__video: self.__video = await self.__get_tracks() self.pc.addTrack(MediaRelay().subscribe(self.__video)) offer = await self.pc.createOffer() await self.pc.setLocalDescription(offer) self.signaling = WebSocketClient(host, port) async def send_offer(): logger.debug(f"Ice Gathering State: {self.pc.iceGatheringState}") if self.pc.iceGatheringState == 'complete': logger.debug("Offer sent") await self.signaling.send_data({ "sdp": self.pc.localDescription.sdp, "type": self.pc.localDescription.type }) else: self.pc.once("icegatheringstatechange", send_offer) @self.signaling.on_connected async def on_connected(_): await send_offer() @self.signaling.on_message async def on_message(message): logger.debug(f"{message.get('type')} received") if message.get("type") == "answer": answer = RTCSessionDescription(sdp=message["sdp"], type=message["type"]) await self.pc.setRemoteDescription(answer) @self.pc.on("connectionstatechange") async def on_connectionstatechange(): if self.pc.connectionState == "failed": await self.pc.close() logger.info(f"Connection state: {self.pc.connectionState}") async def __get_tracks(self): if self.bitrate: video_options = { "video_size": self.resolution, "framerate": "30", "b:v": self.bitrate } else: video_options = {"video_size": self.resolution, "framerate": "30"} if platform.system() == "Windows": video_track = MediaPlayer("video=HP TrueVision HD Camera", format="dshow", options=video_options).video else: video_track = MediaPlayer("/dev/video0", format="v4l2", options=video_options).video return video_track async def close_connection(self): if self.pc and self.__video and self.signaling: self.__video.stop() await self.pc.close() await self.signaling.close() async def video_track(self): if not self.__video: self.__video = await self.__get_tracks() return MediaRelay().subscribe(self.__video)