【iOS対応】LIFFでQRコードリーダーを利用する
LIFF(LINEミニアプリ)とQRコードリーダーの相性は良さそうなものですが、技術的制約でiOSでは利用できません。
しかしながら昨年末飛び込んできたこのニュース。
OK, let's use bug 220184 for custom schemes and getUserMedia.
I am closing this bug here since WKWebView applications can now have access to getUserMedia.
iOS14.3以降で、WKWebViewでgetUserMedia
が動くようになったようです🙌
"getUserMedia
が使えるなら自分で作ればいいじゃない!"ということで、やってみました。結論としてはバッチリ動きます✌️
完成イメージ
こんなものができます。せっかくなので連続読み取りに対応してみました。
プロジェクトの下準備
今回、Webアプリケーションとして動的なものは作りませんのでどの言語・どのフレームワークでも良いのですが、ここではPythonのFlaskを使った場合を例にしていきます。
Python側はデフォルトの設定でインスタンスを起動するのみです。ポート番号は5000にしていますが何でもOKです。
from flask import Flask
app = Flask(__name__)
app.run(port=5000)
続いてHTMLです。body
要素の最後でQRコード読み取りライブラリのjsQR
とLIFFのSDKを読み込むようにしています。Bootstrap5も読み込んでいますが、これも何でもOKです。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<title>QRCode Reader</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col">
<h1>QRコードリーダー</h1>
<div>
<video autoplay playsinline="true" style="width:100%; height: 100%; object-fit: fill;">
</div>
</div>
</div>
<div class="row">
<div class="col">
<span id="decoded-value"></span>
</div>
</div>
<div class="row">
<div class="col">
<button type="button" class="btn btn-primary" onclick="startReader();">Start</button>
<button type="button" class="btn" onclick="stopReader();">Stop</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/jsqr@latest/dist/jsQR.min.js"></script>
<script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
</body>
</html>
ここで一旦動作確認してみます。
$ python run.py
アプリが起動したら http://localhost:5000/static/reader.html にアクセスしましょう。ボタンが2つ表示されていればOKです。
QRコード読み取り処理の追加
ここからが本番です。まずは読み取り処理をクラスとして実装します。body
要素の最後に以下script
要素を追加しましょう。
<script>
class QRCodeReader {
constructor (videoElement) {
this.video = videoElement;
this.canvas = document.createElement("canvas");
this.context = this.canvas.getContext("2d");
this.decoderId;
this.decodedValue;
}
// Start QR Code Reader
start(onCompleted, stopOnComplete) {
this.decoderId = null;
this.decodedValue = null;
// Start camera
navigator.mediaDevices.getUserMedia({
audio: false,
video: {
width: 500,
height: 500,
frameRate: {
max: 5 // 5fps
},
facingMode : {
exact : "environment"
}
}
}).then((mediaStream) => {
// Bind media stream with video
this.video.srcObject = mediaStream;
});
// Start reading
this.decoderId = setInterval(() => {
this.decodeQRCode();
if (this.decodedValue || this.decodedValue == 0) {
if (stopOnComplete) {
this.stop();
}
onCompleted(this.decodedValue);
}
}, 200); // 5fps -> read every 200ms
};
// Stop QR Code Reader
stop() {
if (this.video.srcObject) {
// Stop camera
this.video.srcObject.getVideoTracks().forEach((track) => {
track.stop();
});
// Stop QR Code decoder
clearInterval(this.decoderId);
}
};
decodeQRCode() {
const width = this.video.videoWidth;
const height = this.video.videoHeight;
if (width > 0 && height > 0) {
// Get snapshot from video
this.canvas.width = width;
this.canvas.height = height;
this.context.clearRect(0, 0, width, height);
this.context.drawImage(this.video, 0, 0, width, height);
const imageData = this.context.getImageData(0, 0, width, height);
// Decode QR Code
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
this.decodedValue = code.data;
}
}
}
}
</script>
続いてこのQRCodeReader
クラスを使ってカメラ映像を表示する処理や、読み取りの実行処理を追加していきます。先のクラス定義の下にコードを追加もしくは別script
要素として追加しましょう。
<script>
// QRコードリーダーの初期化
const reader = new QRCodeReader(document.querySelector("video"));
// QRコードリーダー起動処理
const startReader = () => {
reader.start((value) => {
// QRコード読み取り時に実行される処理(valueが読み取り値)
document.querySelector("#decoded-value").textContent = value;
}, false); // false:読み取り後にリーダーを閉じない
}
// QRコードリーダー停止処理
const stopReader = () => {
reader.stop();
}
</script>
ここまで追加したら、ページを再読み込みしてStart reader
ボタンを押してください。カメラが起動しますので、何かQRコードを読み取ってみましょう。読み取った文字列がボタンの上あたりに表示されたら成功です。表示されない場合はFlaskを再起動してから再読み込みし直してみてください。
なお読み取り頻度については200ms(毎秒5回)にしていますが、ここは負荷と反応速度とをトレードオフにしてチューニングできるところかと思います。
LIFF化する
最後に、このQRコードリーダーがLIFFとして動作するようにしていきます。
インターネットアクセスの提供
LIFFとして利用するには、HTTPSでインターネットからアクセスできる必要があります。開発PCを暫定的にインターネット公開するためにはngrokを利用しましょう。インストールしたら以下の通り実行します。ポート番号はFlaskアプリに合わせてください。
$ ngrok http 5000
起動したら、ngrokにより提供されたインターネットアドレス(xxxxxx
部分は可変)を利用して、https://xxxxxx.ngrok.io/static/reader.html にアクセスしてみましょう。ローカルホストへのアクセスと同じものが表示されたら成功です。ngrokはこの記事の最後まで停止しないでください。
LIFFアプリの登録
LINE Developers ConsoleにてLIFFアプリを作成し、Endpoint URLに先のngrokのURL(reader.html
まで)を登録します。LIFFアプリの登録手順については公式のドキュメントなどを参考にしてください。
なおMessaging API(BOT)のチャネルにLIFFを追加する手順で解説している記事が多く存在していますが、現在はLINEログインのチャンネルに追加するのが正しい手順です。
登録完了後、以下の情報を控えておきます。値は例です。
- LIFF ID: 1234567890-ABCDEFGH
- LIFF URL: https://liff.line.me/1234567890-ABCDEFGH
LIFFの初期化処理の追加
コンソールで控えておいたLIFF IDを使用してLIFFアプリを初期化する処理を追加します。最後に追加したscript
要素の末尾に以下の処理を追加します。
// LIFFの初期化
liff.init({
liffId: "1234567890-ABCDEFGH"
})
.then(() => {
accessToken = liff.getAccessToken();
// AccessTokenの表示。これをサーバーサイドに渡してユーザー情報等を取得
alert(accessToken);
})
.catch((err) => {
alert(err);
});
処理を追加したら再読み込みして試してみましょう。PCのブラウザではアクセストークンが取得できないのでnull
が、LINE上ではアクセストークンのJWTが表示されると思います。その後の読み取りは、冒頭の完成イメージの通り。
おわりに
このように、iOSでもLIFF(LINEミニアプリ)上でQRコードリーダーを使うことができるようになりました。もちろんiOS14.3以降が前提ですのでユースケース次第では注意が必要です。
取得した値はサンプルではただボタンの上に表示しているだけですが、LIFFアプリの中で如何様にも利用することができます。
SDK標準のQRコードリーダーよりも制約が少ないと思いますので、アイディア次第でいろんなことができるんじゃないかと思います👍
Discussion