instascan icon indicating copy to clipboard operation
instascan copied to clipboard

IOS safari 11.4- Back camera opening issue

Open SopraUser opened this issue 5 years ago • 45 comments

Hi, I am using Instascan and facing issue with ios safari. No error is given by instascan but it is not opening back camera. Every time it is opening front camera. Same code is working fine with Android(Chrome, Firefox).

var self;
isQRScannerInitialised = false;
function testQrCode(textBoxId,DivWidth){
	self = initialiseQRScanner(DivWidth);
	
	self.scanner.addListener('scan', function (content, image) {
		 if(textBoxId == $("#hiddenTextBox").val()){
				audio.play();
				var code = content;
		        $("#"+textBoxId).val(code);
		    	isQRScannerInitialised = false;
		    	
			}
		    
    }, false);
    
}

function initialiseQRScanner(DivWidth){
	
	self = this;
	
    if(isQRScannerInitialised == false){
    	
    	var tempVideo = document.getElementsByTagName("video")[0];
    	tempVideo.width=DivWidth;
    	tempVideo.height=480;
    	
    	self.scanner = new Instascan.Scanner({ video: document.getElementsByTagName("video")[0],mirror:false, scanPeriod: 1});
    	
		Instascan.Camera.getCameras().then(function (cameras) {
		    self.cameras = cameras;
		    if (self.cameras.length > 0) {
		    	 if(cameras[0].name.match(/back/) || cameras[0].name.match(/Back/)){
		    		self.activeCameraId = cameras[0].id;
			    	self.scanner.start(cameras[0]);
		    	} else if(cameras[1].name.match(/back/) || cameras[1].name.match(/Back/)){
		    		self.activeCameraId = cameras[1].id;
			    	self.scanner.start(cameras[1]);
		    	}
		    	isQRScannerInitialised = true;
		    	
		    } else {
		    	
		    	alert('No cameras found.');
		    	isQRScannerInitialised = false;
		    	return;
		    }
		  }).catch(function (e) { 
				isQRScannerInitialised = false;
				alert("QR error name:-"+e.name + " & QR error message:-"+e.message); console.error(e); 
			});
		
	}
	return self;
}

SopraUser avatar Sep 27 '18 06:09 SopraUser

Do you have access to the apple device? Do you know if the demo is working?

kibagami-jubei avatar Sep 27 '18 15:09 kibagami-jubei

It looks like this has been having issue with iOS safari but I've read that people were able to overcome this issue by adding meta tags. Just dig around this forum and you'll see. I'm working on something similar, so I'm interested. What's holding me back is that I do not have a device readily available.

kibagami-jubei avatar Sep 27 '18 15:09 kibagami-jubei

I was trying the demo out on an iPhone, it did not work.

kibagami-jubei avatar Sep 28 '18 20:09 kibagami-jubei

Yeah, been working on this, I can't get this to work. Trying to go back and re-read threads. I got the front-facing camera to work by adding the latest web RTC adapter and adding the playsinline attribute to the video tag. It can read the cameras but does nothing when swapping to the back camera.

kibagami-jubei avatar Sep 28 '18 23:09 kibagami-jubei

i had added playsinline attribute inside video tag and mentioned meta tag also.

SopraUser avatar Sep 29 '18 05:09 SopraUser

i had added playsinline attribute inside video tag and mentioned meta tag also.

Someone had actually fixed this in one of the threads. I'm trying that out right now but have to wait for someone with an iPhone to come to work.

kibagami-jubei avatar Oct 01 '18 15:10 kibagami-jubei

Hey, I got this to work but there's still the issue, despite choosing cameras, the default will always be the back rear camera. So if you're in the need to pick the front camera, you're screwed (but seriously, who's going to try and scan with the front facing camera).

kibagami-jubei avatar Oct 04 '18 22:10 kibagami-jubei

Here's what I did!

I added the meta tag. <meta name="apple-mobile-web-app-capable" content="yes">

Added this attribute: <video id="scanner" class="video-back" playsinline></video>

Added this JS file: <script type="text/javascript" src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

And then packaged this to release: https://github.com/JoseCDB/instascan/tree/ios-rear-camera. Not sure if you know how to use Gulp with node.js but it's easy. I Iearned it in 15 minutes! I'm using this persons version of Instascan. He knew of the issue and was able to fix it. Hope this helps. Good luck.

kibagami-jubei avatar Oct 04 '18 22:10 kibagami-jubei

I have used meta tag, video tag and adapter-latest.js. And i am using instscan.min.js and don't know how can use the ".js - scanner.js - camara.js" separately.

SopraUser avatar Oct 05 '18 08:10 SopraUser

I have used meta tag, video tag and adapter-latest.js. And i am using instscan.min.js and don't know how can use the ".js - scanner.js - camara.js" separately.

You're supposed to have installed node.js. There's a command prompt that packages all of those files together, the final output would be that minified JS file of the instascanner.

kibagami-jubei avatar Oct 05 '18 15:10 kibagami-jubei

This is a great product and it has been really useful, so thank you for making it wonderful. Here is the issue with this instascan from my research:

  • The class Camera when calling the getCameras returns a collection of Camera objects that are created from the MediaDeviceInfo objects collected through a call to navigator.mediaDevices.enumerateDevices(). These are NOT MediaStream objects which contain the actual stream from the camera used when starting the scanner. These MediaDeviceInfo objects contain id and name properties and nothing more. They are used to create an array of Camera objects.

When you call scanner.start(camera), you are passing in a Camera object that will call the camera.start() method on the camera.

  • The camera.start() method THEN calls the navigator.mediaDevices.getUserMedia(contraints), which returns an Arrary of MediaStream objects based on the given constraints listed in the method.

  • scanner.start() calls _scanner.enableScan(camera) giving it the First MediaStream object returned from the call above in camera. It sets the

                    **This Part Is the Disconnect and what is messing it all up!**
    

The Constraint Issue:

The Camera object passed in uses its id in the constraints as "mandatory" for the searching of the devices. If it is not found, no video will show. Also other "mandatory" constraints are set.

Different devices DO NOT SUPPORT the constraints used! Run this link on the device you want to use and it will tell you what Constraints it supports: https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints

Run this link on the device you want to use and enter a constraint. It will tell you if it is supported: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSupportedConstraints/deviceId

Ipad/Iphone/IOS supported Constraints:

aspectRatio, facingMode, deviceId*** , height, width, aspectRatio and others.

_When I tested this, deviceId will be ignored as a constraint. Try this out in the above link for constraints and you will see it sets the deviceId = "". When I grabbed it in javascript and displayed it, the iphone always returned a blank deviceId.

So, To grab the back camera on the Iphone, you have to use the facingMode and NOT use deviceId.

Android supported Constraints:

deviceId, facingMode***, aspectRatio, height, width, and many others.

I tested this, on a samsung tablet and android phone. The facingMode defaulted to the "user" front camera. I experimented with this code and found that for some reason yet discovered, the facingMode for android is not working. Yet it works in the above link. I am going to continue to investigate that portion and possibly fix this code to work properly with facingMode and android.

** ANSWER TO ANDROID ISSUE* ** The Gulp build of this project includes the webrtc-adapter version ^1.4.0 which messes up the facingMode on Android Chrome. Looking through the code it actually deleted the facing mode.

  • To fix in this project - in index.js, remove the line 2 require('webrtc-adapter'); Then follow the build procedures below. When I tested on a tablet and 4 different android phones on Chrome it all worked again to grab the back camera.

To grab the back camera on the Android with this instascan code, you can use the deviceId. Make sure deviceId is a video constraint like

video: {
  devideId: this.id
}

Camera Constraint Code:

Here is the Code from the project that sets the constraints and gets the MediaStream objects The reason why this code doesn't work properly is that sourceId of the video constraint is not supported for Android or Iphone. This has to be changed to deviceId. Also all of the mandatory items need to go if they are not what your device handles.


async start() {
    let constraints;
    var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

    if (iOS) {
      constraints = {
        audio: false,
        video: {
          facingMode: 'environment',
          mandatory: {
            sourceId: this.id,
            minWidth: 600,
            maxWidth: 800,
            minAspectRatio: 1.6
          },
          optional: []
        }
      };
    } else {
      constraints = {
        audio: false,
        video: {
          mandatory: {
            sourceId: this.id,
            minWidth: 600,
            maxWidth: 800,
            minAspectRatio: 1.6
          },
          optional: []
        }
      };
    }

    this._stream = await Camera._wrapErrors(async () => {
      return await navigator.mediaDevices.getUserMedia(constraints);
});

How to modify InstaScan and minify it:

to get this and change it, I have been using VSCode.

  1. Install Node.js and vsCode.

  2. at a terminal in the directory you want it to download run: git clone https://github.com/JoseCDB/instascan.git

  3. at the terminal cd to the instascan directory: cd instascan

  4. change the javascript and run gulp to release the code: gulp release

  5. This will give you a new minified instascan.min.js item in the dist folder.

I am going to figure out this Android issue hopefully this weekend. I hope this gives you enough insight for any future projects with this awesome library.

apchandler avatar Dec 01 '18 01:12 apchandler

Hi, this should be worked well. First, add adapter.js from https://github.com/webrtc/adapter.

After that clone this: git clone https://github.com/quocthai95/instascan.git Run: npm i to install necessaries

Use gulp to release final code: gulp release

I've checked on Iphone 6 plus (iOS 11.4) & Iphone 8 plus (iOS 11.3) and it worked well. You can swap cameras normally.

Hope you can solve your problem.

quocthai95 avatar Dec 14 '18 03:12 quocthai95

Hi, this should be worked well. First, add adapter.js from https://github.com/webrtc/adapter.

After that clone this: git clone https://github.com/quocthai95/instascan.git Run: npm i to install necessaries

Use gulp to release final code: gulp release

I've checked on Iphone 6 plus (iOS 11.4) & Iphone 8 plus (iOS 11.3) and it worked well. You can swap cameras normally.

Hope you can solve your problem.

When installing necessaries I get "12 vulnerabilities (1 low, 5 moderate, 6 high)". Some "$ npm install --save-dev [package-name]" commands seems to resolve these vulnerabilities, but I cant build it due to the following exception:

$ gulp release assert.js:351 throw err; ^ AssertionError [ERR_ASSERTION]: Task function must be specified at Gulp.set [as _setTask] (C:\Users\John\Downloads\instascan-ios-rear-camera\instascan-ios-rear-camera\node_modules\undertaker\lib\set-task.js:10:3)

How do I get on from here, I'd really like to try this build. It works so well with my android phone?

/John

johnatitide avatar Jan 07 '19 09:01 johnatitide

Hi @johnatitide , Did you install gulp latest version (4.0.0) ? If so, please try to restrict it to 3.9.1. FYI: https://github.com/ampproject/docs/issues/793#issuecomment-354836162 Or can you provide which commands u did to build it.

quocthai95 avatar Jan 07 '19 10:01 quocthai95

Hi @quocthai95

Building works with the following commands: git clone https://github.com/quocthai95/instascan.git npm install [email protected] npm i gulp release

I can now switch camera in Safari on iPad and iPhone. Nice.

johnatitide avatar Jan 08 '19 11:01 johnatitide

Any chance the constraint fixes will be merged back to schmich's repo?

vladimirmoushkov avatar Feb 20 '19 14:02 vladimirmoushkov

Any chance the constraint fixes will be merged back to schmich's repo?

I've created new pull request. Wait for accepting ^^

quocthai95 avatar Feb 21 '19 04:02 quocthai95

@quocthai95 hi, ive followed your steps from https://github.com/schmich/instascan/issues/182#issuecomment-447198290.

i was wondering, im getting front camera as default on my android chrome. wondering how can i make back camera as default? do you have an example code?

fariskas avatar Apr 01 '19 04:04 fariskas

@fariskas , Use regexp to test whether it matches /back/ then start it, so you can have back camera as default.

`Instascan.Camera.getCameras().then(function (cameras) { if(cameras[0].name.match(/back/i)){ scanner.start(cameras[0]); } else if(cameras[1].name.match(/back/i)){ scanner.start(cameras[1]); } }

quocthai95 avatar Apr 03 '19 03:04 quocthai95

This might be useful: I am using a for-loop to find "back" in cameras[i].name but I was unsuccessful and unable to select the back camera on iPad until I wrote cameras[i].name to console.log and found out that I had to look for "achterzijde" (which is "back" in Dutch). So if you are unsuccessful trying to select the back camera in iOS, try translating "back" or write cameras[i].name to console.log to find out what is the back camera's name.

diboma avatar Apr 20 '19 05:04 diboma

I can confirm the demo only works with the front facing camera. This is with Safari on iOS 12.2

dgtlrift avatar Apr 27 '19 20:04 dgtlrift

Has anyone figured out how to use the back camera with Safari on iOS 12+?

nicoeat614 avatar Jun 07 '19 19:06 nicoeat614

Has anyone figured out how to use the back camera with Safari on iOS 12+?

I have been using a modified version of this library for about 7 months now on over 30+ types ios and android phones and tablets. Look at the comments I left above about a modified version to get any camera from the os you are working with. The comments keep talking about adding the webrtc after, which i found to be the whole issue of this library.

I removed webrtc from the project altogether and recompiled it. webrtc is not needed any more with this code when transpiled to es5 or above. The use of webrtc in the library and how it was coded did not allow the correct camera to be retrieved.

I have included the fixed and transpiled regular and minified js files that I have been using for a while now. I use it for school attendance with QrCodes across any phones students and teachers have, which have been about 30+ models at this point. It can grab any camera.

I hope this helps.

Back Camera code

scanner = new Instascan.Scanner({ video: video, scanPeriod: 4, mirror:false })
                .then(handleSuccess)
                .catch(handleError);
             //Start scanning
             scanner.addListener('scan', foundCode);

             Instascan.Camera.getCameras().then(function (cameras) {
                 if (cameras.length > 0) {
                     scanner.start(cameras[0]);
                 }
                 else {
            ...        
                 }
             }).catch (function (e) {
              ...  
             });

instascan.min.zip

https://github.com/schmich/instascan/issues/182#issuecomment-443388022

apchandler avatar Jun 07 '19 20:06 apchandler

This is a great product and it has been really useful, so thank you for making it wonderful. Here is the issue with this instascan from my research:

  • The class Camera when calling the getCameras returns a collection of Camera objects that are created from the MediaDeviceInfo objects collected through a call to navigator.mediaDevices.enumerateDevices(). These are NOT MediaStream objects which contain the actual stream from the camera used when starting the scanner. These MediaDeviceInfo objects contain id and name properties and nothing more. They are used to create an array of Camera objects.

When you call scanner.start(camera), you are passing in a Camera object that will call the camera.start() method on the camera.

  • The camera.start() method THEN calls the navigator.mediaDevices.getUserMedia(contraints), which returns an Arrary of MediaStream objects based on the given constraints listed in the method.
  • scanner.start() calls _scanner.enableScan(camera) giving it the First MediaStream object returned from the call above in camera. It sets the src element to this media stream object.
                    **This Part Is the Disconnect and what is messing it all up!**
    

The Constraint Issue:

The Camera object passed in uses its id in the constraints as "mandatory" for the searching of the devices. If it is not found, no video will show. Also other "mandatory" constraints are set.

Different devices DO NOT SUPPORT the constraints used! Run this link on the device you want to use and it will tell you what Constraints it supports: https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints

Run this link on the device you want to use and enter a constraint. It will tell you if it is supported: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSupportedConstraints/deviceId

Ipad/Iphone/IOS supported Constraints:

aspectRatio, facingMode, deviceId*** , height, width, aspectRatio and others.

_When I tested this, deviceId will be ignored as a constraint. Try this out in the above link for constraints and you will see it sets the deviceId = "". When I grabbed it in javascript and displayed it, the iphone always returned a blank deviceId.

So, To grab the back camera on the Iphone, you have to use the facingMode and NOT use deviceId.

Android supported Constraints:

deviceId, facingMode***, aspectRatio, height, width, and many others.

I tested this, on a samsung tablet and android phone. The facingMode defaulted to the "user" front camera. I experimented with this code and found that for some reason yet discovered, the facingMode for android is not working. Yet it works in the above link. I am going to continue to investigate that portion and possibly fix this code to work properly with facingMode and android.

** ANSWER TO ANDROID ISSUE* ** The Gulp build of this project includes the webrtc-adapter version ^1.4.0 which messes up the facingMode on Android Chrome. Looking through the code it actually deleted the facing mode.

  • To fix in this project - in index.js, remove the line 2 require('webrtc-adapter'); Then follow the build procedures below. When I tested on a tablet and 4 different android phones on Chrome it all worked again to grab the back camera.

To grab the back camera on the Android with this instascan code, you can use the deviceId. Make sure deviceId is a video constraint like

video: {
  devideId: this.id
}

Camera Constraint Code:

Here is the Code from the project that sets the constraints and gets the MediaStream objects The reason why this code doesn't work properly is that sourceId of the video constraint is not supported for Android or Iphone. This has to be changed to deviceId. Also all of the mandatory items need to go if they are not what your device handles.


async start() {
    let constraints;
    var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

    if (iOS) {
      constraints = {
        audio: false,
        video: {
          facingMode: 'environment',
          mandatory: {
            sourceId: this.id,
            minWidth: 600,
            maxWidth: 800,
            minAspectRatio: 1.6
          },
          optional: []
        }
      };
    } else {
      constraints = {
        audio: false,
        video: {
          mandatory: {
            sourceId: this.id,
            minWidth: 600,
            maxWidth: 800,
            minAspectRatio: 1.6
          },
          optional: []
        }
      };
    }

    this._stream = await Camera._wrapErrors(async () => {
      return await navigator.mediaDevices.getUserMedia(constraints);
});

How to modify InstaScan and minify it:

to get this and change it, I have been using VSCode.

  1. Install Node.js and vsCode.
  2. at a terminal in the directory you want it to download run: git clone https://github.com/JoseCDB/instascan.git
  3. at the terminal cd to the instascan directory: cd instascan
  4. change the javascript and run gulp to release the code: gulp release
  5. This will give you a new minified instascan.min.js item in the dist folder.

I am going to figure out this Android issue hopefully this weekend. I hope this gives you enough insight for any future projects with this awesome library.

I followed above steps then edit file camera.js (at line 29) as below facingMode: { exact: "environment" }, It work for me. Thank you @apchandler

glorynguyen avatar Jun 11 '19 07:06 glorynguyen

I am glad you got something to work for you! Thanks @glorynguyen for posting this fix too!

apchandler avatar Jun 11 '19 15:06 apchandler

Unfortunately these solutions do not work on iOS 13. It is simply blank (not even black). When remote debugging Safari, also no errors are shown in the console.

tinyoverflow avatar Aug 01 '19 10:08 tinyoverflow

Has anyone figured out how to use the back camera with Safari on iOS 12+?

I have been using a modified version of this library for about 7 months now on over 30+ types ios and android phones and tablets. Look at the comments I left above about a modified version to get any camera from the os you are working with. The comments keep talking about adding the webrtc after, which i found to be the whole issue of this library.

I removed webrtc from the project altogether and recompiled it. webrtc is not needed any more with this code when transpiled to es5 or above. The use of webrtc in the library and how it was coded did not allow the correct camera to be retrieved.

I have included the fixed and transpiled regular and minified js files that I have been using for a while now. I use it for school attendance with QrCodes across any phones students and teachers have, which have been about 30+ models at this point. It can grab any camera.

I hope this helps.

Back Camera code

scanner = new Instascan.Scanner({ video: video, scanPeriod: 4, mirror:false })
                .then(handleSuccess)
                .catch(handleError);
             //Start scanning
             scanner.addListener('scan', foundCode);

             Instascan.Camera.getCameras().then(function (cameras) {
                 if (cameras.length > 0) {
                     scanner.start(cameras[0]);
                 }
                 else {
            ...        
                 }
             }).catch (function (e) {
              ...  
             });

instascan.min.zip

#182 (comment)

This solves my problem, thank you very much.

paulpwo avatar Dec 20 '19 18:12 paulpwo

Whats the latest fix for this? I'we tried a few from the thread but i only seem to get the front camera. Don't know how to use Gulp or actually compile .js .git projects, i mainly code in C#/Asp.net Mvc so this is a bit new to me. apchandler's solution gives me errors. (Video not defined) Currently loading an "fixed" instascan.min.js for Android devices and his version for IOS, But i can't seem to get it to work.

dallebull avatar Jan 29 '20 08:01 dallebull

Whats the latest fix for this? I'we tried a few from the thread but i only seem to get the front camera. Don't know how to use Gulp or actually compile .js .git projects, i mainly code in C#/Asp.net Mvc so this is a bit new to me. apchandler's solution gives me errors. (Video not defined) Currently loading an "fixed" instascan.min.js for Android devices and his version for IOS, But i can't seem to get it to work.

Do you have the html video element present?

<div class="preview-container">
       <video id="preview"></video>
     </div>

When you run instascan you need to speficy a video element like:

var self = this;
    self.scanner = new Instascan.Scanner({ video: document.getElementById('preview'), scanPeriod: 5 });
    self.scanner.addListener('scan', function (content, image) {
      self.scans.unshift({ date: +(Date.now()), content: content });
    });
    Instascan.Camera.getCameras().then(function (cameras) {
      self.cameras = cameras;
      if (cameras.length > 0) {
        self.activeCameraId = cameras[0].id;
        self.scanner.start(cameras[0]);
      } else {
        console.error('No cameras found.');
      }
    }).catch(function (e) {
      console.error(e);
    });

apchandler avatar Jan 29 '20 23:01 apchandler

Yeah, i got it working with:

<video class="player" style="max-width:100%" id="preview" playsinline></video>

<script>
.....
  Instascan.Camera.getCameras().then(function (cameras) {
        if (cameras.length > 0) {
          scanner.start(cameras[0]);
        } else {
          console.error('No cameras found.');
        }
      }).catch(function (e) {
        console.error(e);
      });
</script>

Starting cameras[0] for Iphone/Ios Devices. And this for Android:

<script>
.....
  Instascan.Camera.getCameras().then(function (cameras) {
                if (cameras.length > 0) {
                    var selectedCam = cameras[0];
                    $.each(cameras, (i, c) => {
                        if (c.name.indexOf('back') != -1) {
                            selectedCam = c;
                            return false;
                        }
                    });
                    scanner.start(selectedCam);
                }
                else {
                    console.error('No cameras found.');
                }
              });
</script>

Seems like the problem was that i only checked if Request.UserAgent.Contains("ios") and not "iphone", so i ran the Android script for the Iphone as well.

dallebull avatar Jan 30 '20 09:01 dallebull