mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-29 00:33:51 +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;
|
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) {
|
@media (max-width: 768px) {
|
||||||
.container {
|
.container {
|
||||||
@@ -1264,6 +1356,19 @@ body {
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
bottom: 80px;
|
bottom: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-ipt input {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera-container {
|
||||||
|
width: 130px;
|
||||||
|
height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
@@ -1344,67 +1449,3 @@ body {
|
|||||||
height: 80px;
|
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.audioPlayer = null;
|
||||||
this.live2dManager = null;
|
this.live2dManager = null;
|
||||||
this.cameraStream = null;
|
this.cameraStream = null;
|
||||||
|
this.currentFacingMode = 'user';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化应用
|
// 初始化应用
|
||||||
@@ -127,6 +128,8 @@ class App {
|
|||||||
async initCamera() {
|
async initCamera() {
|
||||||
const cameraContainer = document.getElementById('cameraContainer');
|
const cameraContainer = document.getElementById('cameraContainer');
|
||||||
const cameraVideo = document.getElementById('cameraVideo');
|
const cameraVideo = document.getElementById('cameraVideo');
|
||||||
|
const cameraSwitch = document.getElementById('cameraSwitch');
|
||||||
|
const dialBtn = document.getElementById('dialBtn');
|
||||||
|
|
||||||
if (!cameraContainer || !cameraVideo) {
|
if (!cameraContainer || !cameraVideo) {
|
||||||
log('摄像头元素未找到,跳过初始化', 'warning');
|
log('摄像头元素未找到,跳过初始化', 'warning');
|
||||||
@@ -188,11 +191,24 @@ class App {
|
|||||||
}
|
}
|
||||||
log('正在请求摄像头权限...', 'info');
|
log('正在请求摄像头权限...', 'info');
|
||||||
this.cameraStream = await navigator.mediaDevices.getUserMedia({
|
this.cameraStream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: { width: 320, height: 240, facingMode: 'user' },
|
video: { width: 320, height: 240, facingMode: this.currentFacingMode },
|
||||||
audio: false
|
audio: false
|
||||||
});
|
});
|
||||||
cameraVideo.srcObject = this.cameraStream;
|
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');
|
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');
|
log('摄像头已启动', 'success');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} 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 = '描述一下看到的物品') => {
|
window.takePhoto = (question = '描述一下看到的物品') => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const canvas = document.createElement('canvas');
|
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
|
// Dial button
|
||||||
const dialBtn = document.getElementById('dialBtn');
|
const dialBtn = document.getElementById('dialBtn');
|
||||||
if (dialBtn) {
|
if (dialBtn) {
|
||||||
@@ -115,6 +123,7 @@ class UIController {
|
|||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
wsHandler.disconnect();
|
wsHandler.disconnect();
|
||||||
this.updateDialButton(false);
|
this.updateDialButton(false);
|
||||||
|
if (cameraSwitch) cameraSwitch.classList.remove('active');
|
||||||
this.addChatMessage('Disconnected, see you next time~😊', false);
|
this.addChatMessage('Disconnected, see you next time~😊', false);
|
||||||
} else {
|
} else {
|
||||||
// Check if OTA URL is filled
|
// Check if OTA URL is filled
|
||||||
@@ -153,6 +162,7 @@ class UIController {
|
|||||||
if (isActive) {
|
if (isActive) {
|
||||||
// 关闭摄像头
|
// 关闭摄像头
|
||||||
if (typeof window.stopCamera === 'function') {
|
if (typeof window.stopCamera === 'function') {
|
||||||
|
if (cameraSwitch) cameraSwitch.classList.remove('active');
|
||||||
window.stopCamera();
|
window.stopCamera();
|
||||||
}
|
}
|
||||||
cameraContainer.classList.remove('active');
|
cameraContainer.classList.remove('active');
|
||||||
|
|||||||
@@ -44,6 +44,15 @@
|
|||||||
|
|
||||||
<!-- 主容器 -->
|
<!-- 主容器 -->
|
||||||
<div class="container">
|
<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">
|
<div class="camera-container" id="cameraContainer">
|
||||||
<video id="cameraVideo" autoplay playsinline muted></video>
|
<video id="cameraVideo" autoplay playsinline muted></video>
|
||||||
|
|||||||
Reference in New Issue
Block a user