 Hey, everyone. I'm back again to talk about building my camera. This time I want to talk about actually taking control of the device camera and taking pictures with it. I already talked about how you get a hold of an image when you don't have access to the camera. So here we don't have to worry quite so much about compatibility, except to make sure that we use feature detection to disable this feature where it isn't available. As it happens, though, the release of Safari 11, the latest stable version of all the major browsers now actually supports the GetUserMedia API, which is the core feature that we need. So let's have a look at how we handle all of this. As I said, one of the first things we need to do is feature detection. So I have a Constance file here, which just has a couple of things. So I'm checking for the image capture object in the window. I'm checking for the media devices object in Navigator. We'll use those to determine that these two APIs actually exist. So I've extracted all of the camera related code for my app into this helper class that I've just called CameraHelper. This is gonna handle all of the interaction with the device so that the UI layer can just concentrate on how to present this information to the user. So one of the first things we need to do is if the feature is available in the browser, it doesn't actually mean we've got a camera. So we need a way to let the UI layer determine what cameras are available. So that's what this method is for, the GetCameras method. If the media devices API is available, then we'll use that, the Navigator media devices enumerate devices method to get a hold of all of the media devices that are attached to our phone or laptop or whatever. But this will include video cameras as well as microphones. So we then filter this to get only devices that are video input devices. And then we return that. And if this API isn't available, then GetCameras will just return an empty array. Once the UI knows that there is a camera available, it can call us to say that it wants to start a stream and it can pass in a device ID. Now this device ID comes from the objects that were returned from GetCameras. And this allows the UI to say that it specifically wants one of those cameras. So this allows for UI features like switching between the front-facing and rear-facing cameras, or just to pick a known camera from a dropdown, for example. Then when we actually want to get the data from the camera, we need to use the navigator.mediodivices.getUserMedia call to get a stream. Now there's a bit of terminology here which is a little bit confusing. There's a few overloaded words. So start off with a stream is just a bunch of media information. And it will consist of tracks, one or more tracks. And those tracks could be video or audio. So in theory, a stream can have multiple video tracks and multiple audio tracks. In this case, we're expecting to get just video and I'll say why in a moment. And we should just have one. So we have a stream and we have a track and the track is the first video track. Now for each of these streams and tracks, there are things called constraints, settings and capabilities. A stream has both constraints and capabilities. The browser has capabilities and a track has capabilities, constraints and settings. And then when we want to actually take a photo, if we're using the image capture API, that again takes another settings object. So I apologize if you get confused with constraints and capabilities and settings. There are several things called that. That's just the way the API is. So the first place that we're going to use this is in our actual get user media call, we pass in constraints for the stream. This is our way of telling the browser what kind of camera we want. So most of our constraints are defined as a constant here at the top of the file. You can see that we say that we want video. This is why we're not gonna get any audio because the audio property is by default false. So we're only saying we want video. We're not setting the device ID here, but we set it in the function. I'll show that in a sec. We say that we want the facing mode to be either user, that is the camera is facing the user. It's a selfie camera or environment. It's facing away from the user into whatever you're looking at. And we say that the width and height should ideally be, this is 1080p HD resolution. Now with these constraints, I could have said that the facing mode was just user. I could have said I only want selfie mode or I could have said that I want the height, the width and height to be exactly 1080p resolution. But if these constraints can't be satisfied, then you won't get any stream at all. So if I ran this with exact here or having the just user on my laptop here, this would actually fail because my laptop will only give me 720p resolution out of the camera and it only supports an environment camera. I mean, technically it's facing the user, but it describes it as an environment camera. So we're using whatever device ID was passed in from the UI layer to change the constraints for the video so that when we call get user media, we're getting a particular camera. So that's the initial constraints for get user media. We also have various settings we can put on the stream once it's going. So once our stream is going, we pull out the first video track of that stream and we store it away. We can now set options, set constraints on the track. When we want to set an option on a track, we need to make sure that it's actually supported. So there are two things that we need to look at. One is at the top of the file here, we have this supported constraints object which we're setting with navigator.media devices dot get supported constraints. Assuming that we support the media devices API, that is. This will tell us which constraints the browser supports. So these are the ones that the browser knows about. It's things like white balance and exposure, brightness, zoom, focus distance, things like that. These are the things that we can set to change how the camera is physically operating. What kind of image that we're getting back from the camera. And this is real time. This is affecting the video feed that is coming from the camera. So now we know which constraints the browser supports. We also need to take into account which constraints the track that we currently have supports because it might be different. For example, on my phone, the selfie camera doesn't have a flash. It doesn't support zoom or anything like that. Whereas the environment camera does have a flash. So we can, on that one, we'll have a different set of constraints, sorry, a different set of capabilities that we can apply constraints to. So if we want to get the available capabilities for the track, that's another API call. If we are currently dealing with the track because we might not actually have started our stream and if that track supports the get capabilities method because this is relatively new method. So this is a feature detection. If it supports both of those things then we call that function and it gives us the list of capabilities for the track. This is the same kind of object as the supported capabilities for the browser only now we know that specifically this track also supports them. So when we want to change a constraint on this video feed, we need to check that it is a supported constraint. So we check in our supported constraints that it exists and that it is true. We then get the capabilities for the current track and if that's also true, then we'll make a note of that. We'll set in our track constraints object that's recording what constraints we want that this particular value is set to this name. And then we'll apply all the constraints that we've got. The constraints object is just a little bit weird instead of being an object with keys. It's an object with a single key, which is called advanced and that's an array and inside that you put in more objects. So because you can't directly map things because you could have multiple entries for the same constraint. Oh, we just have to, I'm just building this array here. And then we actually apply those constraints to the track. With newer browsers, we can actually use an API called the image capture API. And this lets us direct the control actually taking a photo. But we still need to support older browsers that don't support this. So there is a fallback way which involves using canvas. So if we support image capture, we do one thing but let's just quickly have a look at the fallback case. So we create a canvas and we set its width to the same as the video that we're getting. And then we draw the video into the canvas. So that would just draw the current frame into the canvas. And then we can use the helper that I talked about last time to turn that canvas into a blob, which we can then store or manipulate or whatever else we want to do. One of the problems with this is that GetUserMedia itself for the video streams only supports up to HD resolution. And obviously if you have a modern mobile phone, your phone is probably much better than 4K resolution when it actually takes photos. So this is why we want to use the image capture API. So if the image capture API is supported, then we take that stream and we're going to get out of its track and we're going to create this image capture object for that track. And then the simple thing that we can do is we can say the capture.take photo that will return us a photo from the camera. And this, because we're specifically telling it to take a photo, we use the full resolution of the camera to get us that photo. So this is already a huge improvement. However, if we have a modern browser that allows us to get the capabilities of the camera, we can also set some additional features. So here, you see I'm checking this photo capabilities object and I skipped over it initially, but in the start stream, if image capture is supported, I set the photo capabilities object to be the return result of get photo capabilities on an image capture API for the track. Now, these photo capabilities, it's a much smaller list of things. It's just something called fill light mode. It's red-eye reduction and then image height and image width. In my app, I don't actually want to change the image height and image width. I want the full resolution. But the other two are pretty interesting. So if this track supports fill light mode, that's firing the flash. So the options for fill light mode when we actually take a photo, assuming it's supported will be either auto, off or on. So on meaning always fire the flash. Auto meaning do it depending on the light levels, whatever the camera would normally do. Similarly for red-eye reduction. So the way we do that is that in take photo, if we have this photo capabilities object and the fill light mode includes whatever we're trying to set the flash to, then set the settings fill light mode to be that. And similarly, if the photo capabilities red-eye reduction is controllable, then we'll allow whatever our red-eye reduction setting is to be set there. And red-eye reduction and flash are just public properties on the camera helper so that they can be set from the UX layer whenever. So then we can pass that settings object to take photo and it will fire the flash if we arrest it to, use the red-eye reduction, it will do whatever we want. So that's settings capabilities and constraints for streams, tracks and the taking a photo. Hopefully that's all clear now. Yeah, there we go. Proper access to all of the features of the camera. Thanks very much for watching and until next time, happy coding. Thanks for watching. If you'd like to see more of our videos, click here and see you again. Cheers.