[mirotalk] - add qr and share btn
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
# ====================================================
|
||||
# MiroTalk P2P v.1.7.29 - Environment Configuration
|
||||
# MiroTalk P2P v.1.7.30 - Environment Configuration
|
||||
# ====================================================
|
||||
|
||||
# App environment
|
||||
|
||||
@@ -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
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
Generated
+6
-17
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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: `
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user