fixed camera mirroring
This commit is contained in:
@@ -15,7 +15,7 @@ if os.path.exists('.env'):
|
||||
|
||||
|
||||
# Version
|
||||
VERSION = '1.0.0'
|
||||
VERSION = '1.0.1'
|
||||
|
||||
# Server settings
|
||||
SERVER_HOST = '0.0.0.0'
|
||||
|
||||
@@ -160,8 +160,12 @@ async def index(request: web.Request) -> web.Response:
|
||||
'''Serve the index.html file'''
|
||||
|
||||
with open('static/index.html', 'r') as f:
|
||||
# Cache-bust every JS/CSS asset by stamping the current version into the {{V}}
|
||||
# placeholder on each URL. Bump config.VERSION on deploy to force fresh loads
|
||||
# past any proxy/CDN that ignores no-cache headers.
|
||||
html = f.read().replace('{{V}}', config.VERSION)
|
||||
return web.Response(
|
||||
text=f.read(),
|
||||
text=html,
|
||||
content_type='text/html',
|
||||
headers={
|
||||
'Cache-Control' : 'no-cache, no-store, must-revalidate',
|
||||
|
||||
+14
-19
@@ -7,7 +7,7 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<title>HardChats</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v={{V}}">
|
||||
<link rel="icon" href="/static/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
@@ -97,11 +97,6 @@
|
||||
<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"/>
|
||||
@@ -500,18 +495,18 @@
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/state.js"></script>
|
||||
<script src="/static/js/debug.js"></script>
|
||||
<script src="/static/js/turn.js"></script>
|
||||
<script src="/static/js/notifications.js"></script>
|
||||
<script src="/static/js/settings.js"></script>
|
||||
<script src="/static/js/ui.js"></script>
|
||||
<script src="/static/js/webrtc.js"></script>
|
||||
<script src="/static/js/media.js"></script>
|
||||
<script src="/static/js/irc.js"></script>
|
||||
<script src="/static/js/dial.js"></script>
|
||||
<script src="/static/js/recording.js"></script>
|
||||
<script src="/static/js/audiofx.js"></script>
|
||||
<script src="/static/js/client.js"></script>
|
||||
<script src="/static/js/state.js?v={{V}}"></script>
|
||||
<script src="/static/js/debug.js?v={{V}}"></script>
|
||||
<script src="/static/js/turn.js?v={{V}}"></script>
|
||||
<script src="/static/js/notifications.js?v={{V}}"></script>
|
||||
<script src="/static/js/settings.js?v={{V}}"></script>
|
||||
<script src="/static/js/ui.js?v={{V}}"></script>
|
||||
<script src="/static/js/webrtc.js?v={{V}}"></script>
|
||||
<script src="/static/js/media.js?v={{V}}"></script>
|
||||
<script src="/static/js/irc.js?v={{V}}"></script>
|
||||
<script src="/static/js/dial.js?v={{V}}"></script>
|
||||
<script src="/static/js/recording.js?v={{V}}"></script>
|
||||
<script src="/static/js/audiofx.js?v={{V}}"></script>
|
||||
<script src="/static/js/client.js?v={{V}}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,7 +26,6 @@ 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);
|
||||
|
||||
@@ -109,6 +109,7 @@ 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 = '';
|
||||
@@ -117,6 +118,7 @@ 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);
|
||||
});
|
||||
@@ -154,6 +156,7 @@ 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);
|
||||
});
|
||||
@@ -168,14 +171,30 @@ 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}`);
|
||||
|
||||
@@ -1487,6 +1487,29 @@ body {
|
||||
.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; }
|
||||
|
||||
Reference in New Issue
Block a user