Skip to content

Commit a451686

Browse files
committed
[*] imporve webwrt web client
1 parent c4322d2 commit a451686

File tree

1 file changed

+211
-33
lines changed

1 file changed

+211
-33
lines changed

lib/wrtc/web-whep-client/index.html

Lines changed: 211 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,31 @@
300300
fill: rgba(255, 255, 255, 0.9);
301301
}
302302

303+
.loading-spinner {
304+
width: 50px;
305+
height: 50px;
306+
border: 3px solid rgba(255, 255, 255, 0.3);
307+
border-top: 3px solid #fff;
308+
border-radius: 50%;
309+
animation: spin 1s linear infinite;
310+
display: none;
311+
position: absolute;
312+
top: 50%;
313+
left: 50%;
314+
margin: -25px 0 0 -25px; /* Half of width/height for centering */
315+
z-index: 10;
316+
pointer-events: none;
317+
}
318+
319+
.loading-spinner.show {
320+
display: block;
321+
}
322+
323+
@keyframes spin {
324+
0% { transform: rotate(0deg); }
325+
100% { transform: rotate(360deg); }
326+
}
327+
303328
.video-controls {
304329
position: absolute;
305330
bottom: 0;
@@ -452,6 +477,56 @@
452477
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
453478
}
454479

480+
.browser-fullscreen-btn {
481+
background: none;
482+
border: none;
483+
color: #fff;
484+
cursor: pointer;
485+
font-size: 17px;
486+
padding: 0 !important;
487+
margin: 0;
488+
width: 32px;
489+
height: 32px;
490+
display: flex;
491+
align-items: center;
492+
justify-content: center;
493+
box-shadow: none;
494+
outline: none;
495+
}
496+
497+
.browser-fullscreen-btn:hover,
498+
.browser-fullscreen-btn:focus {
499+
background: none;
500+
border: none;
501+
color: #fff;
502+
box-shadow: none;
503+
outline: none;
504+
transform: none;
505+
}
506+
507+
/* Web page fullscreen styles */
508+
body.webpage-fullscreen {
509+
padding: 0;
510+
overflow: hidden;
511+
}
512+
513+
body.webpage-fullscreen h1,
514+
body.webpage-fullscreen .controls {
515+
display: none;
516+
}
517+
518+
body.webpage-fullscreen #video-container {
519+
position: fixed;
520+
top: 0;
521+
left: 0;
522+
width: 100vw;
523+
height: 100vh;
524+
max-width: none;
525+
border-radius: 0;
526+
box-shadow: none;
527+
z-index: 1000;
528+
}
529+
455530
.fullscreen-btn {
456531
background: none;
457532
border: none;
@@ -545,6 +620,7 @@ <h1>Wayshot 屏幕共享</h1>
545620
<path d="M8 5v14l11-7z" />
546621
</svg>
547622
</div>
623+
<div class="loading-spinner" id="loading-spinner"></div>
548624
<div class="video-controls">
549625
<button id="play-btn"></button>
550626
<div class="time-display" id="time-display">00:00</div>
@@ -565,6 +641,7 @@ <h1>Wayshot 屏幕共享</h1>
565641
value="100"
566642
/>
567643
</div>
644+
<button class="browser-fullscreen-btn" id="browser-fullscreen-btn"></button>
568645
<button class="fullscreen-btn" id="fullscreen-btn"></button>
569646
</div>
570647
</div>
@@ -585,13 +662,63 @@ <h1>Wayshot 屏幕共享</h1>
585662
const volumeBtn = document.getElementById("volume-btn");
586663
const volumeIcon = document.getElementById("volume-icon");
587664
const volumeSlider = document.getElementById("volume-slider");
665+
const browserFullscreenBtn = document.getElementById("browser-fullscreen-btn");
588666
const fullscreenBtn = document.getElementById("fullscreen-btn");
589667
const playbackStateIndicator = document.getElementById(
590668
"playback-state-indicator",
591669
);
670+
const loadingSpinner = document.getElementById("loading-spinner");
592671

593672
let authEnabled = false;
594673
let hideControlsTimeout;
674+
let currentPeerConnection = null;
675+
let currentWhepClient = null;
676+
let isConnecting = false;
677+
678+
// Function to close existing connection
679+
async function closeExistingConnection() {
680+
try {
681+
// Close WHEP client if exists (this will send HTTP DELETE)
682+
if (currentWhepClient) {
683+
await currentWhepClient.stop();
684+
currentWhepClient = null;
685+
}
686+
687+
// Close peer connection if exists (fallback cleanup)
688+
if (currentPeerConnection) {
689+
// Close all transceivers
690+
currentPeerConnection.getTransceivers().forEach(transceiver => {
691+
if (transceiver.stop) {
692+
transceiver.stop();
693+
}
694+
});
695+
696+
// Close peer connection
697+
currentPeerConnection.close();
698+
currentPeerConnection = null;
699+
}
700+
701+
// Clear video source and reset to black
702+
localVideo.srcObject = null;
703+
localVideo.load(); // Reset video element to black
704+
705+
// Reset UI state
706+
playBtn.textContent = "▶";
707+
playbackStateIndicator.classList.remove("show");
708+
// Don't hide loading spinner here - let the caller handle it
709+
// loadingSpinner.classList.remove("show");
710+
711+
isConnecting = false;
712+
} catch (error) {
713+
console.warn("Error closing connection:", error);
714+
// Ensure state is reset even if close fails
715+
currentWhepClient = null;
716+
currentPeerConnection = null;
717+
localVideo.srcObject = null;
718+
localVideo.load(); // Reset video element to black
719+
isConnecting = false;
720+
}
721+
}
595722

596723
// Switch toggle functionality
597724
authSwitch.addEventListener("click", () => {
@@ -693,6 +820,28 @@ <h1>Wayshot 屏幕共享</h1>
693820
}
694821
}
695822

823+
// Web page fullscreen functionality
824+
let isWebpageFullscreen = false;
825+
826+
function toggleWebpageFullscreen() {
827+
isWebpageFullscreen = !isWebpageFullscreen;
828+
829+
if (isWebpageFullscreen) {
830+
// Enter webpage fullscreen
831+
document.body.classList.add("webpage-fullscreen");
832+
browserFullscreenBtn.textContent = "⤢";
833+
} else {
834+
// Exit webpage fullscreen
835+
document.body.classList.remove("webpage-fullscreen");
836+
browserFullscreenBtn.textContent = "⤢";
837+
}
838+
}
839+
840+
browserFullscreenBtn.addEventListener("click", (e) => {
841+
e.stopPropagation(); // Prevent event from bubbling up to video container
842+
toggleWebpageFullscreen();
843+
});
844+
696845
fullscreenBtn.addEventListener("click", (e) => {
697846
e.stopPropagation(); // Prevent event from bubbling up to video container
698847
toggleVideoFullscreen();
@@ -747,36 +896,10 @@ <h1>Wayshot 屏幕共享</h1>
747896
}
748897
}
749898

750-
// Listen for fullscreen changes
751-
document.addEventListener("fullscreenchange", () => {
752-
if (document.fullscreenElement) {
753-
// Entering fullscreen
754-
videoContainer.classList.add("fullscreen");
755-
fullscreenBtn.textContent = "⛶";
756-
isFullscreen = true;
757-
isMouseOverControls = false;
758-
// Initially hide controls in fullscreen
759-
videoControls.classList.remove("show");
760-
controlBarVisible = false;
761-
// Add mousemove listener for fullscreen controls
762-
videoContainer.addEventListener(
763-
"mousemove",
764-
handleVideoContainerMouseMove,
765-
);
766-
} else {
767-
// Exiting fullscreen
768-
videoContainer.classList.remove("fullscreen");
769-
fullscreenBtn.textContent = "⛶";
770-
isFullscreen = false;
771-
isMouseOverControls = false;
772-
// Remove mousemove listener
773-
videoContainer.removeEventListener(
774-
"mousemove",
775-
handleVideoContainerMouseMove,
776-
);
777-
// Ensure controls are visible in non-fullscreen mode
778-
videoControls.classList.add("show");
779-
controlBarVisible = true;
899+
// Keyboard support for webpage fullscreen (Escape key)
900+
document.addEventListener("keydown", (e) => {
901+
if (e.key === "Escape" && isWebpageFullscreen) {
902+
toggleWebpageFullscreen();
780903
}
781904
});
782905

@@ -906,6 +1029,12 @@ <h1>Wayshot 屏幕共享</h1>
9061029
});
9071030

9081031
function updatePlaybackStateIndicator() {
1032+
// Don't show playback indicator when loading spinner is showing
1033+
if (loadingSpinner.classList.contains("show")) {
1034+
playbackStateIndicator.classList.remove("show");
1035+
return;
1036+
}
1037+
9091038
if (localVideo.paused || localVideo.ended) {
9101039
playbackStateIndicator.classList.add("show");
9111040
} else {
@@ -956,12 +1085,34 @@ <h1>Wayshot 屏幕共享</h1>
9561085
updatePlaybackStateIndicator();
9571086
});
9581087

959-
startWhepBtn.addEventListener("click", () => {
1088+
startWhepBtn.addEventListener("click", async () => {
9601089
const token = tokenInput.value;
9611090
const useAuth = authEnabled;
9621091

1092+
// Show loading spinner and clear video immediately when starting connection process
1093+
loadingSpinner.classList.add("show");
1094+
playbackStateIndicator.classList.remove("show");
1095+
1096+
// Clear video immediately to show black background
1097+
localVideo.srcObject = null;
1098+
localVideo.load();
1099+
1100+
// If already connecting or has existing connection, close it first
1101+
if (isConnecting || currentPeerConnection || currentWhepClient) {
1102+
await closeExistingConnection();
1103+
1104+
// If we're in the middle of connecting, stop here but keep spinner showing
1105+
if (isConnecting) {
1106+
return;
1107+
}
1108+
}
1109+
1110+
// Set connecting state
1111+
isConnecting = true;
1112+
9631113
//Create peerconnection
964-
const pc = (window.pc = new RTCPeerConnection());
1114+
const pc = new RTCPeerConnection();
1115+
currentPeerConnection = pc;
9651116

9661117
//Add recv only transceivers
9671118
pc.addTransceiver("audio");
@@ -981,23 +1132,50 @@ <h1>Wayshot 屏幕共享</h1>
9811132
player.muted = false; // Enable audio
9821133
playBtn.textContent = "❚❚";
9831134

1135+
// Hide loading spinner when video starts
1136+
loadingSpinner.classList.remove("show");
1137+
1138+
isConnecting = false;
1139+
9841140
// Auto-play when video stream starts
9851141
player.play().catch((err) => {
9861142
console.log("Auto-play failed:", err);
9871143
playBtn.textContent = "▶";
1144+
// Hide loading spinner even if auto-play fails
1145+
loadingSpinner.classList.remove("show");
9881146
});
9891147
}
9901148
if (event.track.kind == "audio") {
9911149
console.log("Audio track received");
9921150
}
9931151
};
1152+
1153+
// Handle connection failure
1154+
pc.onconnectionstatechange = () => {
1155+
console.log("Connection state:", pc.connectionState);
1156+
if (pc.connectionState === "failed" || pc.connectionState === "disconnected") {
1157+
loadingSpinner.classList.remove("show");
1158+
playBtn.textContent = "▶";
1159+
isConnecting = false;
1160+
currentPeerConnection = null;
1161+
}
1162+
};
1163+
9941164
//Create whep client
9951165
const whep = new WHEPClient();
1166+
currentWhepClient = whep;
9961167

9971168
let url = location.origin + "/whep";
9981169

9991170
//Start viewing
1000-
whep.view(pc, url, useAuth ? token : null);
1171+
whep.view(pc, url, useAuth ? token : null).catch((err) => {
1172+
console.error("WHEP connection failed:", err);
1173+
loadingSpinner.classList.remove("show");
1174+
playBtn.textContent = "▶";
1175+
isConnecting = false;
1176+
currentPeerConnection = null;
1177+
currentWhepClient = null;
1178+
});
10011179
});
10021180

10031181
// Initialize volume

0 commit comments

Comments
 (0)