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
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							392 lines
						
					
					
						
							13 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> |