There are my idea steps that I try to implement:
- callee start a live and wait to a new caller join
- caller join and then send offer and establish peer-to-peer following WebRTC workflow normally
Actual: I try to log and not see any ice candidate triggered
server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const path = require('path');
const cors = require('cors');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: '*', // You can specify specific origins instead of '*' for better security
methods: ['GET', 'POST']
}
});
app.use(cors());
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.get('/lives/:id', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'callee.html'));
});
app.get('/view/:id', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'caller.html'));
});
io.on('connection', (socket) => {
console.log('New client connected:', socket.id);
socket.on('start-lives', ({ role, roomId }) => {
socket.data = { role, roomId };
socket.join(roomId);
console.log("Owner connected");
});
socket.on('join', (data) => {
socket.join(data.roomId);
socket.to(data.roomId).emit('join', socket.id);
console.log(`Client ${socket.id} joined room ${data.roomId}`);
});
socket.on('offer', (data) => {
socket.to(data.roomId).emit('offer', data.sdp);
});
socket.on('answer', (data) => {
console.log("transfer:answer");
socket.to(data.roomId).emit('answer', data.sdp);
});
socket.on('candidate', (data) => {
socket.to(data.roomId).emit('candidate', data.candidate);
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.data);
});
});
server.listen(8080, () => {
console.log('Server listening on port 8080');
});
callee.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Livestream</title>
<style>
#lives-container {
width: 50vw;
}
#lives-video {
background: url('https://marketplace.canva.com/EAEyZPDsh-4/1/0/1600w/canva-dark-purple-futuristic-stream-starting-soon-twitch-background-9h5ocxbndVU.jpg') no-repeat center;
width: 100%;
}
#btn-start-lives {
display: block;
margin-left: auto;
}
</style>
</head>
<body>
<h1>Livestream</h1>
<section id="lives-container">
<video id="lives-video" autoplay playsinline></video>
<button id="btn-start-lives">Start</button>
<button id="btn-end-lives">End</button>
</section>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script>
const socket = io('http://localhost:8080');
const livesVideo = document.getElementById('lives-video');
const btnStartLives = document.getElementById('btn-start-lives');
const btnEndLives = document.getElementById('btn-end-lives');
const roomId = window.location.pathname.split('/').pop();
let stream;
let pc = new RTCPeerConnection();
pc.onicecandidate = event => {
console.log("send:candidate");
if (event.candidate) {
socket.emit('candidate', { roomId, candidate: event.candidate });
}
}
socket.on('offer', async (sdp) => {
console.log("received:offer");
stream.getTracks().forEach(track => pc.addTrack(track, stream));
await pc.setRemoteDescription(new RTCSessionDescription({ type: 'offer', sdp }));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
console.log("send:answer");
socket.emit('answer', { roomId, sdp: answer.sdp });
});
socket.on('candidate', async (candidate) => {
console.log("received:candidate");
try {
await pc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (e) {
console.error('Error adding received ice candidate', e);
}
});
btnStartLives.addEventListener("click", async function () {
stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
livesVideo.srcObject = stream;
livesVideo.onloadedmetadata = () => {
livesVideo.play();
};
socket.emit('start-lives', { role: 'owner', roomId });
});
btnEndLives.addEventListener("click", async function () {
stream.getTracks().forEach(track => track.stop());
pc.close();
});
</script>
</body>
</html>
caller.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Livestream viewer</title>
</head>
<body>
<section>
<video id="remote-video"></video>
<button id="btn-approval">See lives</button>
</section>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script>
const socket = io('http://localhost:8080');
const remoteVideo = document.getElementById('remote-video');
const btnApproval = document.getElementById('btn-approval');
const roomId = window.location.pathname.split('/').pop();
let pc = new RTCPeerConnection();
remoteVideo.onloadedmetadata = () => {
remoteVideo.play();
}
pc.onicecandidate = event => {
console.log("send:candidate");
if (event.candidate) {
socket.emit('candidate', { roomId, candidate: event.candidate });
}
}
pc.ontrack = event => {
console.log("trigger:ontrack");
remoteVideo.srcObject = event.streams[0];
}
btnApproval.addEventListener("click", async function () {
// let stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
// stream.getTracks().forEach(track => pc.addTrack(track, stream));
socket.emit('join', { roomId });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
console.log("send:offer");
socket.emit('offer', { roomId, sdp: offer.sdp });
this.remove();
});
socket.on('answer', async (sdp) => {
console.log("received:answer");
await pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp }));
});
socket.on('candidate', async (candidate) => {
console.log("received:candidate");
try {
await pc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (e) {
console.error('Error adding received ice candidate', e);
}
});
</script>
</body>
</html>
I try to log and not see any ice candidate triggered, my expected is the ice candidate gathering should be triggered because the setLocalDescription() is called
2
Answers
The API you are using may be outdated.
I followed the web page below to build my app.
https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
I don’t see any stun server list in order to pick up a list of ice candidate
];
my free demo about webrtc