I am encountering a problem with the visibility of screen share in my WebRTC application. Our focus is solely on implementing screen sharing without involving video calls. I have successfully received all SDP answers and ICE negotiations, and I am logging the details in the browser console.
The issue arises when screen sharing is enabled; I can see the "enabled" message in the console, but the shared content is not visible, both for me and other users in the same room. Despite extensive debugging, no errors are logged, and the screen share feed remains invisible.
Our tech stack includes Kurento 7.0.0.0 as the media server, React for the front end, and Node.js for the back end. Can anyone provide insights into why screen share might not be working in this scenario? Any help in understanding and resolving this issue would be greatly appreciated.
What I have tried:
import React, { useState, useRef, useEffect } from 'react';
import io from 'socket.io-client';
const App = () => {
const [ roomName, setRoomName ] = useState('');
const [ screenSharing, setScreenSharing ] = useState(false);
const [ peerConnection, setPeerConnection ] = useState(null);
const [ remoteOffer, setRemoteOffer ] = useState(null);
const [ remoteAnswer, setRemoteAnswer ] = useState(null);
const [ roomUsers, setRoomUsers ] = useState(0);
const videoRef = useRef(null);
const socketRef = useRef(null);
const participantPeerConnections = useRef({});
useEffect(() => {
const socket = io('http://localhost:3001');
socket.on('connect', () => console.log('Connected to the server...'));
socket.on('disconnect', (reason) => console.log('Disconnected from the server. Reason:', reason));
socket.on('roomUsers', (usersCount) => setRoomUsers(usersCount));
const handleStartScreenShareResponse = async (sdpAnswer) => {
try {
console.log('Received SDP Answer:', sdpAnswer);
if (peerConnection) {
const sessionDescription = new RTCSessionDescription({ type: 'answer', sdp: sdpAnswer });
console.log('Attempting to set remote description:', sessionDescription);
await peerConnection.setRemoteDescription(sessionDescription);
console.log('Remote description set successfully.');
}
} catch (error) {
console.error('Error setting remote description:', error);
}
};
socket.on('startScreenShareResponse', handleStartScreenShareResponse);
socket.on('iceCandidate', async (candidate) => {
try {
console.log('Received ICE candidate from server:', candidate);
if (peerConnection) {
await peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));
console.log('ICE candidate added successfully.');
}
} catch (error) {
console.error('Error adding ICE candidate:', error);
}
});
socket.on('offer', async (sdpOffer) => {
try {
console.log('Received SDP offer from participant:', sdpOffer);
setRemoteOffer(JSON.parse(sdpOffer));
if (peerConnection) {
const offer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(offer);
console.log('SDP answer generated for participant:', offer);
if (socketRef.current) {
socketRef.current.emit('answer', JSON.stringify(offer));
}
}
} catch (error) {
console.error('Error handling participant offer:', error);
}
});
socket.on('answer', async (sdpAnswer) => {
try {
console.log('Received SDP answer from participant:', sdpAnswer);
setRemoteAnswer(JSON.parse(sdpAnswer));
} catch (error) {
console.error('Error parsing received answer:', error);
}
});
socketRef.current = socket;
socketRef.current.on('roomInfo', ({ roomName, roomUsers }) => {
console.log(`Room Information - Room: ${roomName}, Users: ${roomUsers.join(', ')}`);
});
return () => {
socket.disconnect();
console.log('Disconnected from the server...');
};
}, []);
useEffect(
() => {
const handleParticipantStartScreenShare = async (participantId, participantSdpOffer) => {
try {
const participantPeerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:40.68.138.182:3478' },
{ urls: 'turn:40.68.138.182:3478', username: 'test', credential: 'test123' },
{ urls: 'stun:stun.l.google.com:19302' }
]
});
console.log('Creating RTCPeerConnection for participant...', participantId);
const participantStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
participantStream
.getTracks()
.forEach((track) => participantPeerConnection.addTrack(track, participantStream));
const participantOffer = JSON.parse(participantSdpOffer);
if (!peerConnection || !participantPeerConnections.current[participantId]) {
console.warn('Received offer from unknown participant. Rejecting offer.');
return;
}
await participantPeerConnection.setRemoteDescription(
new RTCSessionDescription({ type: 'offer', sdp: participantOffer })
);
const participantAnswer = await participantPeerConnection.createAnswer();
await participantPeerConnection.setLocalDescription(participantAnswer);
console.log('SDP answer generated for participant:', participantAnswer);
if (socketRef.current) {
socketRef.current.emit('answer', participantId, JSON.stringify(participantAnswer));
}
participantPeerConnection.onicecandidate = (event) => {
if (event.candidate && socketRef.current) {
console.log('Sending ICE candidate for participant...', event.candidate);
socketRef.current.emit(
'iceCandidate',
roomName,
participantId,
JSON.stringify(event.candidate)
);
}
};
participantPeerConnection.ontrack = (event) => {
console.log('Receiving remote video stream for participant:', participantId);
const participantVideo = document.createElement('video');
participantVideo.srcObject = event.streams[0];
participantVideo.autoPlay = true;
participantVideo.playsInline = true;
document.body.appendChild(participantVideo);
};
participantPeerConnection.oniceconnectionstatechange = (event) => {
console.log(
'ICE connection state changed for participant:',
participantId,
event.target.iceConnectionState
);
};
participantPeerConnection.onnegotiationneeded = async (event) => {
if (!remoteOffer || !remoteAnswer) {
console.warn(
'Negotiation needed for participant but no remote offer or answer received. Cancelling negotiation.'
);
return;
}
try {
const newOffer = await participantPeerConnection.createOffer();
await participantPeerConnection.setLocalDescription(newOffer);
console.log('Generated new offer for participant:', participantId, newOffer);
if (socketRef.current) {
socketRef.current.emit('offer', roomName, participantId, JSON.stringify(newOffer));
}
} catch (error) {
console.error('Error creating new offer for participant:', participantId, error);
}
};
participantPeerConnections.current[participantId] = participantPeerConnection;
socketRef.current.off('participantStartScreenShare', handleParticipantStartScreenShare);
} catch (error) {
console.error('Error handling participant start screen share:', error);
}
};
},
[ roomName ]
);
const joinRoom = () => {
if (socketRef.current) {
socketRef.current.emit('joinRoom', roomName);
console.log(`Joining room: ${roomName}`);
}
};
const startScreenShare = async () => {
try {
const newPeerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:40.68.138.182:3478' },
{ urls: 'turn:40.68.138.182:3478', username: 'test', credential: 'test123' },
{ urls: 'stun:stun.l.google.com:19302' }
]
});
console.log('Creating RTCPeerConnection...');
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
stream.getTracks().forEach((track) => newPeerConnection.addTrack(track, stream));
const offer = await newPeerConnection.createOffer();
await newPeerConnection.setLocalDescription(offer);
console.log('SDP offer generated:', offer);
if (socketRef.current) {
console.log('Sending SDP offer to server...', JSON.stringify(offer));
socketRef.current.emit('startScreenShare', roomName, JSON.stringify(offer));
}
newPeerConnection.ondatachannel = (event) => console.log('Data channel created:', event.channel);
newPeerConnection.oniceconnectionstatechange = (event) =>
console.log('Connection state changed:', event.target.iceConnectionState);
newPeerConnection.onicecandidate = (event) => {
if (event.candidate && socketRef.current) {
console.log('Sending ICE candidate from client...', event.candidate);
socketRef.current.emit('iceCandidate', roomName, JSON.stringify(event.candidate));
}
};
newPeerConnection.ontrack = (event) => {
videoRef.current.srcObject = event.streams[0];
console.log('Receiving remote video stream');
};
newPeerConnection.onnegotiationneeded = async (event) => {
if (!remoteOffer || !remoteAnswer) {
console.warn(
'Offer/answer needed, but no remote offer or answer received. Cancelling negotiation.'
);
return;
}
try {
const newOffer = await newPeerConnection.createOffer();
await newPeerConnection.setLocalDescription(newOffer);
console.log('Generated new offer:', newOffer);
if (socketRef.current) {
socketRef.current.emit('offer', roomName, JSON.stringify(newOffer));
}
} catch (error) {
console.error('Error creating new offer:', error);
}
};
newPeerConnection.onicecandidate = (event) => {
if (event.candidate && socketRef.current) {
console.log('Sending ICE candidate from client...', event.candidate);
socketRef.current.emit('iceCandidate', roomName, JSON.stringify(event.candidate));
}
};
newPeerConnection.oniceconnectionstatechange = (event) =>
console.log('ICE connection state changed:', event.target.iceConnectionState);
setPeerConnection(newPeerConnection);
setScreenSharing(true);
} catch (error) {
console.error('Error starting screen share:', error.message);
}
};
const stopScreenShare = () => {
if (peerConnection) {
peerConnection.close();
setPeerConnection(null);
}
setScreenSharing(false);
videoRef.current.srcObject = null;
if (socketRef.current) {
socketRef.current.emit('stopScreenShare');
}
};
return (
<div>
<h1>Screen Sharing App</h1>
<div>
<input
type="text"
placeholder="Enter room name"
value={roomName}
onChange={(e) => setRoomName(e.target.value)}
/>
<button onClick={joinRoom}>Join Room</button>
<p>Users in the room: {roomUsers}</p>
<p>Screen Sharing: {screenSharing ? 'Enabled' : 'Disabled'}</p>
</div>
{screenSharing ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<video
ref={videoRef}
autoPlay
playsInline
style={{ width: '80%', maxWidth: '800px', marginBottom: '20px' }}
/>
<button onClick={stopScreenShare} style={{ padding: '10px', fontSize: '16px' }}>
Stop Screen Share
</button>
</div>
) : (
<button onClick={startScreenShare} style={{ padding: '10px', fontSize: '16px' }}>
Start Screen Share
</button>
)}
</div>
);
};
export default App;