在庫管理に利用されているバーコード(JANコード)ってご存知ですよね?Quaggajsというライブラリでそのリーダーを簡単に実装できるらしいです。実際にスマホのWebブラウザで動くアプリを作ってみてハマったポイントをメモとして残しておきます。ライブラリの詳細は上記リンク先を参照ください。
#とりあえず作ってみる
とりあえずインストール。
$ npm install quagga
LiveStreamで起動する場合、次のコードで起動できます。
import Quagga from "quagga";
// LiveStreamを表示するvideoDOMを挿入したい親DOM
const camera = document.getElementById("camera");
// QuaggaJSをLiveStreamで起動する
start(camera);
function start(camera) {
// QuaggaJs初期化のConfig
const config = {
inputStream: {
type: "LiveStream",
target: camera,
constraints: {
// MediaStreamConstraintsを参照
audio: false,
video: {
facingMode: "environment",
},
},
},
decoder: {
// バーコードのデコーダー
readers: ["ean_reader"], // EANコードはJANコードの海外での名称
},
};
// QuaggaJSを起動
Quagga.init(
config,
// カメラの起動後に実行
async (err) => {
if (err) {
console.log(err);
} else {
// バーコード読み取りをスタート
Quagga.start();
}
}
);
// コード読み取り完了時の処理
Quagga.onDetected((result) => {
// 読み取れたコードをコンソールに表示
console.log(result.codeResult.code);
// カメラを停止
Quagga.stop();
});
}
このコードでWindows, Mac, iPhoneなどでは動きました。(localhost以外の場合はHTTPS通信に注意!)
少し古いライブラリなのでちょっとドキュメント読んだりするのに手間取るかもしれませんが、概ねこんな感じで実装できます。
#ハマったポイント
###HTTPSじゃないと動かない
ローカル開発しているときにこれでハマりました。スマホで起動したい時には、HTTPS通信じゃないとそもそもカメラが起動できないのを失念してました。https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia
###マルチカメラのレンズ選択
こいつが曲者で、一部のAndroidスマホではデフォルトのレンズが望遠レンズになるらしく、カメラ起動の際にMediaDeviceを選んでやる必要があります。QuaggaJsではカメラ選択はサポートされてないっぽい(やり方あったら教えてください!)ので、カメラは自分で起動する必要があります。この場合は次のコードで起動できます。
import Quagga from "quagga";
// LiveStreamを表示するvideoDOMを挿入したい親DOM
const camera = document.getElementById("camera");
const video = document.createElement("video");
video.autoplay = true;
video.playsInline = true;
camera.appendChild(video);
// カメラを起動しスナップショットをQuaggaJSで読み取る
multiCameraStart(camera, video);
async function multiCameraStart(camera, video) {
// 使用したいカメラを選択
const devices = await navigator.mediaDevices.enumerateDevices();
// カメラを選ぶ。Androidでは"camera2 0"のラベルがついているものが標準らしい。
const videoDevices = devices.filter(
(v) =>
v.kind == "videoinput" && v.label.indexOf("camera2 0, facing back") >= 0
);
if (videoDevices.length == 0) {
// 該当のカメラがないならQuaggaJsをLivestreamで起動
start(camera);
} else {
// 該当のカメラを起動
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
deviceId: videoDevices[0].deviceId,
},
});
// videoにstreamを入力
video.srcObject = stream;
// ビデオトラックを取得
const track = stream.getVideoTracks()[0];
// 読み取り用canvasの作成
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const trackSettings = track.getSettings();
canvas.width = trackSettings.width;
canvas.height = trackSettings.height;
// 0.3秒ごとにcanvasにスナップショットをとり、QuaggaJsでバーコードを読み取る
let interval = window.setInterval(() => {
// canvasにスナップショットを作成
ctx.drawImage(video, 0, 0, trackSettings.width, trackSettings.height);
// canvasの画像をblobに変換
canvas.toBlob(
(blob) => {
if (blob) {
// blobをurlに変換
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = () => {
// quaggaを起動
const config = {
decoder: {
readers: ["ean_reader"],
multiple: false, // 精度向上のため複数入力は解除
},
locate: true, // 精度向上のためlocateオプションを設定
src: reader.result,
singleChannel: false,
};
// QuaggaJsを画像読み取りで起動
Quagga.decodeSingle(config);
};
}
},
"image/jpeg",
1
);
}, 300);
// 読み取りに成功した場合の設定
let code;
let count = 0;
Quagga.onDetected((result) => {
// 3連続で同じコードを得られるまで検知
if (code == result.codeResult.code) {
count++;
} else {
// 前回のコードと違った場合
count = 0;
code = result.codeResult.code;
}
// 3連続で同じコードを得られた場合はカメラを停止
if (count >= 3) {
// コンソールにコードを表示
console.log(code);
// 画像の読み取りを停止
window.clearInterval(interval);
// カメラを停止
const tracks = stream.getVideoTracks();
for (let i = 0; i < tracks.length; i++) {
tracks[i].stop();
}
}
});
}
}
順番に解説していきます。
####カメラデバイスの起動
// 使用したいカメラを選択
const devices = await navigator.mediaDevices.enumerateDevices();
// カメラを選ぶ。Androidでは"camera2 0"のラベルがついているものが標準らしい。
const videoDevices = devices.filter(
(v) =>
v.kind == "videoinput" && v.label.indexOf("camera2 0, facing back") >= 0
);
mediaDevices.enumerateDevices
でメディアデバイスを取得し、その中から適切なデバイスを選んでいます。Androidで調べてみるとラベルにcamera2 0
が含まれているものが標準的なレンズみたいだったので、これを選んで起動しています。devices
のカメラデバイスをそれぞれ一度起動してみるのが良いかもしれません。なお、ブラウザのカメラ使用を許可していないとdevices
のラベルが正確に読みとられないので、事前にカメラ使用を許可させる必要があることにも注意してください!
// 該当のカメラを起動
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
deviceId: videoDevices[0].deviceId,
},
});
// videoにstreamを入力
video.srcObject = stream;
先ほど選んだカメラデバイスのデバイスIDを設定して起動します。なお、全体のコードでは該当のビデオデバイスが取得できなかった場合は仕方なくQuaggaJsをLiveStreamで起動しています。
####スナップショットを作成しBlobURLに変換してQuaggaJsでデコード
// ビデオトラックを取得
const track = stream.getVideoTracks()[0];
// 読み取り用canvasの作成
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const trackSettings = track.getSettings();
canvas.width = trackSettings.width;
canvas.height = trackSettings.height;
ここでは画像を描画するcanvas要素を作成しています。
// 0.3秒ごとにcanvasにスナップショットをとり、QuaggaJsでバーコードを読み取る
let interval = window.setInterval(() => {
// canvasにスナップショットを作成
ctx.drawImage(video, 0, 0, trackSettings.width, trackSettings.height);
// canvasの画像をblobに変換
canvas.toBlob(
(blob) => {
if (blob) {
// blobをurlに変換
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = () => {
// quaggaを起動
const config = {
decoder: {
readers: ["ean_reader"],
multiple: false, // 精度向上のため複数入力は解除
},
locate: true, // 精度向上のためlocateオプションを設定
src: reader.result,
singleChannel: false,
};
// QuaggaJsを画像読み取りで起動
Quagga.decodeSingle(config);
};
}
},
"image/jpeg",
1
);
}, 300);
ここでは適当なインターバルで先ほどのcanvas要素にvideo要素のスナップショットを描画しています。さらに、描画した画像データをblobに変換してQuaggaJsに渡し、画像解析してデコードしてもらいます。
####デコードが完了した場合の処理
// 読み取りに成功した場合の設定
let code;
let count = 0;
Quagga.onDetected((result) => {
// 3連続で同じコードを得られるまで検知
if (code == result.codeResult.code) {
count++;
} else {
// 前回のコードと違った場合
count = 0;
code = result.codeResult.code;
}
// 3連続で同じコードを得られた場合はカメラを停止
if (count >= 3) {
// コンソールにコードを表示
console.log(code);
// 画像の読み取りを停止
window.clearInterval(interval);
// カメラを停止
const tracks = stream.getVideoTracks();
for (let i = 0; i < tracks.length; i++) {
tracks[i].stop();
}
}
});
}
読み取り精度に若干問題があったので、ここでは3連続で同じコードが得られた場合のみ成功としています。カメラを終了する際には、stream
を直接触ってカメラを停止しています。
#まとめ
ライブラリは便利ですが、mozillaなどのドキュメントを正しく理解するのが大切だと改めて気付かされました!
##サンプルアプリ
サンプルアプリを作ってみたので、よかったら使ってみてください。バーコードリーダーサンプルアプリ