fixed camera mirroring and added fip camera button

This commit is contained in:
2026-07-23 06:24:16 +00:00
parent e41f96efd9
commit f4932cacd5
5 changed files with 56 additions and 1 deletions
+5
View File
@@ -97,6 +97,11 @@
<path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/>
</svg>
</button>
<button id="flip-cam-btn" class="control-btn mobile-only" title="Flip Camera">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 3c.98 0 1.87.31 2.6.84l-1.1 1.1c-.44-.28-.95-.44-1.5-.44-1.65 0-3 1.35-3 3h1.75L8.5 16.75 6.25 14.5H8c0-2.21 1.79-4 4-4zm0 10c-.98 0-1.87-.31-2.6-.84l1.1-1.1c.44.28.95.44 1.5.44 1.65 0 3-1.35 3-3h-1.75l2.25-2.25 2.25 2.25H16c0 2.21-1.79 4-4 4z"/>
</svg>
</button>
<button id="screen-btn" class="control-btn" title="Share Screen (S)">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/>
+1
View File
@@ -26,6 +26,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (typeof initVoiceChangerListeners === 'function') initVoiceChangerListeners();
$('mic-btn').addEventListener('click', toggleMic);
$('cam-btn').addEventListener('click', toggleCam);
$('flip-cam-btn')?.addEventListener('click', flipCamera);
$('screen-btn').addEventListener('click', toggleScreen);
$('volume-btn').addEventListener('click', toggleVolume);
$('users-btn').addEventListener('click', toggleSidebar);
+44 -1
View File
@@ -42,7 +42,7 @@ async function toggleCam() {
if (!state.camEnabled) {
try {
const videoStream = await navigator.mediaDevices.getUserMedia({
video: getVideoConstraints()
video: { ...getVideoConstraints(), facingMode: { ideal: state.facingMode || 'user' } }
});
const videoTrack = videoStream.getVideoTracks()[0];
state.localStream.addTrack(videoTrack);
@@ -109,6 +109,49 @@ async function toggleCam() {
updateUI();
}
// Mobile flip-camera button: switch between front ('user') and back ('environment').
// If the camera is off, we just remember the choice for next time it's turned on.
async function flipCamera() {
state.facingMode = (state.facingMode === 'environment') ? 'user' : 'environment';
if (!state.camEnabled) {
updateUI();
return;
}
try {
const newStream = await navigator.mediaDevices.getUserMedia({
video: { ...getVideoConstraints(), facingMode: { ideal: state.facingMode } }
});
const newTrack = newStream.getVideoTracks()[0];
// Swap the camera track in the local stream (leave any screen-share track alone).
const oldTrack = state.localStream.getVideoTracks()[0];
if (oldTrack) {
oldTrack.stop();
state.localStream.removeTrack(oldTrack);
}
state.localStream.addTrack(newTrack);
// Replace on each peer's CAMERA video sender (skip the screen-share sender).
for (const [peerId, peer] of Object.entries(state.peers)) {
const sender = peer.pc.getSenders().find(s => s.track && s.track.kind === 'video' && s !== peer.screenSender);
if (sender) await sender.replaceTrack(newTrack);
}
// Re-apply outgoing shaping to the fresh sender track.
if (typeof applyVideoBitrateCap === 'function') applyVideoBitrateCap();
if (typeof applyAudioOnlyGatingAll === 'function') applyAudioOnlyGatingAll();
updateVideoGrid();
console.log('[Camera] Flipped to', state.facingMode);
} catch (e) {
console.error('[Camera] Flip failed:', e);
// Revert the desired facing so the button state stays truthful.
state.facingMode = (state.facingMode === 'environment') ? 'user' : 'environment';
}
}
async function toggleScreen() {
// Breakout room is voice-only - block enabling screen share.
if (!state.screenEnabled && state.users['local']?.breakout) {
+3
View File
@@ -26,6 +26,9 @@ const state = {
micEnabled: true,
camEnabled: false,
screenEnabled: false,
// Which camera to capture: 'user' (front, default) or 'environment' (back). Flipped
// via the mobile flip-camera button.
facingMode: 'user',
volumeEnabled: true,
maximizedPeer: null,
sidebarOpen: true,
+3
View File
@@ -1475,6 +1475,8 @@ body {
/* Mobile-only settings rows (e.g. Speaker Mode toggle) */
.mobile-only-setting { display: flex; }
/* Mobile-only controls (e.g. flip-camera button) */
.mobile-only { display: flex; }
/* Speaker device picker is useless on mobile - setSinkId isn't supported and
enumerateDevices returns nothing meaningful. Hide it; the Speaker Mode toggle
replaces it. */
@@ -1483,6 +1485,7 @@ body {
/* Default visibility outside the mobile breakpoint. */
.mobile-only-setting { display: none; }
.mobile-only { display: none; }
@media (max-width: 480px) {
.video-tile { border-radius: 8px; }