fixed camera mirroring

This commit is contained in:
2026-07-23 06:43:47 +00:00
parent 2efdc83ab0
commit e19fc103b9
5 changed files with 18 additions and 43 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ if os.path.exists('.env'):
# Version
VERSION = '1.0.1'
VERSION = '1.0.2'
# Server settings
SERVER_HOST = '0.0.0.0'
+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);
-19
View File
@@ -109,7 +109,6 @@ function updateVideoGrid() {
maxView.innerHTML = createVideoTile(maxUser, true);
const tile = maxView.querySelector('.video-tile');
if (tile) tile.onclick = () => exitMaximized();
wireTileFlip(tile);
attachStreamToTile(maxUser.id, maxUser.stream, maxUser.isLocal);
thumbStrip.innerHTML = '';
@@ -118,7 +117,6 @@ function updateVideoGrid() {
thumb.className = 'thumbnail';
thumb.innerHTML = createVideoTile(user, false);
thumb.onclick = () => maximizeVideo(user.id);
wireTileFlip(thumb.querySelector('.video-tile'));
thumbStrip.appendChild(thumb);
attachStreamToTile(user.id, user.stream, user.isLocal);
});
@@ -156,7 +154,6 @@ function updateVideoGrid() {
tile.innerHTML = createVideoTile(user, false);
const tileEl = tile.firstElementChild;
tileEl.onclick = () => maximizeVideo(user.id);
wireTileFlip(tileEl);
grid.appendChild(tileEl);
attachStreamToTile(user.id, user.stream, user.isLocal);
});
@@ -171,30 +168,14 @@ function updateVideoGrid() {
function createVideoTile(user, isMaximized) {
const isScreen = user.isScreen || user.id.includes('-screen');
const isLocalUser = user.isLocal && !isScreen;
// Flip-camera overlay button lives on your own camera tile (mobile only). Keeps it
// out of the crowded top control bar and matches how native video apps place it.
const flipBtn = isLocalUser
? `<button class="tile-flip-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>`
: '';
return `
<div class="video-tile ${user.isLocal ? 'local' : ''} ${user.speaking ? 'speaking' : ''} ${isMaximized ? 'maximized' : ''} ${isScreen ? 'screen-share' : ''}" id="tile-${user.id}">
<video autoplay playsinline ${user.isLocal ? 'muted' : ''}></video>
${flipBtn}
<div class="username">${isScreen ? '🖥️ ' : ''}${escapeHtml(user.username)}${isLocalUser ? ' <span class="you">(you)</span>' : ''}</div>
</div>
`;
}
// Wire the flip-camera overlay button inside a tile (stops the click from also
// maximizing/minimizing the tile).
function wireTileFlip(tileEl) {
const flipBtn = tileEl?.querySelector('.tile-flip-btn');
if (flipBtn) flipBtn.onclick = (e) => {
e.stopPropagation();
if (typeof flipCamera === 'function') flipCamera();
};
}
function attachStreamToTile(id, stream, isLocal) {
setTimeout(() => {
const tile = $(`tile-${id}`);
+11 -23
View File
@@ -1450,7 +1450,10 @@ body {
flex: 1;
justify-content: center;
}
/* Hide the session timer on mobile to make room for the flip-camera button. */
#session-timer { display: none; }
.login-box { padding: 2rem 1.5rem; }
.control-btn { width: 44px; height: 44px; }
@@ -1483,33 +1486,18 @@ body {
.desktop-only-setting { display: none; }
}
/* Default visibility outside the mobile breakpoint. */
.mobile-only-setting { display: none; }
.mobile-only { display: none; }
/* Hide mobile-only elements on desktop. Scoped to a min-width query (rather than an
unconditional rule) so it does NOT override the max-width:768px `display` above via
source order - the two queries are mutually exclusive by viewport. */
@media (min-width: 769px) {
.mobile-only-setting { display: none; }
.mobile-only { display: none; }
}
/* Mirror your own camera self-view so it reads like a mirror (native camera app feel).
Display-only - the stream sent to other people is unaffected. Never mirror a screen share. */
.video-tile.local:not(.screen-share) video { transform: scaleX(-1); }
/* Flip-camera overlay button on your own camera tile (shown on mobile via .mobile-only). */
.tile-flip-btn {
position: absolute;
bottom: 10px;
right: 10px;
width: 42px;
height: 42px;
border-radius: 50%;
border: none;
background: rgba(0, 0, 0, 0.55);
color: #fff;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 5;
padding: 0;
}
.tile-flip-btn svg { width: 22px; height: 22px; }
@media (max-width: 480px) {
.video-tile { border-radius: 8px; }
.video-tile .username { font-size: 0.75rem; padding: 0.5rem 0.75rem; }