mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge pull request #2957 from xinnan-tech/test_live2d
fix:手机前后摄像头切换,调整摄像头为竖屏显示
This commit is contained in:
@@ -1201,6 +1201,98 @@ body {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.chat-stream::-webkit-scrollbar,
|
||||
.modal-body::-webkit-scrollbar,
|
||||
.mcp-tools-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.chat-stream::-webkit-scrollbar-track,
|
||||
.modal-body::-webkit-scrollbar-track,
|
||||
.mcp-tools-list::-webkit-scrollbar-track {
|
||||
background: #2f3136;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-stream::-webkit-scrollbar-thumb,
|
||||
.modal-body::-webkit-scrollbar-thumb,
|
||||
.mcp-tools-list::-webkit-scrollbar-thumb {
|
||||
background: #40444b;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-stream::-webkit-scrollbar-thumb:hover,
|
||||
.modal-body::-webkit-scrollbar-thumb:hover,
|
||||
.mcp-tools-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #4f545c;
|
||||
}
|
||||
|
||||
/* ==================== 摄像头显示区域样式 ==================== */
|
||||
.camera-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 180px;
|
||||
height: 240px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.camera-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.camera-container.dragging {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||
border-color: rgba(88, 101, 242, 0.5);
|
||||
}
|
||||
|
||||
@keyframes flipWithPosition {
|
||||
0% {
|
||||
transform: var(--original-transform) rotateY(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: var(--original-transform) rotateY(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.camera-container.flip {
|
||||
animation: flipWithPosition 0.5s ease-in-out 1 forwards;
|
||||
}
|
||||
|
||||
#cameraVideo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background: #1a1a1a;
|
||||
pointer-events: none;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.camera-switch {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 23px;
|
||||
right: 20px;
|
||||
z-index: 999;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.camera-switch.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ==================== 响应式设计 ==================== */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
@@ -1264,6 +1356,19 @@ body {
|
||||
left: 10px;
|
||||
bottom: 80px;
|
||||
}
|
||||
|
||||
.chat-ipt input {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.camera-container {
|
||||
width: 130px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -1343,68 +1448,4 @@ body {
|
||||
width: 250px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.chat-stream::-webkit-scrollbar,
|
||||
.modal-body::-webkit-scrollbar,
|
||||
.mcp-tools-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.chat-stream::-webkit-scrollbar-track,
|
||||
.modal-body::-webkit-scrollbar-track,
|
||||
.mcp-tools-list::-webkit-scrollbar-track {
|
||||
background: #2f3136;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-stream::-webkit-scrollbar-thumb,
|
||||
.modal-body::-webkit-scrollbar-thumb,
|
||||
.mcp-tools-list::-webkit-scrollbar-thumb {
|
||||
background: #40444b;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-stream::-webkit-scrollbar-thumb:hover,
|
||||
.modal-body::-webkit-scrollbar-thumb:hover,
|
||||
.mcp-tools-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #4f545c;
|
||||
}
|
||||
|
||||
/* ==================== 摄像头显示区域样式 ==================== */
|
||||
.camera-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.camera-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.camera-container.dragging {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||
border-color: rgba(88, 101, 242, 0.5);
|
||||
}
|
||||
|
||||
#cameraVideo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background: #1a1a1a;
|
||||
pointer-events: none;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class App {
|
||||
this.audioPlayer = null;
|
||||
this.live2dManager = null;
|
||||
this.cameraStream = null;
|
||||
this.currentFacingMode = 'user';
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
@@ -127,6 +128,8 @@ class App {
|
||||
async initCamera() {
|
||||
const cameraContainer = document.getElementById('cameraContainer');
|
||||
const cameraVideo = document.getElementById('cameraVideo');
|
||||
const cameraSwitch = document.getElementById('cameraSwitch');
|
||||
const dialBtn = document.getElementById('dialBtn');
|
||||
|
||||
if (!cameraContainer || !cameraVideo) {
|
||||
log('摄像头元素未找到,跳过初始化', 'warning');
|
||||
@@ -188,11 +191,24 @@ class App {
|
||||
}
|
||||
log('正在请求摄像头权限...', 'info');
|
||||
this.cameraStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { width: 320, height: 240, facingMode: 'user' },
|
||||
video: { width: 320, height: 240, facingMode: this.currentFacingMode },
|
||||
audio: false
|
||||
});
|
||||
cameraVideo.srcObject = this.cameraStream;
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
||||
if (videoDevices.length > 1) {
|
||||
if (cameraSwitch) cameraSwitch.classList.add('active');
|
||||
}
|
||||
cameraContainer.classList.add('active');
|
||||
|
||||
// 切换时挂断情况
|
||||
const hasActive = dialBtn.classList.contains('dial-active');
|
||||
if (!hasActive) {
|
||||
cameraContainer.classList.remove('active');
|
||||
cameraSwitch.classList.remove('active');
|
||||
window.stopCamera();
|
||||
}
|
||||
log('摄像头已启动', 'success');
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -217,6 +233,30 @@ class App {
|
||||
}
|
||||
};
|
||||
|
||||
window.switchCamera = async() => {
|
||||
if (window.switchCameraTimer) return;
|
||||
if (this.cameraStream) {
|
||||
const currentTransform = window.getComputedStyle(cameraContainer).transform;
|
||||
const originalTransform = currentTransform === 'none' ? 'translate(0px, 0px)' : currentTransform;
|
||||
cameraContainer.style.setProperty('--original-transform', originalTransform);
|
||||
cameraContainer.classList.add('flip');
|
||||
this.currentFacingMode = this.currentFacingMode === 'user' ? 'environment' : 'user';
|
||||
window.stopCamera();
|
||||
window.startCamera();
|
||||
|
||||
window.switchCameraTimer = setTimeout(() => {
|
||||
if (this.currentFacingMode === 'user') {
|
||||
cameraVideo.style.transform = 'scaleX(-1)';
|
||||
} else {
|
||||
cameraVideo.style.transform = 'scaleX(1)';
|
||||
}
|
||||
window.switchCameraTimer = null;
|
||||
cameraContainer.classList.remove('flip');
|
||||
cameraContainer.style.removeProperty('--original-transform');
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
window.takePhoto = (question = '描述一下看到的物品') => {
|
||||
return new Promise(async (resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
|
||||
@@ -98,6 +98,14 @@ class UIController {
|
||||
});
|
||||
}
|
||||
|
||||
// Camera switch button
|
||||
const cameraSwitch = document.getElementById('cameraSwitch');
|
||||
if (cameraSwitch) {
|
||||
cameraSwitch.addEventListener('click', () => {
|
||||
window.switchCamera();
|
||||
})
|
||||
}
|
||||
|
||||
// Dial button
|
||||
const dialBtn = document.getElementById('dialBtn');
|
||||
if (dialBtn) {
|
||||
@@ -115,6 +123,7 @@ class UIController {
|
||||
if (isConnected) {
|
||||
wsHandler.disconnect();
|
||||
this.updateDialButton(false);
|
||||
if (cameraSwitch) cameraSwitch.classList.remove('active');
|
||||
this.addChatMessage('Disconnected, see you next time~😊', false);
|
||||
} else {
|
||||
// Check if OTA URL is filled
|
||||
@@ -153,6 +162,7 @@ class UIController {
|
||||
if (isActive) {
|
||||
// 关闭摄像头
|
||||
if (typeof window.stopCamera === 'function') {
|
||||
if (cameraSwitch) cameraSwitch.classList.remove('active');
|
||||
window.stopCamera();
|
||||
}
|
||||
cameraContainer.classList.remove('active');
|
||||
|
||||
@@ -44,6 +44,15 @@
|
||||
|
||||
<!-- 主容器 -->
|
||||
<div class="container">
|
||||
|
||||
<!-- 摄像头切换按钮 -->
|
||||
<div class="camera-switch" id="cameraSwitch">
|
||||
<svg class="btn-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15V18M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 摄像头显示区域(可拖动) -->
|
||||
<div class="camera-container" id="cameraContainer">
|
||||
<video id="cameraVideo" autoplay playsinline muted></video>
|
||||
|
||||
Reference in New Issue
Block a user