1

I would like to use x to output a list of all connected webcams with ID. Unfortunately, I always get the following error message (see picture). enter image description here Does anyone have an idea what this could be? I am thankful for any help!

Here is my code:

const devices = [];
var list;
let video;

function setup() {
    list = navigator.mediaDevices.enumerateDevices().then(gotDevices);
    var constraints1 = {
    video: {
      deviceId: {
        exact: list[0].id
      },
    }
  };
    
    canvas = createCanvas(width,height);

    background(255);
    video = createCapture(constraints1);
}

function gotDevices(deviceInfos) {
  for (let i = 0; i !== deviceInfos.length; ++i) {
    const deviceInfo = deviceInfos[i];
    if (deviceInfo.kind == 'videoinput') {
      devices.push({
        label: deviceInfo.label,
        id: deviceInfo.deviceId
      });
    }
  }
  return devices;
}

-------------------------------EDIT (Current status)-----------------------

var deviceList = [];

function preload() {
  navigator.mediaDevices.enumerateDevices().then(getDevices);
}

function setup() {
  var constraints = {
  video: {
      deviceId: {
        exact: deviceList[1].id
      },
    }
  };

canvas = createCanvas(width, height);
background(255);
video = createCapture(constraints);
//console.log(deviceList);
}

function getDevices(devices) {

  //arrayCopy(devices, deviceList);
  for (let i = 0; i < devices.length; ++i) {
    let deviceInfo = devices[i];
    
      //Only get videodevices and push them into deviceList
      if (deviceInfo.kind == 'videoinput') {
        deviceList.push({
        label: deviceInfo.label,
        id: deviceInfo.deviceId
      });
//      console.log("Device name :", devices[i].label);
//      console.log("DeviceID :", devices[i].deviceId);
    }
  }
}

Unfortunately, I get the following error here: enter image description here

  • 1
    Are you sure 'list' is an array? – apodidae Jan 26 '22 at 20:53
  • devices[0].id should run without error. – apodidae Jan 27 '22 at 06:02
  • Unfortunately not. As soon as I enter devices[0].id as a constraint I get the following error: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'id') – MasterKneater Jan 27 '22 at 14:07
  • My comment applied to the original code that you posted. Not the code that I posted. They're two different techniques. The original only works for the first video input that it comes across. The code I posted shows all the video and audio possibilities. You have to decide which one you're going to use. Don't try and mix them until you understand it better. – apodidae Jan 27 '22 at 16:05
  • I'm testing in p5.js web editor with Chrome browser. What are you using to run these demos? – apodidae Jan 27 '22 at 20:01
  • You are right! The problem I have is that the code in p5.js web editor with Chrome browser (just like in firefox) works perfectly. As soon as I run the code from Brackets (or processing) in Firefox or Chrome the error message "Cannot read properties of undefined (reading 'id')" appears. Now I'm pretty much at a loss... – MasterKneater Jan 27 '22 at 21:28

2 Answers2

3

The information that you are looking for is down in the 'getDevices' function. The following runs on my system and will show the device name and id in the console window. It will also create a global array for audio and video devices that you may access in setup(); Note that the deviceList is obtained in preload() which is run only once before the rest of your code.

var deviceList = [];

function preload() {
  navigator.mediaDevices.enumerateDevices().then(getDevices);
}

function setup() {
  var constraints = {
  video:
  {
  }
};
canvas = createCanvas(width, height);
background(255);
video = createCapture(constraints);
//console.log(deviceList);
for (let x = 0; x < deviceList.length; x++) {
  console.log(deviceList[x]);
}

}

function getDevices(devices) {
  // console.log(devices); // To see all devices
  arrayCopy(devices, deviceList);
  for (let i = 0; i < devices.length; ++i) {
    let deviceInfo = devices[i];
    if (deviceInfo.kind == 'videoinput') {
      console.log("Device name :", devices[i].label);
      console.log("DeviceID :", devices[i].deviceId);
    }
  }
}

DropDownList of Devices

// N.B. Will not run in Processing IDE with Safari - Requires p5.js web editor and Chrome browser
// Loads deviceList array into pullDown list
// Drop Down List parts => a.)display field, b.)arrow, c.)listItems
// Syntax: List(x, y, w, h, itemH, txtSize)

let list;
let selectedItem = -1;
let drop = false;
let itemY = [];
var deviceList = [];

function preload() {
  navigator.mediaDevices.enumerateDevices().then(getDevices);
}

class List {

  constructor(x, y, w, h, itemH, txtSize) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.itemH = itemH;
    this.txtSize = txtSize;
    this.arrwX = this.x + this.w;
    this.arrwY = this.y;
    this.arrwH = this.h;
  }

  openVideoInput (videoSelected) {
    var constraints = {
    video: {
    deviceId: {
      exact: deviceList[videoSelected].id
      },
    }
  };
  createCapture(constraints);
}

press(mx, my) {
  // arrow touches
  if ((mx >= this.arrwX) && (mx <= this.arrwX+this.arrwH) && (my >= this.arrwY) && (my <= this.arrwY+this.arrwH)) {
    if (drop == true) {
      drop = false;
    } else {
      drop = true;
    }
  } // list touches
  if (drop) {
    if (deviceList.length > 0) {
      for (let j = 0; j < deviceList.length; j++) {
        if ((mx >= this.x) && (mx <= this.x + this.w) && (my >= itemY[j] ) && (my <= itemY[j] + this.itemH)) {
          selectedItem = j;
          console.log("selectedItem :", selectedItem);
          list.openVideoInput(selectedItem);
          drop = false;
        }
      }
    }
  }
}

displayFieldString(title) {
  fill(255); // background color
  rect(this.x, this.y, this.w, this.h);
  fill(0); // text color
  textSize(this.txtSize);
  text(title, this.x + 10, this.y + this.txtSize);
}

display() {
  if (selectedItem == -1) {
    this.displayFieldString("Select video input:");
  } else {
    this.displayFieldString(deviceList[selectedItem].label);
  }
  // arrow
  fill(255); // arrow background color
  rect(this.arrwX, this.arrwY, this.arrwH, this.arrwH);
  fill(0, 255, 0); // arrow color
  triangle(this.arrwX+5, this.arrwY+5, this.arrwX+this.arrwH-5, this.arrwY+5, this.arrwX+this.arrwH/2, this.arrwY+this.arrwH-5);
  // listItems
  if ((deviceList.length > 0) && (drop)) {
    for (let j = 0; j < deviceList.length; j++) {
      itemY[j] = (this.y + this.h) + j*this.itemH;
      fill(255);
      rect(this.x, itemY[j], this.w, this.itemH);
      fill(0);
      textSize(this.txtSize);
      text(deviceList[j].label, this.x + 10, itemY[j] + this.txtSize);
    }
  }
  if (!drop) {
    rect(this.x, this.y + this.h, this.w, 0);
  }
}
}

function setup() {
  createCanvas(400, 200);
  list = new List(30, 30, 320, 24, 24, 16);
}

function draw() {
  background(220);
  list.display();
}

function getDevices(devices) {

  for (let i = 0; i < devices.length; ++i) {
    let deviceInfo = devices[i];
    //Only get videodevices and push them into deviceList
    if (deviceInfo.kind == 'videoinput') {
      deviceList.push( {
      label:deviceInfo.label,
        id:deviceInfo.deviceId
      }
      );
    }
  }
}

function mousePressed() {
  list.press(mouseX, mouseY);
}

apodidae
  • 1,988
  • 2
  • 5
  • 9
  • Hi apodidae, many thanks for your reply! :-) I thought list should be an array, since I return the device array in the function. At least that was my thought, but I think you are right.... My problem is that I have to read the current device id every time I start the application. So I wonder if there is a way to return the ID's of the devices (webcams) and save them in a separate global variable (optimally an array). I'm just biting my teeth on this one. – MasterKneater Jan 26 '22 at 22:29
  • See edit. Added a global array to hold device list. – apodidae Jan 27 '22 at 01:34
  • Hello apodidae, thanks for your answer! I have now updated the code to the current state, however I am still not able to start my video using the command: exact: deviceList[1].id It is important that the video source is selected directly from the array – MasterKneater Jan 27 '22 at 14:06
  • If you have two video cameras hooked to your system, your edited technique will work. I was able to switch to a second camera by changing the [0] to a [1]. It won't work if you only have one camera connected. – apodidae Jan 27 '22 at 17:05
  • Thanks for the answer. Yes, the original code works for multiple video inputs, because it goes through the entire devices array and pushes each video input into the new array. But exactly, the goal is to control three different cameras using their id. – MasterKneater Jan 27 '22 at 21:24
  • You should be able to run three cameras. I added a DropDownList of video input devices to make it easier. They will all be open at one time so you may need to scroll down to see the last one. – apodidae Jan 27 '22 at 21:46
  • First of all, thank you for implementing the dropdown list. That is really a big help! The last question would be why it is not possible to use the code in Processing or Brackets. But maybe I'll open a new thread for that, since my original question was answered. Thanks again! – MasterKneater Jan 27 '22 at 22:53
  • Preliminarily, I think it's a failure of navigator.mediaDevices.enumerateDevices() in some environments, but requires more study. – apodidae Jan 27 '22 at 23:10
  • I don't think so, because the output to the console of the IDs and devices works without errors (in the getDevices-Function). I have already checked this using console.log. – MasterKneater Jan 27 '22 at 23:24
  • This is what I see in Processing IDE with Safari with two cameras: MediaDeviceInfo {deviceId: "", kind: "audioinput", label: "", groupId: "", toJSON: function} 1 MediaDeviceInfo {deviceId: "", kind: "videoinput", label: "", groupId: "", toJSON: function}. It only found one of the cameras and the fields are not filled in. – apodidae Jan 28 '22 at 00:01
  • That's funny. When I run the script in my project in Brackets all devices are displayed correctly. Only the transfer to the deviceList array does not seem to work. – MasterKneater Jan 28 '22 at 08:18
0

Ok, now that I've looked further on the Internet for a solution and also found one, I thought I'd post it here too. Maybe it still helps someone else. The problem was that the array deviceList[] is not completely filled when the code reaches the following line:

deviceList[1].id within setup()

The easiest way to solve this problem was to postpone the automatically initialization of the p5js library until your callback getDevices() has finished its job. Thanks again to apodidae for his help.

var deviceList = [];

navigator.mediaDevices.enumerateDevices().then(getDevices);

const setup = function () {
    
    var constraints = {
        video: {
            deviceId: {
                exact: deviceList[0].id
      },
    }
  };
    var constraints1 = {
        video: {
            deviceId: {
                exact: deviceList[1].id
      },
    }
  };
canvas = createCanvas(width, height);
background(255);
video = createCapture(constraints);
    video2 = createCapture(constraints1);
console.log(deviceList);
};

const draw = function () {
    
};


function startP5() {
    globalThis.setup = setup; // place callback setup() into global context
    globalThis.draw  = draw;
    new p5;
}


function getDevices(devices) {

  //arrayCopy(devices, deviceList);
  for (let i = 0; i < devices.length; ++i) {
    let deviceInfo = devices[i];
    
      //Only get videodevices and push them into deviceList
      if (deviceInfo.kind == 'videoinput') {
        deviceList.push({
        label: deviceInfo.label,
        id: deviceInfo.deviceId
      });
//      console.log("Device name :", devices[i].label);
//      console.log("DeviceID :", devices[i].deviceId);
    }
  }
    startP5();
}

Here again the link to the other discussion