Last active
May 29, 2025 16:55
-
-
Save shiena/6e682ae0568e41b98d4831f3a779ac66 to your computer and use it in GitHub Desktop.
web camera sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>カメラ画素配列取得</title> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
padding: 20px; | |
} | |
#hiddenVideo { | |
display: none; /* カメラ映像は非表示 */ | |
} | |
#debugCanvas { | |
border: 1px solid #ccc; | |
margin-top: 20px; | |
} | |
button { | |
padding: 10px 20px; | |
margin: 10px; | |
font-size: 16px; | |
} | |
#info { | |
margin-top: 20px; | |
font-family: monospace; | |
background: #f5f5f5; | |
padding: 10px; | |
border-radius: 4px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>カメラ画素配列取得デバッグ</h1> | |
<button id="toggleBtn">カメラ開始</button> | |
<!-- カメラ映像(非表示) --> | |
<video id="hiddenVideo" width="320" height="240" autoplay></video> | |
<!-- デバッグ用Canvas --> | |
<canvas id="debugCanvas" width="320" height="240"></canvas> | |
<div id="info"> | |
<div>状態: <span id="status">待機中</span></div> | |
<div>画素配列サイズ: <span id="arraySize">-</span></div> | |
<div>最初の10ピクセル(RGBA): <span id="pixelSample">-</span></div> | |
</div> | |
<script> | |
const video = document.getElementById('hiddenVideo'); | |
const canvas = document.getElementById('debugCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const toggleBtn = document.getElementById('toggleBtn'); | |
const status = document.getElementById('status'); | |
const arraySize = document.getElementById('arraySize'); | |
const pixelSample = document.getElementById('pixelSample'); | |
let stream = null; | |
let animationId = null; | |
let isStreaming = false; | |
let offscreenCanvas = null; | |
let offscreenCtx = null; | |
// カメラ・ミラー開始/停止 | |
toggleBtn.addEventListener('click', async () => { | |
if (!isStreaming) { | |
// カメラ開始 + ミラー開始 | |
try { | |
status.textContent = 'カメラアクセス中...'; | |
toggleBtn.disabled = true; | |
stream = await navigator.mediaDevices.getUserMedia({ | |
video: { width: 320, height: 240 } | |
}); | |
video.srcObject = stream; | |
video.addEventListener('loadedmetadata', () => { | |
// ケーパビリティ(実際の解像度)を取得 | |
const actualWidth = video.videoWidth; | |
const actualHeight = video.videoHeight; | |
// デバッグ用Canvasをリサイズ | |
canvas.width = actualWidth; | |
canvas.height = actualHeight; | |
// OffscreenCanvasを作成・リサイズ | |
if (typeof OffscreenCanvas !== 'undefined') { | |
offscreenCanvas = new OffscreenCanvas(actualWidth, actualHeight); | |
offscreenCtx = offscreenCanvas.getContext('2d'); | |
console.log('OffscreenCanvas使用'); | |
} else { | |
console.log('OffscreenCanvas非対応 - 通常のCanvasを使用'); | |
} | |
console.log(`カメラ解像度: ${actualWidth} x ${actualHeight}`); | |
// カメラ準備完了後、自動でミラー開始 | |
startMirror(); | |
isStreaming = true; | |
toggleBtn.textContent = 'カメラ停止'; | |
toggleBtn.disabled = false; | |
status.textContent = `カメラ・ミラー動作中(${actualWidth}x${actualHeight}, RAF)`; | |
}); | |
} catch (error) { | |
console.error('カメラアクセスエラー:', error); | |
status.textContent = 'カメラアクセス失敗'; | |
toggleBtn.disabled = false; | |
} | |
} else { | |
// ミラー停止 + カメラ停止 | |
if (animationId) { | |
cancelAnimationFrame(animationId); | |
animationId = null; | |
} | |
if (stream) { | |
stream.getTracks().forEach(track => track.stop()); | |
stream = null; | |
} | |
video.srcObject = null; | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
offscreenCanvas = null; | |
offscreenCtx = null; | |
isStreaming = false; | |
toggleBtn.textContent = 'カメラ開始'; | |
status.textContent = '停止しました'; | |
arraySize.textContent = '-'; | |
pixelSample.textContent = '-'; | |
} | |
}); | |
// ミラー開始 | |
function startMirror() { | |
function animate() { | |
updateMirror(); | |
animationId = requestAnimationFrame(animate); | |
} | |
animate(); | |
} | |
// ミラー更新関数 | |
function updateMirror() { | |
// 実際のビデオサイズを取得 | |
const actualWidth = video.videoWidth; | |
const actualHeight = video.videoHeight; | |
let imageData; | |
if (offscreenCanvas && offscreenCtx) { | |
// OffscreenCanvas使用(高性能) | |
offscreenCtx.drawImage(video, 0, 0, actualWidth, actualHeight); | |
imageData = offscreenCtx.getImageData(0, 0, actualWidth, actualHeight); | |
} else { | |
// 通常のCanvas使用(フォールバック) | |
const tempCanvas = document.createElement('canvas'); | |
const tempCtx = tempCanvas.getContext('2d'); | |
tempCanvas.width = actualWidth; | |
tempCanvas.height = actualHeight; | |
tempCtx.drawImage(video, 0, 0, actualWidth, actualHeight); | |
imageData = tempCtx.getImageData(0, 0, actualWidth, actualHeight); | |
} | |
const pixelArray = imageData.data; // Uint8ClampedArray [R,G,B,A, R,G,B,A, ...] | |
// デバッグ情報表示(フレーム毎に更新すると重いのでたまに更新) | |
if (Math.random() < 0.1) { // 10%の確率で情報更新 | |
arraySize.textContent = `${pixelArray.length} bytes (${pixelArray.length/4} pixels)`; | |
// 最初の10ピクセルのRGBA値を表示 | |
const firstPixels = []; | |
for (let i = 0; i < Math.min(40, pixelArray.length); i += 4) { | |
firstPixels.push(`[${pixelArray[i]},${pixelArray[i+1]},${pixelArray[i+2]},${pixelArray[i+3]}]`); | |
} | |
pixelSample.textContent = firstPixels.join(' '); | |
} | |
// デバッグ用Canvasに表示 | |
ctx.putImageData(imageData, 0, 0); | |
// ここで他のライブラリにpixelArrayを渡す | |
// processImageData(pixelArray, actualWidth, actualHeight); | |
} | |
// ページ離脱時にカメラを停止 | |
window.addEventListener('beforeunload', () => { | |
if (stream) { | |
stream.getTracks().forEach(track => track.stop()); | |
} | |
if (animationId) { | |
cancelAnimationFrame(animationId); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment