@@ -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 ---
638773async 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
0 commit comments