From e75bb4777ac5cef8de55c2a5b73f23c405093d44 Mon Sep 17 00:00:00 2001 From: bangae1 Date: Fri, 19 Sep 2025 21:39:33 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 2 + src/main.js | 3 +- src/webrtc/uiController.js | 7 +++ src/webrtc/webrtcManager.js | 89 ++++++++++++++++++++++++++++++------- 4 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..daaaa8a --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +webrtc 체크 +chrome://webrtc-internals/ \ No newline at end of file diff --git a/src/main.js b/src/main.js index 05b15c0..80e8fb5 100644 --- a/src/main.js +++ b/src/main.js @@ -10,11 +10,12 @@ const hangupButton = document.getElementById('hangup-btn'); const recordButton = document.getElementById('record-btn'); const stopRecordButton = document.getElementById('stop-record-btn'); const audioPlayback = document.getElementById('audio-playback'); +const peerIdInput = document.getElementById('peer-id-input'); // 인스턴스 생성 const rtcManager = new WebRTCManager('http://localhost:3001'); const recorder = new AudioRecorder(); -const ui = new UIController({ callButton, hangupButton, recordButton, stopRecordButton, audioPlayback }); +const ui = new UIController({ callButton, hangupButton, recordButton, stopRecordButton, audioPlayback, peerIdInput }); // UI 이벤트 바인딩 ui.bindEvents(rtcManager, recorder); diff --git a/src/webrtc/uiController.js b/src/webrtc/uiController.js index 3fc5aa4..d7b3ba6 100644 --- a/src/webrtc/uiController.js +++ b/src/webrtc/uiController.js @@ -16,6 +16,8 @@ export class UIController { const peerId = this.peerIdInput.value.trim(); if (!peerId) return alert('상대방 ID를 입력하세요'); rtcManager.createOffer(peerId); + console.log(rtcManager) + }; // 통화 종료 @@ -24,6 +26,7 @@ export class UIController { // 녹음 시작 this.recordButton.onclick = () => { const stream = rtcManager.remoteStream || rtcManager.localStream; + console.log(stream) if (!stream) return alert('스트림이 없습니다'); recorder.start(stream); }; @@ -45,6 +48,10 @@ export class UIController { // 원격 스트림 수신 시 자동 재생 rtcManager.onRemoteStream = (stream) => { const audio = new Audio(); + console.log(rtcManager) + console.log(stream) + audio.muted = false; + audio.volume = 1; audio.srcObject = stream; audio.play().catch(e => console.warn('자동 재생 실패:', e)); }; diff --git a/src/webrtc/webrtcManager.js b/src/webrtc/webrtcManager.js index 4015536..1bc885d 100644 --- a/src/webrtc/webrtcManager.js +++ b/src/webrtc/webrtcManager.js @@ -10,23 +10,42 @@ export class WebRTCManager { this.ws = null; this.myId = null; this.peerId = null; // 상대방 ID (수동 입력 또는 자동 할당) + this.isInitialized = false; // ✅ 설정 + this.addIceCandidateQueue = []; // ❗ 이전 코드에서 선언 누락 → 여기 추가 } async init() { - await this.requestMicPermission() - this.setupSignaling(); + try { + await this.acquireLocalStream(); // ✅ 이름도 의미에 맞게 변경 + this.setupSignaling(); + console.log('✅ WebRTC 매니저 초기화 완료'); + } catch (err) { + console.error('❌ WebRTC 초기화 실패:', err); + throw err; + } } - async requestMicPermission() { + async acquireLocalStream() { try { - const stream = await navigator.mediaDevices.getUserMedia({audio: true}); - console.log('✅ 마이크 접근 성공'); - stream.getTracks().forEach(track => track.stop()); // 임시로 열었다가 닫음 - return true; + // ✅ 실제 사용할 스트림을 획득하고 저장 + this.localStream = await navigator.mediaDevices.getUserMedia({ + audio: { + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true, + sampleRate: 48000, + channelCount: 1 + } + }); + + this.isInitialized = true; + console.log('🎤 마이크 스트림 획득 및 저장 완료'); + console.log('✅ 획득한 트랙 수:', this.localStream.getTracks().length); // → 1 이상이어야 함 } catch (err) { - console.error('❌ 마이크 권한 거부 또는 장치 없음:', err.message); + this.isInitialized = false; + console.error('❌ 마이크 스트림 획득 실패:', err.message); alert('마이크 접근이 필요합니다. 브라우저 설정에서 마이크 권한을 허용해주세요.'); - return false; + throw err; // 상위에서 처리하도록 } } @@ -44,18 +63,34 @@ export class WebRTCManager { console.log('내 ID:', this.myId); } else if (msg.type === 'offer') { this.setRemoteDescription(msg.sdp); - this.addIceCandidateQueue.forEach(c => this.pc.addIceCandidate(c)); - this.addIceCandidateQueue = []; + // ❗ 큐 처리 전에 pc가 생성되어 있어야 함 → createOffer 또는 setRemoteDescription에서 pc 생성됨 + if (this.addIceCandidateQueue?.length) { + this.addIceCandidateQueue.forEach(c => this.addIceCandidate(c)); + this.addIceCandidateQueue = []; + } } else if (msg.type === 'answer') { this.setRemoteDescription(msg.sdp); } else if (msg.type === 'candidate') { this.addIceCandidate(msg.candidate); } }; + + this.ws.onerror = (err) => { + console.error('WebSocket 오류:', err); + }; } createPeerConnection() { - // ✅ STUN 서버 없이 빈 설정 — 인트라넷 내부 IP로 직접 연결 + if (!this.isInitialized || !this.localStream) { + throw new Error('RTC가 초기화되지 않았거나 마이크 스트림이 없습니다.'); + } + + // ✅ 이미 pc가 있으면 재생성 방지 (옵션) + if (this.pc) { + console.warn('⚠️ PeerConnection이 이미 존재합니다. 기존 연결을 종료하고 새로 생성합니다.'); + this.pc.close(); + } + this.pc = new RTCPeerConnection({}); this.localStream.getTracks().forEach(track => { @@ -67,10 +102,8 @@ export class WebRTCManager { if (this.onRemoteStream) this.onRemoteStream(this.remoteStream); }; - // ICE Candidate → 시그널링으로 전송 this.pc.onicecandidate = (event) => { - if (event.candidate) { - console.log('📤 ICE Candidate:', event.candidate.candidate); + if (event.candidate && this.ws?.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ target: this.peerId, type: 'candidate', @@ -78,9 +111,23 @@ export class WebRTCManager { })); } }; + + this.pc.oniceconnectionstatechange = () => { + console.log('❄️ ICE 상태:', this.pc.iceConnectionState); + }; + + this.pc.onconnectionstatechange = () => { + console.log('🔌 연결 상태:', this.pc.connectionState); + }; + console.log('로컬 스트림 트랙 수:', this.localStream.getTracks().length); } async createOffer(targetId) { + // ✅ 추가 검사 + if (!this.isInitialized || !this.localStream) { + throw new Error('마이크 스트림이 준비되지 않았습니다. 초기화를 완료해주세요.'); + } + this.peerId = targetId; this.createPeerConnection(); const offer = await this.pc.createOffer(); @@ -92,18 +139,30 @@ export class WebRTCManager { sdp: offer })); console.log('📤 Offer 전송 완료'); + console.log('📤 Offer SDP:', offer.sdp); + } async setRemoteDescription(sdp) { + // ✅ PeerConnection이 없으면 자동 생성 (수신 측 대응) + if (!this.pc) { + this.createPeerConnection(); // ← pc 생성 + localStream 트랙 추가 + } + await this.pc.setRemoteDescription(new RTCSessionDescription(sdp)); + if (sdp.type === 'offer') { const answer = await this.pc.createAnswer(); await this.pc.setLocalDescription(answer); + this.ws.send(JSON.stringify({ target: this.peerId || 'unknown', type: 'answer', sdp: answer })); + + console.log('📥 Answer 전송 완료'); + console.log('로컬 스트림 트랙 수:', this.localStream.getTracks().length); } }