mirror of
https://github.com/xtr-dev/rondevu-demo.git
synced 2025-12-11 19:13:24 +00:00
Implement combined polling and ICE candidate exchange for host
- Add offerId to RTCPeerConnection mapping for answer processing - Setup ICE candidate handlers on offerer peer connections - Replace answer-only polling with combined pollOffers() endpoint - Process both answers and ICE candidates in single poll operation - Track last poll timestamp to avoid reprocessing old data - Send offerer ICE candidates to server via addOfferIceCandidates() - Reduces HTTP requests and completes bidirectional ICE exchange
This commit is contained in:
69
src/App.jsx
69
src/App.jsx
@@ -210,26 +210,27 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [setupStep, myUsername, rondevu, myServicePublished]);
|
}, [setupStep, myUsername, rondevu, myServicePublished]);
|
||||||
|
|
||||||
// Poll for answered offers (host side)
|
// Combined polling for answers and ICE candidates (host side)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!myServicePublished || !rondevu || Object.keys(offerIdToPeerConnection).length === 0) {
|
if (!myServicePublished || !rondevu || Object.keys(offerIdToPeerConnection).length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Answer Polling] Starting to poll for answers...');
|
console.log('[Host Polling] Starting combined polling for answers and ICE candidates...');
|
||||||
|
|
||||||
const pollForAnswers = async () => {
|
const poll = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await rondevu.getAnsweredOffers(lastAnswerTimestamp);
|
const result = await rondevu.pollOffers(lastAnswerTimestamp);
|
||||||
|
|
||||||
if (result.offers.length > 0) {
|
// Process answers
|
||||||
console.log(`[Answer Polling] Found ${result.offers.length} new answered offer(s)`);
|
if (result.answers.length > 0) {
|
||||||
|
console.log(`[Host Polling] Found ${result.answers.length} new answer(s)`);
|
||||||
|
|
||||||
for (const answer of result.offers) {
|
for (const answer of result.answers) {
|
||||||
const pc = offerIdToPeerConnection[answer.offerId];
|
const pc = offerIdToPeerConnection[answer.offerId];
|
||||||
|
|
||||||
if (pc && pc.signalingState !== 'stable') {
|
if (pc && pc.signalingState !== 'stable') {
|
||||||
console.log(`[Answer Polling] Setting remote answer for offer ${answer.offerId}`);
|
console.log(`[Host Polling] Setting remote answer for offer ${answer.offerId}`);
|
||||||
|
|
||||||
await pc.setRemoteDescription({
|
await pc.setRemoteDescription({
|
||||||
type: 'answer',
|
type: 'answer',
|
||||||
@@ -239,20 +240,47 @@ export default function App() {
|
|||||||
// Update last answer timestamp
|
// Update last answer timestamp
|
||||||
setLastAnswerTimestamp(prev => Math.max(prev, answer.answeredAt));
|
setLastAnswerTimestamp(prev => Math.max(prev, answer.answeredAt));
|
||||||
|
|
||||||
console.log(`✅ [Answer Polling] Remote answer set for offer ${answer.offerId}`);
|
console.log(`✅ [Host Polling] Remote answer set for offer ${answer.offerId}`);
|
||||||
} else if (pc) {
|
} else if (pc) {
|
||||||
console.log(`[Answer Polling] Skipping offer ${answer.offerId} - already in stable state`);
|
console.log(`[Host Polling] Skipping offer ${answer.offerId} - already in stable state`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process ICE candidates
|
||||||
|
let totalIceCandidates = 0;
|
||||||
|
for (const [offerId, candidates] of Object.entries(result.iceCandidates)) {
|
||||||
|
const pc = offerIdToPeerConnection[offerId];
|
||||||
|
|
||||||
|
if (pc && candidates.length > 0) {
|
||||||
|
console.log(`[Host Polling] Processing ${candidates.length} ICE candidate(s) for offer ${offerId}`);
|
||||||
|
|
||||||
|
for (const item of candidates) {
|
||||||
|
if (item.candidate) {
|
||||||
|
try {
|
||||||
|
await pc.addIceCandidate(new RTCIceCandidate(item.candidate));
|
||||||
|
totalIceCandidates++;
|
||||||
|
// Update timestamp
|
||||||
|
setLastAnswerTimestamp(prev => Math.max(prev, item.createdAt));
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[Host Polling] Failed to add ICE candidate for offer ${offerId}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalIceCandidates > 0) {
|
||||||
|
console.log(`✅ [Host Polling] Added ${totalIceCandidates} ICE candidate(s)`);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Answer Polling] Error polling for answers:', err);
|
console.error('[Host Polling] Error polling:', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Poll every 2 seconds
|
// Poll every 2 seconds
|
||||||
const interval = setInterval(pollForAnswers, 2000);
|
const interval = setInterval(poll, 2000);
|
||||||
pollForAnswers(); // Initial poll
|
poll(); // Initial poll
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [myServicePublished, rondevu, offerIdToPeerConnection, lastAnswerTimestamp]);
|
}, [myServicePublished, rondevu, offerIdToPeerConnection, lastAnswerTimestamp]);
|
||||||
@@ -359,7 +387,7 @@ export default function App() {
|
|||||||
const pc = new RTCPeerConnection(getCurrentRtcConfig());
|
const pc = new RTCPeerConnection(getCurrentRtcConfig());
|
||||||
const dc = pc.createDataChannel('chat');
|
const dc = pc.createDataChannel('chat');
|
||||||
|
|
||||||
// Setup handlers
|
// Setup handlers (will be enhanced with offerId later)
|
||||||
setupHostConnection(pc, dc, myUsername);
|
setupHostConnection(pc, dc, myUsername);
|
||||||
|
|
||||||
// Create offer
|
// Create offer
|
||||||
@@ -381,13 +409,24 @@ export default function App() {
|
|||||||
ttl: 300000, // 5 minutes
|
ttl: 300000, // 5 minutes
|
||||||
});
|
});
|
||||||
|
|
||||||
// Map offerIds to peer connections
|
// Map offerIds to peer connections and setup ICE handlers
|
||||||
const offerMapping = {};
|
const offerMapping = {};
|
||||||
const hostConnMap = {};
|
const hostConnMap = {};
|
||||||
publishResult.offers.forEach((offer, idx) => {
|
publishResult.offers.forEach((offer, idx) => {
|
||||||
const conn = connections[idx];
|
const conn = connections[idx];
|
||||||
offerMapping[offer.offerId] = conn.pc;
|
offerMapping[offer.offerId] = conn.pc;
|
||||||
hostConnMap[`host-${idx}`] = { pc: conn.pc, dc: conn.dc, offerId: offer.offerId, status: 'waiting' };
|
hostConnMap[`host-${idx}`] = { pc: conn.pc, dc: conn.dc, offerId: offer.offerId, status: 'waiting' };
|
||||||
|
|
||||||
|
// Setup ICE candidate handler for offerer
|
||||||
|
conn.pc.onicecandidate = (event) => {
|
||||||
|
if (event.candidate) {
|
||||||
|
rondevu.addOfferIceCandidates(
|
||||||
|
fqn,
|
||||||
|
offer.offerId,
|
||||||
|
[event.candidate.toJSON()]
|
||||||
|
).catch(err => console.error(`[Host] Failed to send ICE candidate for offer ${offer.offerId}:`, err));
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
setOfferIdToPeerConnection(offerMapping);
|
setOfferIdToPeerConnection(offerMapping);
|
||||||
|
|||||||
Reference in New Issue
Block a user