Reputation: 1960
I have a task to display a web camera through a website with some code like this:
feather.replace();
const controls = document.querySelector('.controls');
const cameraOptions = document.querySelector('.video-options>select');
const video = document.querySelector('video');
const canvas = document.querySelector('canvas');
const screenshotImage = document.querySelector('img');
const buttons = [...controls.querySelectorAll('button')];
let streamStarted = false;
const [play, pause, screenshot] = buttons;
const constraints = {
video: {
width: {
min: 1280,
ideal: 1920,
max: 2560,
},
height: {
min: 720,
ideal: 1080,
max: 1440
},
}
}
const getCameraSelection = () => {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const videoDevices = devices.filter(device => device.kind === 'videoinput');
const options = videoDevices.map(videoDevice => {
return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
});
cameraOptions.innerHTML = options.join('');
});
}
const startStream = (constraints, changeMode = false) => {
try {
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
if (!changeMode) getCameraSelection();
video.srcObject = stream;
console.log('video.srcObject :', video.srcObject);
video.play();
play.classList.add('d-none');
pause.classList.remove('d-none');
screenshot.classList.remove('d-none');
streamStarted = true;
});
} catch (e) {
console.error(e);
}
}
play.onclick = () => {
if (streamStarted) {
video.play();
play.classList.add('d-none');
pause.classList.remove('d-none');
return;
}
if ('mediaDevices' in navigator) {
const updatedConstraints = {
...constraints,
deviceId: {
exact: cameraOptions.value
}
};
startStream(updatedConstraints);
}
}
cameraOptions.onchange = () => {
const updatedConstraints = {
...constraints,
deviceId: {
exact: cameraOptions.value
}
};
startStream(updatedConstraints, true);
}
pause.onclick = () => {
video.pause();
play.classList.remove('d-none');
pause.classList.add('d-none');
}
screenshot.onclick = () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
screenshotImage.src = canvas.toDataURL('image/webp');
screenshotImage.classList.remove('d-none');
}
.screenshot-image {
width: 150px;
height: 90px;
border-radius: 4px;
border: 2px solid whitesmoke;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
position: absolute;
bottom: 5px;
left: 10px;
background: white;
}
.display-cover {
display: flex;
justify-content: center;
align-items: center;
width: 70%;
margin: 5% auto;
position: relative;
}
video {
width: 100%;
background: rgba(0, 0, 0, 0.2);
}
.video-options {
position: absolute;
left: 20px;
top: 30px;
}
.controls {
position: absolute;
right: 20px;
top: 20px;
display: flex;
}
.controls>button {
width: 45px;
height: 45px;
text-align: center;
border-radius: 100%;
margin: 0 6px;
background: transparent;
}
.controls>button:hover svg {
color: white !important;
}
@media (min-width: 300px) and (max-width: 400px) {
.controls {
flex-direction: column;
}
.controls button {
margin: 5px 0 !important;
}
}
.controls>button>svg {
height: 20px;
width: 18px;
text-align: center;
margin: 0 auto;
padding: 0;
}
.controls button:nth-child(1) {
border: 2px solid #D2002E;
}
.controls button:nth-child(1) svg {
color: #D2002E;
}
.controls button:nth-child(2) {
border: 2px solid #008496;
}
.controls button:nth-child(2) svg {
color: #008496;
}
.controls button:nth-child(3) {
border: 2px solid #00B541;
}
.controls button:nth-child(3) svg {
color: #00B541;
}
.controls>button {
width: 45px;
height: 45px;
text-align: center;
border-radius: 100%;
margin: 0 6px;
background: transparent;
}
.controls>button:hover svg {
color: white;
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<script src="https://unpkg.com/feather-icons"></script>
<div class="display-cover">
<video autoplay></video>
<canvas class="d-none"></canvas>
<div class="video-options">
<select name="" id="" class="custom-select">
<option value="">Select camera</option>
</select>
</div>
<img class="screenshot-image d-none" alt="">
<div class="controls">
<button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
<button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
<button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
</div>
</div>
In order to get multiple camera sources, I added Droidcam. The problem is I can't change the camera source from droidcam to pc camera and vice versa. I have confirmed that video.srcObject
has changed but still, the camera source is not changed.
Why did it happen? How to solve this? Any answer would be appreciated, thanks.
Upvotes: 1
Views: 1863
Reputation: 1960
I just figured out the solution. The mistake occurred at the constraints
object. The valid constraint
looks like this:
{
video: {
width: {
min: 1280,
ideal: 1920,
max: 2560,
},
height: {
min: 720,
ideal: 1080,
max: 1440
},
deviceId: null
}
}
When selecting another input device, constraints.video.deviceId.exact
should be updated with cameraOptions.value
which is the selected option value (deviceId).
Additional ways that can be applied for destroying MediaStreamTrack
before changing the camera source is with MediaStreamTrack.stop()
.
...
let currentStream = null;
...
if (currentStream) {
currentStream.getTracks().forEach(track => {
track.stop();
});
}
...
Full code:
feather.replace();
const controls = document.querySelector('.controls');
const cameraOptions = document.querySelector('.video-options>select');
const video = document.querySelector('video');
const canvas = document.querySelector('canvas');
const screenshotImage = document.querySelector('img');
const buttons = [...controls.querySelectorAll('button')];
let streamStarted = false;
const [play, pause, screenshot] = buttons;
let currentStream = null;
const constraints = {
video: {
width: {
min: 1280,
ideal: 1920,
max: 2560,
},
height: {
min: 720,
ideal: 1080,
max: 1440
},
deviceId: null
}
}
const getCameraSelection = () => {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const videoDevices = devices.filter(device => device.kind === 'videoinput');
const options = videoDevices.map(videoDevice => {
return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
});
cameraOptions.innerHTML = options.join('');
});
}
const startStream = (constraints, changeMode = false) => {
try {
if (currentStream) {
currentStream.getTracks().forEach(track => {
track.stop();
});
}
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
if (!changeMode) getCameraSelection();
video.srcObject = stream;
play.classList.add('d-none');
pause.classList.remove('d-none');
screenshot.classList.remove('d-none');
streamStarted = true;
});
} catch (e) {
console.error(e);
}
}
play.onclick = () => {
if (streamStarted) {
video.play();
play.classList.add('d-none');
pause.classList.remove('d-none');
return;
}
if ('mediaDevices' in navigator) {
startStream(constraints);
}
}
cameraOptions.onchange = () => {
constraints.video.deviceId = {
exact: cameraOptions.value
};
startStream(constraints, true);
}
pause.onclick = () => {
video.pause();
play.classList.remove('d-none');
pause.classList.add('d-none');
}
screenshot.onclick = () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
screenshotImage.src = canvas.toDataURL('image/webp');
screenshotImage.classList.remove('d-none');
}
.screenshot-image {
width: 150px;
height: 90px;
border-radius: 4px;
border: 2px solid whitesmoke;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
position: absolute;
bottom: 5px;
left: 10px;
background: white;
}
.display-cover {
display: flex;
justify-content: center;
align-items: center;
width: 70%;
margin: 5% auto;
position: relative;
}
video {
width: 100%;
background: rgba(0, 0, 0, 0.2);
}
.video-options {
position: absolute;
left: 20px;
top: 30px;
}
.controls {
position: absolute;
right: 20px;
top: 20px;
display: flex;
}
.controls>button {
width: 45px;
height: 45px;
text-align: center;
border-radius: 100%;
margin: 0 6px;
background: transparent;
}
.controls>button:hover svg {
color: white !important;
}
@media (min-width: 300px) and (max-width: 400px) {
.controls {
flex-direction: column;
}
.controls button {
margin: 5px 0 !important;
}
}
.controls>button>svg {
height: 20px;
width: 18px;
text-align: center;
margin: 0 auto;
padding: 0;
}
.controls button:nth-child(1) {
border: 2px solid #D2002E;
}
.controls button:nth-child(1) svg {
color: #D2002E;
}
.controls button:nth-child(2) {
border: 2px solid #008496;
}
.controls button:nth-child(2) svg {
color: #008496;
}
.controls button:nth-child(3) {
border: 2px solid #00B541;
}
.controls button:nth-child(3) svg {
color: #00B541;
}
.controls>button {
width: 45px;
height: 45px;
text-align: center;
border-radius: 100%;
margin: 0 6px;
background: transparent;
}
.controls>button:hover svg {
color: white;
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<script src="https://unpkg.com/feather-icons"></script>
<div class="display-cover">
<video autoplay></video>
<canvas class="d-none"></canvas>
<div class="video-options">
<select name="" id="" class="custom-select">
<option value="">Select camera</option>
</select>
</div>
<img class="screenshot-image d-none" alt="">
<div class="controls">
<button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
<button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
<button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
</div>
</div>
Upvotes: 2