You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Software_Architecture/distance-judgement/mobile/camera_permission_test.html

504 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📷 摄像头权限测试</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.header {
text-align: center;
padding: 20px 0;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
margin-bottom: 30px;
}
.test-section {
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
}
.test-title {
font-size: 18px;
margin-bottom: 15px;
color: #4CAF50;
}
.test-result {
margin: 10px 0;
padding: 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
margin: 5px;
background: #4CAF50;
color: white;
}
.btn:hover {
background: #45a049;
}
.btn:disabled {
background: #666;
cursor: not-allowed;
}
.video-container {
position: relative;
background: #000;
border-radius: 10px;
overflow: hidden;
margin: 20px 0;
max-height: 400px;
}
#testVideo {
width: 100%;
height: auto;
display: block;
}
.log-area {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 15px;
height: 300px;
overflow-y: auto;
font-family: monospace;
font-size: 14px;
line-height: 1.6;
}
.log-entry {
margin-bottom: 5px;
}
.log-success {
color: #4CAF50;
}
.log-error {
color: #f44336;
}
.log-warning {
color: #FF9800;
}
.log-info {
color: #2196F3;
}
.permission-status {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-weight: bold;
margin-left: 10px;
}
.status-granted {
background: #4CAF50;
}
.status-denied {
background: #f44336;
}
.status-prompt {
background: #FF9800;
}
.status-unknown {
background: #666;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📷 摄像头权限测试工具</h1>
<p>全面测试摄像头权限获取方法的正确性</p>
</div>
<div class="test-section">
<h3 class="test-title">🔍 1. 浏览器兼容性检查</h3>
<div id="compatibilityResult" class="test-result">等待测试...</div>
<button class="btn" onclick="testCompatibility()">开始检查</button>
</div>
<div class="test-section">
<h3 class="test-title">🔐 2. 权限状态查询</h3>
<div id="permissionResult" class="test-result">等待测试...</div>
<button class="btn" onclick="checkPermissionStatus()">检查权限状态</button>
</div>
<div class="test-section">
<h3 class="test-title">📱 3. 设备枚举测试</h3>
<div id="deviceResult" class="test-result">等待测试...</div>
<button class="btn" onclick="enumerateDevices()">枚举设备</button>
</div>
<div class="test-section">
<h3 class="test-title">🎥 4. 摄像头访问测试</h3>
<div id="cameraResult" class="test-result">等待测试...</div>
<div class="video-container" style="display: none;" id="videoContainer">
<video id="testVideo" autoplay muted playsinline></video>
</div>
<button class="btn" onclick="testCameraAccess()">请求摄像头权限</button>
<button class="btn" onclick="stopCamera()" style="background: #f44336;">停止摄像头</button>
</div>
<div class="test-section">
<h3 class="test-title">📋 测试日志</h3>
<div id="logArea" class="log-area"></div>
<button class="btn" onclick="clearLog()" style="background: #666;">清空日志</button>
</div>
</div>
<script>
let currentStream = null;
let permissionChangeListener = null;
function log(message, type = 'info') {
const logArea = document.getElementById('logArea');
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.textContent = `${timestamp} - ${message}`;
logArea.appendChild(entry);
logArea.scrollTop = logArea.scrollHeight;
}
function clearLog() {
document.getElementById('logArea').innerHTML = '';
}
function updateResult(elementId, content, type = 'info') {
const element = document.getElementById(elementId);
element.innerHTML = content;
element.className = `test-result log-${type}`;
}
// 1. 浏览器兼容性检查
function testCompatibility() {
log('开始浏览器兼容性检查...', 'info');
const checks = [
{
name: 'MediaDevices API',
test: () => !!navigator.mediaDevices,
required: true
},
{
name: 'getUserMedia方法',
test: () => !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
required: true
},
{
name: 'enumerateDevices方法',
test: () => !!(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices),
required: false
},
{
name: 'Permissions API',
test: () => !!navigator.permissions,
required: false
},
{
name: 'HTTPS环境',
test: () => location.protocol === 'https:' || location.hostname === 'localhost',
required: true
}
];
let resultHtml = '<h4>兼容性检查结果:</h4>';
let allPassed = true;
checks.forEach(check => {
const passed = check.test();
const status = passed ? '✅' : (check.required ? '❌' : '⚠️');
const statusText = passed ? '支持' : '不支持';
resultHtml += `<div>${status} ${check.name}: ${statusText}</div>`;
if (!passed && check.required) {
allPassed = false;
}
log(`${check.name}: ${statusText}`, passed ? 'success' : (check.required ? 'error' : 'warning'));
});
if (allPassed) {
resultHtml += '<div style="color: #4CAF50; margin-top: 10px;"><strong>✅ 浏览器完全兼容摄像头功能</strong></div>';
log('浏览器兼容性检查通过', 'success');
} else {
resultHtml += '<div style="color: #f44336; margin-top: 10px;"><strong>❌ 浏览器不兼容,请使用现代浏览器</strong></div>';
log('浏览器兼容性检查失败', 'error');
}
updateResult('compatibilityResult', resultHtml, allPassed ? 'success' : 'error');
}
// 2. 权限状态查询
async function checkPermissionStatus() {
log('开始权限状态查询...', 'info');
try {
if (!navigator.permissions) {
updateResult('permissionResult', '❌ 浏览器不支持权限查询API', 'warning');
log('浏览器不支持权限查询API', 'warning');
return;
}
const result = await navigator.permissions.query({ name: 'camera' });
const statusMap = {
'granted': { text: '已授权', color: 'success', icon: '✅' },
'denied': { text: '已拒绝', color: 'error', icon: '❌' },
'prompt': { text: '需要询问', color: 'warning', icon: '⚠️' }
};
const status = statusMap[result.state] || { text: '未知', color: 'info', icon: '❓' };
let resultHtml = `
<h4>权限状态查询结果:</h4>
<div>${status.icon} 摄像头权限状态: <span class="permission-status status-${result.state}">${status.text}</span></div>
`;
// 添加权限变化监听
if (permissionChangeListener) {
result.removeEventListener('change', permissionChangeListener);
}
permissionChangeListener = () => {
log(`权限状态变化: ${result.state}`, 'info');
checkPermissionStatus(); // 重新检查
};
result.addEventListener('change', permissionChangeListener);
resultHtml += '<div style="margin-top: 10px;">✅ 已设置权限变化监听</div>';
updateResult('permissionResult', resultHtml, status.color);
log(`摄像头权限状态: ${result.state}`, status.color);
} catch (error) {
const errorMsg = `权限查询失败: ${error.message}`;
updateResult('permissionResult', `${errorMsg}`, 'error');
log(errorMsg, 'error');
}
}
// 3. 设备枚举测试
async function enumerateDevices() {
log('开始设备枚举测试...', 'info');
try {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
updateResult('deviceResult', '❌ 浏览器不支持设备枚举功能', 'error');
log('浏览器不支持设备枚举功能', 'error');
return;
}
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
let resultHtml = `<h4>设备枚举结果:</h4>`;
resultHtml += `<div>📹 发现 ${videoDevices.length} 个视频设备</div>`;
if (videoDevices.length === 0) {
resultHtml += '<div style="color: #f44336;">❌ 未找到任何视频设备</div>';
log('未找到任何视频设备', 'error');
} else {
videoDevices.forEach((device, index) => {
const label = device.label || `摄像头 ${index + 1} (需要权限显示详细信息)`;
resultHtml += `<div>📱 设备 ${index + 1}: ${label}</div>`;
log(`设备 ${index + 1}: ${label} (ID: ${device.deviceId.substr(0, 8)}...)`, 'info');
});
// 检查是否有设备标签
const hasLabels = videoDevices.some(device => device.label);
if (!hasLabels) {
resultHtml += '<div style="color: #FF9800; margin-top: 10px;">⚠️ 设备标签为空,可能需要先获取摄像头权限</div>';
log('设备标签为空,需要摄像头权限才能显示详细信息', 'warning');
}
}
updateResult('deviceResult', resultHtml, videoDevices.length > 0 ? 'success' : 'error');
log(`设备枚举完成,找到 ${videoDevices.length} 个视频设备`, 'success');
} catch (error) {
const errorMsg = `设备枚举失败: ${error.message}`;
updateResult('deviceResult', `${errorMsg}`, 'error');
log(errorMsg, 'error');
}
}
// 4. 摄像头访问测试
async function testCameraAccess() {
log('开始摄像头访问测试...', 'info');
try {
// 先停止之前的流
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
}
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error('浏览器不支持摄像头访问功能');
}
const constraints = {
video: {
facingMode: 'environment', // 优先使用后置摄像头
width: { ideal: 640 },
height: { ideal: 480 },
frameRate: { ideal: 30, max: 30 }
},
audio: false
};
log('正在请求摄像头权限...', 'info');
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
const video = document.getElementById('testVideo');
video.srcObject = currentStream;
// 等待视频开始播放
await new Promise((resolve, reject) => {
video.onloadedmetadata = () => {
log('视频流准备就绪', 'success');
resolve();
};
video.onerror = reject;
setTimeout(() => reject(new Error('视频加载超时')), 10000);
});
// 显示视频容器
document.getElementById('videoContainer').style.display = 'block';
// 获取实际的视频配置
const track = currentStream.getVideoTracks()[0];
const settings = track.getSettings();
let resultHtml = `
<h4>摄像头访问成功!</h4>
<div>✅ 摄像头权限已获取</div>
<div>📹 分辨率: ${settings.width}x${settings.height}</div>
<div>🎯 帧率: ${settings.frameRate}fps</div>
<div>📱 设备: ${track.label || '未知设备'}</div>
`;
if (settings.facingMode) {
resultHtml += `<div>📷 摄像头方向: ${settings.facingMode}</div>`;
}
updateResult('cameraResult', resultHtml, 'success');
log(`摄像头访问成功: ${track.label || '未知设备'}`, 'success');
log(`视频配置: ${settings.width}x${settings.height}@${settings.frameRate}fps`, 'info');
} catch (error) {
let errorMsg = error.message;
let errorType = 'error';
// 详细的错误分析
if (error.name === 'NotAllowedError') {
errorMsg = '摄像头权限被拒绝';
errorType = 'error';
} else if (error.name === 'NotFoundError') {
errorMsg = '未找到可用的摄像头设备';
errorType = 'error';
} else if (error.name === 'NotSupportedError') {
errorMsg = '浏览器不支持摄像头功能';
errorType = 'error';
} else if (error.name === 'NotReadableError') {
errorMsg = '摄像头被其他应用占用';
errorType = 'error';
} else if (error.name === 'OverconstrainedError') {
errorMsg = '摄像头不支持请求的配置';
errorType = 'warning';
} else if (error.name === 'SecurityError') {
errorMsg = '安全限制请在HTTPS环境下访问';
errorType = 'error';
}
let resultHtml = `<h4>摄像头访问失败</h4><div>❌ ${errorMsg}</div>`;
// 提供解决建议
if (error.name === 'NotAllowedError') {
resultHtml += `
<div style="margin-top: 10px;">
<strong>💡 解决方案:</strong><br>
1. 点击浏览器地址栏的摄像头图标或锁图标<br>
2. 选择"允许"摄像头权限<br>
3. 刷新页面重试
</div>
`;
}
updateResult('cameraResult', resultHtml, errorType);
log(`摄像头访问失败: ${errorMsg}`, errorType);
}
}
// 停止摄像头
function stopCamera() {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
document.getElementById('videoContainer').style.display = 'none';
updateResult('cameraResult', '<h4>摄像头已停止</h4><div>📱 视频流已释放</div>', 'info');
log('摄像头已停止,视频流已释放', 'info');
} else {
log('没有活跃的摄像头流', 'warning');
}
}
// 页面加载时自动运行兼容性检查
window.onload = function () {
log('摄像头权限测试工具已加载', 'success');
testCompatibility();
};
// 页面卸载时清理资源
window.onbeforeunload = function () {
stopCamera();
};
</script>
</body>
</html>