| | |
| | | } |
| | | } |
| | | |
| | | // 步骤7 接收音频流并且播放 |
| | | pc.ontrack = (e) => { |
| | | log('✅ 收到远端音频流') |
| | | const stream = e.streams[0] |
| | |
| | | pc.getSenders().map(s => (s.track ? s.track.id : null)).filter(Boolean) |
| | | ) |
| | | |
| | | // 步骤6 发送音频流 |
| | | stream.getTracks().forEach(track => { |
| | | if (!existingTrackIds.has(track.id)) { |
| | | pc.addTrack(track, stream) |
| | |
| | | return stream |
| | | } |
| | | |
| | | // 步骤5 |
| | | async function flushCandidates() { |
| | | if (!pc || !pc.remoteDescription) return |
| | | while (pendingCandidates.length) { |
| | |
| | | return |
| | | } |
| | | |
| | | // 步骤2 |
| | | if (t === 'offer') { |
| | | // 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”) |
| | | offeredByPeer = true |
| | |
| | | return |
| | | } |
| | | |
| | | // 步骤3 |
| | | if (t === 'answer') { |
| | | log('✅ 收到 answer,通话建立') |
| | | if (!pc) createPC() |
| | | await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp)) |
| | | // 步骤4 |
| | | await flushCandidates() |
| | | |
| | | state.value = 'in-call' |
| | |
| | | // ✅ 不会重复 addTrack |
| | | await ensureLocalTracksAdded() |
| | | |
| | | // 步骤1 |
| | | const offer = await pc.createOffer() |
| | | await pc.setLocalDescription(offer) |
| | | |
| | |
| | | log('➡️ 已发送 offer 给', peerUid.value) |
| | | } |
| | | |
| | | // 步骤2 |
| | | async function answerOffer(offerSdp, from) { |
| | | if (!offerSdp) { |
| | | log('❌ offer sdp 为空,无法接听') |
| | |
| | | if (ws) ws.close() |
| | | }) |
| | | </script> |
| | | |