Merge pull request #3 from TheNeikos/claude/fix-screenshare-stream-43ABB
Split peerConnection into separate outgoing/incoming connections
This commit is contained in:
commit
c432384aec
1 changed files with 64 additions and 49 deletions
|
|
@ -430,7 +430,7 @@
|
|||
peerId: null,
|
||||
peerIp: null, // Our own IP as seen by the server
|
||||
peerName: localStorage.getItem('lanshare_name') || null,
|
||||
peers: new Map(), // peer_id -> { name, ip, isSharing, hasAudio, stream, peerConnection }
|
||||
peers: new Map(), // peer_id -> { name, ip, isSharing, hasAudio, stream, outgoingPC, incomingPC }
|
||||
localStream: null,
|
||||
isSharing: false,
|
||||
includeAudio: true,
|
||||
|
|
@ -601,9 +601,11 @@
|
|||
peer.isSharing = false;
|
||||
peer.hasAudio = false;
|
||||
peer.stream = null;
|
||||
if (peer.peerConnection) {
|
||||
peer.peerConnection.close();
|
||||
peer.peerConnection = null;
|
||||
// Only close the incoming connection (their stream to us).
|
||||
// Keep our outgoing connection if we're sharing to them.
|
||||
if (peer.incomingPC) {
|
||||
peer.incomingPC.close();
|
||||
peer.incomingPC = null;
|
||||
}
|
||||
if (state.selectedPeerId === msg.peer_id) {
|
||||
state.selectedPeerId = null;
|
||||
|
|
@ -648,16 +650,16 @@
|
|||
isSharing,
|
||||
hasAudio,
|
||||
stream: null,
|
||||
peerConnection: null,
|
||||
outgoingPC: null, // PC where we send our stream to this peer
|
||||
incomingPC: null, // PC where we receive this peer's stream
|
||||
});
|
||||
}
|
||||
|
||||
function removePeer(peerId) {
|
||||
const peer = state.peers.get(peerId);
|
||||
if (peer) {
|
||||
if (peer.peerConnection) {
|
||||
peer.peerConnection.close();
|
||||
}
|
||||
if (peer.outgoingPC) peer.outgoingPC.close();
|
||||
if (peer.incomingPC) peer.incomingPC.close();
|
||||
state.peers.delete(peerId);
|
||||
|
||||
if (state.selectedPeerId === peerId) {
|
||||
|
|
@ -668,45 +670,47 @@
|
|||
}
|
||||
|
||||
// ==================== WebRTC ====================
|
||||
function createPeerConnection(peerId) {
|
||||
// direction: 'outgoing' (we send offer) or 'incoming' (we receive offer)
|
||||
function createPeerConnection(peerId, direction) {
|
||||
const pc = new RTCPeerConnection(rtcConfig);
|
||||
|
||||
// Debug: Log ICE gathering state changes
|
||||
pc.onicegatheringstatechange = () => {
|
||||
console.log(`[${peerId.slice(0,8)}] ICE gathering state: ${pc.iceGatheringState}`);
|
||||
console.log(`[${peerId.slice(0,8)}:${direction}] ICE gathering state: ${pc.iceGatheringState}`);
|
||||
if (pc.iceGatheringState === 'complete') {
|
||||
const sdp = pc.localDescription?.sdp || '';
|
||||
const candidates = extractCandidatesFromSDP(sdp);
|
||||
console.log(`[${peerId.slice(0,8)}] Final SDP has ${candidates.length} candidates:`);
|
||||
console.log(`[${peerId.slice(0,8)}:${direction}] Final SDP has ${candidates.length} candidates:`);
|
||||
candidates.forEach(c => console.log(` ${c}`));
|
||||
}
|
||||
};
|
||||
|
||||
// Debug: Log ICE connection state
|
||||
pc.oniceconnectionstatechange = () => {
|
||||
console.log(`[${peerId.slice(0,8)}] ICE connection state: ${pc.iceConnectionState}`);
|
||||
console.log(`[${peerId.slice(0,8)}:${direction}] ICE connection state: ${pc.iceConnectionState}`);
|
||||
};
|
||||
|
||||
// Trickle ICE - send candidates as they're discovered
|
||||
pc.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
const c = event.candidate;
|
||||
console.log(`[${peerId.slice(0,8)}] Local candidate: ${c.candidate}`);
|
||||
console.log(`[${peerId.slice(0,8)}:${direction}] Local candidate: ${c.candidate}`);
|
||||
console.log(` → type: ${c.type}, protocol: ${c.protocol}, address: ${c.address}, port: ${c.port}`);
|
||||
|
||||
if (USE_TRICKLE_ICE) {
|
||||
// Replace mDNS .local addresses with our real IP for direct LAN connectivity
|
||||
const candidateObj = event.candidate.toJSON();
|
||||
candidateObj.candidate = replaceMdnsWithIp(candidateObj.candidate, state.peerIp);
|
||||
// Embed direction so the remote side routes to the right PC
|
||||
send({
|
||||
type: 'ice_candidate',
|
||||
from: state.peerId,
|
||||
to: peerId,
|
||||
candidate: JSON.stringify(candidateObj),
|
||||
candidate: JSON.stringify({ ...candidateObj, _dir: direction }),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`[${peerId.slice(0,8)}] ICE candidate gathering complete (null candidate)`);
|
||||
console.log(`[${peerId.slice(0,8)}:${direction}] ICE candidate gathering complete (null candidate)`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -733,7 +737,7 @@
|
|||
};
|
||||
|
||||
pc.onconnectionstatechange = () => {
|
||||
console.log(`[${peerId.slice(0,8)}] Connection state: ${pc.connectionState}`);
|
||||
console.log(`[${peerId.slice(0,8)}:${direction}] Connection state: ${pc.connectionState}`);
|
||||
};
|
||||
|
||||
return pc;
|
||||
|
|
@ -782,15 +786,15 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Don't create a new connection if we already have one that's not closed
|
||||
if (peer.peerConnection && peer.peerConnection.connectionState !== 'closed') {
|
||||
console.log(`[${peerId.slice(0,8)}] sendOfferTo: already have connection`);
|
||||
// Don't create a new outgoing connection if we already have one
|
||||
if (peer.outgoingPC && peer.outgoingPC.connectionState !== 'closed') {
|
||||
console.log(`[${peerId.slice(0,8)}] sendOfferTo: already have outgoing connection`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[${peerId.slice(0,8)}] sendOfferTo: creating offer (${USE_TRICKLE_ICE ? 'trickle' : 'bundled'})`);
|
||||
const pc = createPeerConnection(peerId);
|
||||
peer.peerConnection = pc;
|
||||
const pc = createPeerConnection(peerId, 'outgoing');
|
||||
peer.outgoingPC = pc;
|
||||
|
||||
// Add local stream tracks
|
||||
if (state.localStream) {
|
||||
|
|
@ -828,14 +832,14 @@
|
|||
|
||||
const peer = state.peers.get(fromPeerId);
|
||||
|
||||
// Close existing connection if any (new offer = renegotiation)
|
||||
if (peer.peerConnection) {
|
||||
peer.peerConnection.close();
|
||||
// Close existing incoming connection if any (new offer = renegotiation)
|
||||
if (peer.incomingPC) {
|
||||
peer.incomingPC.close();
|
||||
}
|
||||
|
||||
console.log(`[${fromPeerId.slice(0,8)}] handleOffer: creating answer (${USE_TRICKLE_ICE ? 'trickle' : 'bundled'})`);
|
||||
const pc = createPeerConnection(fromPeerId);
|
||||
peer.peerConnection = pc;
|
||||
const pc = createPeerConnection(fromPeerId, 'incoming');
|
||||
peer.incomingPC = pc;
|
||||
|
||||
// Replace any mDNS .local addresses in the remote SDP with the peer's real IP
|
||||
const sdp = JSON.parse(sdpJson);
|
||||
|
|
@ -869,14 +873,14 @@
|
|||
|
||||
async function handleAnswer(fromPeerId, sdpJson) {
|
||||
const peer = state.peers.get(fromPeerId);
|
||||
if (!peer || !peer.peerConnection) {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleAnswer: no peer connection`);
|
||||
if (!peer || !peer.outgoingPC) {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleAnswer: no outgoing peer connection`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only set remote description if we're in the right state
|
||||
if (peer.peerConnection.signalingState !== 'have-local-offer') {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleAnswer: wrong state: ${peer.peerConnection.signalingState}`);
|
||||
if (peer.outgoingPC.signalingState !== 'have-local-offer') {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleAnswer: wrong state: ${peer.outgoingPC.signalingState}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -888,22 +892,32 @@
|
|||
remoteCandidates.forEach(c => console.log(` ${c}`));
|
||||
|
||||
console.log(`[${fromPeerId.slice(0,8)}] handleAnswer: setting remote description`);
|
||||
await peer.peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));
|
||||
await peer.outgoingPC.setRemoteDescription(new RTCSessionDescription(sdp));
|
||||
}
|
||||
|
||||
function handleIceCandidate(fromPeerId, candidateJson) {
|
||||
const peer = state.peers.get(fromPeerId);
|
||||
if (!peer || !peer.peerConnection) {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleIceCandidate: no peer connection`);
|
||||
if (!peer) {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleIceCandidate: peer not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.parse(candidateJson);
|
||||
const dir = data._dir;
|
||||
delete data._dir;
|
||||
|
||||
// Their outgoing PC corresponds to our incoming PC (and vice versa)
|
||||
const pc = dir === 'outgoing' ? peer.incomingPC : peer.outgoingPC;
|
||||
if (!pc) {
|
||||
console.warn(`[${fromPeerId.slice(0,8)}] handleIceCandidate: no matching PC for direction ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace any mDNS .local addresses with the peer's real IP
|
||||
const candidate = JSON.parse(candidateJson);
|
||||
candidate.candidate = replaceMdnsWithIp(candidate.candidate, peer.ip);
|
||||
console.log(`[${fromPeerId.slice(0,8)}] Remote candidate: ${candidate.candidate}`);
|
||||
data.candidate = replaceMdnsWithIp(data.candidate, peer.ip);
|
||||
console.log(`[${fromPeerId.slice(0,8)}:${dir}] Remote candidate: ${data.candidate}`);
|
||||
|
||||
peer.peerConnection.addIceCandidate(new RTCIceCandidate(candidate))
|
||||
pc.addIceCandidate(new RTCIceCandidate(data))
|
||||
.then(() => console.log(`[${fromPeerId.slice(0,8)}] Successfully added remote candidate`))
|
||||
.catch(err => console.error(`[${fromPeerId.slice(0,8)}] Error adding ICE candidate:`, err));
|
||||
}
|
||||
|
|
@ -953,11 +967,12 @@
|
|||
|
||||
state.isSharing = false;
|
||||
|
||||
// Close all peer connections we initiated
|
||||
// Only close outgoing connections (where we were sending our stream).
|
||||
// Keep incoming connections alive so we can still view others' streams.
|
||||
for (const [peerId, peer] of state.peers) {
|
||||
if (peer.peerConnection) {
|
||||
peer.peerConnection.close();
|
||||
peer.peerConnection = null;
|
||||
if (peer.outgoingPC) {
|
||||
peer.outgoingPC.close();
|
||||
peer.outgoingPC = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue