Merge pull request #2957 from xinnan-tech/test_live2d

fix:手机前后摄像头切换,调整摄像头为竖屏显示
This commit is contained in:
欣南科技
2026-02-11 11:32:53 +08:00
committed by GitHub
4 changed files with 165 additions and 65 deletions
+105 -64
View File
@@ -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);
}
+41 -1
View File
@@ -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');
+9
View File
@@ -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>