Skip to content

an html webcam playerยค

i like having control of my software and knowing what is going on. recently i tried tried compositing my stream using more native html features rather than configuring obs. i really liked how it felt and wanted to explore video capabilities more.

this approach, and it extended to a screen share, makes it possible to quickly remix video streams.

%%
our `video` is represented as a `form` to give easier access form data in the javascript.

{{one.data}}

        one=\
```html
<form class="webcam" name="one">
<label for="camera">cameras</label>
<select name="camera"></select>
<select name="direction">
<option value="user">forward</option>
<option value="environment">back</option>
</select>
<br/>
<video autoplay=""></video>
</form>
```

we make two videos to demonstrate this works for multiple video streams, and we could add a screen share.

{{two.data}}

        two=\
```html
<form class="webcam" name="two">
<label for="camera">cameras</label>
<select name="camera"></select>
<select name="direction">
<option value="user">forward</option>
<option value="environment">back</option>
</select>
<br/>
<video autoplay=""></video>
</form>
```


our javascript needs update the avaiable cameras dynamically.
user intervention is needed to give access to a camera.

        updateVideoSources=\
```js
function updateVideoSources(device, camera){
    if (device.kind == "videoinput") {
      let option = document.createElement('option');
      if (!camera.querySelector(`[value="${device.deviceId}"]`)){
          option.setAttribute("value", device.deviceId);
          option.insertAdjacentHTML( 'beforeend', device.label || "unknown" );
          camera.appendChild(option);
      }
  };
}

```

we iterate through any elements on the page and update the media devices available while adding hooks for updating sources.

        webcams=\
```js
Array.from(document.getElementsByClassName("webcam")).forEach(
    (form) =&gt; {
        let [video] = form.getElementsByTagName("video");
        let camera = form.elements.camera;
        let direction = form.elements.direction;
        if (!navigator.mediaDevices?.enumerateDevices) {
          console.log("enumerateDevices() not supported.");
        } else {
          navigator.mediaDevices
            .enumerateDevices({video: true, audio: false})
            .then((devices) =&gt; {
              devices.forEach((device) =&gt; {updateVideoSources(device, camera)});
            })
            .catch((err) =&gt; {console.error(`${err.name}: ${err.message}`);});
        }
        function change(){
            console.log("changing source");
            navigator.mediaDevices.getUserMedia({ 
                video: {
                    facingMode: form.elements.direction.value,
                    deviceId: {
                        exact: form.elements.camera.value
                    }
                }, audio: false 
            })
          .then(stream =&gt; video.srcObject = stream)
          .catch(console.log);
        }
        change();
        camera.addEventListener("change", change);
        direction.addEventListener("change", change);
    }
)
```


<script>{{updateVideoSources}}

{{webcams}}</script>

our video is represented as a form to give easier access form data in the javascript.


    one=\
<form name=one class=webcam>
    <label for=camera>cameras</label>
    <select name=camera></select> 
    <select name=direction>
        <option value=user>forward</option>
        <option value=environment>back</option>
    </select> 
    <br/>
    <video autoplay></video>
</form>

we make two videos to demonstrate this works for multiple video streams, and we could add a screen share.


    two=\
<form name=two class=webcam>
    <label for=camera>cameras</label>
    <select name=camera></select> 
    <select name=direction>
        <option value=user>forward</option>
        <option value=environment>back</option>
    </select> 
    <br/>
    <video autoplay></video>
</form>

our javascript needs update the avaiable cameras dynamically. user intervention is needed to give access to a camera.

    updateVideoSources=\
function updateVideoSources(device, camera){
    if (device.kind == "videoinput") {
      let option = document.createElement('option');
      if (!camera.querySelector(`[value="${device.deviceId}"]`)){
          option.setAttribute("value", device.deviceId);
          option.insertAdjacentHTML( 'beforeend', device.label || "unknown" );
          camera.appendChild(option);
      }
  };
}

we iterate through any elements on the page and update the media devices available while adding hooks for updating sources.

    webcams=\
Array.from(document.getElementsByClassName("webcam")).forEach(
    (form) => {
        let [video] = form.getElementsByTagName("video");
        let camera = form.elements.camera;
        let direction = form.elements.direction;
        if (!navigator.mediaDevices?.enumerateDevices) {
          console.log("enumerateDevices() not supported.");
        } else {
          navigator.mediaDevices
            .enumerateDevices({video: true, audio: false})
            .then((devices) => {
              devices.forEach((device) => {updateVideoSources(device, camera)});
            })
            .catch((err) => {console.error(`${err.name}: ${err.message}`);});
        }
        function change(){
            console.log("changing source");
            navigator.mediaDevices.getUserMedia({ 
                video: {
                    facingMode: form.elements.direction.value,
                    deviceId: {
                        exact: form.elements.camera.value
                    }
                }, audio: false 
            })
          .then(stream => video.srcObject = stream)
          .catch(console.log);
        }
        change();
        camera.addEventListener("change", change);
        direction.addEventListener("change", change);
    }
)