What sorts of errors do you want to detect?
If your machine doesn't have the hardware (camera, mic) it needs to make your MediaStream, you can find this out by using .enumerateDevices() before you try to use .getUserMedia(). A function like this might give you the information you need for an {audio: true, video:true} MediaStream.
async function canIDoIt () {
const devices = await navigator.mediaDevices.enumerateDevices()
let hasAudio = false
let hasVideo = false
devices.forEach(function(device) {
if (device.kind === 'videoinput') hasVideo = true
if (device.kind === 'audioinput') hasAudio = true
})
return hasAudio && hasVideo
}
Using that is a good start for a robust media app: you can tell your user they don't have the correct hardware right away before diving into to the arcana of the errors thrown by .getUserMedia().
If your user denies permission to .getUserMedia() access their media devices, it will throw an error with error.message containing the string "permission denied". Keep in mind that when the user denies permission, your program doesn't get back much descriptive information about the devices. Because cybercreeps.
If your user's devices cannot handle the constraints you present to .getUserMedia(), you'll get "constraint violation" in the error.message string. The kinds of device constraints you can violate are things like
{video: { width:{exact:1920},
height:{exact:1080}}}
Avoiding exact in your constraints reduces the chance of constraint violations. Instead you can give something like this.
{video: { width:{min:480, ideal: 720, max: 1920},
height:{min:270, ideal:1280, max: 1040}}}
Other errors are probably more specific to a particular machine and browser. In practically all cases the thrown error object contains an explanatory error.message.