カメラ撮影サイト(Android修正版)
カメラで撮影しよう!(Android修正版)
デバッグ情報: 読み込み中...
💾 ダウンロード
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const photo = document.getElementById('photo');
const recordedVideo = document.getElementById('recordedVideo');
const modeSelect = document.getElementById('mode');
const cameraSelect = document.getElementById('cameraSelect');
const startBtn = document.getElementById('start');
const fullscreenBtn = document.getElementById('fullscreen');
const captureBtn = document.getElementById('capture');
const recordBtn = document.getElementById('record');
const stopRecordBtn = document.getElementById('stopRecord');
const downloadBtn = document.getElementById('download');
const photoControls = document.getElementById('photoControls');
const videoControls = document.getElementById('videoControls');
const output = document.getElementById('output');
const gallery = document.getElementById('gallery');
const clearGalleryBtn = document.getElementById('clearGallery');
const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');
const closeModal = document.querySelector('.close');
const debugInfo = document.getElementById('debugInfo');
const ctx = canvas.getContext('2d');
let stream = null;
let mediaRecorder = null;
let recordedChunks = [];
let currentFacingMode = 'user';
let galleryItems = JSON.parse(localStorage.getItem('cameraGallery') || '[]');
// セキュアコンテキストチェック
function checkSecureContext() {
const isSecure = window.isSecureContext;
const protocol = location.protocol;
debugInfo.textContent = isSecure ? '✅ セキュア(許可画面が出るはず)' : '❌ 非セキュア! HTTPS/localhostに変更を';
if (!isSecure) {
alert('非セキュア環境です! localhostサーバーでテストしてください。許可画面が出ません。');
}
return isSecure;
}
// APIサポートチェック
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert('このブラウザはカメラをサポートしていません。最新のChromeをおすすめします。');
debugInfo.textContent = '❌ ブラウザ非対応';
throw new Error('Unsupported browser');
}
// ギャラリー機能(省略、以前と同じ)
function renderGallery() {
gallery.innerHTML = '';
galleryItems.forEach((item, index) => {
const div = document.createElement('div');
div.className = 'gallery-item';
if (item.type === 'photo') {
const img = document.createElement('img');
img.src = item.url;
img.alt = `写真 ${index + 1}`;
img. = () => openModal(img.src, 'photo');
div.appendChild(img);
} else {
const vid = document.createElement('video');
vid.src = item.url;
vid.controls = false;
vid.muted = true;
vid. = () => openModal(vid.src, 'video');
div.appendChild(vid);
}
const delBtn = document.createElement('button');
delBtn.textContent = '削除';
delBtn.style.fontSize = '12px';
delBtn.style.marginTop = '5px';
delBtn. = (e) => { e.stopPropagation(); deleteItem(index); };
div.appendChild(delBtn);
gallery.appendChild(div);
});
}
function saveGallery() {
localStorage.setItem('cameraGallery', JSON.stringify(galleryItems));
}
function addToGallery(url, type) {
galleryItems.unshift({ url, type, timestamp: Date.now() });
if (galleryItems.length > 50) galleryItems = galleryItems.slice(0, 50);
saveGallery();
renderGallery();
}
function deleteItem(index) {
galleryItems.splice(index, 1);
saveGallery();
renderGallery();
}
function openModal(src, type) {
modalContent.innerHTML = '';
if (type === 'photo') {
const img = document.createElement('img');
img.src = src;
modalContent.appendChild(img);
} else {
const vid = document.createElement('video');
vid.src = src;
vid.controls = true;
vid.playsinline = true;
modalContent.appendChild(vid);
}
modal.style.display = 'block';
}
closeModal. = () => { modal.style.display = 'none'; };
window. = (e) => { if (e.target === modal) modal.style.display = 'none'; };
clearGalleryBtn. = () => {
if (confirm('ギャラリーをクリアしますか?')) {
galleryItems = [];
saveGallery();
renderGallery();
}
};
// モード切り替え
modeSelect.addEventListener('change', (e) => {
const mode = e.target.value;
if (stream) stopStream();
updateUIForMode(mode);
});
function updateUIForMode(mode) {
if (mode === 'photo') {
photoControls.style.display = 'block';
videoControls.style.display = 'none';
photo.style.display = 'block';
recordedVideo.style.display = 'none';
captureBtn.disabled = !stream;
} else {
photoControls.style.display = 'none';
videoControls.style.display = 'block';
photo.style.display = 'none';
recordedVideo.style.display = 'block';
recordBtn.disabled = !stream;
}
output.style.display = 'block';
downloadBtn.style.display = 'none';
}
// カメラ選択
cameraSelect.addEventListener('change', (e) => {
currentFacingMode = e.target.value;
if (stream) {
stopStream();
startCamera();
}
});
// ストリーム停止(強化: 完全停止)
function stopStream() {
if (stream) {
stream.getTracks().forEach(track => {
track.stop();
});
stream = null;
video.srcObject = null; // 重要: ソースをクリアして黒い画面回避
}
startBtn.disabled = false;
fullscreenBtn.disabled = true;
recordBtn.disabled = true;
captureBtn.disabled = true;
stopRecordBtn.disabled = true;
}
// カメラ開始(Android向けconstraints簡略化)
async function startCamera() {
try {
if (!checkSecureContext()) {
throw new Error('非セキュアコンテキスト');
}
// Android黒い画面回避: シンプルconstraints + デフォルトfacingMode
const constraints = {
video: {
facingMode: currentFacingMode === 'default' ? 'user' : currentFacingMode, // デフォルトをuserに
width: { ideal: 640 }, // 低解像度で安定
height: { ideal: 480 }
},
audio: false
};
debugInfo.textContent = '🔄 許可を求め中...';
stream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = stream;
await video.play(); // 明示的にplay
startBtn.disabled = true;
fullscreenBtn.disabled = false;
debugInfo.textContent = '✅ カメラ接続成功!';
const mode = modeSelect.value;
if (mode === 'photo') captureBtn.disabled = false;
else recordBtn.disabled = false;
} catch (err) {
console.error('詳細エラー:', err);
let errorMsg = `エラー: ${err.name} - ${err.message}`;
if (err.name === 'NotAllowedError') errorMsg += '\n許可ブロック。Chrome設定 > カメラ > 許可を!';
else if (err.name === 'NotFoundError') errorMsg += '\nカメラなし。';
else if (err.name === 'OverconstrainedError') errorMsg += '\n設定ミス。デフォルトカメラで再試行。';
else if (err.name === 'NotReadableError') errorMsg += '\nカメラ使用中。アプリ閉じて。';
else errorMsg += '\nコンソール(F12)確認を!';
alert(errorMsg);
debugInfo.textContent = `❌ ${err.name}: ${err.message}`;
}
}
startBtn.addEventListener('click', startCamera);
// フルスクリーン
fullscreenBtn.addEventListener('click', () => {
if (video.requestFullscreen) video.requestFullscreen();
else if (video.webkitRequestFullscreen) video.webkitRequestFullscreen();
});
// 写真撮影
captureBtn.addEventListener('click', () => {
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
ctx.drawImage(video, 0, 0);
const dataUrl = canvas.toDataURL('image/png');
photo.src = dataUrl;
photo.style.display = 'block';
addToGallery(dataUrl, 'photo');
downloadBtn.href = dataUrl;
downloadBtn.download = 'photo.png';
downloadBtn.textContent = '💾 写真をダウンロード';
downloadBtn.style.display = 'inline-block';
downloadBtn.style.fontSize = '18px';
downloadBtn.style.padding = '15px 25px';
downloadBtn.style.textDecoration = 'none';
downloadBtn.style.background = '#28a745';
downloadBtn.style.color = 'white';
downloadBtn.style.borderRadius = '8px';
});
// 動画録画開始
recordBtn.addEventListener('click', () => {
recordedChunks = [];
mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0) recordedChunks.push(e.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
recordedVideo.src = url;
recordedVideo.style.display = 'block';
addToGallery(url, 'video');
downloadBtn.href = url;
downloadBtn.download = 'video.webm';
downloadBtn.textContent = '💾 動画をダウンロード';
downloadBtn.style.display = 'inline-block';
downloadBtn.style.fontSize = '18px';
downloadBtn.style.padding = '15px 25px';
downloadBtn.style.textDecoration = 'none';
downloadBtn.style.background = '#28a745';
downloadBtn.style.color = 'white';
downloadBtn.style.borderRadius = '8px';
};
mediaRecorder.start(1000);
recordBtn.disabled = true;
stopRecordBtn.disabled = false;
recordBtn.textContent = '⏸ 録画中...';
});
// 動画録画停止
stopRecordBtn.addEventListener('click', () => {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
}
recordBtn.disabled = false;
stopRecordBtn.disabled = true;
recordBtn.textContent = '▶ 録画開始';
});
// 初期化
updateUIForMode(modeSelect.value);
renderGallery();
checkSecureContext();
// ページ終了時クリーンアップ
window.addEventListener('beforeunload', stopStream);