前提
- 自前で証明書を発行済みでhttpsでlocalhostにアクセスできる
- iPhoneとPCは同一WiFiにつながっている
- iPhoneは1台、PCも1台
- iPhoneは直接見れない位置にある
という条件で、iPhoneのカメラが捉えている映像を確認したく、PCでプレビューできるようにしてみました。
仕組み
本当はWebRTCとかで、サーバを介さずに、iPhoneとPCがやりとりできるのがベストだと思うのですが、今回は手っ取り早く実装するために、socket.ioを使って通信します。
- iPhone側のウェブサイト 👉 カメラが捉えている映像をbase64に変換したものをサーバに送り続ける
- サーバ 👉 iPhoneのブラウザから送られてくる文字列をPCのブラウザに送る
- PC側のウェブサイト 👉 サーバから受け取ったbase64を画像としてレンダリングする
という流れです。
camera.html(iPhone側のページ)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>camera</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no" /> <style> body { margin: 0; padding: 0; } video { width: 100%; height: 100%; } </style> <script src="./socket.io.min.js"></script> </head> <body> <video id="video" autoplay playsinline muted></video> <script> navigator.mediaDevices.getUserMedia({ audio: false, video: { facingMode: { exact: 'environment' // リアカメラを使用 } } }).then((stream) => { const FPS = 8; const socket = io.connect(); const video = document.getElementById('video'); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); video.oncanplay = () => { // 通信量を抑えるためにサイズを小さくする const width = video.clientWidth / 2; const height = video.clientHeight / 2; tick(); function tick() { const begin = Date.now(); canvas.width = width; canvas.height = height; ctx.drawImage(video, 0, 0, width, height); const dataURL = canvas.toDataURL('image/jpeg'); // サーバにbase64を送る socket.emit('video', { dataURL }); const delay = 1000 / FPS - (Date.now() - begin); // 8FPSで動かす setTimeout(tick, delay); } }; video.srcObject = stream; }).catch((err) => alert(err)); </script> </body> </html>
app.js(サーバ)
const https = require('https'); const path = require('path'); const fs = require('fs'); const express = require('express'); const app = require('express')(); // httpsじゃないとカメラを開放できないので、事前に「server.key」「server.crt」を作っておく // https://blog.kimizuka.org/entry/2022/12/26/234957 const options = { key: fs.readFileSync(path.resolve(__dirname, './server.key')), cert: fs.readFileSync(path.resolve(__dirname, './server.crt')) }; app.use('/', express.static(`${__dirname}/public`)); const server = https.createServer(options, app).listen(3000); const io = require('socket.io')(server); io.on('connection', (socket) => { socket.on('video', (dataURL) => { // iPhoneのブラウザから送られてきた文字列をPCのブラウザに送る io.emit('video', dataURL); }); });
index.html(PC側のページ)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>tv</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no" /> <style> body { margin: 0; padding: 0; } </style> <script src="./socket.io.min.js"></script> </head> <body> <img id="img" /> <script> const socket = io.connect(); const img = document.getElementById('img'); socket.on('video', ({ dataURL }) => { img.src = dataURL; // サーバから送られてきたbase64をimageタグのsrcに設定する }); </script> </body> </html>
これで、iPhoneのカメラが捉えている映像をPCでリアルタイムプレビューするウェブサイトができました。
camera.htmlには、ローカルIPでアクセスする必要があります。
僕は、こういうときのためにMacのメニューバーに常にローカルIPを表示しています。
blog.kimizuka.org