[mirotalk] - add qr and share btn

This commit is contained in:
Miroslav Pejic
2026-02-12 15:13:33 +01:00
parent 62b50c7028
commit a66ff70083
10 changed files with 177 additions and 34 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# ====================================================
# MiroTalk P2P v.1.7.29 - Environment Configuration
# MiroTalk P2P v.1.7.30 - Environment Configuration
# ====================================================
# App environment
+1 -1
View File
@@ -2,7 +2,7 @@
/**
* ==============================================
* MiroTalk P2P v.1.7.29 - Configuration File
* MiroTalk P2P v.1.7.30 - Configuration File
* ==============================================
*
* Branding and customizations require a license:
+1 -1
View File
@@ -45,7 +45,7 @@ dependencies: {
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.29
* @version 1.7.30
*
*/
+6 -17
View File
@@ -1,12 +1,12 @@
{
"name": "mirotalk",
"version": "1.7.29",
"version": "1.7.30",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mirotalk",
"version": "1.7.29",
"version": "1.7.30",
"license": "AGPL-3.0",
"dependencies": {
"@mattermost/client": "11.3.0",
@@ -31,7 +31,7 @@
"jsonwebtoken": "^9.0.3",
"nodemailer": "^8.0.1",
"openai": "^6.21.0",
"qs": "^6.14.1",
"qs": "^6.14.2",
"socket.io": "^4.8.3",
"swagger-ui-express": "^5.0.1",
"uuid": "13.0.0"
@@ -226,7 +226,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -265,7 +264,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -584,7 +582,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -606,7 +603,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz",
"integrity": "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
@@ -619,7 +615,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -635,7 +630,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz",
"integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api-logs": "0.211.0",
"import-in-the-middle": "^2.0.0",
@@ -1029,7 +1023,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
@@ -1046,7 +1039,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/resources": "2.5.0",
@@ -1064,7 +1056,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz",
"integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=14"
}
@@ -1477,7 +1468,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2802,7 +2792,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -5406,9 +5395,9 @@
}
},
"node_modules/qs": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.7.29",
"version": "1.7.30",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
@@ -63,7 +63,7 @@
"jsonwebtoken": "^9.0.3",
"nodemailer": "^8.0.1",
"openai": "^6.21.0",
"qs": "^6.14.1",
"qs": "^6.14.2",
"socket.io": "^4.8.3",
"swagger-ui-express": "^5.0.1",
"uuid": "13.0.0"
+53 -5
View File
@@ -30,16 +30,14 @@
box-sizing: border-box;
}
html,
body {
height: 100%;
}
html {
height: 100%;
color-scheme: dark;
background-color: var(--cr-bg-color);
}
body {
min-height: 100%;
margin: 0;
font-family:
Inter,
@@ -366,6 +364,14 @@ body::before {
align-items: center;
}
.cr-actions {
display: inline-flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.cr-preview {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
@@ -403,6 +409,40 @@ body::before {
cursor: not-allowed;
}
.cr-qr-wrap {
margin-top: 10px;
padding: 12px;
border-radius: 14px;
border: 1px solid var(--cr-card-border);
background: rgba(255, 255, 255, 0.03);
display: grid;
gap: 10px;
place-items: center;
}
.cr-qr {
width: 180px;
height: 180px;
padding: 10px;
border-radius: 14px;
border: 1px solid var(--cr-input-border);
background: var(--cr-input-bg);
display: grid;
place-items: center;
}
.cr-qr canvas,
.cr-qr img {
display: block;
max-width: 100%;
max-height: 100%;
border-radius: 10px;
}
.cr-qr-hint {
text-align: center;
}
@media (max-width: 520px) {
.cr-card {
padding: 24px 18px;
@@ -416,4 +456,12 @@ body::before {
.cr-toggle-grid {
grid-template-columns: 1fr 1fr;
}
.cr-preview-row {
grid-template-columns: 1fr;
}
.cr-actions {
justify-content: flex-start;
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ let brand = {
},
about: {
imageUrl: '../images/mirotalk-logo.gif',
title: 'WebRTC P2P v1.7.29',
title: 'WebRTC P2P v1.7.30',
html: `
<button
id="support-button"
+2 -2
View File
@@ -15,7 +15,7 @@
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.29
* @version 1.7.30
*
*/
@@ -13660,7 +13660,7 @@ function showAbout() {
Swal.fire({
background: swBg,
position: 'center',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.7.29',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.7.30',
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
customClass: { image: 'img-about' },
html: `
+95
View File
@@ -13,6 +13,9 @@ document.addEventListener('DOMContentLoaded', () => {
const statusEl = document.getElementById('crStatus');
const previewEl = document.getElementById('crPreviewUrl');
const copyBtn = document.getElementById('crCopy');
const shareBtn = document.getElementById('crShare');
const qrWrapEl = document.getElementById('crQrWrap');
const qrEl = document.getElementById('crQr');
const randomRoomBtn = document.getElementById('crRandomRoom');
const roomEl = document.getElementById('room');
@@ -33,6 +36,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (audioEl) audioEl.checked = true;
if (videoEl) videoEl.checked = true;
let qrCode = null;
const setError = (msg) => {
if (!errorEl) return;
if (!msg) {
@@ -56,6 +61,11 @@ document.addEventListener('DOMContentLoaded', () => {
const boolToFlag = (checked) => (checked ? '1' : '0');
const canWebShare = typeof navigator?.share === 'function';
if (shareBtn) {
shareBtn.hidden = !canWebShare;
}
const uuidv4 = () => {
// Prefer native implementation when available.
if (typeof crypto?.randomUUID === 'function') {
@@ -125,12 +135,79 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
const getThemeColor = (name, fallback) => {
try {
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return value || fallback;
} catch {
return fallback;
}
};
const ensureQrCode = () => {
if (!qrEl) return null;
if (qrCode) return qrCode;
if (typeof window.QRCode !== 'function') return null;
// Prefer a standard dark-on-light QR for best scanning reliability.
const colorDark = getThemeColor('--cr-bg-color', '#000000');
const colorLight = getThemeColor('--cr-text', '#ffffff');
const correctLevel = window.QRCode.CorrectLevel?.M;
const options = {
width: 180,
height: 180,
colorDark,
colorLight,
};
if (correctLevel) {
options.correctLevel = correctLevel;
}
qrCode = new window.QRCode(qrEl, options);
return qrCode;
};
const updateShareAndQr = (joinUrl) => {
const hasValidUrl = !!joinUrl;
if (shareBtn) {
shareBtn.disabled = !hasValidUrl;
}
if (qrWrapEl) {
qrWrapEl.hidden = !hasValidUrl;
}
if (!hasValidUrl) return;
const qr = ensureQrCode();
if (!qr) return;
try {
qr.clear();
qr.makeCode(joinUrl.toString());
} catch {
// No-op: QR generation failure should not block the form.
}
};
const updatePreview = () => {
if (!previewEl) return;
const url = buildJoinUrlForPreview();
const room = safe(roomEl?.value);
previewEl.value = room ? url.toString() : `${window.location.origin}/join?room=...`;
if (copyBtn) copyBtn.disabled = !room;
let joinUrl = null;
try {
joinUrl = buildJoinUrl();
} catch {
joinUrl = null;
}
updateShareAndQr(joinUrl);
};
const copyToClipboard = async (text) => {
@@ -186,6 +263,24 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
if (shareBtn && canWebShare) {
shareBtn.addEventListener('click', async () => {
setError('');
setStatus('');
try {
const joinUrl = buildJoinUrl();
await navigator.share({
title: document.title || 'MiroTalk Room',
url: joinUrl.toString(),
});
} catch (err) {
// Ignore user cancellation; surface other errors.
const msg = err && err.name === 'AbortError' ? '' : err?.message;
if (msg) setError(msg);
}
});
}
form.addEventListener('submit', (e) => {
e.preventDefault();
setError('');
+15 -4
View File
@@ -167,10 +167,20 @@
<label class="cr-label" for="crPreviewUrl">Join link</label>
<div class="cr-preview-row">
<input id="crPreviewUrl" class="cr-input cr-preview" type="text" readonly />
<button id="crCopy" class="cr-copy" type="button" aria-label="Copy join link">
<span class="cr-copy-icon" aria-hidden="true"></span>
<span>Copy</span>
</button>
<div class="cr-actions" aria-label="Link actions">
<button id="crCopy" class="cr-copy" type="button" aria-label="Copy join link">
<span class="cr-copy-icon" aria-hidden="true"></span>
<span>Copy</span>
</button>
<button id="crShare" class="cr-copy" type="button" aria-label="Share join link" hidden>
<span class="cr-copy-icon" aria-hidden="true"></span>
<span>Share</span>
</button>
</div>
</div>
<div id="crQrWrap" class="cr-qr-wrap" hidden>
<div id="crQr" class="cr-qr" role="img" aria-label="Join link QR code"></div>
<p class="cr-hint cr-qr-hint">Scan with your camera to open the join link.</p>
</div>
<p id="crStatus" class="cr-hint" role="status" aria-live="polite"></p>
</div>
@@ -182,6 +192,7 @@
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<script defer src="../js/customizeRoom.js"></script>
</body>
</html>