parent
f35fdda7a2
commit
737267567e
3 changed files with 125 additions and 9 deletions
|
|
@ -16,10 +16,12 @@ const state = {
|
|||
localStream: null,
|
||||
rawStream: null,
|
||||
videoStream: null,
|
||||
screenStream: null,
|
||||
audioContext: null,
|
||||
peerConnections: new Map(),
|
||||
muted: false,
|
||||
sharingVideo: false,
|
||||
sharingScreen: false,
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||
},
|
||||
voicePresencePollId: null,
|
||||
|
|
@ -62,6 +64,7 @@ const el = {
|
|||
voiceConnection: document.getElementById("voice-connection"),
|
||||
vcChannelName: document.getElementById("vc-channel-name"),
|
||||
voiceVideoBtn: document.getElementById("voice-video-btn"),
|
||||
voiceScreenBtn: document.getElementById("voice-screen-btn"),
|
||||
voiceMuteBtn: document.getElementById("voice-mute-btn"),
|
||||
voiceLeaveBtn: document.getElementById("voice-leave-btn"),
|
||||
videoGrid: document.getElementById("video-grid"),
|
||||
|
|
@ -563,7 +566,17 @@ function stopAndClearVideoPipeline() {
|
|||
}
|
||||
state.voice.videoStream = null;
|
||||
state.voice.sharingVideo = false;
|
||||
document.getElementById(`video-${state.me.id}`)?.parentElement?.remove();
|
||||
document.getElementById(`video-${state.me.id}-camera`)?.parentElement?.remove();
|
||||
updateVideoGridVisibility();
|
||||
}
|
||||
|
||||
function stopAndClearScreenPipeline() {
|
||||
if (state.voice.screenStream) {
|
||||
for (const track of state.voice.screenStream.getTracks()) track.stop();
|
||||
}
|
||||
state.voice.screenStream = null;
|
||||
state.voice.sharingScreen = false;
|
||||
document.getElementById(`video-${state.me.id}-screen`)?.parentElement?.remove();
|
||||
updateVideoGridVisibility();
|
||||
}
|
||||
|
||||
|
|
@ -576,14 +589,16 @@ function updateVideoGridVisibility() {
|
|||
}
|
||||
}
|
||||
|
||||
function renderVideo(peerId, displayName, stream) {
|
||||
let videoEl = document.getElementById(`video-${peerId}`);
|
||||
function renderVideo(peerId, displayName, stream, source) {
|
||||
const videoId = `video-${peerId}-${source}`;
|
||||
let videoEl = document.getElementById(videoId);
|
||||
if (!videoEl) {
|
||||
const container = document.createElement("div");
|
||||
container.className = "video-item";
|
||||
const label = source === 'screen' ? `${escapeHtml(displayName)}'s Screen` : escapeHtml(displayName);
|
||||
container.innerHTML = `
|
||||
<video id="video-${peerId}" autoplay playsinline ${peerId === state.me.id ? "muted" : ""}></video>
|
||||
<div class="video-label">${escapeHtml(displayName)}</div>
|
||||
<video id="${videoId}" autoplay playsinline ${peerId === state.me.id ? "muted" : ""}></video>
|
||||
<div class="video-label">${label}</div>
|
||||
`;
|
||||
el.videoGrid.appendChild(container);
|
||||
videoEl = container.querySelector("video");
|
||||
|
|
@ -646,6 +661,12 @@ function ensurePeerConnection(peerId) {
|
|||
}
|
||||
}
|
||||
|
||||
if (state.voice.screenStream) {
|
||||
for (const track of state.voice.screenStream.getTracks()) {
|
||||
pc.addTrack(track, state.voice.screenStream);
|
||||
}
|
||||
}
|
||||
|
||||
pc.onicecandidate = (event) => {
|
||||
if (!event.candidate || !state.voice.ws) return;
|
||||
state.voice.ws.send(JSON.stringify({
|
||||
|
|
@ -669,7 +690,13 @@ function ensurePeerConnection(peerId) {
|
|||
audio.srcObject = event.streams[0];
|
||||
} else if (event.track.kind === "video") {
|
||||
const peer = state.members.find(m => m.id === peerId) || { display_name: "Unknown" };
|
||||
renderVideo(peerId, peer.display_name, event.streams[0]);
|
||||
// Distinguish screen share from camera by checking track labels or signaling
|
||||
// For simplicity, we can use the stream index or a dedicated track check.
|
||||
// A more robust way is to have separate transceivers/signaling.
|
||||
// Here we check if the label suggests a screen.
|
||||
const isScreen = event.track.label.toLowerCase().includes('screen') ||
|
||||
event.track.label.toLowerCase().includes('monitor');
|
||||
renderVideo(peerId, peer.display_name, event.streams[0], isScreen ? 'screen' : 'camera');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -785,7 +812,12 @@ async function joinVoice() {
|
|||
await handleSignal(msg.from_user_id, msg.kind, msg.data);
|
||||
} else if (msg.type === "video_status_changed") {
|
||||
if (!msg.is_sharing_video) {
|
||||
document.getElementById(`video-${msg.user_id}`)?.parentElement?.remove();
|
||||
document.getElementById(`video-${msg.user_id}-camera`)?.parentElement?.remove();
|
||||
updateVideoGridVisibility();
|
||||
}
|
||||
} else if (msg.type === "screen_status_changed") {
|
||||
if (!msg.is_sharing_screen) {
|
||||
document.getElementById(`video-${msg.user_id}-screen`)?.parentElement?.remove();
|
||||
updateVideoGridVisibility();
|
||||
}
|
||||
}
|
||||
|
|
@ -825,9 +857,10 @@ async function toggleVideo() {
|
|||
if (state.voice.ws) {
|
||||
state.voice.ws.send(JSON.stringify({ type: "set_video_status", is_sharing_video: false }));
|
||||
}
|
||||
const trackId = `video-${state.me.id}-camera`;
|
||||
for (const pc of state.voice.peerConnections.values()) {
|
||||
const senders = pc.getSenders();
|
||||
const videoSender = senders.find(s => s.track && s.track.kind === "video");
|
||||
const videoSender = senders.find(s => s.track && s.track.kind === "video" && !s.track.label.toLowerCase().includes('screen'));
|
||||
if (videoSender) pc.removeTrack(videoSender);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -835,7 +868,7 @@ async function toggleVideo() {
|
|||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
state.voice.videoStream = stream;
|
||||
state.voice.sharingVideo = true;
|
||||
renderVideo(state.me.id, state.me.display_name, stream);
|
||||
renderVideo(state.me.id, state.me.display_name, stream, 'camera');
|
||||
|
||||
if (state.voice.ws) {
|
||||
state.voice.ws.send(JSON.stringify({ type: "set_video_status", is_sharing_video: true }));
|
||||
|
|
@ -858,6 +891,49 @@ async function toggleVideo() {
|
|||
lucide.createIcons();
|
||||
}
|
||||
|
||||
async function toggleScreenShare() {
|
||||
if (state.voice.sharingScreen) {
|
||||
stopAndClearScreenPipeline();
|
||||
if (state.voice.ws) {
|
||||
state.voice.ws.send(JSON.stringify({ type: "set_screen_status", is_sharing_screen: false }));
|
||||
}
|
||||
for (const pc of state.voice.peerConnections.values()) {
|
||||
const senders = pc.getSenders();
|
||||
const screenSender = senders.find(s => s.track && s.track.kind === "video" && (s.track.label.toLowerCase().includes('screen') || s.track.label.toLowerCase().includes('monitor')));
|
||||
if (screenSender) pc.removeTrack(screenSender);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
|
||||
state.voice.screenStream = stream;
|
||||
state.voice.sharingScreen = true;
|
||||
renderVideo(state.me.id, state.me.display_name, stream, 'screen');
|
||||
|
||||
// Stop sharing if user clicks "Stop sharing" in browser UI
|
||||
stream.getVideoTracks()[0].onended = () => {
|
||||
if (state.voice.sharingScreen) toggleScreenShare();
|
||||
};
|
||||
|
||||
if (state.voice.ws) {
|
||||
state.voice.ws.send(JSON.stringify({ type: "set_screen_status", is_sharing_screen: true }));
|
||||
}
|
||||
|
||||
for (const pc of state.voice.peerConnections.values()) {
|
||||
for (const track of stream.getTracks()) {
|
||||
pc.addTrack(track, stream);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("screen share denied", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
el.voiceScreenBtn.innerHTML = state.voice.sharingScreen ? '<i data-lucide="monitor"></i>' : '<i data-lucide="monitor-off"></i>';
|
||||
el.voiceScreenBtn.style.color = state.voice.sharingScreen ? 'var(--green)' : 'var(--text-muted)';
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// --- Mobile Logic ---
|
||||
|
||||
function toggleMobileMenu() {
|
||||
|
|
@ -1032,6 +1108,7 @@ async function init() {
|
|||
};
|
||||
|
||||
el.voiceVideoBtn.onclick = toggleVideo;
|
||||
el.voiceScreenBtn.onclick = toggleScreenShare;
|
||||
el.voiceMuteBtn.onclick = toggleMute;
|
||||
el.voiceLeaveBtn.onclick = leaveVoice;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue