Skip to content

Commit 4002aa3

Browse files
authored
Merge pull request #23 from devanshu-puri/feature/screen-share
Add screen sharing functionality: start/stop share, display remote sc…
2 parents 016fcd9 + 7fe67af commit 4002aa3

File tree

1 file changed

+168
-21
lines changed

1 file changed

+168
-21
lines changed

src/pages/App.jsx

Lines changed: 168 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,41 @@ function generateQR(text, size = 200) {
4646
return qrUrl
4747
}
4848

49+
//Helper function
50+
async function toggleScreenShare() {
51+
if (isScreenSharing) {
52+
stopScreenShare();
53+
} else {
54+
try {
55+
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
56+
screenStreamRef.current = screenStream;
57+
setIsScreenSharing(true);
58+
59+
// Send screen stream to all connected peers
60+
peers.forEach((peer) => {
61+
const conn = peerRef.current.connections[peer.id]?.[0];
62+
if (conn) {
63+
const sender = conn.peerConnection.getSenders().find(s => s.track.kind === "video");
64+
if (sender) sender.replaceTrack(screenStream.getVideoTracks()[0]);
65+
}
66+
});
67+
68+
screenStream.getVideoTracks()[0].onended = stopScreenShare; // stops when user clicks browser stop
69+
} catch (err) {
70+
console.error("Screen share failed:", err);
71+
}
72+
}
73+
}
74+
75+
function stopScreenShare() {
76+
if (screenStreamRef.current) {
77+
screenStreamRef.current.getTracks().forEach(track => track.stop());
78+
screenStreamRef.current = null;
79+
}
80+
setIsScreenSharing(false);
81+
}
82+
83+
4984
export default function App() {
5085
const [cfg, setCfg] = useLocalStorage('peer.cfg', defaultConfig)
5186
const [label, setLabel] = useLocalStorage('peer.label', '')
@@ -74,8 +109,15 @@ export default function App() {
74109
const audioRef = useRef(null)
75110
const chatEndRef = useRef(null)
76111

77-
112+
//Add State and Refs for Screen Sharing
113+
const [isScreenSharing, setIsScreenSharing] = useState(false);
114+
const [remoteScreenStream, setRemoteScreenStream] = useState(null);
115+
const remoteScreenRef = useRef(null); // For displaying remote screen
116+
// State to track if local screen is being shared
117+
const [sharingScreen, setSharingScreen] = useState(false);
78118
//adding function for better optimization
119+
const localScreenRef = useRef(null);
120+
79121
function getStatusColor(status) {
80122
switch(status) {
81123
case 'online': return '#10b981';
@@ -86,6 +128,46 @@ export default function App() {
86128
}
87129
}
88130

131+
132+
async function startScreenShare() {
133+
try {
134+
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
135+
setSharingScreen(true);
136+
localScreenRef.current = stream;
137+
138+
// Send this stream to all connected peers
139+
Object.values(connRef.current).forEach(conn => {
140+
if (conn && conn.open && conn.peerConnection) {
141+
const sender = conn.peerConnection.getSenders().find(s => s.track?.kind === 'video');
142+
if (sender) sender.replaceTrack(stream.getVideoTracks()[0]);
143+
}
144+
});
145+
146+
// Stop sharing if user stops manually
147+
stream.getVideoTracks()[0].onended = () => stopScreenShare();
148+
} catch (err) {
149+
console.error('Screen share error:', err);
150+
pushLog('Screen share error: ' + err.message);
151+
}
152+
}
153+
154+
function stopScreenShare() {
155+
// Revert back to normal video/mic track
156+
Object.values(connRef.current).forEach(conn => {
157+
if (conn && conn.open && conn.peerConnection && mediaRef.current) {
158+
const sender = conn.peerConnection.getSenders().find(s => s.track?.kind === 'video');
159+
if (sender) sender.replaceTrack(mediaRef.current.getVideoTracks()[0]);
160+
}
161+
});
162+
163+
if (localScreenRef.current) {
164+
localScreenRef.current.getTracks().forEach(track => track.stop());
165+
localScreenRef.current = null;
166+
}
167+
168+
setSharingScreen(false);
169+
}
170+
89171
const peerOptions = useMemo(() => ({
90172
host: cfg.host, port: Number(cfg.port), secure: !!cfg.secure, path: cfg.path || '/',
91173
config: {
@@ -118,17 +200,33 @@ export default function App() {
118200
setupDataConnection(conn)
119201
})
120202

121-
peer.on('call', async (call) => {
122-
try {
123-
const stream = await getMic()
124-
call.answer(stream)
125-
setStreamActive(true)
126-
call.on('stream', (remote) => attachRemoteAudio(remote))
127-
call.on('close', () => setStreamActive(false))
128-
} catch (e) {
129-
pushLog('Mic error: ' + e.message)
130-
}
131-
})
203+
peer.on('call', async (call) => {
204+
try {
205+
if (call.metadata?.type === 'screen') {
206+
// This is a screen share
207+
call.answer(); // usually no local screen stream needed
208+
call.on('stream', (remote) => {
209+
setRemoteScreenStream(remote); // <-- store remote screen stream
210+
if (remoteScreenRef.current) {
211+
remoteScreenRef.current.srcObject = remote;
212+
}
213+
});
214+
call.on('close', () => setRemoteScreenStream(null));
215+
} else {
216+
// Normal audio call
217+
const stream = await getMic();
218+
call.answer(stream);
219+
setStreamActive(true);
220+
call.on('stream', (remote) => attachRemoteAudio(remote));
221+
call.on('close', () => setStreamActive(false));
222+
}
223+
} catch (e) {
224+
pushLog('Call error: ' + e.message);
225+
}
226+
});
227+
228+
229+
132230

133231
return () => {
134232
peer.destroy()
@@ -585,15 +683,26 @@ export default function App() {
585683
</div>
586684

587685
<div className="row" style={{ marginTop: 12 }}>
588-
<div className="grow">
589-
<div className="small">Your Peer ID</div>
590-
<div className="mono" style={{ wordBreak: 'break-all' }}>{myId || 'Starting…'}</div>
591-
</div>
592-
<button className="secondary" onClick={() => copy(myId)} disabled={!myId}>Copy ID</button>
593-
<button className="secondary" onClick={() => setShowQR(!showQR)} disabled={!myId}>
594-
{showQR ? 'Hide QR' : 'Show QR'}
595-
</button>
596-
</div>
686+
<div className="grow">
687+
<div className="small">Your Peer ID</div>
688+
<div className="mono" style={{ wordBreak: 'break-all' }}>{myId || 'Starting…'}</div>
689+
</div>
690+
691+
<button className="secondary" onClick={() => copy(myId)} disabled={!myId}>Copy ID</button>
692+
<button className="secondary" onClick={() => setShowQR(!showQR)} disabled={!myId}>
693+
{showQR ? 'Hide QR' : 'Show QR'}
694+
</button>
695+
696+
{/* Screen Share Button */}
697+
<button
698+
className="primary"
699+
onClick={toggleScreenShare}
700+
disabled={!connected || streamActive}
701+
>
702+
{isScreenSharing ? "Stop Screen Share" : "Share Screen"}
703+
</button>
704+
</div>
705+
597706

598707
{showQR && myId && (
599708
<div className="card" style={{ marginTop: 16, padding: 16, textAlign: 'center' }}>
@@ -688,9 +797,47 @@ export default function App() {
688797
<button disabled={!connected} onClick={() => callPeer(peerIdInput)} className="primary">Call</button>
689798
<button disabled={!streamActive} onClick={endCall} className="danger">End Call</button>
690799
<button disabled={!streamActive} onClick={toggleMute}>{muted ? 'Unmute' : 'Mute'}</button>
800+
801+
<button onClick={startScreenShare} disabled={!connected || sharingScreen} className="secondary">Start Screen Share</button>
802+
<button onClick={stopScreenShare} disabled={!sharingScreen} className="secondary">Stop Screen Share</button>
691803
</div>
804+
805+
<div className="row" style={{ marginTop: 10 }}>
806+
<button
807+
className="secondary"
808+
onClick={startScreenShare}
809+
disabled={!connected || sharingScreen}
810+
>
811+
{sharingScreen ? 'Sharing...' : 'Share Screen'}
812+
</button>
813+
<button
814+
className="secondary"
815+
onClick={stopScreenShare}
816+
disabled={!sharingScreen}
817+
>
818+
Stop Sharing
819+
</button>
820+
</div>
821+
692822
</div>
693823

824+
{/* Screen Share Display Card */}
825+
<div className="card" style={{ padding: 16, marginTop: 16 }}>
826+
<div className="small">Shared Screen</div>
827+
{remoteScreenStream ? (
828+
<video
829+
ref={remoteScreenRef}
830+
autoPlay
831+
playsInline
832+
style={{ width: '100%', borderRadius: '8px', marginTop: '8px' }}
833+
/>
834+
) : (
835+
<div className="small" style={{ opacity: 0.5, marginTop: 8 }}>
836+
No screen being shared yet.
837+
</div>
838+
)}
839+
</div>
840+
694841
<div className="card" style={{ padding: 16 }}>
695842
<div className="small">Status</div>
696843
<div className="row" style={{ marginTop: 6, gap: 6 }}>

0 commit comments

Comments
 (0)