'Echo from video recorder/player (On page load)

I have a video player and recorder that allows the user to record themselves, play back the video, and download.

The code renders:

• Two video players vertically aligned with Record and Stop Record Buttons.

• (Recorder) The top video player is where the user can watch themselves live as they record.

• (Playback) The bottom video player is where the user can playback and download their recording.

The video player works across browsers and devices with the exception of an echo on page load.

Problem: The bug I am experiencing is this...

• After a user has granted the browser access to the microphone and camera when the user loads the page there is an loud screeching echo.

• To stop the echo the user has to manually mute the sound on the top video recorder.

• It's an annoying user experience.

What I've tried:
I tried to fix this by muting the sound on the top video player.

Unfortunately, I can't get this to work and it would only be a workaround.

I appreciate any guidance and/or help.


<meta name="viewport" content="width=device-width, initial-scale=1">

<style>
body {font-family: Arial;}

.tab {
  overflow: hidden;
  border: 1px solid #ccc;
  background-color: #f1f1f1;
}

.tab button {
  background-color: inherit;
  float: left;
  border: none;
  outline: none;
  cursor: pointer;
  padding: 14px 16px;
  transition: 0.3s;
  font-size: 17px;
}

/* Change background color of buttons on hover */
.tab button:hover {
  background-color: #FFCC00;
}

/* Create an active/current tablink class */
.tab button.active {
  background-color: #FFFFCC;
}

/* Style the tab content */
.tabcontent {
  display: none;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-top: none;
}
</style>


<div class="tab">
  <button class="tablinks" onclick="openCity(event, 'Audio')">Audio</button>
  <button class="tablinks" onclick="openCity(event, 'Video')">Video</button>
</div>

<div id="Audio" class="tabcontent">

//Audio recorder/player code to go here. 

</div>

<div id="Video" class="tabcontent">

<style>

.button {
  background-color: #66CC00;
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 4px;
  transition-duration: 0.4s;
}

  .button:hover {
  border: 2px solid #66CC00;
  background-color: #ffffff;
  color: #333333;
}
  
  .button2 {
  background-color: #f44336;
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 4px;
  transition-duration: 0.4s;
}
  
  .button2:hover {
  border: 2px solid #f44336;
  background-color: #ffffff;
  color: #333333;
}
  
p.capitalize {
  text-transform: capitalize;
}
</style>
    <meta charset="UTF-8">
    <title>MediaCapture and Streams API</title>
    <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" href="main.css">


<center>
<h2 style="color:#333333">Record your practice.</h2>
</center>

        <center><video controls=""></video></center>
        <center>
<h4 style="color: #333333" class="capitalize">If you hear an echo hover on the recorder above and disable the sound.</h4>
</center>

        <center>

<button class="button" id="btnStart">START RECORDING ⏺</button>  <button class="button button2" id="btnStop">STOP RECORDING ⏹️ </button>

</center>


<center><video id="vid2" controls=""></video></center>
        <center>
<h4 style="color: #333333" class="capitalize">Watch your practice. Click the three dots to download.</h4>
</center>
        
    <script>
        
        let constraintObj = { 
            audio: true, 
            video: { 
                facingMode: "user", 
                width: { min: 250, ideal: 560, max: 560 },               
                height: { min: 250, ideal: 315, max: 315 } 
            }            
        }; 
        // width: 100%, height: 315  -- preference only
        // facingMode: {exact: "user"}
        // facingMode: "environment"
        
        //handle older browsers that might implement getUserMedia in some way
        if (navigator.mediaDevices === undefined) {
            navigator.mediaDevices = {};
            navigator.mediaDevices.getUserMedia = function(constraintObj) {
                let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
                if (!getUserMedia) {
                    return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
                }
                return new Promise(function(resolve, reject) {
                    getUserMedia.call(navigator, constraintObj, resolve, reject);
                });
            }
        }else{
            navigator.mediaDevices.enumerateDevices()
            .then(devices => {
                devices.forEach(device=>{
                    console.log(device.kind.toUpperCase(), device.label);
                    //, device.deviceId
                })
            })
            .catch(err=>{
                console.log(err.name, err.message);
            })
        }

        navigator.mediaDevices.getUserMedia(constraintObj)
        .then(function(mediaStreamObj) {
            //connect the media stream to the first video element
            let video = document.querySelector('video');
            if ("srcObject" in video) {
                video.srcObject = mediaStreamObj;
            } else {
                //old version
                video.src = window.URL.createObjectURL(mediaStreamObj);
            }
            
            video.onloadedmetadata = function(ev) {
                //show in the video element what is being captured by the webcam
                video.play();
            };
            
            //add listeners for saving video/audio
            let start = document.getElementById('btnStart');
            let stop = document.getElementById('btnStop');
            let vidSave = document.getElementById('vid2');
            let mediaRecorder = new MediaRecorder(mediaStreamObj);
            let chunks = [];
            
            start.addEventListener('click', (ev)=>{
                mediaRecorder.start();
                console.log(mediaRecorder.state);
            })
            stop.addEventListener('click', (ev)=>{
                mediaRecorder.stop();
                console.log(mediaRecorder.state);
            });
            mediaRecorder.ondataavailable = function(ev) {
                chunks.push(ev.data);
            }
            mediaRecorder.onstop = (ev)=>{
                let blob = new Blob(chunks, { 'type' : 'video/mp4;' });
                chunks = [];
                let videoURL = window.URL.createObjectURL(blob);
                vidSave.src = videoURL;
            }
        })
        .catch(function(err) { 
            console.log(err.name, err.message); 
        });
        
        /*********************************
        getUserMedia returns a Promise
        resolve - returns a MediaStream Object
        reject returns one of the following errors
        AbortError - generic unknown cause
        NotAllowedError (SecurityError) - user rejected permissions
        NotFoundError - missing media track
        NotReadableError - user permissions given but hardware/OS error
        OverconstrainedError - constraint video settings preventing
        TypeError - audio: false, video: false
        *********************************/
    </script>

</div>


<script>
function openCity(evt, cityName) {
  var i, tabcontent, tablinks;
  tabcontent = document.getElementsByClassName("tabcontent");
  for (i = 0; i < tabcontent.length; i++) {
    tabcontent[i].style.display = "none";
  }
  tablinks = document.getElementsByClassName("tablinks");
  for (i = 0; i < tablinks.length; i++) {
    tablinks[i].className = tablinks[i].className.replace(" active", "");
  }
  document.getElementById(cityName).style.display = "block";
  evt.currentTarget.className += " active";
}
</script>
   


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source