From a2e322edcb8018f7ac58f5957a6de542aee54bcf Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Sat, 28 Nov 2020 01:53:29 +0100 Subject: [PATCH] Rework custom video controls. Move custom video controls into its own function. Remove some DOM traversals, avoid querySelector. Remove dead code. --- static/sfu.html | 14 +--- static/sfu.js | 201 +++++++++++++++++++++++++----------------------- 2 files changed, 106 insertions(+), 109 deletions(-) diff --git a/static/sfu.html b/static/sfu.html index 4da671a..462a69e 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -209,21 +209,13 @@ - - diff --git a/static/sfu.js b/static/sfu.js index f728a08..fc2f2a3 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -1002,19 +1002,17 @@ function muteLocalTracks(mute) { } /** + * setMedia adds a new media element corresponding to stream c. + * * @param {Stream} c * @param {boolean} isUp + * - indicates whether the stream goes in the up direction * @param {HTMLVideoElement} [video] + * - the video element to add. If null, a new element with custom + * controls will be created. */ function setMedia(c, isUp, video) { let peersdiv = document.getElementById('peers'); - 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) { @@ -1026,21 +1024,28 @@ function setMedia(c, isUp, video) { let media = /** @type {HTMLVideoElement} */ (document.getElementById('media-' + c.id)); - if(!media) { + if(media) { + if(video) { + throw new Error("Duplicate video"); + } + } else { if(video) { media = video; } else { media = document.createElement('video'); - media.controls = false; if(isUp) media.muted = true; + media.srcObject = c.stream; } + media.classList.add('media'); media.autoplay = true; /** @ts-ignore */ media.playsinline = true; media.id = 'media-' + c.id; div.appendChild(media); + if(!video) + addCustomControls(media, div, c); } let label = document.getElementById('label-' + c.id); @@ -1051,129 +1056,129 @@ function setMedia(c, isUp, video) { div.appendChild(label); } - if(!video) { - let template = document.getElementById('videocontrols-template') - .firstElementChild; - let top_template = document.getElementById('top-videocontrols-template') - .firstElementChild; - - let top_controls = document.getElementById('topcontrols-' + c.id); - if(template && !top_controls) { - top_controls = /** @type{HTMLElement} */(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 = /** @type{HTMLElement} */(template.cloneNode(true)); - controls.id = 'controls-' + c.id; - div.appendChild(controls); - let volume = controls.querySelector(".fa-volume-up"); - if(media.muted) { - if (volume) { - volume.classList.remove("fa-volume-up"); - volume.classList.add("fa-volume-off"); - } - } - if (local_media && local_media.kind === "local") - volume.parentElement.remove(); - } - - media.srcObject = c.stream; - } - setLabel(c); setMediaStatus(c); showVideo(); resizePeers(); - registerControlEvent(div.id); } /** - * @param {HTMLVideoElement} video + * @param {Element} elt */ -async function videoPIP(video) { - /** @ts-ignore */ - if (video.requestPictureInPicture) { - /** @ts-ignore */ - await video.requestPictureInPicture(); +function cloneHTMLElement(elt) { + if(!(elt instanceof HTMLElement)) + throw new Error('Unexpected element type'); + return /** @type{HTMLElement} */(elt.cloneNode(true)); +} + +/** + * @param {HTMLVideoElement} media + * @param {HTMLElement} container + * @param {Stream} c + */ +function addCustomControls(media, container, c) { + media.controls = false; + let controls = document.getElementById('controls-' + c.id); + if(controls) { + console.warn('Attempted to add duplicate controls'); + return; + } + + let template = + document.getElementById('videocontrols-template').firstElementChild; + controls = cloneHTMLElement(template); + controls.id = 'controls-' + c.id; + + let volume = getVideoButton(controls, 'volume'); + if(c.kind === 'local') { + volume.remove(); } else { - displayWarning("Video PIP Mode not supported!"); + setVolumeButton( + /** @type{HTMLElement} */(volume.firstElementChild), + media.muted, + ); + } + + container.appendChild(controls); + registerControlHandlers(media, container); +} + +/** + * @param {HTMLElement} container + * @param {string} name + */ +function getVideoButton(container, name) { + return /** @type {HTMLElement} */(container.getElementsByClassName(name)[0]); +} + +/** + * @param {HTMLElement} button + * @param {boolean} muted + */ +function setVolumeButton(button, muted) { + if(!muted) { + button.classList.remove("fa-volume-off"); + button.classList.add("fa-volume-up"); + } else { + button.classList.remove("fa-volume-up"); + button.classList.add("fa-volume-off"); } } /** - * @param {HTMLElement} target + * @param {HTMLVideoElement} media + * @param {HTMLElement} container */ -function getParentVideo(target) { - // target is the element, parent the div
- let control = target.parentElement.parentElement; - let id = control.id.split('-')[1]; - let media = /** @type {HTMLVideoElement} */ - (document.getElementById('media-' + id)); - if (!media) { - displayError("Cannot find media!"); - } - return media; -} - -/** - * @param {string} peerid - */ -function registerControlEvent(peerid) { - let peer = document.getElementById(peerid); - //Add event listener when a video component is added to the DOM - let volume = /** @type {HTMLElement} */(peer.querySelector("span.volume")); +function registerControlHandlers(media, container) { + let volume = getVideoButton(container, 'volume'); if (volume) { volume.onclick = function(event) { event.preventDefault(); - let volume = /** @type{HTMLElement} */(event.target); - let video = getParentVideo(volume); - if(volume.className.indexOf("fa-volume-off") !== -1) { - volume.classList.remove("fa-volume-off"); - volume.classList.add("fa-volume-up"); - video.muted = false; - } else { - volume.classList.remove("fa-volume-up"); - volume.classList.add("fa-volume-off"); - // mute video sound - video.muted = true; - } + media.muted = !media.muted; + setVolumeButton( + /** @type{HTMLElement} */(event.target), + media.muted, + ); }; } - let pip = /** @type {HTMLElement} */(peer.querySelector("span.pip")); + let pip = getVideoButton(container, 'pip'); if(pip) { /** @ts-ignore */ if(HTMLVideoElement.prototype.requestPictureInPicture) { - pip.onclick = function(event) { - event.preventDefault(); - let pip = /** @type{HTMLElement} */(event.target); - let video = getParentVideo(pip); - videoPIP(video); + pip.onclick = function(e) { + e.preventDefault(); + /** @ts-ignore */ + if(media.requestPictureInPicture) { + /** @ts-ignore */ + media.requestPictureInPicture(); + } else { + displayWarning('Picture in Picture not supported.'); + } }; } else { pip.style.display = 'none'; } } - let fs = /** @type {HTMLElement} */(peer.querySelector("span.fullscreen")); + let fs = getVideoButton(container, 'fullscreen'); if(fs) { - fs.onclick = function(event) { - event.preventDefault(); - let fs = /** @type {HTMLElement} */(event.target); - let video = getParentVideo(fs); - if(video.requestFullscreen) { - video.requestFullscreen(); - } else { - displayWarning("Video Fullscreen not supported!"); - } - }; + if(HTMLVideoElement.prototype.requestFullscreen) { + fs.onclick = function(e) { + e.preventDefault(); + if(media.requestFullscreen) { + media.requestFullscreen(); + } else { + displayWarning('Full screen not supported!'); + } + }; + } else { + fs.style.display = 'none'; + } } } - /** * @param {string} id */