UNPKG

@face-detector/core

Version:

Face Detector Web SDK Core Package

3 lines (2 loc) 29.9 kB
"use strict";var e=Object.defineProperty,t=(t,s,i)=>((t,s,i)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[s]=i)(t,"symbol"!=typeof s?s+"":s,i);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("@mediapipe/tasks-vision");var i="undefined"!=typeof document?document.currentScript:null,n=(e=>(e.INITIALIZING="initializing",e.READY="ready",e.RUNNING="running",e.MEASURING="measuring",e.COMPLETED="completed",e.FAILED="failed",e))(n||{});class a{constructor(e,s){t(this,"_config"),t(this,"_eventEmitter"),this._config=e,this._eventEmitter=s}get config(){return this._config}get eventEmitter(){return this._eventEmitter}}const r=class e{constructor(){t(this,"debugEnabled",!1)}static getInstance(){return e.instance||(e.instance=new e),e.instance}setDebugMode(e){this.debugEnabled=e}debug(e,...t){this.debugEnabled&&console.log(`[FaceDetectionSDK] ${e}`,...t)}error(e,...t){console.error(`[FaceDetectionSDK ERROR] ${e}`,...t)}warn(e,...t){console.warn(`[FaceDetectionSDK WARN] ${e}`,...t)}};t(r,"instance");const o=r.getInstance();class c extends a{constructor(e,s){super(e,s),t(this,"faceDetector"),t(this,"faceDetectionManagerConfig"),t(this,"minDetectionConfidence"),t(this,"_isDetected"),t(this,"unsubFrameCaptured"),t(this,"unsubFrameWarmUp"),t(this,"_facePosition",null),t(this,"_ROI",null),t(this,"imageWidth",0),t(this,"imageHeight",0),t(this,"onResults",e=>{var t,s;const i=e.detections.length>0,n=this.isDetected;if(this.isDetected=i,i&&this.imageWidth>0&&this.imageHeight>0){const i=e.detections[0];i.boundingBox&&(this.facePosition=this.convertBoundingBoxToFacePosition(i.boundingBox),this.ROI=function(e,t,s){const i=function(e,t,s){const i=e.width*t*.6,n=e.height*s*.6;return{left:e.xCenter*t-i/2,top:e.yCenter*s-n/2,width:i,height:n}}(e,t,s),n=Math.max(0,Math.floor(i.left)),a=Math.max(0,Math.floor(i.top));return{sx:n,sy:a,sw:Math.max(1,Math.floor(Math.min(i.width,t-n))),sh:Math.max(1,Math.floor(Math.min(i.height,s-a)))}}(this.facePosition,this.imageWidth,this.imageHeight),n||o.debug("Face detected",{confidence:null==(s=null==(t=i.categories)?void 0:t[0])?void 0:s.score,position:this.facePosition}))}else n&&o.debug("Face lost"),this.facePosition=null,this.ROI=null,this.eventEmitter.emit("face:lost",void 0),this.eventEmitter.emit("system:running",void 0)}),this.faceDetectionManagerConfig=e,this.minDetectionConfidence=e.minDetectionConfidence,this._isDetected=!1,o.debug("FaceDetectionManager initialized",{minDetectionConfidence:e.minDetectionConfidence,delegate:e.delegate})}get isDetected(){return this._isDetected}set isDetected(e){this._isDetected=e}set facePosition(e){this._facePosition!==e&&(this._facePosition=e,this.eventEmitter.emit("face:position",e))}get facePosition(){return this._facePosition}get ROI(){return this._ROI}set ROI(e){this._ROI=e}async initialize(e){this.imageWidth=e.videoWidth,this.imageHeight=e.videoHeight,this.unSubscribeEventListeners(),this.faceDetector&&(this.faceDetector.close(),this.faceDetector=null);const t=await s.FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm"),i={baseOptions:{modelAssetPath:"https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite",delegate:this.faceDetectionManagerConfig.delegate},minDetectionConfidence:this.minDetectionConfidence,runningMode:"VIDEO"};this.faceDetector=await s.FaceDetector.createFromOptions(t,i),this.setupEventListeners(e),o.debug("MediaPipe Face Detection initialized",{videoSize:`${e.videoWidth}x${e.videoHeight}`,confidence:this.minDetectionConfidence})}reset(){this.facePosition=null,this.ROI=null,this.isDetected=!1,o.debug("FaceDetectionManager reset completed")}validateFacePosition(e,t=!1){const s=performance.now(),i=this.faceDetector.detectForVideo(e,s);t||this.onResults(i)}stop(){this._isDetected=!1}dispose(){this.isDetected=!1,this.faceDetector&&(this.faceDetector.close(),this.faceDetector=null),this.unSubscribeEventListeners()}setupEventListeners(e){var t;null==(t=this.unsubFrameWarmUp)||t.call(this),this.unsubFrameWarmUp=this.eventEmitter.subscribe("frame:warmUp",()=>{this.validateFacePosition(e,!0)})}unSubscribeEventListeners(){var e,t;null==(e=this.unsubFrameCaptured)||e.call(this),null==(t=this.unsubFrameWarmUp)||t.call(this),this.unsubFrameCaptured=void 0,this.unsubFrameWarmUp=void 0}convertBoundingBoxToFacePosition(e){return{xCenter:(e.originX+e.width/2)/this.imageWidth,yCenter:(e.originY+e.height/2)/this.imageHeight,width:e.width/this.imageWidth,height:e.height/this.imageHeight}}}class h{constructor(){t(this,"worker"),t(this,"pending",new Map),t(this,"requestId",0),t(this,"messageHandlers",new Map)}async initialize(e){this.worker&&(this.worker.terminate(),this.worker=null),this.worker=new Worker(e.workerUrl,{type:"module"}),e.messageHandlers&&Object.entries(e.messageHandlers).forEach(([e,t])=>{this.registerMessageHandler(e,t)}),this.worker.onmessage=t=>{const s=t.data;if("id"in s&&this.pending.has(s.id)){const{resolve:e}=this.pending.get(s.id);return e(s),void this.pending.delete(s.id)}const i=this.messageHandlers.get(s.type)||e.onMessage;i&&i(s)}}registerMessageHandler(e,t){this.messageHandlers.set(e,t)}sendMessage(e,t){if(!this.worker)throw new Error("WorkerService not initialized");this.worker.postMessage(e,t||[])}sendRequest(e,t){return this.worker?new Promise((s,i)=>{this.pending.set(e.id,{resolve:s,reject:i}),this.worker.postMessage(e,t||[])}):Promise.reject(new Error("WorkerService not initialized"))}generateRequestId(){return this.requestId++}clearPendingRequests(){this.pending.forEach(({reject:e})=>{e(new Error("WorkerService disposed"))}),this.pending.clear()}dispose(){this.clearPendingRequests(),this.messageHandlers.clear(),this.worker&&(this.worker.terminate(),this.worker=null)}}class u extends a{constructor(e,s){super(e,s),t(this,"workerService"),t(this,"_dataBucket"),t(this,"_progressPercentage"),t(this,"targetDataLength"),t(this,"unsubFaceLost"),t(this,"unsubCompleted"),this.targetDataLength=e.targetDataLength,this._dataBucket={sigR:[],sigG:[],sigB:[],timestamps:[]},o.debug("DataProcessingManager initialized",{targetDataLength:e.targetDataLength})}get dataBucket(){return this._dataBucket}get progressPercentage(){return this._progressPercentage}async initialize(e,t=!1){this.unSubscribeEventListeners();try{await this.initializeWorkerService(),this.clearDataBucket(),this.setUpEventListeners(),this.updateProgressPercentage(0),await this.connectVideoCanvas(e,t),o.debug("DataProcessingManager Worker initialized")}catch(s){o.error("DataProcessingManager initialization failed",s)}}reset(){this.clearDataBucket(),this.workerService&&this.workerService.clearPendingRequests(),this.updateProgressPercentage(0)}async dispose(){this.stop(),this.workerService&&(this.workerService.dispose(),this.workerService=null),this.unSubscribeEventListeners()}stop(){this.clearDataBucket(),this.workerService&&this.workerService.clearPendingRequests()}async connectVideoCanvas(e,t=!1){if("true"===e.dataset.transferred)return!0;try{const s=e.transferControlToOffscreen();e.dataset.transferred="true";const i={type:"connectVideoCanvas",id:this.workerService.generateRequestId(),extractingCanvas:s,willReadFrequently:t};return(await this.workerService.sendRequest(i,[s])).success}catch(s){return o.error("Offscreen canvas connection failed",s),!1}}async releaseVideoCanvas(){const e={type:"releaseVideoCanvas",id:this.workerService.generateRequestId()};return(await this.workerService.sendRequest(e)).success}processFrame(e,t=!1){const s={type:"processFrame",frame:e,isWarmUp:t};this.workerService.sendMessage(s,[e])}setUpEventListeners(){var e,t;null==(e=this.unsubFaceLost)||e.call(this),this.unsubFaceLost=this.eventEmitter.subscribe("face:lost",()=>{this.clearDataBucket(),this.updateProgressPercentage(this.calculateProgressPercentage())}),null==(t=this.unsubCompleted)||t.call(this),this.unsubCompleted=this.eventEmitter.subscribe("system:completed",()=>{})}async initializeWorkerService(){this.workerService=new h,await this.workerService.initialize({workerUrl:new URL("data:text/javascript;base64,bGV0IGV4dHJhY3RpbmdDYW52YXMgPSBudWxsOw0KbGV0IGV4dHJhY3RpbmdDb250ZXh0ID0gbnVsbDsNCg0Kc2VsZi5vbm1lc3NhZ2UgPSBmdW5jdGlvbiAoZSkgew0KICBjb25zdCBtZXNzYWdlID0gZS5kYXRhOw0KDQogIHN3aXRjaCAobWVzc2FnZS50eXBlKSB7DQogICAgY2FzZSAnY29ubmVjdFZpZGVvQ2FudmFzJzoNCiAgICAgIGhhbmRsZUNvbm5lY3RWaWRlb0NhbnZhcyhtZXNzYWdlLCBlKTsNCiAgICAgIGJyZWFrOw0KICAgIGNhc2UgJ3Byb2Nlc3NGcmFtZSc6DQogICAgICBoYW5kbGVQcm9jZXNzRnJhbWUobWVzc2FnZSwgZSk7DQogICAgICBicmVhazsNCiAgICBjYXNlICdyZWxlYXNlVmlkZW9DYW52YXMnOg0KICAgICAgaGFuZGxlUmVsZWFzZVZpZGVvQ2FudmFzKG1lc3NhZ2UsIGUpOw0KICAgICAgYnJlYWs7DQogICAgZGVmYXVsdDoNCiAgICAgIGNvbnNvbGUud2Fybign7JWMIOyImCDsl4bripQg66mU7Iuc7KeAIO2DgOyehTonLCBtZXNzYWdlLnR5cGUpOw0KICB9DQp9Ow0KDQpzZWxmLm9ubWVzc2FnZWVycm9yID0gZnVuY3Rpb24gKGUpIHsNCiAgY29uc29sZS5lcnJvcign7JuM7LukIOuplOyLnOyngCDsspjrpqwg7Jik66WYOicsIGUpOw0KfTsNCg0KZnVuY3Rpb24gaGFuZGxlQ29ubmVjdFZpZGVvQ2FudmFzKG1lc3NhZ2UsIGV2ZW50KSB7DQogIHRyeSB7DQogICAgY29uc3QgcmVjZWl2ZWRFeHRyYWN0aW5nQ2FudmFzID0NCiAgICAgIChtZXNzYWdlICYmIG1lc3NhZ2UuZXh0cmFjdGluZ0NhbnZhcykgfHwgKGV2ZW50ICYmIGV2ZW50LmRhdGEgJiYgZXZlbnQuZGF0YS5leHRyYWN0aW5nQ2FudmFzKTsNCg0KICAgIGlmICghcmVjZWl2ZWRFeHRyYWN0aW5nQ2FudmFzKSB7DQogICAgICB0aHJvdyBuZXcgRXJyb3IoJ+yYpO2UhOyKpO2BrOumsCDsupTrsoTsiqTqsIAg66mU7Iuc7KeA66GcIOyghOuLrOuQmOyngCDslYrslZjsirXri4jri6QnKTsNCiAgICB9DQoNCiAgICBjb25zdCB3aWxsUmVhZEZyZXF1ZW50bHkgPSBtZXNzYWdlICYmIG1lc3NhZ2Uud2lsbFJlYWRGcmVxdWVudGx5Ow0KICAgIGV4dHJhY3RpbmdDYW52YXMgPSByZWNlaXZlZEV4dHJhY3RpbmdDYW52YXM7DQogICAgZXh0cmFjdGluZ0NvbnRleHQgPSBleHRyYWN0aW5nQ2FudmFzLmdldENvbnRleHQoJzJkJywgeyB3aWxsUmVhZEZyZXF1ZW50bHkgfSk7DQoNCiAgICBpZiAoIWV4dHJhY3RpbmdDb250ZXh0KSB7DQogICAgICB0aHJvdyBuZXcgRXJyb3IoJ+yYpO2UhOyKpO2BrOumsCDsupTrsoTsiqQgMkQg7Luo7YWN7Iqk7Yq4IOyDneyEsSDsi6TtjKgnKTsNCiAgICB9DQoNCiAgICBzZWxmLnBvc3RNZXNzYWdlKHsNCiAgICAgIHR5cGU6ICdjb25uZWN0VmlkZW9DYW52YXNSZXNwb25zZScsDQogICAgICBzdWNjZXNzOiB0cnVlLA0KICAgICAgaWQ6IG1lc3NhZ2UuaWQsDQogICAgfSk7DQogIH0gY2F0Y2ggKGVycm9yKSB7DQogICAgY29uc29sZS5lcnJvcign67mE65SU7JikIOy6lOuyhOyKpCDsl7DqsrAg7Jik66WYOicsIGVycm9yKTsNCiAgICBzZWxmLnBvc3RNZXNzYWdlKHsNCiAgICAgIHR5cGU6ICdjb25uZWN0VmlkZW9DYW52YXNSZXNwb25zZScsDQogICAgICBzdWNjZXNzOiBmYWxzZSwNCiAgICAgIGlkOiBtZXNzYWdlLmlkLA0KICAgIH0pOw0KICB9DQp9DQoNCmZ1bmN0aW9uIGhhbmRsZVJlbGVhc2VWaWRlb0NhbnZhcyhtZXNzYWdlKSB7DQogIGxldCBzdWNjZXNzID0gZmFsc2U7DQogIGlmIChleHRyYWN0aW5nQ2FudmFzKSB7DQogICAgc3VjY2VzcyA9IHRydWU7DQogIH0NCg0KICBzZWxmLnBvc3RNZXNzYWdlKHsNCiAgICB0eXBlOiAncmVsZWFzZVZpZGVvQ2FudmFzUmVzcG9uc2UnLA0KICAgIHN1Y2Nlc3M6IHN1Y2Nlc3MsDQogICAgaWQ6IG1lc3NhZ2UuaWQsDQogIH0pOw0KICBleHRyYWN0aW5nQ2FudmFzID0gbnVsbDsNCiAgZXh0cmFjdGluZ0NvbnRleHQgPSBudWxsOw0KfQ0KDQpmdW5jdGlvbiBoYW5kbGVQcm9jZXNzRnJhbWUobWVzc2FnZSwgZXZlbnQpIHsNCiAgY29uc3QgeyBpc1dhcm1VcCB9ID0gbWVzc2FnZTsNCiAgbGV0IGV4dHJhY3RlZFJHQiA9IG51bGw7DQogIGxldCBmcmFtZSA9IG51bGw7DQoNCiAgdHJ5IHsNCiAgICBmcmFtZSA9IGV4dHJhY3RGcmFtZUZyb21FdmVudChldmVudCk7DQogICAgaWYgKGZyYW1lKSB7DQogICAgICAvLyBSR0Ig7LaU7LacIOyaqSDsupTrsoTsiqTsl5DshJwg7ZSE66CI7J6E7JeQ7IScIFJHQiDstpTstpwNCiAgICAgIGV4dHJhY3RlZFJHQiA9IGV4dHJhY3RSR0JGcm9tRnJhbWUoZnJhbWUpOw0KICAgIH0NCiAgfSBjYXRjaCAoZXJyb3IpIHsNCiAgICBjb25zb2xlLmVycm9yKCftlITroIjsnoQg7LKY66asIOyYpOulmDonLCBlcnJvcik7DQogICAgZXh0cmFjdGVkUkdCID0gbnVsbDsNCiAgfSBmaW5hbGx5IHsNCiAgICBpZiAoZnJhbWUgJiYgdHlwZW9mIGZyYW1lLmNsb3NlID09PSAnZnVuY3Rpb24nKSB7DQogICAgICBmcmFtZS5jbG9zZSgpOw0KICAgIH0NCiAgICBzZW5kUHJvY2Vzc0ZyYW1lUmVzcG9uc2UoZXh0cmFjdGVkUkdCLCBpc1dhcm1VcCk7DQogIH0NCn0NCg0KZnVuY3Rpb24gc2VuZFByb2Nlc3NGcmFtZVJlc3BvbnNlKGV4dHJhY3RlZFJHQiwgaXNXYXJtVXApIHsNCiAgc2VsZi5wb3N0TWVzc2FnZSh7DQogICAgdHlwZTogJ3Byb2Nlc3NGcmFtZVJlc3BvbnNlJywNCiAgICBleHRyYWN0ZWRSR0I6IGV4dHJhY3RlZFJHQiwNCiAgICBpc1dhcm1VcDogaXNXYXJtVXAsDQogIH0pOw0KfQ0KDQpmdW5jdGlvbiBleHRyYWN0UkdCRnJvbUZyYW1lKGZyYW1lKSB7DQogIGlmICghZXh0cmFjdGluZ0NhbnZhcyB8fCAhZXh0cmFjdGluZ0NvbnRleHQpIHsNCiAgICB0aHJvdyBuZXcgRXJyb3IoJ+yYpO2UhOyKpO2BrOumsCDsupTrsoTsiqTqsIAg66mU7Iuc7KeA66GcIOyghOuLrOuQmOyngCDslYrslZjsirXri4jri6QnKTsNCiAgfQ0KICAvLyDsupTrsoTsiqQg7YGs6riwIOyhsOyglQ0KICBpZiAoZXh0cmFjdGluZ0NhbnZhcy53aWR0aCAhPT0gZnJhbWUud2lkdGggfHwgZXh0cmFjdGluZ0NhbnZhcy5oZWlnaHQgIT09IGZyYW1lLmhlaWdodCkgew0KICAgIGV4dHJhY3RpbmdDYW52YXMud2lkdGggPSBmcmFtZS53aWR0aDsNCiAgICBleHRyYWN0aW5nQ2FudmFzLmhlaWdodCA9IGZyYW1lLmhlaWdodDsNCiAgfSBlbHNlIHsNCiAgICBleHRyYWN0aW5nQ29udGV4dC5jbGVhclJlY3QoMCwgMCwgZXh0cmFjdGluZ0NhbnZhcy53aWR0aCwgZXh0cmFjdGluZ0NhbnZhcy5oZWlnaHQpOw0KICB9DQoNCiAgZXh0cmFjdGluZ0NvbnRleHQuZHJhd0ltYWdlKGZyYW1lLCAwLCAwLCBmcmFtZS53aWR0aCwgZnJhbWUuaGVpZ2h0KTsNCg0KICBjb25zdCBpbWFnZURhdGEgPSBleHRyYWN0aW5nQ29udGV4dC5nZXRJbWFnZURhdGEoMCwgMCwgZnJhbWUud2lkdGgsIGZyYW1lLmhlaWdodCk7DQogIHJldHVybiBnZXRSR0JGcm9tSW1hZ2VEYXRhKGltYWdlRGF0YSk7DQp9DQoNCmZ1bmN0aW9uIGV4dHJhY3RGcmFtZUZyb21FdmVudChldmVudCkgew0KICBjb25zdCBtZXNzYWdlID0gZXZlbnQuZGF0YTsNCiAgaWYgKG1lc3NhZ2UgJiYgbWVzc2FnZS5mcmFtZSBpbnN0YW5jZW9mIEltYWdlQml0bWFwKSB7DQogICAgcmV0dXJuIG1lc3NhZ2UuZnJhbWU7DQogIH0NCg0KICBpZiAoZXZlbnQuZGF0YSBpbnN0YW5jZW9mIEltYWdlQml0bWFwKSB7DQogICAgcmV0dXJuIGV2ZW50LmRhdGE7DQogIH0NCg0KICByZXR1cm4gbnVsbDsNCn0NCg0KZnVuY3Rpb24gZ2V0UkdCRnJvbUltYWdlRGF0YShpbWFnZURhdGEpIHsNCiAgY29uc3QgYnVmZmVyRGF0YSA9IG5ldyBVaW50MzJBcnJheShpbWFnZURhdGEuZGF0YS5idWZmZXIpOw0KICBjb25zdCBsZW4gPSBidWZmZXJEYXRhLmxlbmd0aDsNCiAgbGV0IHNpZ1IgPSAwOw0KICBsZXQgc2lnRyA9IDA7DQogIGxldCBzaWdCID0gMDsNCiAgZm9yIChsZXQgaSA9IDA7IGkgPCBsZW47IGkrKykgew0KICAgIGNvbnN0IHBpeGVsID0gYnVmZmVyRGF0YVtpXTsNCiAgICBzaWdSICs9IHBpeGVsICYgMHhmZjsNCiAgICBzaWdHICs9IChwaXhlbCA+PiA4KSAmIDB4ZmY7DQogICAgc2lnQiArPSAocGl4ZWwgPj4gMTYpICYgMHhmZjsNCiAgfQ0KICBjb25zdCBzYW1wbGVkUGl4ZWxzID0gbGVuOw0KICByZXR1cm4gew0KICAgIHNpZ1I6IHNpZ1IgLyBzYW1wbGVkUGl4ZWxzLA0KICAgIHNpZ0c6IHNpZ0cgLyBzYW1wbGVkUGl4ZWxzLA0KICAgIHNpZ0I6IHNpZ0IgLyBzYW1wbGVkUGl4ZWxzLA0KICAgIHRpbWVzdGFtcDogRGF0ZS5ub3coKSAqIDEwMDAsDQogIH07DQp9DQo=","undefined"==typeof document?require("url").pathToFileURL(__filename).href:i&&"SCRIPT"===i.tagName.toUpperCase()&&i.src||new URL("index.cjs",document.baseURI).href).href,messageHandlers:{processFrameResponse:e=>{this.handleProcessFrameResponse(e)}}})}unSubscribeEventListeners(){var e,t;null==(e=this.unsubFaceLost)||e.call(this),this.unsubFaceLost=void 0,null==(t=this.unsubCompleted)||t.call(this),this.unsubCompleted=void 0}isDataBucketFull(){return this._dataBucket.timestamps.length>=this.targetDataLength}clearDataBucket(){this._dataBucket={sigR:[],sigG:[],sigB:[],timestamps:[]}}pushToDataBucket(e){this._dataBucket.sigR.push(e.sigR),this._dataBucket.sigG.push(e.sigG),this._dataBucket.sigB.push(e.sigB),this._dataBucket.timestamps.push(e.timestamp)}getReport(){return{rawData:this._dataBucket,quality:{positionError:0,yPositionError:0,dataPointCount:this._dataBucket.timestamps.length}}}calculateProgressPercentage(){return Math.round(this._dataBucket.timestamps.length/this.targetDataLength*100)}updateProgressPercentage(e){this._progressPercentage=e,this.eventEmitter.emit("progress:updated",this._progressPercentage)}handleProcessFrameResponse(e){e.isWarmUp?this.eventEmitter.emit("frame:warmUp",void 0):null!==e.extractedRGB&&(this.pushToDataBucket(e.extractedRGB),this.updateProgressPercentage(this.calculateProgressPercentage()),this.isDataBucketFull()&&(this.updateProgressPercentage(100),o.debug("Data collection completed",{dataPoints:this._dataBucket.timestamps.length,target:this.targetDataLength}),this.eventEmitter.emit("system:completed",this.getReport())))}}class m extends a{constructor(e,s,i){super(e,s),t(this,"webCamStream",null),t(this,"videoElement"),t(this,"_width"),t(this,"_height"),t(this,"_frame",null),this.videoElement=i,this._width=e.width,this._height=e.height,this.setupEventListeners(),o.debug("WebCamManager initialized",{size:`${e.width}x${e.height}`})}get frame(){return this._frame}get width(){return this._width}get height(){return this._height}initialize(){return new Promise(async(e,t)=>{try{this.webCamStream=await navigator.mediaDevices.getUserMedia({video:{width:this.width,height:this.height}}),this.handleWebcamMalfunction(),this.videoElement.srcObject=this.webCamStream,this.videoElement.oncanplay=()=>{this.startWebcam(),o.debug("Webcam stream connected",{size:`${this.width}x${this.height}`}),e()},this.videoElement.oninvalid,this.videoElement.onerror=e=>{o.error("Video stream load failed",e),this.eventEmitter.emit("webcam:videoError",void 0),t(new Error(`Video stream load failed: ${e}`))}}catch(s){o.error("Webcam initialization failed",s),t(s)}})}async reset(){this.stopWebCam(),await this.initialize()}startWebcam(){try{this.videoElement.play()}catch(e){o.error("Webcam start failed",e)}}stopWebCam(){var e;null==(e=this.webCamStream)||e.getTracks().forEach(e=>e.stop()),this.webCamStream=null,o.debug("Webcam stream stopped")}validateVideoReadyState(){return!(this.videoElement.readyState<3)}async captureFrame(e){try{this._frame=await createImageBitmap(this.videoElement,e.sx,e.sy,e.sw,e.sh)}catch(t){o.error("Frame capture failed",t)}}setupEventListeners(){}handleWebcamMalfunction(){var e,t,s,i;null==(t=null==(e=this.webCamStream)?void 0:e.getTracks()[0])||t.addEventListener("mute",()=>{console.log("webcam muted"),this.checkWebcamMutedAfterTimeout(1e3)}),null==(i=null==(s=this.webCamStream)?void 0:s.getTracks()[0])||i.addEventListener("ended",()=>{console.log("webcam ended"),this.eventEmitter.emit("webcam:streamEnded",void 0)})}checkWebcamMutedAfterTimeout(e){setTimeout(()=>{var e;const t=null==(e=this.webCamStream)?void 0:e.getTracks();if(t){t[0].muted&&this.eventEmitter.emit("webcam:streamMuted",void 0)}},e)}stop(){this.videoElement&&this.videoElement.pause(),this._frame=null}dispose(){this.stop(),this.stopWebCam(),this.videoElement&&(this.videoElement.srcObject=null)}clearFrame(){this._frame=null}}class g{constructor(e,s){t(this,"eventEmitter"),t(this,"measurementConfig"),t(this,"_shouldExcuteRGBExtraction"),t(this,"_countdown"),t(this,"isRunning",!1),t(this,"processingTimer"),t(this,"unsubscribeFunctions",[]),t(this,"dataProcessingManager"),t(this,"faceDetectionManager"),t(this,"webCamManager"),t(this,"loopCount"),t(this,"exptected"),t(this,"minSpacing"),this.eventEmitter=s,this.measurementConfig=e.measurementConfig,this.dataProcessingManager=new u(e.dataProcessingManagerConfig,s),this.faceDetectionManager=new c(e.faceDetectionManagerConfig,s),this.webCamManager=new m(e.webCamManagerConfig,s,e.measurementConfig.videoElement),this._shouldExcuteRGBExtraction=!0,o.debug("MeasurementLoop initialized",{countdown:e.measurementConfig.countdown,processingFps:e.measurementConfig.processingFps,targetDataLength:e.dataProcessingManagerConfig.targetDataLength}),this.resetProcess(),this.setUpEventListeners()}get shouldExcuteRGBExtraction(){return this._shouldExcuteRGBExtraction}get countdown(){return this._countdown}set countdown(e){this._countdown=e}get progressPercentage(){return this.dataProcessingManager.progressPercentage}get facePosition(){return this.faceDetectionManager.facePosition}setUpEventListeners(){this.unsubscribeFunctions.forEach(e=>e()),this.unsubscribeFunctions=[],this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:initialize",async()=>{await this.initialize()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:reset",()=>{this.resetSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:start",()=>{this.startSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:completed",()=>{this.stopSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:stop",()=>{this.stopSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:terminate",async()=>{await this.terminateSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:toggleMeasure",e=>{this.enableRGBExtraction(e)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("webcam:streamMuted",()=>{this.eventEmitter.emit("system:failed",new Error("Webcam stream muted")),this.stopSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("webcam:videoError",()=>{this.eventEmitter.emit("system:failed",new Error("Webcam video error")),this.stopSystem()})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("webcam:streamEnded",()=>{this.eventEmitter.emit("system:failed",new Error("Webcam stream ended")),this.stopSystem()}))}async initialize(){this.resetProcess();try{await this.webCamManager.initialize(),await Promise.all([this.dataProcessingManager.initialize(this.measurementConfig.extractingCanvas,this.measurementConfig.willReadFrequently),this.faceDetectionManager.initialize(this.measurementConfig.videoElement)]),await this.processWarmUpWhileInitializing(),this.eventEmitter.emit("system:ready",void 0)}catch(e){o.error("MeasurementLoop initialization failed",e),this.eventEmitter.emit("system:failed",e)}}resetSystem(){try{this.dataProcessingManager.reset(),this.faceDetectionManager.reset(),this.resetProcess(),this.eventEmitter.emit("system:ready",void 0)}catch(e){o.error("MeasurementLoop system reset failed",e),this.eventEmitter.emit("system:failed",e)}}async terminateSystem(){this.isRunning=!1,this.clearProcessingTimer(),this.webCamManager.dispose(),this.dataProcessingManager.dispose(),this.faceDetectionManager.dispose(),this.dispose()}dispose(){this.unsubscribeFunctions.forEach(e=>e()),this.unsubscribeFunctions=[]}stopSystem(){this.isRunning=!1,this.clearProcessingTimer(),this.webCamManager.clearFrame(),this.dataProcessingManager.stop(),this.faceDetectionManager.stop()}async startSystem(){this.isRunning||(await Promise.all([this.processCountdown(),this.processWarmUpWhileCountdown()]),o.debug("Measurement loop started"),this.runLoop())}runLoop(){if(!this.webCamManager.validateVideoReadyState())return void this.eventEmitter.emit("system:failed",new Error("Video is not ready"));this.isRunning=!0;const e=1e3/this.measurementConfig.processingFps;var t,s,i;this.exptected=performance.now()+e,this.minSpacing=(t=.15*e,s=6,i=12,Number.isNaN(t)?t:Math.max(s,Math.min(t,i))),this.processLoop(e)}processLoop(e){if(!this.shouldContinueLoop())return void this.handleLoopTermination();const t=this.getCalcualteInterval();this.processingTimer=setTimeout(()=>{this.shouldContinueLoop()?(this.processFrame(),this.exptected+=e,this.shouldContinueLoop()?this.processLoop(e):this.handleLoopTermination()):this.handleLoopTermination()},t)}async processFrame(){this.loopCount++;const e=this.shouldValidateFacePosition(),t=this.shouldExtractRGB();this.emitProcessingState(t),e&&this.faceDetectionManager.validateFacePosition(this.measurementConfig.videoElement),(t||e)&&t&&await this.extractAndProcessRGB()}emitProcessingState(e){e&&this.eventEmitter.emit("system:measuring",void 0)}async extractAndProcessRGB(){null!==this.faceDetectionManager.ROI&&(await this.webCamManager.captureFrame(this.faceDetectionManager.ROI),this.dataProcessingManager.processFrame(this.webCamManager.frame))}async warmUp(){this.faceDetectionManager.validateFacePosition(this.measurementConfig.videoElement,!0),null!==this.webCamManager.frame&&this.dataProcessingManager.processFrame(this.webCamManager.frame,!0)}shouldContinueLoop(){return!!this.isRunning&&(this.webCamManager.validateVideoReadyState()?!this.dataProcessingManager.isDataBucketFull():(this.eventEmitter.emit("system:failed",new Error("Video connection lost")),!1))}handleLoopTermination(){this.isRunning=!1,this.clearProcessingTimer()}async processCountdown(){for(this.countdown=this.measurementConfig.countdown;this.countdown>0;)this.countdown--,this.eventEmitter.emit("countdown:tick",this.countdown),await new Promise(e=>setTimeout(e,1e3))}async processWarmUpWhileCountdown(){for(;this.countdown>0;)await this.warmUp(),await new Promise(e=>setTimeout(e,100))}async processWarmUpWhileInitializing(){for(let e=0;e<5;e++)await this.warmUp(),await new Promise(e=>setTimeout(e,100))}clearProcessingTimer(){this.processingTimer&&(clearTimeout(this.processingTimer),this.processingTimer=void 0)}resetProcess(){this.countdown=0,this.loopCount=0,this.exptected=0,this._shouldExcuteRGBExtraction=!0,this.clearProcessingTimer()}getCalcualteInterval(){const e=performance.now(),t=this.exptected-e,s=t<=0?this.minSpacing:Math.max(t,this.minSpacing);return Math.max(s,0)}shouldValidateFacePosition(){return this.loopCount%(this.measurementConfig.processingFps/this.measurementConfig.validationFps)===0}shouldExtractRGB(){return this.faceDetectionManager.isDetected&&this.shouldExcuteRGBExtraction}enableRGBExtraction(e){try{if(this._shouldExcuteRGBExtraction===e)return;this._shouldExcuteRGBExtraction=e,this._shouldExcuteRGBExtraction?(this.dataProcessingManager.reset(),this.eventEmitter.emit("system:measuring",void 0)):this.eventEmitter.emit("system:running",void 0)}catch(t){o.error("RGB extraction toggle failed",t),this.eventEmitter.emit("system:failed",t)}}}class d{constructor(e){t(this,"state"),t(this,"eventEmitter"),t(this,"unsubscribeFunctions",[]),t(this,"validTransitions",{[n.INITIALIZING]:[n.READY,n.FAILED,n.INITIALIZING],[n.READY]:[n.RUNNING,n.MEASURING,n.INITIALIZING,n.FAILED,n.READY],[n.RUNNING]:[n.MEASURING,n.READY,n.FAILED,n.RUNNING],[n.MEASURING]:[n.RUNNING,n.COMPLETED,n.READY,n.FAILED,n.MEASURING],[n.COMPLETED]:[n.INITIALIZING,n.FAILED,n.COMPLETED],[n.FAILED]:[n.INITIALIZING,n.FAILED]}),this.state=n.INITIALIZING,this.eventEmitter=e,this.setupEventListeners(),o.debug("StateMachine initialized")}getState(){return this.state}setState(e){const t=this.state;this.isValidTransition(t,e)?(t!==e&&(this.state=e,o.debug("State changed",{from:t,to:e})),this.eventEmitter.emit("state:changed",e)):o.warn("Invalid state transition",{from:t,to:e})}setupEventListeners(){this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:initialize",()=>{this.setState(n.INITIALIZING)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:ready",()=>{this.setState(n.READY)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:start",()=>{this.setState(n.RUNNING)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:running",()=>{this.setState(n.RUNNING)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:measuring",()=>{this.setState(n.MEASURING)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:completed",()=>{this.setState(n.COMPLETED)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:stop",()=>{this.setState(n.READY)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:failed",()=>{this.setState(n.FAILED)})),this.unsubscribeFunctions.push(this.eventEmitter.subscribe("system:reset",()=>{this.setState(n.INITIALIZING)}))}dispose(){this.unsubscribeFunctions.forEach(e=>e()),this.unsubscribeFunctions=[]}isValidTransition(e,t){var s;return(null==(s=this.validTransitions[e])?void 0:s.includes(t))??!1}}class l{constructor(){t(this,"events",{})}emit(e,t){this.events[e]&&this.events[e].forEach(e=>e(t))}subscribe(e,t){return this.events[e]||(this.events[e]=[]),this.events[e].push(t),()=>{this.events[e]&&(this.events[e]=this.events[e].filter(e=>e!==t))}}unsubscribe(e,t){this.events[e]&&(this.events[e]=this.events[e].filter(e=>e!==t))}removeAllListeners(e){e?delete this.events[e]:this.events={}}}const b=(e,t)=>({faceDetectionManagerConfig:{minDetectionConfidence:.5,delegate:"CPU"},webCamManagerConfig:{width:640,height:480},measurementConfig:{countdown:3,processingFps:30,validationFps:1,retryCount:3,extractingCanvas:t,videoElement:e,willReadFrequently:!1},dataProcessingManagerConfig:{targetDataLength:450},debug:!0});exports.EventEmitter=l,exports.FaceDetector=class{constructor(e,s){let i;t(this,"stateMachine"),t(this,"measurementLoop"),t(this,"eventEmitter"),t(this,"activeUnsubscribeCompleted"),t(this,"activeUnsubscribeFailed"),i=e instanceof HTMLVideoElement?b(e,s):e,o.setDebugMode(i.debug),o.debug("FaceDetector initialization started",{debug:i.debug}),this.eventEmitter=new l,this.stateMachine=new d(this.eventEmitter),this.measurementLoop=new g(i,this.eventEmitter),this.eventEmitter.emit("system:initialize",void 0)}getState(){return this.stateMachine.getState()}getProgress(){return this.measurementLoop.progressPercentage}getCountdown(){return this.measurementLoop.countdown}getFacePosition(){return this.measurementLoop.facePosition}subscribeState(e){return this.eventEmitter.subscribe("state:changed",e),()=>this.eventEmitter.unsubscribe("state:changed",e)}subscribeProgress(e){return this.eventEmitter.subscribe("progress:updated",e),()=>this.eventEmitter.unsubscribe("progress:updated",e)}subscribeCountdown(e){return this.eventEmitter.subscribe("countdown:tick",e),()=>this.eventEmitter.unsubscribe("countdown:tick",e)}subscribeFacePosition(e){return this.eventEmitter.subscribe("face:position",e),()=>this.eventEmitter.unsubscribe("face:position",e)}async run(){if(this.getState()!==n.READY)throw new Error(`Cannot start detection. Current state: ${this.getState()}`);this.cleanupActiveSubscriptions();const e=performance.now();return this.eventEmitter.emit("system:start",void 0),new Promise((t,s)=>{this.activeUnsubscribeCompleted=this.eventEmitter.subscribe("system:completed",s=>{this.cleanupActiveSubscriptions(),t(s),o.debug("Measurement completed",{duration:`${((performance.now()-e)/1e3).toFixed(2)}s`,state:this.getState()})}),this.activeUnsubscribeFailed=this.eventEmitter.subscribe("system:failed",e=>{this.cleanupActiveSubscriptions(),s(e)})})}cleanupActiveSubscriptions(){var e,t;null==(e=this.activeUnsubscribeCompleted)||e.call(this),null==(t=this.activeUnsubscribeFailed)||t.call(this),this.activeUnsubscribeCompleted=void 0,this.activeUnsubscribeFailed=void 0}stop(){this.eventEmitter.emit("system:stop",void 0)}terminate(){this.eventEmitter.emit("system:terminate",void 0)}reset(){this.eventEmitter.emit("system:reset",void 0)}enableRGBExtraction(e){this.eventEmitter.emit("system:toggleMeasure",e)}},exports.MeasurementLoop=g,exports.ProcessState=n,exports.StateMachine=d,exports.defaultConfig=b; //# sourceMappingURL=index.cjs.map