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.
392 lines
12 KiB
392 lines
12 KiB
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>设备选择器测试</title>
|
|
<style>
|
|
body {
|
|
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
|
color: white;
|
|
font-family: 'Microsoft YaHei', sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 500px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
.video-container {
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border-radius: 15px;
|
|
margin: 20px 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.video-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-radius: 8px 8px 0 0;
|
|
}
|
|
|
|
.device-select-btn {
|
|
background: #2196F3;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.device-selector {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.device-selector-content {
|
|
background: rgba(0, 20, 40, 0.95);
|
|
border: 2px solid #00aaff;
|
|
border-radius: 15px;
|
|
padding: 20px;
|
|
max-width: 90%;
|
|
max-height: 80%;
|
|
overflow-y: auto;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.device-selector h3 {
|
|
margin: 0 0 15px 0;
|
|
color: #00aaff;
|
|
text-align: center;
|
|
}
|
|
|
|
.device-list {
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.device-item {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
border: 2px solid transparent;
|
|
}
|
|
|
|
.device-item:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-color: #00aaff;
|
|
}
|
|
|
|
.device-item.selected {
|
|
background: rgba(0, 170, 255, 0.3);
|
|
border-color: #00aaff;
|
|
}
|
|
|
|
.device-name {
|
|
font-weight: bold;
|
|
color: #00aaff;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.device-id {
|
|
font-size: 12px;
|
|
color: #ccc;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.device-kind {
|
|
display: inline-block;
|
|
background: #4CAF50;
|
|
color: white;
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
font-size: 10px;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.device-selector-buttons {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
margin: 0 5px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.btn:disabled {
|
|
background: #666;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.loading {
|
|
text-align: center;
|
|
color: #ccc;
|
|
padding: 20px;
|
|
}
|
|
|
|
.log {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
margin: 20px 0;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<h1>🧪 设备选择器测试</h1>
|
|
|
|
<div class="video-container">
|
|
<div class="video-header">
|
|
<span>📹 视频设备</span>
|
|
<button class="device-select-btn" onclick="showDeviceSelector()">📷 选择设备</button>
|
|
</div>
|
|
<div id="videoPlaceholder" style="text-align: center; padding: 40px; color: #ccc;">
|
|
点击"选择设备"开始使用摄像头
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 设备选择弹窗 -->
|
|
<div class="device-selector" id="deviceSelector" style="display: none;">
|
|
<div class="device-selector-content">
|
|
<h3>📷 选择视频设备</h3>
|
|
|
|
<!-- 本地设备列表 -->
|
|
<div>
|
|
<h4 style="color: #4CAF50; margin: 15px 0 10px 0;">📱 本地设备</h4>
|
|
<div class="device-list" id="localDeviceList">
|
|
<div class="loading">正在扫描本地设备...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="device-selector-buttons">
|
|
<button class="btn btn-secondary" onclick="hideDeviceSelector()">❌ 取消</button>
|
|
<button class="btn btn-primary" onclick="refreshDevices()">🔄 刷新设备</button>
|
|
<button class="btn" onclick="useSelectedDevice()" id="useDeviceBtn" disabled>✅ 使用选择的设备</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="log" id="logPanel">
|
|
<div>系统初始化中...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let availableDevices = [];
|
|
let selectedDeviceId = null;
|
|
let selectedDeviceInfo = null;
|
|
|
|
// 日志函数
|
|
function log(message, type = 'info') {
|
|
const logPanel = document.getElementById('logPanel');
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const entry = document.createElement('div');
|
|
entry.style.color = type === 'error' ? '#ff6b6b' : type === 'success' ? '#51cf66' : '#74c0fc';
|
|
entry.textContent = `${timestamp} - ${message}`;
|
|
logPanel.appendChild(entry);
|
|
logPanel.scrollTop = logPanel.scrollHeight;
|
|
}
|
|
|
|
// 扫描设备
|
|
async function scanDevices() {
|
|
log('正在扫描可用视频设备...', 'info');
|
|
try {
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
|
|
throw new Error('浏览器不支持设备枚举功能');
|
|
}
|
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
availableDevices = devices.filter(device => device.kind === 'videoinput');
|
|
|
|
log(`发现 ${availableDevices.length} 个视频设备`, 'success');
|
|
|
|
} catch (error) {
|
|
log(`设备扫描失败: ${error.message}`, 'error');
|
|
availableDevices = [];
|
|
}
|
|
}
|
|
|
|
// 显示设备选择器
|
|
async function showDeviceSelector() {
|
|
log('打开设备选择器', 'info');
|
|
const selector = document.getElementById('deviceSelector');
|
|
selector.style.display = 'flex';
|
|
|
|
await scanDevices();
|
|
updateDeviceList();
|
|
}
|
|
|
|
// 隐藏设备选择器
|
|
function hideDeviceSelector() {
|
|
document.getElementById('deviceSelector').style.display = 'none';
|
|
clearDeviceSelection();
|
|
}
|
|
|
|
// 刷新设备
|
|
async function refreshDevices() {
|
|
document.getElementById('localDeviceList').innerHTML = '<div class="loading">正在扫描设备...</div>';
|
|
|
|
await scanDevices();
|
|
updateDeviceList();
|
|
}
|
|
|
|
// 更新设备列表
|
|
function updateDeviceList() {
|
|
const localList = document.getElementById('localDeviceList');
|
|
|
|
if (availableDevices.length === 0) {
|
|
localList.innerHTML = '<div style="color: #ff6b6b; text-align: center; padding: 20px;">未发现本地摄像头设备<br><small>请确保已连接摄像头并允许浏览器访问</small></div>';
|
|
return;
|
|
}
|
|
|
|
localList.innerHTML = '';
|
|
availableDevices.forEach((device, index) => {
|
|
const deviceItem = document.createElement('div');
|
|
deviceItem.className = 'device-item';
|
|
deviceItem.onclick = () => selectDevice(device.deviceId, {
|
|
label: device.label || `摄像头 ${index + 1}`,
|
|
kind: device.kind,
|
|
isRemote: false
|
|
});
|
|
|
|
const deviceName = device.label || `摄像头 ${index + 1}`;
|
|
const isFrontCamera = deviceName.toLowerCase().includes('front') || deviceName.toLowerCase().includes('前');
|
|
const isBackCamera = deviceName.toLowerCase().includes('back') || deviceName.toLowerCase().includes('后');
|
|
|
|
let cameraIcon = '📷';
|
|
if (isFrontCamera) cameraIcon = '🤳';
|
|
else if (isBackCamera) cameraIcon = '📹';
|
|
|
|
deviceItem.innerHTML = `
|
|
<div class="device-name">${cameraIcon} ${deviceName}</div>
|
|
<div class="device-id">${device.deviceId}</div>
|
|
<div class="device-kind">本地设备</div>
|
|
`;
|
|
localList.appendChild(deviceItem);
|
|
});
|
|
}
|
|
|
|
// 选择设备
|
|
function selectDevice(deviceId, deviceInfo) {
|
|
// 清除之前的选择
|
|
document.querySelectorAll('.device-item').forEach(item => {
|
|
item.classList.remove('selected');
|
|
});
|
|
|
|
// 选择当前设备
|
|
event.currentTarget.classList.add('selected');
|
|
selectedDeviceId = deviceId;
|
|
selectedDeviceInfo = deviceInfo;
|
|
|
|
// 启用使用按钮
|
|
document.getElementById('useDeviceBtn').disabled = false;
|
|
|
|
log(`已选择设备: ${deviceInfo.label}`, 'info');
|
|
}
|
|
|
|
// 清除设备选择
|
|
function clearDeviceSelection() {
|
|
selectedDeviceId = null;
|
|
selectedDeviceInfo = null;
|
|
document.getElementById('useDeviceBtn').disabled = true;
|
|
document.querySelectorAll('.device-item').forEach(item => {
|
|
item.classList.remove('selected');
|
|
});
|
|
}
|
|
|
|
// 使用选择的设备
|
|
async function useSelectedDevice() {
|
|
if (!selectedDeviceId || !selectedDeviceInfo) {
|
|
log('请先选择一个设备', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
log(`正在启动设备: ${selectedDeviceInfo.label}`, 'info');
|
|
|
|
const constraints = {
|
|
video: {
|
|
deviceId: { exact: selectedDeviceId },
|
|
width: { ideal: 640 },
|
|
height: { ideal: 480 }
|
|
},
|
|
audio: false
|
|
};
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
|
|
// 创建视频元素显示
|
|
const placeholder = document.getElementById('videoPlaceholder');
|
|
placeholder.innerHTML = `
|
|
<video autoplay muted playsinline style="width: 100%; height: auto;"></video>
|
|
<div style="font-size: 12px; color: #ccc; margin-top: 10px;">
|
|
正在使用: ${selectedDeviceInfo.label}
|
|
</div>
|
|
`;
|
|
|
|
const videoElement = placeholder.querySelector('video');
|
|
videoElement.srcObject = stream;
|
|
|
|
hideDeviceSelector();
|
|
log(`设备启动成功: ${selectedDeviceInfo.label}`, 'success');
|
|
|
|
} catch (error) {
|
|
let errorMsg = error.message;
|
|
if (error.name === 'NotAllowedError') {
|
|
errorMsg = '设备权限被拒绝,请允许访问摄像头';
|
|
} else if (error.name === 'NotFoundError') {
|
|
errorMsg = '设备未找到或已被占用';
|
|
}
|
|
|
|
log(`设备启动失败: ${errorMsg}`, 'error');
|
|
}
|
|
}
|
|
|
|
// 初始化
|
|
window.addEventListener('load', () => {
|
|
log('设备选择器测试页面已加载', 'success');
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html> |