Skip to content

Commit 7e56b94

Browse files
author
tianzhao
committed
png to svg
1 parent 2be9410 commit 7e56b94

2 files changed

Lines changed: 235 additions & 47 deletions

File tree

main.js

Lines changed: 231 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -296,46 +296,154 @@ class PoseEstimator {
296296

297297
// 设置摄像头
298298
async _setupCamera() {
299-
// 创建隐藏的video元素用于处理摄像头流
300-
this.video = document.createElement('video');
301-
this.video.id = 'video'; // 设置id为video
302-
this.video.style.display = 'none'; // 确保不显示video画布
303-
this.video.style.visibility = 'hidden'; // 额外隐藏属性
304-
this.video.style.position = 'absolute'; // 绝对定位
305-
this.video.style.left = '-9999px'; // 移出视窗
306-
this.video.autoplay = true;
307-
this.video.playsInline = true;
308-
this.video.muted = true;
309-
document.body.appendChild(this.video);
310-
311-
// 获取摄像头流
312-
this.stream = await navigator.mediaDevices.getUserMedia({ 'video': true });
313-
this.video.srcObject = this.stream;
299+
try {
300+
// 创建隐藏的video元素用于处理摄像头流
301+
this.video = document.createElement('video');
302+
303+
// 验证video元素创建成功
304+
if (!this.video) {
305+
throw new Error('无法创建video元素');
306+
}
307+
308+
this.video.id = 'video'; // 设置id为video
309+
this.video.style.display = 'none'; // 确保不显示video画布
310+
this.video.style.visibility = 'hidden'; // 额外隐藏属性
311+
this.video.style.position = 'absolute'; // 绝对定位
312+
this.video.style.left = '-9999px'; // 移出视窗
313+
this.video.autoplay = true;
314+
this.video.playsInline = true;
315+
this.video.muted = true;
316+
317+
// 验证document.body存在
318+
if (!document.body) {
319+
throw new Error('document.body不存在,无法添加video元素');
320+
}
321+
322+
document.body.appendChild(this.video);
323+
324+
// 再次验证video元素仍然存在
325+
if (!this.video) {
326+
throw new Error('video元素在添加到DOM后变为null');
327+
}
328+
329+
// 检查摄像头权限和可用性
330+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
331+
throw new Error('浏览器不支持摄像头访问或运行在非HTTPS环境');
332+
}
333+
334+
// 获取摄像头流
335+
console.log('正在请求摄像头权限...');
336+
this.stream = await navigator.mediaDevices.getUserMedia({
337+
'video': {
338+
width: { ideal: 640 },
339+
height: { ideal: 480 },
340+
facingMode: 'user'
341+
}
342+
});
343+
344+
// 验证流获取成功
345+
if (!this.stream) {
346+
throw new Error('无法获取摄像头流');
347+
}
348+
349+
// 最终验证video元素存在后再设置srcObject
350+
if (!this.video) {
351+
throw new Error('设置srcObject时video元素为null');
352+
}
353+
354+
this.video.srcObject = this.stream;
355+
console.log('摄像头流已设置到video元素');
356+
357+
} catch (error) {
358+
console.error('摄像头设置失败:', error);
359+
// 清理资源
360+
if (this.stream) {
361+
this.stream.getTracks().forEach(track => track.stop());
362+
this.stream = null;
363+
}
364+
if (this.video && this.video.parentNode) {
365+
this.video.parentNode.removeChild(this.video);
366+
this.video = null;
367+
}
368+
throw error;
369+
}
314370

315371
// 等待视频元数据加载完成
316-
await new Promise((resolve) => {
372+
await new Promise((resolve, reject) => {
373+
if (!this.video) {
374+
reject(new Error('video元素在等待元数据时为null'));
375+
return;
376+
}
377+
378+
const timeout = setTimeout(() => {
379+
reject(new Error('视频元数据加载超时'));
380+
}, 10000); // 10秒超时
381+
317382
this.video.onloadedmetadata = () => {
383+
clearTimeout(timeout);
318384
console.log('视频元数据已加载');
319385
resolve(this.video);
320386
};
387+
388+
this.video.onerror = (error) => {
389+
clearTimeout(timeout);
390+
reject(new Error(`视频加载错误: ${error.message || error}`));
391+
};
321392
});
322393

394+
// 验证video元素仍然存在
395+
if (!this.video) {
396+
throw new Error('video元素在元数据加载后为null');
397+
}
398+
323399
// 开始播放视频
324-
await this.video.play();
400+
try {
401+
await this.video.play();
402+
console.log('视频开始播放');
403+
} catch (playError) {
404+
console.error('视频播放失败:', playError);
405+
throw new Error(`视频播放失败: ${playError.message}`);
406+
}
325407

326408
// 等待视频真正开始播放并有数据
327-
await new Promise((resolve) => {
409+
await new Promise((resolve, reject) => {
410+
if (!this.video) {
411+
reject(new Error('video元素在等待播放就绪时为null'));
412+
return;
413+
}
414+
415+
let attempts = 0;
416+
const maxAttempts = 100; // 最多尝试10秒
417+
328418
const checkVideoReady = () => {
419+
attempts++;
420+
421+
if (!this.video) {
422+
reject(new Error('video元素在检查过程中变为null'));
423+
return;
424+
}
425+
329426
if (this.video.readyState >= 2 && this.video.videoWidth > 0 && this.video.videoHeight > 0) {
330427
console.log('视频已准备好播放');
331428
resolve();
429+
} else if (attempts >= maxAttempts) {
430+
reject(new Error(`视频准备超时,当前状态: readyState=${this.video.readyState}, width=${this.video.videoWidth}, height=${this.video.videoHeight}`));
332431
} else {
333432
setTimeout(checkVideoReady, 100);
334433
}
335434
};
336435
checkVideoReady();
337436
});
338437

438+
// 最终验证
439+
if (!this.video) {
440+
throw new Error('video元素在设置canvas尺寸时为null');
441+
}
442+
443+
if (!this.canvas) {
444+
throw new Error('canvas元素为null');
445+
}
446+
339447
// 设置canvas尺寸与视频流一致
340448
this.canvas.width = this.video.videoWidth;
341449
this.canvas.height = this.video.videoHeight;
@@ -546,7 +654,7 @@ class PoseEstimator {
546654

547655
// 设置文本样式
548656
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
549-
this.ctx.fillRect(10, 10, 220, 240);
657+
this.ctx.fillRect(10, 10, 200, 300);
550658

551659
this.ctx.fillStyle = 'white';
552660
this.ctx.font = '12px Arial';
@@ -604,12 +712,39 @@ class PoseEstimator {
604712

605713
// 公共启动方法
606714
async start() {
607-
// 设置参数控制事件监听器(确保DOM已加载)
608-
this._setupParameterControls();
609-
610-
await this._setupCamera();
611-
await this._loadModel();
612-
this._detectPoseInRealTime();
715+
try {
716+
// 设置参数控制事件监听器(确保DOM已加载)
717+
this._setupParameterControls();
718+
719+
// 检查基本环境
720+
if (!this.canvas) {
721+
throw new Error('Canvas元素未初始化');
722+
}
723+
724+
if (!this.ctx) {
725+
throw new Error('Canvas上下文未初始化');
726+
}
727+
728+
console.log('开始设置摄像头...');
729+
await this._setupCamera();
730+
731+
console.log('开始加载AI模型...');
732+
await this._loadModel();
733+
734+
console.log('开始实时检测循环...');
735+
this._detectPoseInRealTime();
736+
737+
console.log('姿态估计器启动完成');
738+
739+
} catch (error) {
740+
console.error('姿态估计器启动失败:', error);
741+
742+
// 清理已分配的资源
743+
this.cleanup();
744+
745+
// 重新抛出错误供上层处理
746+
throw error;
747+
}
613748
}
614749
}
615750

@@ -636,45 +771,98 @@ function hideLoadingStatus() {
636771

637772
// --- Main Execution ---
638773
async function main() {
639-
const canvas = document.getElementById('canvas');
640-
if (!canvas) {
641-
console.error('Canvas元素未找到');
642-
return;
643-
}
644-
645774
try {
646-
// 1. 初始化缓存管理器
775+
// 1. 基础环境检查
776+
showLoadingStatus('检查运行环境...');
777+
778+
// 检查HTTPS环境
779+
if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
780+
throw new Error('摄像头访问需要HTTPS环境或本地环境');
781+
}
782+
783+
// 检查浏览器兼容性
784+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
785+
throw new Error('浏览器不支持摄像头访问API');
786+
}
787+
788+
if (!window.tf && !window.poseDetection) {
789+
throw new Error('TensorFlow.js库未正确加载');
790+
}
791+
792+
// 检查Canvas元素
793+
const canvas = document.getElementById('canvas');
794+
if (!canvas) {
795+
throw new Error('Canvas元素未找到,请检查HTML结构');
796+
}
797+
798+
if (!canvas.getContext) {
799+
throw new Error('浏览器不支持Canvas API');
800+
}
801+
802+
// 2. 初始化缓存管理器
647803
showLoadingStatus('初始化缓存系统...');
648-
await modelCacheManager.initDB();
804+
try {
805+
await modelCacheManager.initDB();
806+
} catch (dbError) {
807+
console.warn('IndexedDB初始化失败,将使用内存缓存:', dbError);
808+
// 继续执行,只是没有持久化缓存
809+
}
649810

650-
// 2. 预加载模型(后台进行)
811+
// 3. 预加载模型(后台进行)
651812
showLoadingStatus('预加载AI模型...');
652-
const preloadPromise = PoseEstimator.preloadModels();
813+
const preloadPromise = PoseEstimator.preloadModels().catch(error => {
814+
console.warn('模型预加载失败,将在需要时加载:', error);
815+
// 不阻止主流程,模型可以在需要时加载
816+
});
653817

654-
// 3. 创建姿态估计器实例
818+
// 4. 创建姿态估计器实例
819+
showLoadingStatus('初始化姿态估计器...');
655820
globalEstimator = new PoseEstimator(canvas);
656821

657-
// 4. 等待预加载完成(如果还没完成)
822+
// 5. 等待预加载完成(如果还没完成)
658823
await preloadPromise;
659824

660-
// 5. 启动姿态估计器
825+
// 6. 启动姿态估计器
661826
showLoadingStatus('启动摄像头和AI检测...');
662827
await globalEstimator.start();
663828

664829
hideLoadingStatus();
665-
console.log('姿态估计器启动成功');
830+
console.log('🎉 姿态估计器启动成功!');
666831

667832
// 显示缓存统计信息
668833
const cacheStats = {
669834
memoryCache: modelCacheManager.modelCache.size,
670-
dbInitialized: !!modelCacheManager.db
835+
dbInitialized: !!modelCacheManager.db,
836+
environment: location.protocol,
837+
userAgent: navigator.userAgent.substring(0, 50) + '...'
671838
};
672-
console.log('缓存统计:', cacheStats);
839+
console.log('📊 系统状态:', cacheStats);
673840

674841
} catch (error) {
675-
console.error('启动姿态估计器失败:', error);
842+
console.error('启动姿态估计器失败:', error);
676843
hideLoadingStatus();
677-
alert('启动失败,请检查摄像头权限或刷新页面重试');
844+
845+
// 根据错误类型提供不同的用户提示
846+
let userMessage = '启动失败,请尝试以下解决方案:\n\n';
847+
848+
if (error.message.includes('HTTPS')) {
849+
userMessage += '• 请使用HTTPS协议访问此页面\n• 或在本地环境(localhost)中运行';
850+
} else if (error.message.includes('摄像头') || error.message.includes('getUserMedia')) {
851+
userMessage += '• 请允许摄像头访问权限\n• 确保摄像头未被其他应用占用\n• 尝试刷新页面重新授权';
852+
} else if (error.message.includes('TensorFlow')) {
853+
userMessage += '• 网络连接问题,AI库加载失败\n• 请检查网络连接后刷新页面';
854+
} else if (error.message.includes('Canvas')) {
855+
userMessage += '• 浏览器不支持Canvas功能\n• 请使用现代浏览器(Chrome, Firefox, Safari, Edge)';
856+
} else {
857+
userMessage += '• 请刷新页面重试\n• 检查浏览器控制台获取详细错误信息\n• 确保使用现代浏览器';
858+
}
859+
860+
userMessage += '\n\n详细错误: ' + error.message;
861+
862+
alert(userMessage);
863+
864+
// 显示错误状态
865+
showLoadingStatus('❌ 启动失败: ' + error.message);
678866
}
679867
}
680868

manifest.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
"scope": "/",
1111
"icons": [
1212
{
13-
"src": "icon-192.png",
13+
"src": "icon-192.svg",
1414
"sizes": "192x192",
15-
"type": "image/png",
15+
"type": "image/svg",
1616
"purpose": "any maskable"
1717
},
1818
{
19-
"src": "icon-512.png",
19+
"src": "icon-512.svg",
2020
"sizes": "512x512",
21-
"type": "image/png",
21+
"type": "image/svg",
2222
"purpose": "any maskable"
2323
}
2424
],

0 commit comments

Comments
 (0)