javascriptvideogetusermedianavigatordeviceid

Change video resource for an ongoing video, change the device id


Usecase: Change the available video src by selecting from a dropdown. Also the video is on.

I want to change the video source for an ongoing video stream started by navigator.mediaDevices.getUserMedia. I tried, closing the existing tracks, making the srcObject null and starting it again by passing new deviceId to the constraints.

JS

$(document).ready(function() {
  var videoDeviceList = [];
  var selectedDeviceId = "";
  if (!navigator.mediaDevices.enumerateDevices) {
    console.log("enumerateDevices() not supported.");
  } else {
    // List cameras and microphones.
    navigator.mediaDevices.enumerateDevices()
      .then((devices) => {
        devices.forEach((device) => {
          if (device.kind === 'videoinput') {
            selectedDeviceId = selectedDeviceId === "" ? device.deviceId : selectedDeviceId;
            videoDeviceList.push({
              deviceId: device.deviceId,
              deviceLabel: device.label
            });
          }
        });
        videoDeviceList.forEach((item) => {
          $("#videoList").append("<option id='" + item.deviceId + "'>" + item.deviceLabel + "</option>");
        });

      })
      .catch((err) => {
        console.error(`${err.name}: ${err.message}`);
      });

  }
  $("#btn").click(function() {
    $(this).attr("disabled", true);
    startOrUpdateCameraFeed();
  });
  $('#videoList').change(function() {
    selectedDeviceId = $(this).find('option:selected').attr('id');
    console.log("selectedDeviceId: ", selectedDeviceId);
    startOrUpdateCameraFeed();
  });

  function startOrUpdateCameraFeed() {
    const video = document.getElementById('video');
    // to shut all the open or running video streams
    if (window.videoStream !== null && window.videoStream !== undefined && window.videoStream.length > 0 &&
      video) {
      window.videoStream.forEach((stream) => {
        if (stream) {
          if ('getTracks' in stream && stream.getTracks().length > 0) {
            stream.getTracks().forEach((track) => stopTrack(track));
          }
          if ('getVideoTracks' in stream && stream.getVideoTracks().length > 0) {
            stream.getVideoTracks().forEach((track) => stopTrack(track));
          }
          if ('getAudioTracks' in stream && stream.getAudioTracks().length > 0) {
            stream.getAudioTracks().forEach((track) => stopTrack(track));
          }
        }
      });
      window.videoStream = [];
      video.srcObject = null;
    }
    // start a new feed with changed camera device id
    var constraints = {
      deviceId: {
        exact: selectedDeviceId
      }
    };
    console.log("constraints: ", constraints);
    navigator.mediaDevices
      .getUserMedia({
        video: true,
        video: {
          width: {
            ideal: 4096,
            max: 4096
          },
          height: {
            ideal: 2160,
            max: 2160
          },
          deviceId: constraints,
        }
      }).then(function(selfStream) {
        if (window.videoStream === null || window.videoStream === undefined) {
          window.videoStream = [];
        }
        if (video) {
          console.log('called video: ', window.videoStream);
          video.srcObject = selfStream;
          window.videoStream.push(selfStream);
        }
      })
      .catch(function(err0r) {
        console.log('Something went wrong regular before video!', err0r);
      });
  }
  function stopTrack (track){
    if (track && 'stop' in track) {
      track.stop();
    }
  };
});

HTML && CSS

<b>Please give permissions for the site to view the camera list. </b><br/><br/>
Change the camera source:
<select id="videoList">
  <option disabled>Select</option>
</select>
<div id="videoContainer">
  <video id="video" height="100%" width="100%" autoplay></video>
</div>

<button id="btn">
  Start initial feed
</button>

video {
  height: 100%;
  width:100%;
  border: 1px solid red;
}
#videoContainer {
  height: 150px;
  width: 250px;
}

Please check the fiddle link for the question. https://jsfiddle.net/jivansupe/576qjcn9/1/

The video element shows new device id but the source does not change. thanks in advance.


Solution

  • I had made a mistake with passing the new deviceId.

    Fix:

    var constraints = {
      exact: selectedDeviceId
    };
    

    Updated code:

    $(document).ready(function() {
      var videoDeviceList = [];
      var selectedDeviceId = "";
      if (!navigator.mediaDevices.enumerateDevices) {
        console.log("enumerateDevices() not supported.");
      } else {
        // List cameras and microphones.
        navigator.mediaDevices.enumerateDevices()
          .then((devices) => {
            devices.forEach((device) => {
              if (device.kind === 'videoinput') {
                selectedDeviceId = selectedDeviceId === "" ? device.deviceId : selectedDeviceId;
                videoDeviceList.push({
                  deviceId: device.deviceId,
                  deviceLabel: device.label
                });
              }
            });
            videoDeviceList.forEach((item) => {
              $("#videoList").append("<option id='" + item.deviceId + "'>" + item.deviceLabel + "</option>");
            });
    
          })
          .catch((err) => {
            console.error(`${err.name}: ${err.message}`);
          });
    
      }
      $("#btn").click(function() {
        $(this).attr("disabled", true);
        startOrUpdateCameraFeed();
      });
      $('#videoList').change(function() {
        selectedDeviceId = $(this).find('option:selected').attr('id');
        console.log("selectedDeviceId: ", selectedDeviceId);
        startOrUpdateCameraFeed();
      });
    
      function startOrUpdateCameraFeed() {
        const video = document.getElementById('video');
        // to shut all the open or running video streams
        if (window.videoStream !== null && window.videoStream !== undefined && window.videoStream.length > 0 &&
          video) {
          window.videoStream.forEach((stream) => {
            if (stream) {
              if ('getTracks' in stream && stream.getTracks().length > 0) {
                stream.getTracks().forEach((track) => stopTrack(track));
              }
              if ('getVideoTracks' in stream && stream.getVideoTracks().length > 0) {
                stream.getVideoTracks().forEach((track) => stopTrack(track));
              }
              if ('getAudioTracks' in stream && stream.getAudioTracks().length > 0) {
                stream.getAudioTracks().forEach((track) => stopTrack(track));
              }
            }
          });
          window.videoStream = [];
          video.srcObject = null;
        }
        // start a new feed with changed camera device id
        // BELOW IS THE CODE CHANGE
        var constraints = {
          exact: selectedDeviceId
        };
        console.log("constraints: ", constraints);
        navigator.mediaDevices
          .getUserMedia({
            video: true,
            video: {
              width: {
                ideal: 4096,
                max: 4096
              },
              height: {
                ideal: 2160,
                max: 2160
              },
              deviceId: constraints,
            }
          }).then(function(selfStream) {
            if (window.videoStream === null || window.videoStream === undefined) {
              window.videoStream = [];
            }
            if (video) {
              console.log('called video: ', window.videoStream);
              video.srcObject = selfStream;
              window.videoStream.push(selfStream);
            }
          })
          .catch(function(err0r) {
            console.log('Something went wrong regular before video!', err0r);
          });
      }
      function stopTrack (track){
        if (track && 'stop' in track) {
          track.stop();
        }
      };
    });
    

    thanks.