LoginSignup
43
39

More than 3 years have passed since last update.

PWAでカメラを使うためiOSとAndroidで異なるmanifestを読み込む

Last updated at Posted at 2019-08-07

TL;DR

・iOSのPWAでは表示モードがstandaloneの場合にカメラを開くことができない。
・Androidでの起動時にはstandalone、iOSではbrowserで起動する。
・ページアクセス時にuserAgentからOSを判定し、OSに応じて異なるmanifestを読み込む。

カメラを開く最小限のPWA構成

ファイル構成は下記の通りです。
icon.pngは適当な192x192の画像を用意してください。

root/
 ├ icon.png
 ├ index.html
 ├ js/
 │ └ camera.js
 ├ manifest.json
 └ sw.js
index.html
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="manifest" href="manifest.json">
        <link rel="apple-touch-icon" href="icon.png" sizes="192x192">
    </head>

    <body onload="openCamera();">
        <video autoplay playsinline></video>
    </body>

    <script src="js/camera.js"></script>
    <script>
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('sw.js').then(function(registration) {
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            }).catch(function(err) {
                console.log('ServiceWorker registration failed: ', err);
            });
        }
    </script>
</html>
sw.js
var CACHE_NAME = 'pwa-camera-test-caches';
var urlsToCache = ['/index.html', '/js/camera.js'];

self.addEventListener('install', function(event) {
    event.waitUntil(
        caches
            .open(CACHE_NAME)
            .then(function(cache) {
                return cache.addAll(urlsToCache);
            })
    );
});

self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches
            .match(event.request)
            .then(function(response) {
                return response ? response : fetch(event.request);
            })
    );
});
manifest.json
{
    "name": "pwa camera test",
    "short_name": "PWA CAMERA",
    "icons": [{
          "src": "icon.png",
          "sizes": "192x192",
          "type": "image/png"
        }],
    "start_url": "index.html",
    "display": "standalone"
}
camera.js
function openCamera() {
    var video = document.querySelector('video');
    navigator.mediaDevices = navigator.mediaDevices
    || ((navigator.mozGetUserMedia 
    || navigator.webkitGetUserMedia) ? {
        getUserMedia: function(c) {
            return new Promise(function(y, n) {
                (navigator.mozGetUserMedia ||
                navigator.webkitGetUserMedia).call(navigator, c, y, n);
            });
        }
    } : null);
    var constraints = { video: { facingMode: 'environment', width: 1280, height: 720 } };
    navigator.mediaDevices.getUserMedia(constraints)
        .then(function(stream) {
            video.srcObject = stream;
            video.onloadedmetadata = function(e) {
                video.play();
            };
        })
        .catch(function(err) {
            console.log(err);
        });
}

PWA参考:シンプルなPWAサンプルここに置いておきますね
カメラ参考:「MediaDevices.getUserMedia() 」について

これでカメラを開く最低限のPWAアプリの完成です。
https接続のできる場所にアップロードして動作を確認します。
今回はGithub Pagesを使用しました。

ブラウザ

iOS Android
IMG_2902.PNG Screenshot_2019-08-07-16-37-30.jpg

インストール(ホーム画面に追加)

iOS Android
IMG_2903.PNG Screenshot_2019-08-07-16-37-52.jpg

PWA

iOS Android
IMG_2904.PNG Screenshot_2019-08-07-16-38-02.jpg

冒頭で述べた通り、iOSでは表示モードがstandaloneの場合にカメラを開くことができません。
参考:PWA来てるからカメラアプリ作れるんじゃね?と思ったら失敗した話

表示モードをbrowserにすると単にブラウザが起動されるため、iOSでもカメラを開くことができますが、本来standaloneで起動できるAndroidもブラウザでの起動になってしまいます。

そこで、表示モードがstandalonebrowserのmanifestをそれぞれ用意し、OSによって読込先を変更することで、Androidでの起動時にはstandalone、iOSではbrowserで起動するようにします。

OS別のmanifest読み込み

manifest.jsonと同階層に、表示モードをbrowserに変更しただけのmanifest_ios.jsonを作成します。
また、index.htmlでuserAgentからOSを判別し、OSに応じて読み込むmanifestを変更します。

manifest_ios.json
{
    "name": "pwa camera test",
    "short_name": "PWA CAMERA",
    "icons": [{
          "src": "icon.png",
          "sizes": "192x192",
          "type": "image/png"
        }],
    "start_url": "index.html",
    "display": "browser"
}
index.html
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- スクリプトから追加するため削除 -->
        <!-- <link rel="manifest" href="manifest.json"> -->
        <link rel="apple-touch-icon" href="icon.png" sizes="192x192">
    </head>

    <body onload="openCamera();">
        <video autoplay playsinline></video>
    </body>

    <script src="js/camera.js"></script>
    <script>
        // manifestのlinkタグを生成
        function setManifest(path) {
            const manifest = document.createElement('link');
            manifest.rel = 'manifest';
            manifest.href = path;
            document.head.appendChild(manifest);
        }

        // OSに応じて読み込むmanifestを変更
        var userAgent = navigator.userAgent.toLowerCase();
        if (userAgent.indexOf("iphone") > 0 || userAgent.indexOf("ipad") > 0) {
            setManifest('manifest_ios.json')
        } else {
            setManifest('manifest.json')
        }

        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('sw.js').then(function(registration) {
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            }).catch(function(err) {
                console.log('ServiceWorker registration failed: ', err);
            });
        }
    </script>
</html>

これでホーム画面のアイコンから起動した時、AndroidではPWAのstandaloneとして起動、iOSではSafariのbrowserで起動するようになりました。
iOS/Android共にカメラを使用できるほか、AndroidではPWAのキャッシュ機能を使用できるためオフラインでも動作も可能です。

まとめ

記事作成時点ではiOSのPWAのstandaloneではカメラを起動できなかったため、manifestをOSに応じて差し替える事で、AndroidではPWAアプリとして動作させつつiOSでのカメラ起動を可能にしました。
iOSでもSafari以外からカメラにアクセスできるようになると嬉しいですね。
あまりスマートな解決策ではない気がしているため、より良い方法がありましたらご教授ください。

43
39
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
39