From fa694e67bfeab0a0cfd847a2e00402b65f271432 Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Mon, 28 Sep 2020 17:00:25 +0200 Subject: [PATCH 01/12] improve html interface, add custom controls for video --- static/sfu.css | 94 ++++++++++++++++++++++++++++++++++++------------- static/sfu.html | 8 +++++ static/sfu.js | 87 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 164 insertions(+), 25 deletions(-) diff --git a/static/sfu.css b/static/sfu.css index 93229a9..56a6118 100644 --- a/static/sfu.css +++ b/static/sfu.css @@ -12,7 +12,7 @@ .topnav { padding-left: 0; - height: 4rem; + height: 3.5rem; z-index: 1039; } @@ -61,7 +61,7 @@ } .navbar { position: relative; - padding: .5rem; + padding: .1rem; } .topnav .navbar-brand { @@ -192,7 +192,7 @@ .full-width { width: calc(100vw - 200px); - height: calc(var(--vh, 1vh) * 100 - 64px); + height: calc(var(--vh, 1vh) * 100 - 56px); } .full-width-active { @@ -204,7 +204,7 @@ } .users-header { - height: 4rem; + height: 3.5rem; padding: 10px; background: #610a86; font-size: .95rem; @@ -221,7 +221,7 @@ } .reply { - height: 60px; + height: 53px; width: 100%; background-color: #eae7e5; padding: 10px 5px 10px 5px; @@ -242,7 +242,7 @@ } textarea.form-reply { - height: 2.6em; + height: 2.1em; margin-right: .5em; } @@ -290,7 +290,7 @@ textarea.form-reply { word-wrap: break-word; display: inline-block; margin: 1em 0 0; - max-width: 80%; + max-width: 90%; } .message-sender { @@ -345,7 +345,7 @@ textarea.form-reply { } .video-container { - height: calc(var(--vh, 1vh) * 100 - 64px); + height: calc(var(--vh, 1vh) * 100 - 56px); position: relative; background: rgba(0, 0, 0, 0.91); /* Display only when showing video */ @@ -376,12 +376,52 @@ textarea.form-reply { opacity: 0; } +.video-controls { + position: absolute; + width: 100%; + left: 0; + bottom: 40px; + text-align: center; + color: #e1e1e1; + font-size: 2.3em; + transition: all .5s ease-out; + opacity: 0; +} + +.peer:hover > .video-controls { + opacity: 1; + transition: all .7s ease-out; +} + +.video-controls span { + margin-right: 30px; + transition: all .5s ease-out; + opacity: .5; + cursor: pointer; + text-shadow: 2px 2px 4px rgba(123, 123, 123, 0.65); +} + +.video-controls span:last-child { + margin-right: 0; +} + +.video-controls span:hover { + opacity: 1; + transition: opacity .5s ease-out; +} + +.video-controls .volume { + width: 44px; + display: inline-block; + text-align: center; +} + .mobile-container { display: block !important; } .login-container { - height: calc(var(--vh, 1vh) * 100 - 64px); + height: calc(var(--vh, 1vh) * 100 - 56px); position: relative; display: block; } @@ -566,6 +606,7 @@ h1 { /* force to fill height */ height: 100% !important; min-width: 300px; + overflow: hidden; } #inputform { @@ -574,7 +615,7 @@ h1 { #box { overflow: auto; - height: calc(100% - 60px); + height: calc(100% - 53px); padding: 10px; } @@ -641,7 +682,8 @@ h1 { .media { width: 100%; - max-height: calc(var(--vh, 1vh) * 100 - 84px); + min-width: 220px; /* min-size for controls buttons */ + max-height: calc(var(--vh, 1vh) * 100 - 76px); padding-bottom: 20px; object-fit: contain; } @@ -672,13 +714,13 @@ h1 { } .sidenav a { - padding: 10px; + padding: 10px 20px; text-decoration: none; font-size: 30px; color: #dbd9d9; display: block; transition: 0.3s; - line-height: 1.2; + line-height: 1.0; } .sidenav a:hover { @@ -689,7 +731,8 @@ h1 { cursor: pointer; position: absolute; top: 0; - right: 5px; + right: 0; + height: 56px; } .sidenav label{ @@ -702,12 +745,16 @@ h1 { margin-top: 15px; } +.sidenav-header { + height: 56px; +} + .sidenav-header h2{ color: #fff; padding: 10px; margin: 0; max-width: 70%; - line-height: 44px; + line-height: 36px; } .sidenav-content { @@ -841,13 +888,14 @@ header .collapse { cursor: pointer; padding-top: 5px; margin-right: 20px; + margin-left: 5px; } .sfu-header { font-size: 1.3rem; font-weight: 900; color: #dbd9d9; - line-height: 44px; + line-height: 34px; } .header-sep { @@ -934,14 +982,6 @@ header .collapse { display: none; } - .topnav { - height: 3.5rem; - } - - .users-header { - height: 3.5rem; - } - .full-width { height: calc(var(--vh, 1vh) * 100 - 56px); } @@ -955,6 +995,10 @@ header .collapse { margin-bottom: 60px; } + .video-controls { + opacity: 1; + } + .login-container { position: fixed; height: calc(var(--vh, 1vh) * 100 - 56px); @@ -1010,6 +1054,8 @@ header .collapse { display: block; } + .sidenav a {padding: 10px 5px;} + .sidenav-header h2 { line-height: 36px; } diff --git a/static/sfu.html b/static/sfu.html index 1632b94..68daf80 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -186,6 +186,14 @@ + + diff --git a/static/sfu.js b/static/sfu.js index 84f9898..794615c 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -953,7 +953,7 @@ function setMedia(c, isUp) { media.autoplay = true; /** @ts-ignore */ media.playsinline = true; - media.controls = true; + media.controls = false; if(isUp) media.muted = true; div.appendChild(media); @@ -967,14 +967,99 @@ function setMedia(c, isUp) { div.appendChild(label); } + let template = document.getElementById('videocontrols-template').firstElementChild; + let controls = document.getElementById('controls-' + c.id); + if (template && !controls) { + controls = template.cloneNode(true); + controls.id = 'controls-' + c.id; + div.appendChild(controls); + if(media.muted) { + let volume = controls.querySelector(".fa-volume-up"); + if (volume) { + volume.classList.remove("fa-volume-up"); + volume.classList.add("fa-volume-off"); + } + } + } + media.srcObject = c.stream; setLabel(c); setMediaStatus(c); showVideo(); resizePeers(); + registerControlEvent(div.id); } +/** + * @param {HTMLVideoElement} video + */ +async function videoPIP(video) { + if (video.requestPictureInPicture) { + await video.requestPictureInPicture(); + } else { + displayWarning("Video PIP Mode not supported!"); + } +} + +/** + * @param {HTMLElement} target + */ +function getParentVideo(target) { + // target is the element, parent the div
+ let control = target.parentElement.parentElement; + let hash = control.id.split('-')[1]; + let media = /** @type {HTMLVideoElement} */ + (document.getElementById('media-' + hash)); + if (!media) { + displayWarning("Cannot find media!"); + } + return media; +} + +/** + * @param {string} peerid + */ +function registerControlEvent(peerid) { + let peer = document.getElementById(peerid); + let control_list = peer.querySelectorAll("span"); + + function control_event(event) { + event.preventDefault(); + let control_type = event.target.getAttribute("data-type"); + let video = getParentVideo(event.target); + switch (control_type) { + case "bt-volume": + if (event.target.className.indexOf("fa-volume-off") !== -1) { + event.target.classList.remove("fa-volume-off"); + event.target.classList.add("fa-volume-up"); + video.muted = false; + } else { + event.target.classList.remove("fa-volume-up"); + event.target.classList.add("fa-volume-off"); + // mute video sound + video.muted = true; + } + break; + case "bt-pip": + videoPIP(video); + break; + case "bt-fullscreen": + if (video.requestFullscreen) { + video.requestFullscreen(); + } else { + displayWarning("Video Fullscreen not supported!"); + } + break; + } + } + + for (let i = 0; i < control_list.length; i += 1) { + control_list[i].onclick = control_event; + } +} + + /** * @param {string} id */ From fc152d43775671e16f3a45662c9a8cb79a6e5637 Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Mon, 5 Oct 2020 11:15:31 +0200 Subject: [PATCH 02/12] allow to hide chat, improve settings bar and others --- static/sfu.css | 79 +++++++++++++++++++++++++++-------------- static/sfu.html | 88 +++++++++++++++++++++++++--------------------- static/sfu.js | 93 ++++++++++++++++++++++++++++--------------------- 3 files changed, 154 insertions(+), 106 deletions(-) diff --git a/static/sfu.css b/static/sfu.css index 56a6118..70aff2f 100644 --- a/static/sfu.css +++ b/static/sfu.css @@ -164,7 +164,7 @@ } .app { - background-color: #f7f7f7; + background-color: #f4f4f4; overflow: hidden; margin: 0; padding: 0; @@ -177,8 +177,13 @@ margin: 0; } +.coln-left-hide { + flex: 0; +} + .coln-right { flex: 70%; + position: relative; } /* Clear floats after the columns */ @@ -277,7 +282,6 @@ textarea.form-reply { .select-inline { display: inline-block; - width: inherit; } .message { @@ -291,10 +295,11 @@ textarea.form-reply { display: inline-block; margin: 1em 0 0; max-width: 90%; + text-align: left; } .message-sender { - background: #ececec; + background: #e6e6e6; } .message-private { @@ -321,12 +326,13 @@ textarea.form-reply { } .message-content { - margin: 0 !important; - padding: 5px !important; + margin: 0; + padding: 0; + padding-left: 5px; word-wrap: break-word; + word-break: break-word; font-weight: 400; font-size: 14px; - padding-bottom: 0 !important; color: #202035; } @@ -336,6 +342,14 @@ textarea.form-reply { text-shadow: none; } +.message-footer { + margin: 0; + padding: 0; + margin-bottom: -5px; + line-height: .9; + text-align: right; +} + .message-time { margin-left: 1em; } @@ -350,17 +364,16 @@ textarea.form-reply { background: rgba(0, 0, 0, 0.91); /* Display only when showing video */ display: block; - transition: all 1s ease-out; - opacity: 1; } .collapse-video { display: none; /*on top of video peers*/ z-index: 1002; - position: fixed; - top: 70px; - right: 10px; + position: absolute; + top: 5px; + left: 10px; + cursor: pointer; } .collapse-video .open-chat { @@ -368,12 +381,11 @@ textarea.form-reply { height: 60px; fill: #fff; padding: 10px; + fill: #b8b8b8; } .no-video { display: none; - transition: opacity 1s ease-out; - opacity: 0; } .video-controls { @@ -381,9 +393,9 @@ textarea.form-reply { width: 100%; left: 0; bottom: 40px; - text-align: center; + text-align: right; color: #e1e1e1; - font-size: 2.3em; + font-size: 1.8em; transition: all .5s ease-out; opacity: 0; } @@ -394,7 +406,7 @@ textarea.form-reply { } .video-controls span { - margin-right: 30px; + margin-right: 20px; transition: all .5s ease-out; opacity: .5; cursor: pointer; @@ -402,7 +414,7 @@ textarea.form-reply { } .video-controls span:last-child { - margin-right: 0; + /*margin-right: 0;*/ } .video-controls span:hover { @@ -411,7 +423,7 @@ textarea.form-reply { } .video-controls .volume { - width: 44px; + width: 25px; display: inline-block; text-align: center; } @@ -598,14 +610,15 @@ h1 { #chat { padding: 0; margin: 0; - background-color: #f9f9f9; + background-color: #f4f4f4; background-size: cover; overflow-y: scroll; border: none; border-right: 4px solid #e6e6e6; /* force to fill height */ height: 100% !important; - min-width: 300px; + width: 100%; + min-width: 200px; overflow: hidden; } @@ -740,6 +753,10 @@ h1 { margin-top: 15px; } +.sidenav .label-first { + margin-top: 0; +} + .sidenav form{ display: block; margin-top: 15px; @@ -758,7 +775,7 @@ h1 { } .sidenav-content { - padding: 20px; + padding: 10px; background: #fff; height: 100%; } @@ -767,6 +784,18 @@ h1 { margin: 0; } +fieldset { + margin: 0; + margin-top: 20px; + border: 1px solid #e9e8e8; + padding: 8px; + border-radius: 4px; +} +legend { + padding: 2px; + color: #4d4f51; +} + .nav-menu { margin: 0; padding: 0; @@ -828,7 +857,7 @@ h1 { background-color: #fff; max-width: 300px; min-width: 200px; - margin-top: 11px; + margin-top: 7px; overflow: auto; right: 7px; box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); @@ -962,11 +991,6 @@ header .collapse { /* END Sidebar Left */ -@media screen and (max-height: 450px) { - .sidenav-content {padding: 10px;} - .sidenav a {font-size: 18px;} -} - /*@media only screen and (max-width: 768px) {*/ @media only screen and (max-device-width: 768px) { .nav-link { @@ -1019,6 +1043,7 @@ header .collapse { .coln-right { flex: none; + position: relative; } .full-width { diff --git a/static/sfu.html b/static/sfu.html index 68daf80..fc260bc 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -114,7 +114,9 @@
-
+
+
+
diff --git a/static/sfu.js b/static/sfu.js index 794615c..06b04b6 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -1012,7 +1012,7 @@ function getParentVideo(target) { let media = /** @type {HTMLVideoElement} */ (document.getElementById('media-' + hash)); if (!media) { - displayWarning("Cannot find media!"); + displayError("Cannot find media!"); } return media; } @@ -1022,41 +1022,38 @@ function getParentVideo(target) { */ function registerControlEvent(peerid) { let peer = document.getElementById(peerid); - let control_list = peer.querySelectorAll("span"); - - function control_event(event) { - event.preventDefault(); - let control_type = event.target.getAttribute("data-type"); + //Add event listener when a video component is added to the DOM + peer.querySelector("span.volume").onclick = function(event) { let video = getParentVideo(event.target); - switch (control_type) { - case "bt-volume": - if (event.target.className.indexOf("fa-volume-off") !== -1) { - event.target.classList.remove("fa-volume-off"); - event.target.classList.add("fa-volume-up"); - video.muted = false; - } else { - event.target.classList.remove("fa-volume-up"); - event.target.classList.add("fa-volume-off"); - // mute video sound - video.muted = true; - } - break; - case "bt-pip": - videoPIP(video); - break; - case "bt-fullscreen": - if (video.requestFullscreen) { - video.requestFullscreen(); - } else { - displayWarning("Video Fullscreen not supported!"); - } - break; + if (event.target.className.indexOf("fa-volume-off") !== -1) { + event.target.classList.remove("fa-volume-off"); + event.target.classList.add("fa-volume-up"); + video.muted = false; + } else { + event.target.classList.remove("fa-volume-up"); + event.target.classList.add("fa-volume-off"); + // mute video sound + video.muted = true; } - } + }; - for (let i = 0; i < control_list.length; i += 1) { - control_list[i].onclick = control_event; - } + peer.querySelector("span.pip").onclick = function(event) { + let video = getParentVideo(event.target); + videoPIP(video); + }; + + peer.querySelector("span.fullscreen").onclick = function(event) { + let video = getParentVideo(event.target); + if (video.requestFullscreen) { + video.requestFullscreen(); + } else { + displayWarning("Video Fullscreen not supported!"); + } + }; + + peer.querySelector("span.expand").onclick = function(event) { + console.log("Not implemented for now!!"); + }; } @@ -1370,12 +1367,6 @@ function addToChatbox(peerId, dest, nick, time, kind, message) { (nick || '(anon)'); user.classList.add('message-user'); header.appendChild(user); - if(time) { - let tm = document.createElement('span'); - tm.textContent = formatTime(time); - tm.classList.add('message-time'); - header.appendChild(tm); - } header.classList.add('message-header'); container.appendChild(header); } @@ -1403,6 +1394,17 @@ function addToChatbox(peerId, dest, nick, time, kind, message) { lastMessage = {}; } + if(time) { + let tm = document.createElement('span'); + let datetime = new Date(time); + tm.textContent = datetime.getHours() + ':' + datetime.getMinutes(); + tm.classList.add('message-time'); + let footer = document.createElement('p'); + footer.classList.add('message-footer'); + footer.appendChild(tm); + container.appendChild(footer); + } + let box = document.getElementById('box'); box.appendChild(row); if(box.scrollHeight > box.clientHeight) { @@ -1614,6 +1616,13 @@ function chatResizer(e) { function start_drag(e) { let left_width = (start_width + e.clientX - start_x) * 100 / full_width; + // set min chat width to 200px + let min_left_width = 200 * 100 / full_width; + if (left_width < min_left_width) { + left.style.display = "none"; + document.getElementById('collapse-video').style.display = "block"; + return; + } left.style.flex = left_width.toString(); right.style.flex = (100 - left_width).toString(); } @@ -1741,6 +1750,12 @@ document.getElementById('collapse-video').onclick = function(e) { if(!(this instanceof HTMLElement)) throw new Error('Unexpected type for this'); let width = window.innerWidth; + let left = document.getElementById("left"); + if (left.style.display === "" || left.style.display === "none") { + //left chat is hidden, we show the chat and hide collapse button + left.style.display = "block"; + this.style.display = ""; + } if (width <= 768) { let user_box = document.getElementById('userDropdown'); if (user_box.classList.contains("show")) { From 31deef47f588cc36849671b7feb5b8cb03f7d855 Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Wed, 7 Oct 2020 09:33:38 +0200 Subject: [PATCH 03/12] fix video resizing, add a button to close chat --- static/sfu.css | 80 +++++++++++++++++++++++++++++++++++++------------ static/sfu.html | 24 ++++++++++++--- static/sfu.js | 62 ++++++++++++++++++++++++++------------ 3 files changed, 124 insertions(+), 42 deletions(-) diff --git a/static/sfu.css b/static/sfu.css index 70aff2f..e811ef7 100644 --- a/static/sfu.css +++ b/static/sfu.css @@ -367,13 +367,13 @@ textarea.form-reply { } .collapse-video { - display: none; - /*on top of video peers*/ - z-index: 1002; - position: absolute; - top: 5px; - left: 10px; - cursor: pointer; + display: none; + /*on top of video peers*/ + z-index: 1002; + position: absolute; + top: 5px; + left: 10px; + cursor: pointer; } .collapse-video .open-chat { @@ -388,24 +388,30 @@ textarea.form-reply { display: none; } -.video-controls { +.video-controls, .top-video-controls { position: absolute; width: 100%; left: 0; - bottom: 40px; - text-align: right; + bottom: 25px; + text-align: center; color: #e1e1e1; - font-size: 1.8em; + font-size: 1.5em; transition: all .5s ease-out; opacity: 0; } -.peer:hover > .video-controls { +.top-video-controls { + text-align: right; + bottom: inherit; + top: 0; +} + +.peer:hover > .video-controls, .peer:hover > .top-video-controls { opacity: 1; transition: all .7s ease-out; } -.video-controls span { +.video-controls span, .top-video-controls span { margin-right: 20px; transition: all .5s ease-out; opacity: .5; @@ -417,7 +423,7 @@ textarea.form-reply { /*margin-right: 0;*/ } -.video-controls span:hover { +.video-controls span:hover, .top-video-controls span:hover { opacity: 1; transition: opacity .5s ease-out; } @@ -462,7 +468,7 @@ textarea.form-reply { width: 100%; z-index: 1; text-align: center; - line-height: 25px; + line-height: 24px; color: #ffffff; } @@ -605,12 +611,13 @@ h1 { #chatbox { height: 100%; + position: relative; } #chat { padding: 0; margin: 0; - background-color: #f4f4f4; + background-color: #f8f8f8; background-size: cover; overflow-y: scroll; border: none; @@ -618,7 +625,7 @@ h1 { /* force to fill height */ height: 100% !important; width: 100%; - min-width: 200px; + min-width: 300px; overflow: hidden; } @@ -632,6 +639,24 @@ h1 { padding: 10px; } +.close-chat { + position: absolute; + top: 2px; + right: 14px; + width: 25px; + font-size: 1em; + text-align: center; + font-weight: 700; + color: #8f8f8f; + cursor: pointer; + border: 1px solid transparent; +} + +.close-chat:hover, .close-chat:active { + border: 1px solid #dfdfdf; + border-radius: 4px; +} + #disconnectbutton, #connectbutton { margin-left: 80px; margin-top: 15px; @@ -667,7 +692,7 @@ h1 { display: grid; grid-template-columns: repeat(1, 1fr); grid-template-rows: repeat(1, auto); - row-gap: 0; + row-gap: 5px; column-gap: 10px; position: absolute; top: 0; @@ -680,6 +705,7 @@ h1 { z-index: 1000; background-size: cover; overflow: hidden; + vertical-align: top!important; } .peer { @@ -687,6 +713,7 @@ h1 { margin-bottom: auto; position: relative; border: 2px solid rgba(0,0,0,0); + background: #80808014; } .peer-active { @@ -695,7 +722,6 @@ h1 { .media { width: 100%; - min-width: 220px; /* min-size for controls buttons */ max-height: calc(var(--vh, 1vh) * 100 - 76px); padding-bottom: 20px; object-fit: contain; @@ -989,6 +1015,13 @@ header .collapse { margin-right: 5px; } +.close-icon { + font: normal 1em/1 Arial, sans-serif; + display: inline-block; +} + +.close-icon:before{ content: "\2715"; } + /* END Sidebar Left */ /*@media only screen and (max-width: 768px) {*/ @@ -1010,6 +1043,15 @@ header .collapse { height: calc(var(--vh, 1vh) * 100 - 56px); } + .collapse-video { + left: inherit; + right: 10px; + } + + .close-chat { + display: none; + } + .video-container { position: fixed; height: calc(var(--vh, 1vh) * 100 - 56px); diff --git a/static/sfu.html b/static/sfu.html index fc260bc..529c1dd 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -92,6 +92,9 @@
+
+ +
@@ -195,10 +198,23 @@ + + diff --git a/static/sfu.js b/static/sfu.js index 06b04b6..ac75be1 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -335,7 +335,9 @@ function setViewportHeight() { document.documentElement.style.setProperty( '--vh', `${window.innerHeight/100}px`, ); -}; + // Ajust video component size + resizePeers(); +} setViewportHeight(); // On resize and orientation change, we update viewport height @@ -375,7 +377,7 @@ function setVisibility(id, visible) { function setButtonsVisibility() { let permissions = serverConnection.permissions; let local = !!findUpMedia('local'); - let share = !!findUpMedia('screenshare') + let share = !!findUpMedia('screenshare'); // don't allow multiple presentations setVisibility('presentbutton', permissions.present && !local); @@ -383,7 +385,7 @@ function setButtonsVisibility() { // allow multiple shared documents setVisibility('sharebutton', permissions.present && - ('getDisplayMedia' in navigator.mediaDevices)) + ('getDisplayMedia' in navigator.mediaDevices)); setVisibility('unsharebutton', share); setVisibility('mediaoptions', permissions.present); @@ -967,7 +969,17 @@ function setMedia(c, isUp) { div.appendChild(label); } - let template = document.getElementById('videocontrols-template').firstElementChild; + let template = document.getElementById('videocontrols-template') + .firstElementChild; + let top_template = document.getElementById('top-videocontrols-template') + .firstElementChild; + + let top_controls = document.getElementById('top-controls-' + c.id); + if (template && !top_controls) { + top_controls = top_template.cloneNode(true); + top_controls.id = 'top-controls-' + c.id; + div.appendChild(top_controls); + } let controls = document.getElementById('controls-' + c.id); if (template && !controls) { controls = template.cloneNode(true); @@ -1117,6 +1129,9 @@ function setLabel(c, fallback) { } function resizePeers() { + // Window resize can call this method too early + if (!serverConnection) + return; let count = Object.keys(serverConnection.up).length + Object.keys(serverConnection.down).length; @@ -1125,24 +1140,29 @@ function resizePeers() { if (!count) // No video, nothing to resize. return; - let container = document.getElementById("video-container") - // Peers div has total padding of 30px, we remove 30 on offsetHeight - let max_video_height = Math.trunc((peers.offsetHeight - 30) / columns); + let container = document.getElementById("video-container"); + // Peers div has total padding of 40px, we remove 40 on offsetHeight + // Grid has row-gap of 5px + let margins = (columns - 1) * 5 + 40; + let rows = Math.ceil(count / columns); - let media_list = document.getElementsByClassName("media"); + if (count <= 2 && container.offsetHeight > container.offsetWidth) { + peers.style['grid-template-columns'] = "repeat(1, 1fr)"; + rows = count; + } else { + peers.style['grid-template-columns'] = `repeat(${columns}, 1fr)`; + } + if (rows === 1) + return; + let max_video_height = (peers.offsetHeight - margins) / rows; + let media_list = peers.querySelectorAll(".media"); for(let i = 0; i < media_list.length; i++) { let media = media_list[i]; if(!(media instanceof HTMLMediaElement)) { console.warn('Unexpected media'); continue; } - media.style['max_height'] = max_video_height + "px"; - } - - if (count <= 2 && container.offsetHeight > container.offsetWidth) { - peers.style['grid-template-columns'] = "repeat(1, 1fr)"; - } else { - peers.style['grid-template-columns'] = `repeat(${columns}, 1fr)`; + media.style['max-height'] = max_video_height + "px"; } } @@ -1616,11 +1636,9 @@ function chatResizer(e) { function start_drag(e) { let left_width = (start_width + e.clientX - start_x) * 100 / full_width; - // set min chat width to 200px - let min_left_width = 200 * 100 / full_width; + // set min chat width to 300px + let min_left_width = 300 * 100 / full_width; if (left_width < min_left_width) { - left.style.display = "none"; - document.getElementById('collapse-video').style.display = "block"; return; } left.style.flex = left_width.toString(); @@ -1777,6 +1795,12 @@ document.getElementById('switch-video').onclick = function(e) { document.getElementById('collapse-video').style.display = "block"; }; +document.getElementById('close-chat').onclick = function(e) { + e.preventDefault(); + left.style.display = "none"; + document.getElementById('collapse-video').style.display = "block"; +}; + window.onclick = function(event) { let user_box = document.getElementById('userDropdown'); if (user_box.classList.contains("show") && event.target.id != "user") { From 2e0b1959642ed0db3d6144371fee0b5541d1e50b Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Wed, 7 Oct 2020 09:46:00 +0200 Subject: [PATCH 04/12] fix resizing when we have only 2 videos --- static/sfu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/sfu.js b/static/sfu.js index ac75be1..0b7838d 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -1143,8 +1143,8 @@ function resizePeers() { let container = document.getElementById("video-container"); // Peers div has total padding of 40px, we remove 40 on offsetHeight // Grid has row-gap of 5px - let margins = (columns - 1) * 5 + 40; let rows = Math.ceil(count / columns); + let margins = (rows - 1) * 5 + 40; if (count <= 2 && container.offsetHeight > container.offsetWidth) { peers.style['grid-template-columns'] = "repeat(1, 1fr)"; @@ -1152,7 +1152,7 @@ function resizePeers() { } else { peers.style['grid-template-columns'] = `repeat(${columns}, 1fr)`; } - if (rows === 1) + if (count === 1) return; let max_video_height = (peers.offsetHeight - margins) / rows; let media_list = peers.querySelectorAll(".media"); From 2388dfd3517cea913e5f70182c6e93d9af56a88b Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Mon, 12 Oct 2020 12:02:31 +0200 Subject: [PATCH 05/12] allow to disable camera on videoElement --- static/sfu.css | 9 ++++-- static/sfu.html | 11 ++++--- static/sfu.js | 80 +++++++++++++++++++++++++++++++++++++------------ 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/static/sfu.css b/static/sfu.css index e811ef7..0d5df13 100644 --- a/static/sfu.css +++ b/static/sfu.css @@ -403,7 +403,7 @@ textarea.form-reply { .top-video-controls { text-align: right; bottom: inherit; - top: 0; + top: 5px; } .peer:hover > .video-controls, .peer:hover > .top-video-controls { @@ -434,6 +434,11 @@ textarea.form-reply { text-align: center; } +.video-controls span.disabled, .video-controls span.disabled:hover, .top-video-controls span.disabled:hover{ + opacity: .2; + color: #c8c8c8 +} + .mobile-container { display: block !important; } @@ -1045,7 +1050,7 @@ header .collapse { .collapse-video { left: inherit; - right: 10px; + right: 60px; } .close-chat { diff --git a/static/sfu.html b/static/sfu.html index 529c1dd..56d34b1 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -201,20 +201,23 @@ + + + - - -
diff --git a/static/sfu.js b/static/sfu.js index 0b7838d..8c9e8fd 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -239,6 +239,11 @@ function hideVideo(force) { return; let video_container = document.getElementById('video-container'); video_container.classList.add('no-video'); + let left = document.getElementById("left"); + if (left.style.display !== "none") { + // hide all video buttons used to switch video on mobile layout + closeVideoControls(); + } } function closeVideoControls() { @@ -743,15 +748,18 @@ async function setMaxVideoThroughput(c, bps) { /** * @param {string} [id] + * @param {boolean} [disableVideo] */ -async function addLocalMedia(id) { +async function addLocalMedia(id, disableVideo) { if(!getUserPass()) return; let settings = getSettings(); let audio = settings.audio ? {deviceId: settings.audio} : false; - let video = settings.video ? {deviceId: settings.video} : false; + let video = false; + if (!disableVideo) + video = settings.video ? {deviceId: settings.video} : false; if(audio) { if(settings.studioMode) { @@ -937,6 +945,14 @@ function muteLocalTracks(mute) { */ function setMedia(c, isUp) { let peersdiv = document.getElementById('peers'); + let settings = getSettings(); + let local_media; + + for(let id in serverConnection.up) { + if (id === c.id) { + local_media = serverConnection.up[id]; + } + } let div = document.getElementById('peer-' + c.id); if(!div) { @@ -974,24 +990,32 @@ function setMedia(c, isUp) { let top_template = document.getElementById('top-videocontrols-template') .firstElementChild; - let top_controls = document.getElementById('top-controls-' + c.id); + let top_controls = document.getElementById('topcontrols-' + c.id); if (template && !top_controls) { - top_controls = top_template.cloneNode(true); - top_controls.id = 'top-controls-' + c.id; - div.appendChild(top_controls); + top_controls = top_template.cloneNode(true); + top_controls.id = 'topcontrols-' + c.id; + div.appendChild(top_controls); } let controls = document.getElementById('controls-' + c.id); if (template && !controls) { - controls = template.cloneNode(true); - controls.id = 'controls-' + c.id; - div.appendChild(controls); - if(media.muted) { - let volume = controls.querySelector(".fa-volume-up"); - if (volume) { - volume.classList.remove("fa-volume-up"); - volume.classList.add("fa-volume-off"); + controls = template.cloneNode(true); + controls.id = 'controls-' + c.id; + div.appendChild(controls); + if(media.muted) { + let volume = controls.querySelector(".fa-volume-up"); + if (volume) { + volume.classList.remove("fa-volume-up"); + volume.classList.add("fa-volume-off"); + } } - } + let camera = controls.querySelector("span.camera"); + if (local_media && local_media.kind === "local") { + if (!settings.video) { + if (camera) + camera.classList.add("camera-off"); + } + } else + camera.remove(); } media.srcObject = c.stream; @@ -1020,9 +1044,9 @@ async function videoPIP(video) { function getParentVideo(target) { // target is the element, parent the div
let control = target.parentElement.parentElement; - let hash = control.id.split('-')[1]; + let id = control.id.split('-')[1]; let media = /** @type {HTMLVideoElement} */ - (document.getElementById('media-' + hash)); + (document.getElementById('media-' + id)); if (!media) { displayError("Cannot find media!"); } @@ -1033,9 +1057,11 @@ function getParentVideo(target) { * @param {string} peerid */ function registerControlEvent(peerid) { + let settings = getSettings(); let peer = document.getElementById(peerid); //Add event listener when a video component is added to the DOM peer.querySelector("span.volume").onclick = function(event) { + event.preventDefault(); let video = getParentVideo(event.target); if (event.target.className.indexOf("fa-volume-off") !== -1) { event.target.classList.remove("fa-volume-off"); @@ -1050,11 +1076,13 @@ function registerControlEvent(peerid) { }; peer.querySelector("span.pip").onclick = function(event) { + event.preventDefault(); let video = getParentVideo(event.target); videoPIP(video); }; peer.querySelector("span.fullscreen").onclick = function(event) { + event.preventDefault(); let video = getParentVideo(event.target); if (video.requestFullscreen) { video.requestFullscreen(); @@ -1063,8 +1091,21 @@ function registerControlEvent(peerid) { } }; - peer.querySelector("span.expand").onclick = function(event) { - console.log("Not implemented for now!!"); + peer.querySelector("span.camera").onclick = function(event) { + event.preventDefault(); + let video = getParentVideo(event.target); + let id = video.id.split("-")[1]; + if (!settings.video) + return; + if (event.target.getAttribute("data-type") === "bt-camera") { + addLocalMedia(id, true); + event.target.setAttribute("data-type", "bt-camera-off"); + event.target.parentElement.classList.add("disabled"); + } else { + event.target.setAttribute("data-type", "bt-camera"); + event.target.parentElement.classList.remove("disabled"); + addLocalMedia(id); + } }; } @@ -1797,6 +1838,7 @@ document.getElementById('switch-video').onclick = function(e) { document.getElementById('close-chat').onclick = function(e) { e.preventDefault(); + let left = document.getElementById("left"); left.style.display = "none"; document.getElementById('collapse-video').style.display = "block"; }; From 5a526c24f6a834c01c4d25902d3af1388c60c5a1 Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Mon, 12 Oct 2020 12:48:12 +0200 Subject: [PATCH 06/12] sfu.js: fix display message time in chat box --- static/sfu.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/static/sfu.js b/static/sfu.js index 8c9e8fd..e5ab97c 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -1380,8 +1380,9 @@ function formatLines(lines) { function formatTime(time) { let delta = Date.now() - time; let date = new Date(time); + let m = date.getMinutes(); if(delta >= 0) - return date.toTimeString().slice(null, 8); + return date.getHours() + ':' + ((m < 10) ? '0' : '') + m; return date.toLocaleString(); } @@ -1409,6 +1410,8 @@ function addToChatbox(peerId, dest, nick, time, kind, message) { let container = document.createElement('div'); container.classList.add('message'); row.appendChild(container); + let footer = document.createElement('p'); + footer.classList.add('message-footer'); if(!peerId) container.classList.add('message-system'); if(userpass.username === nick) @@ -1430,12 +1433,19 @@ function addToChatbox(peerId, dest, nick, time, kind, message) { header.appendChild(user); header.classList.add('message-header'); container.appendChild(header); + if(time) { + let tm = document.createElement('span'); + tm.textContent = formatTime(time); + tm.classList.add('message-time'); + footer.appendChild(tm); + } } p.classList.add('message-content'); container.appendChild(p); lastMessage.nick = (nick || null); lastMessage.peerId = peerId; lastMessage.dest = (dest || null); + container.appendChild(footer); } else { let asterisk = document.createElement('span'); asterisk.textContent = '*'; @@ -1455,17 +1465,6 @@ function addToChatbox(peerId, dest, nick, time, kind, message) { lastMessage = {}; } - if(time) { - let tm = document.createElement('span'); - let datetime = new Date(time); - tm.textContent = datetime.getHours() + ':' + datetime.getMinutes(); - tm.classList.add('message-time'); - let footer = document.createElement('p'); - footer.classList.add('message-footer'); - footer.appendChild(tm); - container.appendChild(footer); - } - let box = document.getElementById('box'); box.appendChild(row); if(box.scrollHeight > box.clientHeight) { From 358cdd918df267ebd661a668e88ca11ac577dd9b Mon Sep 17 00:00:00 2001 From: Alain Takoudjou Date: Thu, 29 Oct 2020 09:55:32 +0100 Subject: [PATCH 07/12] make video control buttons more visible --- static/sfu.css | 11 +++++++---- static/sfu.html | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/static/sfu.css b/static/sfu.css index 0d5df13..380cc47 100644 --- a/static/sfu.css +++ b/static/sfu.css @@ -406,6 +406,10 @@ textarea.form-reply { top: 5px; } +.vc-overlay { + background: linear-gradient(180deg, rgb(0 0 0 / 0%) 0%, rgb(0 0 0 / 7%) 0%, rgb(0 0 0 / 24%) 100%); +} + .peer:hover > .video-controls, .peer:hover > .top-video-controls { opacity: 1; transition: all .7s ease-out; @@ -413,10 +417,9 @@ textarea.form-reply { .video-controls span, .top-video-controls span { margin-right: 20px; - transition: all .5s ease-out; - opacity: .5; + transition: all .7s ease-out; + opacity: 1; cursor: pointer; - text-shadow: 2px 2px 4px rgba(123, 123, 123, 0.65); } .video-controls span:last-child { @@ -424,7 +427,7 @@ textarea.form-reply { } .video-controls span:hover, .top-video-controls span:hover { - opacity: 1; + opacity: .5; transition: opacity .5s ease-out; } diff --git a/static/sfu.html b/static/sfu.html index 56d34b1..9863995 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -197,7 +197,7 @@