Skip to content

Instantly share code, notes, and snippets.

@shiena
Last active May 29, 2025 16:55
Show Gist options
  • Save shiena/6e682ae0568e41b98d4831f3a779ac66 to your computer and use it in GitHub Desktop.
Save shiena/6e682ae0568e41b98d4831f3a779ac66 to your computer and use it in GitHub Desktop.
web camera sample
<!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