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)