[mirotalk] - feat: add animated disconnect banner for lost signaling connection

This commit is contained in:
Miroslav Pejic
2026-04-28 14:12:24 +02:00
parent b333fdb24d
commit 409b246959
8 changed files with 188 additions and 7 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# ====================================================
# MiroTalk P2P v.1.8.17 - Environment Configuration
# MiroTalk P2P v.1.8.18 - Environment Configuration
# ====================================================
# App environment
+1 -1
View File
@@ -2,7 +2,7 @@
/**
* ==============================================
* MiroTalk P2P v.1.8.17 - Configuration File
* MiroTalk P2P v.1.8.18 - Configuration File
* ==============================================
*
* This file is the central configuration source.
+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.8.17
* @version 1.8.18
*
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.8.17",
"version": "1.8.18",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
+126
View File
@@ -91,6 +91,132 @@ body {
/* background: url('../images/bg.svg'); */
}
/*--------------------------------------------------------------
# Disconnect banner
--------------------------------------------------------------*/
.disconnect-banner {
position: fixed;
top: 18px;
left: 50%;
transform: translateX(-50%) translateY(calc(-100% - 30px));
z-index: 99999;
opacity: 0;
pointer-events: none;
transition:
transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 0.35s ease;
will-change: transform, opacity;
}
.disconnect-banner.visible {
transform: translateX(-50%) translateY(0);
opacity: 1;
pointer-events: auto;
}
.disconnect-banner-inner {
display: flex;
align-items: center;
gap: 14px;
padding: 11px 22px 11px 16px;
background: linear-gradient(135deg, rgba(28, 28, 32, 0.82), rgba(18, 18, 20, 0.9));
backdrop-filter: blur(18px) saturate(160%);
-webkit-backdrop-filter: blur(18px) saturate(160%);
border: 1px solid rgba(255, 80, 80, 0.4);
border-radius: 50px;
box-shadow:
0 4px 24px rgba(0, 0, 0, 0.55),
0 0 0 1px rgba(255, 80, 80, 0.12) inset,
0 0 28px rgba(220, 50, 50, 0.18);
color: #fff;
white-space: nowrap;
transition:
border-color 0.4s ease,
box-shadow 0.4s ease;
}
.disconnect-banner.reconnected .disconnect-banner-inner {
border-color: rgba(52, 211, 100, 0.45);
box-shadow:
0 4px 24px rgba(0, 0, 0, 0.55),
0 0 0 1px rgba(52, 211, 100, 0.12) inset,
0 0 28px rgba(30, 180, 80, 0.22);
}
.disconnect-banner-icon-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 50%;
background: rgba(220, 50, 50, 0.22);
border: 1px solid rgba(255, 80, 80, 0.3);
font-size: 15px;
color: #ff6b6b;
flex-shrink: 0;
transition:
background 0.4s ease,
border-color 0.4s ease,
color 0.4s ease;
animation: bannerIconPulse 1.8s ease-in-out infinite;
}
.disconnect-banner.reconnected .disconnect-banner-icon-wrap {
background: rgba(30, 180, 80, 0.22);
border-color: rgba(52, 211, 100, 0.35);
color: #4ade80;
animation: none;
}
.disconnect-banner-text {
display: flex;
flex-direction: column;
gap: 2px;
line-height: 1.3;
}
#disconnectBannerTitle {
font-size: 13px;
font-weight: 700;
letter-spacing: 0.02em;
color: #ff6b6b;
transition: color 0.4s ease;
}
.disconnect-banner.reconnected #disconnectBannerTitle {
color: #4ade80;
}
#disconnectBannerMsg {
font-size: 11.5px;
font-weight: 500;
color: rgba(220, 220, 230, 0.75);
letter-spacing: 0.01em;
}
.disconnect-banner-spinner {
font-size: 15px;
color: rgba(255, 255, 255, 0.45);
flex-shrink: 0;
transition: opacity 0.3s ease;
}
.disconnect-banner.reconnected .disconnect-banner-spinner {
opacity: 0;
}
@keyframes bannerIconPulse {
0%,
100% {
box-shadow: 0 0 0 0 rgba(220, 50, 50, 0.35);
}
50% {
box-shadow: 0 0 0 6px rgba(220, 50, 50, 0);
}
}
/*--------------------------------------------------------------
# Google Translate
--------------------------------------------------------------*/
+1 -1
View File
@@ -109,7 +109,7 @@ let brand = {
},
about: {
imageUrl: '../images/mirotalk-logo.gif',
title: 'WebRTC P2P v1.8.17',
title: 'WebRTC P2P v1.8.18',
html: `
<button
id="support-button"
+40 -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.8.17
* @version 1.8.18
*
*/
@@ -504,6 +504,13 @@ const speechRecognitionStop = getId('speechRecognitionStop');
// Media
const sinkId = 'sinkId' in HTMLMediaElement.prototype;
// Disconnect banner
const banner = getId('disconnectBanner');
const icon = getId('disconnectBannerIcon');
const title = getId('disconnectBannerTitle');
const msg = getId('disconnectBannerMsg');
const spinner = getId('disconnectBannerSpinner');
//....
const isRulesActive = true; // Presenter can do anything, guest is slightly moderate, if false no Rules for the room.
@@ -1483,6 +1490,7 @@ async function sendToDataChannel(config) {
async function handleConnect() {
console.log('03. Connected to signaling server');
hideDisconnectBanner();
myPeerId = signalingSocket.id;
console.log('04. My peer id [ ' + myPeerId + ' ]');
@@ -3148,6 +3156,7 @@ function handleIceCandidate(config) {
function handleDisconnect(reason) {
console.log('Disconnected from signaling server', { reason: reason });
showDisconnectBanner();
checkRecording();
for (const peer_id in peerConnections) {
@@ -15532,7 +15541,7 @@ function showAbout() {
Swal.fire({
background: swBg,
position: 'center',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.17',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.18',
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
customClass: { image: 'img-about' },
html: `
@@ -15818,6 +15827,35 @@ function applyBoxShadowEffect(element, color, delay = 200) {
}
}
/**
* Show a persistent banner indicating the signaling server connection was lost.
*/
function showDisconnectBanner() {
if (!banner) return;
banner.classList.remove('reconnected');
icon.className = 'fa-solid fa-wifi-exclamation';
title.textContent = 'Connection lost';
msg.innerHTML = 'Reconnecting to signaling server\u2026';
spinner.style.opacity = '1';
// Trigger transition
requestAnimationFrame(() => banner.classList.add('visible'));
}
/**
* Hide the disconnect banner (or briefly show a reconnected confirmation).
*/
function hideDisconnectBanner() {
if (!banner || !banner.classList.contains('visible')) return;
banner.classList.add('reconnected');
icon.className = 'fa-solid fa-circle-check';
title.textContent = 'Back online';
msg.textContent = 'Connection restored successfully';
setTimeout(() => {
banner.classList.remove('visible');
setTimeout(() => banner.classList.remove('reconnected'), 420);
}, 2800);
}
/**
* Basic user logging using https://sweetalert2.github.io & https://animate.style/
* @param {string} type of popup
+17
View File
@@ -73,6 +73,23 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- Disconnect banner -->
<div id="disconnectBanner" class="disconnect-banner" role="status" aria-live="assertive" aria-atomic="true">
<div class="disconnect-banner-inner">
<span class="disconnect-banner-icon-wrap">
<i id="disconnectBannerIcon" class="fa-solid fa-wifi-exclamation"></i>
</span>
<div class="disconnect-banner-text">
<span id="disconnectBannerTitle">Connection lost</span>
<span id="disconnectBannerMsg">Reconnecting to signaling server&hellip;</span>
</div>
<span class="disconnect-banner-spinner" id="disconnectBannerSpinner">
<i class="fa-solid fa-circle-notch fa-spin"></i>
</span>
</div>
</div>
<!-- just 4SEO Optimization -->
<div id="webRTCSeo">