import { Comment } from '@my/ui' import { useEffect, useRef, useState } from 'react' import { RTCPeerConnection, RTCSessionDescription, MediaStream } from 'react-native-webrtc' type WebRTCMessageEvent = MessageEvent type DataChannel = { send: (data: string) => void close: () => void readyState: string addEventListener: (event: string, callback: (event: WebRTCMessageEvent) => void) => void removeEventListener: (event: string, callback: (event: WebRTCMessageEvent) => void) => void } const createStreamConnection = (connectionId: string, serverAddress: string) => { const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], }) pc.addEventListener('connectionstatechange', (event) => { console.log('Connection state changed:', pc.connectionState) }) pc.addEventListener('addstream', (event) => { console.log('Stream added:', event) }) pc.addEventListener('removestream', (event) => { console.log('Stream removed:', event) }) pc.addEventListener('track', (event) => { console.log('Track received:', event.streams[0]) }) pc.addEventListener('icecandidate', (event) => { if (event.candidate) { fetch(`${serverAddress}/camera/ice/${connectionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event.candidate.toJSON()), }).catch((err) => console.error('Failed to send ICE candidate:', err)) } }) pc.addEventListener('icecandidateerror', (event) => { console.error('ICE candidate error:', event) }) pc.addEventListener('iceconnectionstatechange', (event) => { console.log('ICE connection state:', pc.iceConnectionState) }) pc.addEventListener('icegatheringstatechange', (event) => { console.log('ICE gathering state:', pc.iceGatheringState) }) pc.addEventListener('negotiationneeded', (event) => { console.log('Negotiation needed') }) pc.addEventListener('signalingstatechange', (event) => { console.log('Signaling state:', pc.signalingState) }) return pc } const destroyStreamConnection = (pc: RTCPeerConnection) => { pc.close() } export const useCameraStream = ( localStream: MediaStream | null, setComments: React.Dispatch>, serverAddress: string ) => { const [dataChannel, setDataChannel] = useState(null) const peerConnection = useRef(null) useEffect(() => { const setupWebRTC = async () => { if (!localStream || peerConnection.current) return try { const connectionResponse = await fetch(`${serverAddress}/camera`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), }).catch((error) => { throw new Error(`Initial connection failed: ${error.message}`) }) const { connection_id } = await connectionResponse.json() const pc = createStreamConnection(connection_id, serverAddress) localStream.getTracks().forEach((track) => pc.addTrack(track, localStream)) const channel = pc.createDataChannel('camera') channel.addEventListener('message', (event) => { try { const message = JSON.parse(event.data as string) if (message?.type === 'comments' && Array.isArray(message?.data)) { setComments((prev) => { return [...prev, ...message.data].slice(-10) }) } } catch (error) { console.error('Failed to parse data channel message:', error) } }) setDataChannel(channel) const offer = await pc.createOffer({}) await pc.setLocalDescription(offer) const offerResponse = await fetch(`${serverAddress}/camera`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sdp: pc.localDescription?.sdp, type: pc.localDescription?.type, }), }) const responseData = await offerResponse.json() await pc.setRemoteDescription( new RTCSessionDescription({ sdp: responseData.sdp, type: responseData.type }) ) peerConnection.current = pc } catch (error) { console.error('Failed to setup WebRTC:', error) } } setupWebRTC() return () => { if (dataChannel) { dataChannel.close() setDataChannel(null) } if (peerConnection.current) { destroyStreamConnection(peerConnection.current) peerConnection.current = null } } }, [localStream, setComments, serverAddress]) return { dataChannel } }