2019/8/10 追記
ここに掲載しているコードでは、iOS12や、Android 9 でカメラを起動できなくなっています。
iOS12等に対応したコードを、こちらの記事に掲載しました。
今までに2回、サイトからのQRコード読み取りについて書きましたが、今回が完全版となります。
どのあたりが完全版かというと、ビデオカメラでの読み取りに対応していない iOS10 以下の iPhone や、iPhone で Safari 以外のブラウザを使用した場合でも、QRコードを読み取れるようにしたものです。ビデオカメラでの読み取りに対応していない場合、通常の(静止画の)カメラで読み取ることになります。
このコードを実行するには、以下のライブラリが必要となります。
LazarSoft/jsqrcode https://github.com/LazarSoft/jsqrcode
実際に試してみようと思う方は、以下のサイトを参考にして、ライブラリを改変する必要があります(または、改変したライブラリをダウンロードします)。
ネイティブアプリ不要!モバイルWebサイトにQRコードリーダーを実装する方法
なお、実際に動作するサンプルを以下に置いています。
https://frostmoon.net/r-test/
注意事項
- QRコードをビデオカメラで読み取ることのできるスマホは、Android、iOS11以上のiPhoneです。
- iOSでビデオカメラから読み取ることができるブラウザは、Safariだけです。
- PCでもビデオカメラで読み取ることができますが、対応するカメラが付いている必要があります。
- カメラからの読み取りをインターネット上のサイトで行うには、サイトがSSL化(httpsから始まるURL)されている必要があります。
上記サンプルページのソースコード全文は、以下になります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, intial-scale=1, mininum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style>
.qrbtn {
background-color: red;
color: #fff;
margin: 3em auto;
width: 100%;
height: 2em;
padding: 20px;
border-radius: 9px;
}
</style>
</head>
<body>
<h1>QRコード読み取りデモ</h1>
<div id="video-input">
<div style="text-align: center">
<video id="video" style="width: 80%; height: auto;" autoplay playsinline></video>
</div>
<img id="img" />
<div style="display: none">
<canvas id="canvas"></canvas>
</div>
<div>
<button type="button" id="changeCamera">前面/背面 切り替え</button>
</div>
<div style="margin-top: 3em">
<p style="font-weight: bold; margin-bottom: 5px">アクティブなカメラ</p>
<p id="active-camera" style="margin-top: 5px"></p>
</div>
</div>
<div id="photo-input" style="display: none">
<div style="text-align: center">
<label for="input-qr" class="qrbtn">QRコードを読み取る</label>
<input type="file" id="input-qr" accept="image/*" capture="environment" tabindex="-1" style="display: none" onchange="openQRCamera(this);">
</div>
</div>
<div style="margin-top: 3em">
<label for="qr">読み取ったQRコード<br></label>
<input type="text" id="qr" value="" style="width:100%">
</div>
<div style="margin-top: 3em">
<button type="button" id="toCamera">通常カメラでの読み取りに切り替え</button>
<button type="button" id="toMovie" style="display: none">ビデオカメラでの読み取りに切り替え</button>
</div>
</body>
<script src="./js/qr/qr_packed.js" charset="UTF-8"></script>
<script type="text/javascript">
var localStream = null;
var ios = /iPad|iPhone|iPod/.test(navigator.userAgent);
var devices;
var activeIndex;
var iosRear = false;
var postCount = 0;
function decodeImageFromBase64(data, callback) {
qrcode.callback = callback;
qrcode.decode(data);
}
function decode() {
if (localStream) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var h;
var w;
w = video.videoWidth;
h = video.videoHeight;
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);
ctx.drawImage(video, 0, 0, w, h);
decodeImageFromBase64(canvas.toDataURL('image/png'), function (decodeInformation) {
var input = document.getElementById('qr');
if (!(decodeInformation instanceof Error)) {
input.value = decodeInformation;
}
});
}
}
function openQRCamera(node) {
var reader = new FileReader();
reader.onload = function() {
node.value = '';
qrcode.callback = function(res) {
if (res instanceof Error) {
alert('QRコードが見つかりませんでした。QRコードがカメラのフレーム内に収まるよう、再度撮影してください。');
} else {
var qr = document.getElementById('qr');
qr.value = res;
}
};
qrcode.decode(reader.result);
};
reader.readAsDataURL(node.files[0]);
}
window.onload = function() {
var modeChange = function(mode) {
if (mode === 'camera') {
document.getElementById('video-input').style.display = 'none';
document.getElementById('photo-input').style.display = 'block';
document.getElementById('toCamera').style.display = 'none';
document.getElementById('toMovie').style.display = 'block';
} else {
document.getElementById('video-input').style.display = 'block';
document.getElementById('photo-input').style.display = 'none';
document.getElementById('toCamera').style.display = 'block';
document.getElementById('toMovie').style.display = 'none';
}
};
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
modeChange('camera');
return;
}
// カメラ情報取得
navigator.mediaDevices.enumerateDevices()
.then(function(cameras) {
var cams = [];
cameras.forEach(function(device) {
if (device.kind === 'videoinput') {
cams.push({
'id': device.deviceId,
'name': device.label
});
}
});
devices = cams;
changeCamera(devices.length - 1);
})
.catch(function (err) {
alert('カメラが見つかりません');
});
var video = document.getElementById('video');
var startReadQR = function() {
setInterval('decode();', 500);
};
var changeCamera = function(index) {
if (localStream) {
localStream.getVideoTracks()[0].stop();
}
activeIndex = index;
iosRear = !iosRear;
var p = document.getElementById('active-camera');
p.innerHTML = devices[activeIndex].name + '(' + devices[activeIndex].id + ')';
setCamera();
};
var setCamera = function() {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || windiow.navigator.mozGetUserMedia;
window.URL = window.URL || window.webkitURL;
var videoOptions;
if (ios) {
videoOptions = {
facingMode: {
exact: (iosRear) ? 'environment' : 'user'
},
mandatory: {
sourceId: devices[activeIndex].id,
minWidth: 600,
maxWidth: 800,
minAspectRatio: 1.6
},
optional: []
};
} else {
videoOptions = {
mandatory: {
sourceId: devices[activeIndex].id,
minWidth: 600,
maxWidth: 800,
minAspectRatio: 1.6
},
optional: []
};
}
navigator.getUserMedia(
{
audio: false,
video: videoOptions
},
function (stream) {
if (ios) {
video.srcObject = stream;
} else {
// 2019-07-21修正
// video.src = window.URL.createObjectURL(stream);
// 以下のコードでないと動かなくなったようです
video.srcObject = stream;
}
localStream = stream;
},
function (err) {
}
);
startReadQR();
};
document.getElementById('toCamera').addEventListener('click', function() {
modeChange('camera');
});
document.getElementById('toMovie').addEventListener('click', function() {
modeChange('video');
});
document.getElementById('changeCamera').addEventListener('click', function() {
var newIndex = activeIndex + 1;
if (newIndex >= devices.length) {
newIndex = 0;
}
changeCamera(newIndex);
}, false);
};
</script>
</html>
こんにちは。貴重なコードありがとうございます。
iOS12(iPad)からビデオカメラ機能を確認したところ、
サンプルサイト・コードを移植して自作したサイトともに、動かない状態です。
navigator.mediaDevices.enumerateDevicesがSafariに対応していないようですが、関係あるでしょうか。
iOS12のSafariの場合、video のサイズが640×480、または1280×720のどちらかしか動作しないとか、frameRate: 15 を指定しなければならないとかの情報が散見されます。
おそらくは、navigator.mediaDevices.enumerateDevices を実行する前に、navigator.mediaDevices.getUserMedia で 、iOS12 の Safari だけの適切なオプションを指定する必要があると思います。
今、いろいろと試してみているところではありますが、なかなかうまく動作してくれません。
新バージョンでの仕様変更によるものなのですね。私も試しておりますが、なかなかうまくいきませんでした。
既に試されてるとのことで、進展を祈ります。(私の方でも、参考にさせていただきます。)