@georapbox/capture-photo-element
Advanced tools
Comparing version 4.0.1 to 4.1.0
@@ -1,2 +0,11 @@ | ||
Object.defineProperty({},"CapturePhoto",{get:function(){return n},set:void 0,enumerable:!0,configurable:!0});let t=(t,e,i)=>(Number.isNaN(e)&&(e=0),Number.isNaN(i)&&(i=0),Math.min(Math.max(t,Math.min(e,i)),Math.max(e,i))),e="capture-photo",i=` | ||
/*! | ||
* @georapbox/capture-photo-element | ||
* A custom element that implements the MediaDevices.getUserMedia() method of the MediaDevices interface to capture a photo in the browser. | ||
* | ||
* @version 4.1.0 | ||
* @homepage https://github.com/georapbox/capture-photo-element#readme | ||
* @author George Raptis <georapbox@gmail.com> | ||
* @license MIT | ||
*/ | ||
var u=(r,t,i)=>(Number.isNaN(t)&&(t=0),Number.isNaN(i)&&(i=0),Math.min(Math.max(r,Math.min(t,i)),Math.max(t,i)));var a="capture-photo",g=` | ||
:host { | ||
@@ -26,4 +35,4 @@ display: block; | ||
} | ||
`,o=document.createElement("template");o.innerHTML=` | ||
<style>${i}</style> | ||
`,d=document.createElement("template");d.innerHTML=` | ||
<style>${g}</style> | ||
@@ -53,3 +62,3 @@ <video part="video" playsinline></video> | ||
<div part="output-container" id="output"></div> | ||
`;class n extends HTMLElement{#t={};#e=null;#i=null;#o=null;#n=null;#a=null;#s=null;#r=null;#l=null;constructor(){super(),this.#t=this.getSupportedConstraints(),this.shadowRoot||this.attachShadow({mode:"open"}).appendChild(o.content.cloneNode(!0))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","pan","tilt","zoom"]}attributeChangedCallback(t,e,i){if(!this.isConnected)return;let o=this.getTrackCapabilities(),n=this.getTrackSettings();if("no-image"===t&&e!==i&&this.#u(),"facing-mode"===t&&e!==i&&"facingMode"in this.#t){let t=["user","environment"].includes(this.facingMode||"");"facingMode"in n&&t&&(this.stopVideoStream(),this.startVideoStream())}if("camera-resolution"===t&&e!==i&&"string"==typeof this.cameraResolution&&this.cameraResolution.trim().length>0){let[t=0,e=0]=this.cameraResolution.split("x").map(t=>Number(t));if(t>0&&e>0&&"width"in o&&"height"in o){let i=!!(o.width?.min&&o.width?.max)&&t>=o?.width?.min&&t<=o?.width?.max,a=!!(o.height?.min&&o.height?.max)&&e>=o?.height?.min&&e<=o?.height?.max;"width"in n&&"height"in n&&i&&a&&(this.stopVideoStream(),this.startVideoStream())}}if("pan"===t&&e!==i&&"pan"in this.#t){let t=!!("pan"in o&&o.pan?.min&&o.pan?.max)&&this.pan>=o.pan.min&&this.pan<=o.pan.max;"pan"in n&&"number"==typeof this.pan&&t&&this.#h("pan",this.pan)}if("tilt"===t&&e!==i&&"tilt"in this.#t){let t=!!("tilt"in o&&o.tilt?.min&&o.tilt?.max)&&this.tilt>=o.tilt.min&&this.tilt<=o.tilt.max;"tilt"in n&&"number"==typeof this.tilt&&t&&this.#h("tilt",this.tilt)}if("zoom"===t&&e!==i&&"zoom"in this.#t){let t=!!("zoom"in o&&o.zoom?.min&&o.zoom?.max)&&this.zoom>=o.zoom.min&&this.zoom<=o.zoom.max;"zoom"in n&&"number"==typeof this.zoom&&t&&this.#h("zoom",this.zoom)}}connectedCallback(){if(this.#d("autpoPlay"),this.#d("noImage"),this.#d("facingMode"),this.#d("cameraResolution"),this.#d("pan"),this.#d("tilt"),this.#d("zoom"),this.#d("calculateFileSize"),this.#i=this.shadowRoot?.querySelector("canvas")||null,this.#o=this.shadowRoot?.getElementById("output")||null,this.#n=this.shadowRoot?.querySelector("video")||null,this.#a=this.shadowRoot?.querySelector('slot[name="capture-button"]')||null,this.#s=this.#c(),this.#r=this.shadowRoot?.querySelector('slot[name="facing-mode-button"]')||null,this.#l=this.#m(),this.#n?.addEventListener("loadedmetadata",this.#p),this.#a?.addEventListener("slotchange",this.#g),this.#s?.addEventListener("click",this.#b),this.#r?.addEventListener("slotchange",this.#f),this.#l?.addEventListener("click",this.#v),!n.isSupported())return this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));this.autoPlay&&this.startVideoStream()}disconnectedCallback(){this.stopVideoStream(),this.#l?.removeEventListener("click",this.#v),this.#s?.removeEventListener("click",this.#b),this.#n?.removeEventListener("canplay",this.#p),this.#a?.removeEventListener("slotchange",this.#g),this.#r?.removeEventListener("slotchange",this.#f)}get autoPlay(){return this.hasAttribute("auto-play")}set autoPlay(t){this.toggleAttribute("auto-play",!!t)}get noImage(){return this.hasAttribute("no-image")}set noImage(t){this.toggleAttribute("no-image",!!t)}get facingMode(){return this.getAttribute("facing-mode")||"user"}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")||""}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get pan(){return Number(this.getAttribute("pan"))||0}set pan(t){this.setAttribute("pan",null!=t?t.toString():t)}get tilt(){return Number(this.getAttribute("tilt"))||0}set tilt(t){this.setAttribute("tilt",null!=t?t.toString():t)}get zoom(){return Number(this.getAttribute("zoom"))||1}set zoom(t){this.setAttribute("zoom",null!=t?t.toString():t)}get loading(){return this.hasAttribute("loading")}get calculateFileSize(){return this.hasAttribute("calculate-file-size")}set calculateFileSize(t){this.toggleAttribute("calculate-file-size",!!t)}#v=t=>{t.preventDefault(),this.loading||(this.facingMode="user"!==this.facingMode&&this.facingMode?"user":"environment")};#b=t=>{t.preventDefault(),this.capture()};#p=t=>{let i=t.target;i.play().then(()=>{this.dispatchEvent(new CustomEvent(`${e}:video-play`,{bubbles:!0,composed:!0,detail:{video:i}}))}).catch(t=>{this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}).finally(()=>{this.removeAttribute("loading")})};#u(){this.#o&&Array.from(this.#o.childNodes).forEach(t=>t.remove())}#h(e,i){if(!this.#e||!e||!i)return;let[o]=this.#e.getVideoTracks(),n=this.getTrackCapabilities();e in this.getTrackSettings()&&o.applyConstraints({advanced:[{[e]:t(Number(i),n[e]?.min||1,n[e]?.max||1)}]})}#g=t=>{t.target?.name==="capture-button"&&(this.#s?.removeEventListener("click",this.#b),this.#s=this.#c(),this.#s&&(this.#s.addEventListener("click",this.#b),"BUTTON"===this.#s.nodeName||this.#s.hasAttribute("role")||this.#s.setAttribute("role","button")))};#f=t=>{t.target?.name==="facing-mode-button"&&(this.#l?.removeEventListener("click",this.#v),this.#l=this.#m(),this.#l&&(this.#l.addEventListener("click",this.#v),"BUTTON"===this.#l.nodeName||this.#l.hasAttribute("role")||this.#l.setAttribute("role","button")))};#m(){return this.#r&&this.#r.assignedElements({flatten:!0}).find(t=>"BUTTON"===t.nodeName||"facing-mode-button"===t.getAttribute("slot"))||null}#c(){return this.#a&&this.#a.assignedElements({flatten:!0}).find(t=>"BUTTON"===t.nodeName||"capture-button"===t.getAttribute("slot"))||null}#d(t){if(Object.prototype.hasOwnProperty.call(this,t)){let e=this[t];delete this[t],this[t]=e}}async startVideoStream(){if(!n.isSupported()||this.#e)return;this.setAttribute("loading","");let t={video:{facingMode:{ideal:this.facingMode||"user"},pan:!0,tilt:!0,zoom:!0},audio:!1};if("string"==typeof this.cameraResolution&&this.cameraResolution.trim().length>0){let[e=0,i=0]=this.cameraResolution.split("x").map(t=>Number(t));e>0&&i>0&&(t.video.width=e,t.video.height=i)}try{this.#e=await navigator.mediaDevices.getUserMedia(t),this.#n&&(this.#n.srcObject=this.#e),this.#h("pan",this.pan),this.#h("tilt",this.tilt),this.#h("zoom",this.zoom);let e=this.getTrackSettings();"facingMode"in e&&this.#r&&(this.#r.hidden=!1)}catch(t){this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}finally{this.removeAttribute("loading")}}stopVideoStream(){if(!this.#n||!this.#e)return;let[t]=this.#e.getVideoTracks();t?.stop(),this.#n.srcObject=null,this.#e=null}async capture(){if(!this.loading&&this.#i&&this.#n)try{let t=this.#i.getContext("2d"),i=this.#n.videoWidth,o=this.#n.videoHeight;this.#i.width=i,this.#i.height=o,t?.drawImage(this.#n,0,0,i,o);let n=this.#i.toDataURL("image/png");if("string"==typeof n&&n.includes("data:image")){if(!this.noImage){let t=new Image;t.src=n,t.width=i,t.height=o,t.setAttribute("part","output-image"),this.#u(),this.#o?.appendChild(t)}let t={dataURI:n,width:i,height:o};if(this.calculateFileSize)try{let e=await fetch(n),i=(await e.blob()).size;i&&(t.size=i)}catch(t){}this.dispatchEvent(new CustomEvent(`${e}:success`,{bubbles:!0,composed:!0,detail:t}))}}catch(t){this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}}getSupportedConstraints(){return n.isSupported()&&navigator.mediaDevices.getSupportedConstraints()||{}}getTrackCapabilities(){if(!this.#e)return{};let[t]=this.#e.getVideoTracks();return t&&"function"==typeof t.getCapabilities&&t.getCapabilities()||{}}getTrackSettings(){if(!this.#e)return{};let[t]=this.#e.getVideoTracks();return t&&"function"==typeof t.getSettings&&t.getSettings()||{}}static isSupported(){return!!navigator.mediaDevices?.getUserMedia}static defineCustomElement(t=e){"undefined"==typeof window||window.customElements.get(t)||window.customElements.define(t,n)}}n.defineCustomElement();export{n as CapturePhoto}; | ||
`;var l=class r extends HTMLElement{#r={};#t=null;#h=null;#l=null;#e=null;#c=null;#i=null;#n=null;#s=null;constructor(){super(),this.#r=this.getSupportedConstraints(),this.shadowRoot||this.attachShadow({mode:"open"}).appendChild(d.content.cloneNode(!0))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","pan","tilt","zoom","torch"]}attributeChangedCallback(t,i,s){if(!this.isConnected)return;let e=this.getTrackCapabilities(),n=this.getTrackSettings();if(t==="no-image"&&i!==s&&this.#g(),t==="facing-mode"&&i!==s&&"facingMode"in this.#r){let o=["user","environment"].includes(this.facingMode||"");"facingMode"in n&&o&&this.#y()}if(t==="camera-resolution"&&i!==s&&typeof this.cameraResolution=="string"&&this.cameraResolution.trim().length>0){let[o=0,c=0]=this.cameraResolution.split("x").map(h=>Number(h));if(o>0&&c>0&&"width"in e&&"height"in e){let h=e.width?.min&&e.width?.max?o>=e?.width?.min&&o<=e?.width?.max:!1,m=e.height?.min&&e.height?.max?c>=e?.height?.min&&c<=e?.height?.max:!1;"width"in n&&"height"in n&&h&&m&&this.#y()}}if(t==="pan"&&i!==s&&"pan"in this.#r){let o="pan"in e&&e.pan?.min&&e.pan?.max?this.pan>=e.pan.min&&this.pan<=e.pan.max:!1;typeof this.pan=="number"&&o&&this.#a("pan",this.pan)}if(t==="tilt"&&i!==s&&"tilt"in this.#r){let o="tilt"in e&&e.tilt?.min&&e.tilt?.max?this.tilt>=e.tilt.min&&this.tilt<=e.tilt.max:!1;typeof this.tilt=="number"&&o&&this.#a("tilt",this.tilt)}if(t==="zoom"&&i!==s&&"zoom"in this.#r){let o="zoom"in e&&e.zoom?.min&&e.zoom?.max?this.zoom>=e.zoom.min&&this.zoom<=e.zoom.max:!1;typeof this.zoom=="number"&&o&&this.#a("zoom",this.zoom)}t==="torch"&&i!==s&&"torch"in this.#r&&this.#a("torch",this.torch)}connectedCallback(){if(this.#o("autpoPlay"),this.#o("noImage"),this.#o("facingMode"),this.#o("cameraResolution"),this.#o("pan"),this.#o("tilt"),this.#o("zoom"),this.#o("torch"),this.#o("calculateFileSize"),this.#h=this.shadowRoot?.querySelector("canvas")||null,this.#l=this.shadowRoot?.getElementById("output")||null,this.#e=this.shadowRoot?.querySelector("video")||null,this.#c=this.shadowRoot?.querySelector('slot[name="capture-button"]')||null,this.#i=this.#v(),this.#n=this.shadowRoot?.querySelector('slot[name="facing-mode-button"]')||null,this.#s=this.#b(),this.#e?.addEventListener("loadedmetadata",this.#m),this.#c?.addEventListener("slotchange",this.#p),this.#i?.addEventListener("click",this.#d),this.#n?.addEventListener("slotchange",this.#f),this.#s?.addEventListener("click",this.#u),!r.isSupported())return this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));this.autoPlay&&this.startVideoStream()}disconnectedCallback(){this.stopVideoStream(),this.#s?.removeEventListener("click",this.#u),this.#i?.removeEventListener("click",this.#d),this.#e?.removeEventListener("canplay",this.#m),this.#c?.removeEventListener("slotchange",this.#p),this.#n?.removeEventListener("slotchange",this.#f)}get autoPlay(){return this.hasAttribute("auto-play")}set autoPlay(t){this.toggleAttribute("auto-play",!!t)}get noImage(){return this.hasAttribute("no-image")}set noImage(t){this.toggleAttribute("no-image",!!t)}get facingMode(){return this.getAttribute("facing-mode")||"user"}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")||""}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get pan(){return Number(this.getAttribute("pan"))||0}set pan(t){this.setAttribute("pan",t!=null?t.toString():t)}get tilt(){return Number(this.getAttribute("tilt"))||0}set tilt(t){this.setAttribute("tilt",t!=null?t.toString():t)}get zoom(){return Number(this.getAttribute("zoom"))||1}set zoom(t){this.setAttribute("zoom",t!=null?t.toString():t)}get torch(){return this.hasAttribute("torch")}set torch(t){this.toggleAttribute("torch",!!t)}get loading(){return this.hasAttribute("loading")}get calculateFileSize(){return this.hasAttribute("calculate-file-size")}set calculateFileSize(t){this.toggleAttribute("calculate-file-size",!!t)}#u=t=>{t.preventDefault(),!this.loading&&(this.facingMode=this.facingMode==="user"||!this.facingMode?"environment":"user")};#d=t=>{t.preventDefault(),this.capture()};#m=t=>{let i=t.target;i.play().then(()=>{this.dispatchEvent(new CustomEvent(`${a}:video-play`,{bubbles:!0,composed:!0,detail:{video:i}}))}).catch(s=>{this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:s}}))}).finally(()=>{this.removeAttribute("loading")})};#g(){this.#l&&Array.from(this.#l.childNodes).forEach(t=>t.remove())}#a(t,i){if(!this.#t)return;let[s]=this.#t.getVideoTracks(),e=this.getTrackCapabilities(),n=this.getTrackSettings(),o=t==="pan"||t==="tilt"||t==="zoom"?u(Number(i),e[t]?.min||1,e[t]?.max||1):i;t in n&&s.applyConstraints({advanced:[{[t]:o}]}).catch(()=>{})}#p=t=>{t.target?.name==="capture-button"&&(this.#i?.removeEventListener("click",this.#d),this.#i=this.#v(),this.#i&&(this.#i.addEventListener("click",this.#d),this.#i.nodeName!=="BUTTON"&&!this.#i.hasAttribute("role")&&this.#i.setAttribute("role","button")))};#f=t=>{t.target?.name==="facing-mode-button"&&(this.#s?.removeEventListener("click",this.#u),this.#s=this.#b(),this.#s&&(this.#s.addEventListener("click",this.#u),this.#s.nodeName!=="BUTTON"&&!this.#s.hasAttribute("role")&&this.#s.setAttribute("role","button")))};#b(){return this.#n&&this.#n.assignedElements({flatten:!0}).find(t=>t.nodeName==="BUTTON"||t.getAttribute("slot")==="facing-mode-button")||null}#v(){return this.#c&&this.#c.assignedElements({flatten:!0}).find(t=>t.nodeName==="BUTTON"||t.getAttribute("slot")==="capture-button")||null}#y(){this.stopVideoStream(),this.startVideoStream()}#o(t){let i=this;if(Object.prototype.hasOwnProperty.call(i,t)){let s=i[t];delete i[t],i[t]=s}}async startVideoStream(){if(!r.isSupported()||this.#t)return;this.setAttribute("loading","");let t={video:{facingMode:{ideal:this.facingMode||"user"},pan:!0,tilt:!0,zoom:!0,torch:this.torch},audio:!1};if(typeof this.cameraResolution=="string"&&this.cameraResolution.trim().length>0){let[i=0,s=0]=this.cameraResolution.split("x").map(e=>Number(e));i>0&&s>0&&(t.video.width=i,t.video.height=s)}try{this.#t=await navigator.mediaDevices.getUserMedia(t),this.#e&&(this.#e.srcObject=this.#t),this.#a("pan",this.pan),this.#a("tilt",this.tilt),this.#a("zoom",this.zoom),"facingMode"in this.getTrackSettings()&&this.#n&&(this.#n.hidden=!1)}catch(i){this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:i}}))}finally{this.removeAttribute("loading")}}stopVideoStream(){if(!this.#e||!this.#t)return;let[t]=this.#t.getVideoTracks();t?.stop(),this.#e.srcObject=null,this.#t=null}async capture(){if(!(this.loading||!this.#h||!this.#e))try{let t=this.#h.getContext("2d"),i=this.#e.videoWidth,s=this.#e.videoHeight;this.#h.width=i,this.#h.height=s,t?.drawImage(this.#e,0,0,i,s);let e=this.#h.toDataURL("image/png");if(typeof e=="string"&&e.includes("data:image")){if(!this.noImage){let o=new Image;o.src=e,o.width=i,o.height=s,o.alt="Captured photo",o.setAttribute("part","output-image"),this.#g(),this.#l?.appendChild(o)}let n={dataURI:e,width:i,height:s};if(this.calculateFileSize)try{let h=(await(await fetch(e)).blob()).size;h&&(n.size=h)}catch{}this.dispatchEvent(new CustomEvent(`${a}:success`,{bubbles:!0,composed:!0,detail:n}))}}catch(t){this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}}getSupportedConstraints(){return r.isSupported()?navigator.mediaDevices.getSupportedConstraints()||{}:{}}getTrackCapabilities(){if(!this.#t)return{};let[t]=this.#t.getVideoTracks();return t&&typeof t.getCapabilities=="function"?t.getCapabilities()||{}:{}}getTrackSettings(){if(!this.#t)return{};let[t]=this.#t.getVideoTracks();return t&&typeof t.getSettings=="function"?t.getSettings()||{}:{}}static isSupported(){return!!navigator.mediaDevices?.getUserMedia}static defineCustomElement(t=a){typeof window<"u"&&!window.customElements.get(t)&&window.customElements.define(t,r)}};l.defineCustomElement();export{l as CapturePhoto}; | ||
//# sourceMappingURL=capture-photo-defined.js.map |
@@ -39,2 +39,3 @@ /** | ||
zoom: boolean; | ||
torch: boolean; | ||
}; | ||
@@ -60,2 +61,3 @@ /** | ||
* @property {number} zoom - The zoom value of the camera. | ||
* @property {boolean} torch - Whether or not the fill light is connected. | ||
* @property {boolean} loading - Whether or not the video stream is loading. | ||
@@ -71,2 +73,3 @@ * @property {boolean} calculateFileSize - Whether or not to calculate the file size of the captured image. | ||
* @atttribute {number} zoom - Reflects the zoom property. | ||
* @atttribute {boolean} torch - Reflects the torch property. | ||
* @atttribute {boolean} loading - Reflects the loading property. | ||
@@ -178,3 +181,9 @@ * @atttribute {boolean} calculate-file-size - Reflects the calculateFileSize property. | ||
get zoom(): number; | ||
set torch(value: boolean); | ||
/** | ||
* @type {boolean} torch - Whether or not the fill light is connected. | ||
* @attribute torch - Reflects the torch attribute. | ||
*/ | ||
get torch(): boolean; | ||
/** | ||
* @type {boolean} loading - Whether or not the video stream is loading. | ||
@@ -181,0 +190,0 @@ * @attribute loading - Reflects the loading attribute. |
@@ -1,2 +0,11 @@ | ||
let t=(t,e,i)=>(Number.isNaN(e)&&(e=0),Number.isNaN(i)&&(i=0),Math.min(Math.max(t,Math.min(e,i)),Math.max(e,i))),e="capture-photo",i=` | ||
/*! | ||
* @georapbox/capture-photo-element | ||
* A custom element that implements the MediaDevices.getUserMedia() method of the MediaDevices interface to capture a photo in the browser. | ||
* | ||
* @version 4.1.0 | ||
* @homepage https://github.com/georapbox/capture-photo-element#readme | ||
* @author George Raptis <georapbox@gmail.com> | ||
* @license MIT | ||
*/ | ||
var l=(r,t,i)=>(Number.isNaN(t)&&(t=0),Number.isNaN(i)&&(i=0),Math.min(Math.max(r,Math.min(t,i)),Math.max(t,i)));var a="capture-photo",g=` | ||
:host { | ||
@@ -26,4 +35,4 @@ display: block; | ||
} | ||
`,o=document.createElement("template");o.innerHTML=` | ||
<style>${i}</style> | ||
`,d=document.createElement("template");d.innerHTML=` | ||
<style>${g}</style> | ||
@@ -53,3 +62,3 @@ <video part="video" playsinline></video> | ||
<div part="output-container" id="output"></div> | ||
`;class n extends HTMLElement{#t={};#e=null;#i=null;#o=null;#n=null;#a=null;#s=null;#r=null;#l=null;constructor(){super(),this.#t=this.getSupportedConstraints(),this.shadowRoot||this.attachShadow({mode:"open"}).appendChild(o.content.cloneNode(!0))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","pan","tilt","zoom"]}attributeChangedCallback(t,e,i){if(!this.isConnected)return;let o=this.getTrackCapabilities(),n=this.getTrackSettings();if("no-image"===t&&e!==i&&this.#u(),"facing-mode"===t&&e!==i&&"facingMode"in this.#t){let t=["user","environment"].includes(this.facingMode||"");"facingMode"in n&&t&&(this.stopVideoStream(),this.startVideoStream())}if("camera-resolution"===t&&e!==i&&"string"==typeof this.cameraResolution&&this.cameraResolution.trim().length>0){let[t=0,e=0]=this.cameraResolution.split("x").map(t=>Number(t));if(t>0&&e>0&&"width"in o&&"height"in o){let i=!!(o.width?.min&&o.width?.max)&&t>=o?.width?.min&&t<=o?.width?.max,a=!!(o.height?.min&&o.height?.max)&&e>=o?.height?.min&&e<=o?.height?.max;"width"in n&&"height"in n&&i&&a&&(this.stopVideoStream(),this.startVideoStream())}}if("pan"===t&&e!==i&&"pan"in this.#t){let t=!!("pan"in o&&o.pan?.min&&o.pan?.max)&&this.pan>=o.pan.min&&this.pan<=o.pan.max;"pan"in n&&"number"==typeof this.pan&&t&&this.#h("pan",this.pan)}if("tilt"===t&&e!==i&&"tilt"in this.#t){let t=!!("tilt"in o&&o.tilt?.min&&o.tilt?.max)&&this.tilt>=o.tilt.min&&this.tilt<=o.tilt.max;"tilt"in n&&"number"==typeof this.tilt&&t&&this.#h("tilt",this.tilt)}if("zoom"===t&&e!==i&&"zoom"in this.#t){let t=!!("zoom"in o&&o.zoom?.min&&o.zoom?.max)&&this.zoom>=o.zoom.min&&this.zoom<=o.zoom.max;"zoom"in n&&"number"==typeof this.zoom&&t&&this.#h("zoom",this.zoom)}}connectedCallback(){if(this.#d("autpoPlay"),this.#d("noImage"),this.#d("facingMode"),this.#d("cameraResolution"),this.#d("pan"),this.#d("tilt"),this.#d("zoom"),this.#d("calculateFileSize"),this.#i=this.shadowRoot?.querySelector("canvas")||null,this.#o=this.shadowRoot?.getElementById("output")||null,this.#n=this.shadowRoot?.querySelector("video")||null,this.#a=this.shadowRoot?.querySelector('slot[name="capture-button"]')||null,this.#s=this.#c(),this.#r=this.shadowRoot?.querySelector('slot[name="facing-mode-button"]')||null,this.#l=this.#m(),this.#n?.addEventListener("loadedmetadata",this.#p),this.#a?.addEventListener("slotchange",this.#g),this.#s?.addEventListener("click",this.#b),this.#r?.addEventListener("slotchange",this.#f),this.#l?.addEventListener("click",this.#v),!n.isSupported())return this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));this.autoPlay&&this.startVideoStream()}disconnectedCallback(){this.stopVideoStream(),this.#l?.removeEventListener("click",this.#v),this.#s?.removeEventListener("click",this.#b),this.#n?.removeEventListener("canplay",this.#p),this.#a?.removeEventListener("slotchange",this.#g),this.#r?.removeEventListener("slotchange",this.#f)}get autoPlay(){return this.hasAttribute("auto-play")}set autoPlay(t){this.toggleAttribute("auto-play",!!t)}get noImage(){return this.hasAttribute("no-image")}set noImage(t){this.toggleAttribute("no-image",!!t)}get facingMode(){return this.getAttribute("facing-mode")||"user"}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")||""}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get pan(){return Number(this.getAttribute("pan"))||0}set pan(t){this.setAttribute("pan",null!=t?t.toString():t)}get tilt(){return Number(this.getAttribute("tilt"))||0}set tilt(t){this.setAttribute("tilt",null!=t?t.toString():t)}get zoom(){return Number(this.getAttribute("zoom"))||1}set zoom(t){this.setAttribute("zoom",null!=t?t.toString():t)}get loading(){return this.hasAttribute("loading")}get calculateFileSize(){return this.hasAttribute("calculate-file-size")}set calculateFileSize(t){this.toggleAttribute("calculate-file-size",!!t)}#v=t=>{t.preventDefault(),this.loading||(this.facingMode="user"!==this.facingMode&&this.facingMode?"user":"environment")};#b=t=>{t.preventDefault(),this.capture()};#p=t=>{let i=t.target;i.play().then(()=>{this.dispatchEvent(new CustomEvent(`${e}:video-play`,{bubbles:!0,composed:!0,detail:{video:i}}))}).catch(t=>{this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}).finally(()=>{this.removeAttribute("loading")})};#u(){this.#o&&Array.from(this.#o.childNodes).forEach(t=>t.remove())}#h(e,i){if(!this.#e||!e||!i)return;let[o]=this.#e.getVideoTracks(),n=this.getTrackCapabilities();e in this.getTrackSettings()&&o.applyConstraints({advanced:[{[e]:t(Number(i),n[e]?.min||1,n[e]?.max||1)}]})}#g=t=>{t.target?.name==="capture-button"&&(this.#s?.removeEventListener("click",this.#b),this.#s=this.#c(),this.#s&&(this.#s.addEventListener("click",this.#b),"BUTTON"===this.#s.nodeName||this.#s.hasAttribute("role")||this.#s.setAttribute("role","button")))};#f=t=>{t.target?.name==="facing-mode-button"&&(this.#l?.removeEventListener("click",this.#v),this.#l=this.#m(),this.#l&&(this.#l.addEventListener("click",this.#v),"BUTTON"===this.#l.nodeName||this.#l.hasAttribute("role")||this.#l.setAttribute("role","button")))};#m(){return this.#r&&this.#r.assignedElements({flatten:!0}).find(t=>"BUTTON"===t.nodeName||"facing-mode-button"===t.getAttribute("slot"))||null}#c(){return this.#a&&this.#a.assignedElements({flatten:!0}).find(t=>"BUTTON"===t.nodeName||"capture-button"===t.getAttribute("slot"))||null}#d(t){if(Object.prototype.hasOwnProperty.call(this,t)){let e=this[t];delete this[t],this[t]=e}}async startVideoStream(){if(!n.isSupported()||this.#e)return;this.setAttribute("loading","");let t={video:{facingMode:{ideal:this.facingMode||"user"},pan:!0,tilt:!0,zoom:!0},audio:!1};if("string"==typeof this.cameraResolution&&this.cameraResolution.trim().length>0){let[e=0,i=0]=this.cameraResolution.split("x").map(t=>Number(t));e>0&&i>0&&(t.video.width=e,t.video.height=i)}try{this.#e=await navigator.mediaDevices.getUserMedia(t),this.#n&&(this.#n.srcObject=this.#e),this.#h("pan",this.pan),this.#h("tilt",this.tilt),this.#h("zoom",this.zoom);let e=this.getTrackSettings();"facingMode"in e&&this.#r&&(this.#r.hidden=!1)}catch(t){this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}finally{this.removeAttribute("loading")}}stopVideoStream(){if(!this.#n||!this.#e)return;let[t]=this.#e.getVideoTracks();t?.stop(),this.#n.srcObject=null,this.#e=null}async capture(){if(!this.loading&&this.#i&&this.#n)try{let t=this.#i.getContext("2d"),i=this.#n.videoWidth,o=this.#n.videoHeight;this.#i.width=i,this.#i.height=o,t?.drawImage(this.#n,0,0,i,o);let n=this.#i.toDataURL("image/png");if("string"==typeof n&&n.includes("data:image")){if(!this.noImage){let t=new Image;t.src=n,t.width=i,t.height=o,t.setAttribute("part","output-image"),this.#u(),this.#o?.appendChild(t)}let t={dataURI:n,width:i,height:o};if(this.calculateFileSize)try{let e=await fetch(n),i=(await e.blob()).size;i&&(t.size=i)}catch(t){}this.dispatchEvent(new CustomEvent(`${e}:success`,{bubbles:!0,composed:!0,detail:t}))}}catch(t){this.dispatchEvent(new CustomEvent(`${e}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}}getSupportedConstraints(){return n.isSupported()&&navigator.mediaDevices.getSupportedConstraints()||{}}getTrackCapabilities(){if(!this.#e)return{};let[t]=this.#e.getVideoTracks();return t&&"function"==typeof t.getCapabilities&&t.getCapabilities()||{}}getTrackSettings(){if(!this.#e)return{};let[t]=this.#e.getVideoTracks();return t&&"function"==typeof t.getSettings&&t.getSettings()||{}}static isSupported(){return!!navigator.mediaDevices?.getUserMedia}static defineCustomElement(t=e){"undefined"==typeof window||window.customElements.get(t)||window.customElements.define(t,n)}}export{n as CapturePhoto}; | ||
`;var u=class r extends HTMLElement{#r={};#t=null;#h=null;#l=null;#e=null;#c=null;#i=null;#n=null;#s=null;constructor(){super(),this.#r=this.getSupportedConstraints(),this.shadowRoot||this.attachShadow({mode:"open"}).appendChild(d.content.cloneNode(!0))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","pan","tilt","zoom","torch"]}attributeChangedCallback(t,i,s){if(!this.isConnected)return;let e=this.getTrackCapabilities(),n=this.getTrackSettings();if(t==="no-image"&&i!==s&&this.#g(),t==="facing-mode"&&i!==s&&"facingMode"in this.#r){let o=["user","environment"].includes(this.facingMode||"");"facingMode"in n&&o&&this.#y()}if(t==="camera-resolution"&&i!==s&&typeof this.cameraResolution=="string"&&this.cameraResolution.trim().length>0){let[o=0,c=0]=this.cameraResolution.split("x").map(h=>Number(h));if(o>0&&c>0&&"width"in e&&"height"in e){let h=e.width?.min&&e.width?.max?o>=e?.width?.min&&o<=e?.width?.max:!1,m=e.height?.min&&e.height?.max?c>=e?.height?.min&&c<=e?.height?.max:!1;"width"in n&&"height"in n&&h&&m&&this.#y()}}if(t==="pan"&&i!==s&&"pan"in this.#r){let o="pan"in e&&e.pan?.min&&e.pan?.max?this.pan>=e.pan.min&&this.pan<=e.pan.max:!1;typeof this.pan=="number"&&o&&this.#a("pan",this.pan)}if(t==="tilt"&&i!==s&&"tilt"in this.#r){let o="tilt"in e&&e.tilt?.min&&e.tilt?.max?this.tilt>=e.tilt.min&&this.tilt<=e.tilt.max:!1;typeof this.tilt=="number"&&o&&this.#a("tilt",this.tilt)}if(t==="zoom"&&i!==s&&"zoom"in this.#r){let o="zoom"in e&&e.zoom?.min&&e.zoom?.max?this.zoom>=e.zoom.min&&this.zoom<=e.zoom.max:!1;typeof this.zoom=="number"&&o&&this.#a("zoom",this.zoom)}t==="torch"&&i!==s&&"torch"in this.#r&&this.#a("torch",this.torch)}connectedCallback(){if(this.#o("autpoPlay"),this.#o("noImage"),this.#o("facingMode"),this.#o("cameraResolution"),this.#o("pan"),this.#o("tilt"),this.#o("zoom"),this.#o("torch"),this.#o("calculateFileSize"),this.#h=this.shadowRoot?.querySelector("canvas")||null,this.#l=this.shadowRoot?.getElementById("output")||null,this.#e=this.shadowRoot?.querySelector("video")||null,this.#c=this.shadowRoot?.querySelector('slot[name="capture-button"]')||null,this.#i=this.#v(),this.#n=this.shadowRoot?.querySelector('slot[name="facing-mode-button"]')||null,this.#s=this.#b(),this.#e?.addEventListener("loadedmetadata",this.#m),this.#c?.addEventListener("slotchange",this.#p),this.#i?.addEventListener("click",this.#d),this.#n?.addEventListener("slotchange",this.#f),this.#s?.addEventListener("click",this.#u),!r.isSupported())return this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));this.autoPlay&&this.startVideoStream()}disconnectedCallback(){this.stopVideoStream(),this.#s?.removeEventListener("click",this.#u),this.#i?.removeEventListener("click",this.#d),this.#e?.removeEventListener("canplay",this.#m),this.#c?.removeEventListener("slotchange",this.#p),this.#n?.removeEventListener("slotchange",this.#f)}get autoPlay(){return this.hasAttribute("auto-play")}set autoPlay(t){this.toggleAttribute("auto-play",!!t)}get noImage(){return this.hasAttribute("no-image")}set noImage(t){this.toggleAttribute("no-image",!!t)}get facingMode(){return this.getAttribute("facing-mode")||"user"}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")||""}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get pan(){return Number(this.getAttribute("pan"))||0}set pan(t){this.setAttribute("pan",t!=null?t.toString():t)}get tilt(){return Number(this.getAttribute("tilt"))||0}set tilt(t){this.setAttribute("tilt",t!=null?t.toString():t)}get zoom(){return Number(this.getAttribute("zoom"))||1}set zoom(t){this.setAttribute("zoom",t!=null?t.toString():t)}get torch(){return this.hasAttribute("torch")}set torch(t){this.toggleAttribute("torch",!!t)}get loading(){return this.hasAttribute("loading")}get calculateFileSize(){return this.hasAttribute("calculate-file-size")}set calculateFileSize(t){this.toggleAttribute("calculate-file-size",!!t)}#u=t=>{t.preventDefault(),!this.loading&&(this.facingMode=this.facingMode==="user"||!this.facingMode?"environment":"user")};#d=t=>{t.preventDefault(),this.capture()};#m=t=>{let i=t.target;i.play().then(()=>{this.dispatchEvent(new CustomEvent(`${a}:video-play`,{bubbles:!0,composed:!0,detail:{video:i}}))}).catch(s=>{this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:s}}))}).finally(()=>{this.removeAttribute("loading")})};#g(){this.#l&&Array.from(this.#l.childNodes).forEach(t=>t.remove())}#a(t,i){if(!this.#t)return;let[s]=this.#t.getVideoTracks(),e=this.getTrackCapabilities(),n=this.getTrackSettings(),o=t==="pan"||t==="tilt"||t==="zoom"?l(Number(i),e[t]?.min||1,e[t]?.max||1):i;t in n&&s.applyConstraints({advanced:[{[t]:o}]}).catch(()=>{})}#p=t=>{t.target?.name==="capture-button"&&(this.#i?.removeEventListener("click",this.#d),this.#i=this.#v(),this.#i&&(this.#i.addEventListener("click",this.#d),this.#i.nodeName!=="BUTTON"&&!this.#i.hasAttribute("role")&&this.#i.setAttribute("role","button")))};#f=t=>{t.target?.name==="facing-mode-button"&&(this.#s?.removeEventListener("click",this.#u),this.#s=this.#b(),this.#s&&(this.#s.addEventListener("click",this.#u),this.#s.nodeName!=="BUTTON"&&!this.#s.hasAttribute("role")&&this.#s.setAttribute("role","button")))};#b(){return this.#n&&this.#n.assignedElements({flatten:!0}).find(t=>t.nodeName==="BUTTON"||t.getAttribute("slot")==="facing-mode-button")||null}#v(){return this.#c&&this.#c.assignedElements({flatten:!0}).find(t=>t.nodeName==="BUTTON"||t.getAttribute("slot")==="capture-button")||null}#y(){this.stopVideoStream(),this.startVideoStream()}#o(t){let i=this;if(Object.prototype.hasOwnProperty.call(i,t)){let s=i[t];delete i[t],i[t]=s}}async startVideoStream(){if(!r.isSupported()||this.#t)return;this.setAttribute("loading","");let t={video:{facingMode:{ideal:this.facingMode||"user"},pan:!0,tilt:!0,zoom:!0,torch:this.torch},audio:!1};if(typeof this.cameraResolution=="string"&&this.cameraResolution.trim().length>0){let[i=0,s=0]=this.cameraResolution.split("x").map(e=>Number(e));i>0&&s>0&&(t.video.width=i,t.video.height=s)}try{this.#t=await navigator.mediaDevices.getUserMedia(t),this.#e&&(this.#e.srcObject=this.#t),this.#a("pan",this.pan),this.#a("tilt",this.tilt),this.#a("zoom",this.zoom),"facingMode"in this.getTrackSettings()&&this.#n&&(this.#n.hidden=!1)}catch(i){this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:i}}))}finally{this.removeAttribute("loading")}}stopVideoStream(){if(!this.#e||!this.#t)return;let[t]=this.#t.getVideoTracks();t?.stop(),this.#e.srcObject=null,this.#t=null}async capture(){if(!(this.loading||!this.#h||!this.#e))try{let t=this.#h.getContext("2d"),i=this.#e.videoWidth,s=this.#e.videoHeight;this.#h.width=i,this.#h.height=s,t?.drawImage(this.#e,0,0,i,s);let e=this.#h.toDataURL("image/png");if(typeof e=="string"&&e.includes("data:image")){if(!this.noImage){let o=new Image;o.src=e,o.width=i,o.height=s,o.alt="Captured photo",o.setAttribute("part","output-image"),this.#g(),this.#l?.appendChild(o)}let n={dataURI:e,width:i,height:s};if(this.calculateFileSize)try{let h=(await(await fetch(e)).blob()).size;h&&(n.size=h)}catch{}this.dispatchEvent(new CustomEvent(`${a}:success`,{bubbles:!0,composed:!0,detail:n}))}}catch(t){this.dispatchEvent(new CustomEvent(`${a}:error`,{bubbles:!0,composed:!0,detail:{error:t}}))}}getSupportedConstraints(){return r.isSupported()?navigator.mediaDevices.getSupportedConstraints()||{}:{}}getTrackCapabilities(){if(!this.#t)return{};let[t]=this.#t.getVideoTracks();return t&&typeof t.getCapabilities=="function"?t.getCapabilities()||{}:{}}getTrackSettings(){if(!this.#t)return{};let[t]=this.#t.getVideoTracks();return t&&typeof t.getSettings=="function"?t.getSettings()||{}:{}}static isSupported(){return!!navigator.mediaDevices?.getUserMedia}static defineCustomElement(t=a){typeof window<"u"&&!window.customElements.get(t)&&window.customElements.define(t,r)}};export{u as CapturePhoto}; | ||
//# sourceMappingURL=capture-photo.js.map |
{ | ||
"name": "@georapbox/capture-photo-element", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"description": "A custom element that implements the MediaDevices.getUserMedia() method of the MediaDevices interface to capture a photo in the browser.", | ||
@@ -10,26 +10,2 @@ "main": "dist/capture-photo.js", | ||
"types": "dist/capture-photo.d.ts", | ||
"targets": { | ||
"capture-photo": { | ||
"context": "browser", | ||
"outputFormat": "esmodule", | ||
"source": "src/capture-photo.js", | ||
"optimize": true, | ||
"isLibrary": true, | ||
"distDir": "dist", | ||
"engines": { | ||
"browsers": "> 0.5%, last 2 versions, not dead" | ||
} | ||
}, | ||
"capture-photo-defined": { | ||
"context": "browser", | ||
"outputFormat": "esmodule", | ||
"source": "src/capture-photo-defined.js", | ||
"optimize": true, | ||
"isLibrary": true, | ||
"distDir": "dist", | ||
"engines": { | ||
"browsers": "> 0.5%, last 2 versions, not dead" | ||
} | ||
} | ||
}, | ||
"files": [ | ||
@@ -40,3 +16,4 @@ "/src", | ||
"scripts": { | ||
"lint": "eslint . --ext .js", | ||
"lint": "eslint", | ||
"format": "prettier --ignore-unknown --write .", | ||
"test": "web-test-runner", | ||
@@ -46,7 +23,9 @@ "test:watch": "npm run test -- --watch", | ||
"types": "tsc --project tsconfig.json", | ||
"dev:parcel": "parcel watch", | ||
"dev": "npm-run-all clean dev:parcel", | ||
"build:parcel": "parcel build", | ||
"build": "npm-run-all clean build:parcel", | ||
"clean": "rimraf dist .parcel-cache", | ||
"dev:esbuild": "node ./scripts/dev.mjs", | ||
"dev": "npm-run-all clean dev:esbuild", | ||
"serve:dev": "web-dev-server --node-resolve --open /docs/ --watch", | ||
"start": "npm-run-all --parallel \"serve:dev -- {1}\" dev", | ||
"build:esbuild": "node ./scripts/build.mjs", | ||
"build": "npm-run-all clean build:esbuild", | ||
"clean": "rimraf dist", | ||
"prepare": "npm-run-all clean lint test build types" | ||
@@ -73,14 +52,21 @@ }, | ||
"devDependencies": { | ||
"@eslint/js": "~9.9.0", | ||
"@esm-bundle/chai": "~4.3.4-fix.0", | ||
"@open-wc/testing": "~4.0.0", | ||
"@web/test-runner": "~0.18.0", | ||
"@web/dev-server": "~0.4.6", | ||
"@web/test-runner": "~0.18.3", | ||
"@web/test-runner-playwright": "~0.11.0", | ||
"eslint": "~8.56.0", | ||
"eslint-plugin-wc": "~2.0.4", | ||
"esbuild": "~0.23.1", | ||
"eslint": "~9.9.0", | ||
"eslint-plugin-wc": "~2.1.0", | ||
"globals": "~15.9.0", | ||
"npm-run-all": "~4.1.5", | ||
"parcel": "~2.9.3", | ||
"rimraf": "~5.0.5", | ||
"sinon": "~17.0.1", | ||
"typescript": "~5.3.3" | ||
"prettier": "~3.3.3", | ||
"rimraf": "~6.0.1", | ||
"sinon": "~18.0.0", | ||
"typescript": "~5.5.4" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
@@ -8,3 +8,3 @@ [![npm version](https://img.shields.io/npm/v/@georapbox/capture-photo-element.svg)](https://www.npmjs.com/package/@georapbox/capture-photo-element) | ||
[constraints]: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#parameters | ||
[license]: https://georapbox.mit-license.org/@2022 | ||
[license]: https://github.com/georapbox/capture-photo-element/blob/main/LICENSE | ||
[changelog]: https://github.com/georapbox/capture-photo-element/blob/main/CHANGELOG.md | ||
@@ -89,2 +89,3 @@ | ||
| `zoom`<sup>1</sup> | ✓ | Number | - | `1` | Defines the camera's zoom level if supported by the camera hardware. You can access the min & max supported values for zoom level, using `getTrackCapabilities().zoom`. | | ||
| `torch`<sup>1</sup> | ✓ | Boolean | - | `false` | Determines if the camera's fill light should be turned on if supported by the camera hardware. This works only when `facingMode` is set to `environment`. You can access the supported values for torch, using `getTrackCapabilities().torch`. **NOTE:** The support for this feature is known to be limited and heavily dependent on the device, browser, and operating system. | | ||
| `loading` | ✓ | Boolean | - | `false` | **Readonly**. Indicates if the component is ready for interaction. It is used internally but is also exposed as a readonly property for purposes such as styling, etc. | | ||
@@ -169,4 +170,51 @@ | `calculateFileSize`<br>*`calculate-file-size`* | ✓ | Boolean | - | `false` | Indicates if the component should calculate the file size of the generated image. If set to `true` the file size (in bytes) will be included in the event detail object when the `capture-photo:success` event is fired. The reason for not calculating the file size by default is that it might be an "expensive" operation, especially for large images, therefore it is recommended to set this property to `true` only if you need the file size. | | ||
## Development setup | ||
### Prerequisites | ||
The project requires `Node.js` and `npm` to be installed on your environment. Preferrably, use [nvm](https://github.com/nvm-sh/nvm) Node Version Manager and use the version of Node.js specified in the `.nvmrc` file by running `nvm use`. | ||
### Install dependencies | ||
Install the project dependencies by running the following command. | ||
```sh | ||
npm install | ||
``` | ||
### Build for development | ||
Watch for changes and start a development server by running the following command. | ||
```sh | ||
npm start | ||
``` | ||
### Linting | ||
Lint the code by running the following command. | ||
```sh | ||
npm run lint | ||
``` | ||
### Testing | ||
Run the tests by running any of the following commands. | ||
```sh | ||
npm test | ||
npm run test:watch # watch mode | ||
``` | ||
### Build for production | ||
Create a production build by running the following command. | ||
```sh | ||
npm run build | ||
``` | ||
## License | ||
[The MIT License (MIT)][license] |
@@ -22,3 +22,3 @@ // @ts-check | ||
* @typedef {Object} ExtendedMediaTrackConstraints | ||
* @property {MediaTrackConstraints & {pan: boolean, tilt: boolean, zoom: boolean}} video - The video constraints. | ||
* @property {MediaTrackConstraints & {pan: boolean, tilt: boolean, zoom: boolean, torch: boolean}} video - The video constraints. | ||
* @property {MediaTrackConstraints | boolean} audio - The audio constraints. | ||
@@ -31,3 +31,3 @@ */ | ||
const styles = /* css */` | ||
const styles = /* css */ ` | ||
:host { | ||
@@ -61,3 +61,3 @@ display: block; | ||
template.innerHTML = /* html */` | ||
template.innerHTML = /* html */ ` | ||
<style>${styles}</style> | ||
@@ -104,2 +104,3 @@ | ||
* @property {number} zoom - The zoom value of the camera. | ||
* @property {boolean} torch - Whether or not the fill light is connected. | ||
* @property {boolean} loading - Whether or not the video stream is loading. | ||
@@ -115,2 +116,3 @@ * @property {boolean} calculateFileSize - Whether or not to calculate the file size of the captured image. | ||
* @atttribute {number} zoom - Reflects the zoom property. | ||
* @atttribute {boolean} torch - Reflects the torch property. | ||
* @atttribute {boolean} loading - Reflects the loading property. | ||
@@ -186,3 +188,3 @@ * @atttribute {boolean} calculate-file-size - Reflects the calculateFileSize property. | ||
static get observedAttributes() { | ||
return ['no-image', 'facing-mode', 'camera-resolution', 'pan', 'tilt', 'zoom']; | ||
return ['no-image', 'facing-mode', 'camera-resolution', 'pan', 'tilt', 'zoom', 'torch']; | ||
} | ||
@@ -214,4 +216,3 @@ | ||
if ('facingMode' in trackSettings && isValidFacingMode) { | ||
this.stopVideoStream(); | ||
this.startVideoStream(); | ||
this.#restartVideoStream(); | ||
} | ||
@@ -225,13 +226,14 @@ } | ||
if (width > 0 && height > 0 && 'width' in trackCapabilities && 'height' in trackCapabilities) { | ||
const widthInAllowedRange = trackCapabilities.width?.min && trackCapabilities.width?.max | ||
? width >= trackCapabilities?.width?.min && width <= trackCapabilities?.width?.max | ||
: false; | ||
const widthInAllowedRange = | ||
trackCapabilities.width?.min && trackCapabilities.width?.max | ||
? width >= trackCapabilities?.width?.min && width <= trackCapabilities?.width?.max | ||
: false; | ||
const heightInAllowedRange = trackCapabilities.height?.min && trackCapabilities.height?.max | ||
? height >= trackCapabilities?.height?.min && height <= trackCapabilities?.height?.max | ||
: false; | ||
const heightInAllowedRange = | ||
trackCapabilities.height?.min && trackCapabilities.height?.max | ||
? height >= trackCapabilities?.height?.min && height <= trackCapabilities?.height?.max | ||
: false; | ||
if ('width' in trackSettings && 'height' in trackSettings && widthInAllowedRange && heightInAllowedRange) { | ||
this.stopVideoStream(); | ||
this.startVideoStream(); | ||
this.#restartVideoStream(); | ||
} | ||
@@ -243,8 +245,9 @@ } | ||
if (name === 'pan' && oldValue !== newValue && 'pan' in this.#supportedConstraints) { | ||
const panInAllowedRange = 'pan' in trackCapabilities && trackCapabilities.pan?.min && trackCapabilities.pan?.max | ||
? this.pan >= trackCapabilities.pan.min && this.pan <= trackCapabilities.pan.max | ||
: false; | ||
const panInAllowedRange = | ||
'pan' in trackCapabilities && trackCapabilities.pan?.min && trackCapabilities.pan?.max | ||
? this.pan >= trackCapabilities.pan.min && this.pan <= trackCapabilities.pan.max | ||
: false; | ||
if ('pan' in trackSettings && typeof this.pan === 'number' && panInAllowedRange) { | ||
this.#applyPTZ('pan', this.pan); | ||
if (typeof this.pan === 'number' && panInAllowedRange) { | ||
this.#applyConstraint('pan', this.pan); | ||
} | ||
@@ -254,8 +257,9 @@ } | ||
if (name === 'tilt' && oldValue !== newValue && 'tilt' in this.#supportedConstraints) { | ||
const tiltInAllowedRange = 'tilt' in trackCapabilities && trackCapabilities.tilt?.min && trackCapabilities.tilt?.max | ||
? this.tilt >= trackCapabilities.tilt.min && this.tilt <= trackCapabilities.tilt.max | ||
: false; | ||
const tiltInAllowedRange = | ||
'tilt' in trackCapabilities && trackCapabilities.tilt?.min && trackCapabilities.tilt?.max | ||
? this.tilt >= trackCapabilities.tilt.min && this.tilt <= trackCapabilities.tilt.max | ||
: false; | ||
if ('tilt' in trackSettings && typeof this.tilt === 'number' && tiltInAllowedRange) { | ||
this.#applyPTZ('tilt', this.tilt); | ||
if (typeof this.tilt === 'number' && tiltInAllowedRange) { | ||
this.#applyConstraint('tilt', this.tilt); | ||
} | ||
@@ -265,10 +269,15 @@ } | ||
if (name === 'zoom' && oldValue !== newValue && 'zoom' in this.#supportedConstraints) { | ||
const zoomInAllowedRange = 'zoom' in trackCapabilities && trackCapabilities.zoom?.min && trackCapabilities.zoom?.max | ||
? this.zoom >= trackCapabilities.zoom.min && this.zoom <= trackCapabilities.zoom.max | ||
: false; | ||
const zoomInAllowedRange = | ||
'zoom' in trackCapabilities && trackCapabilities.zoom?.min && trackCapabilities.zoom?.max | ||
? this.zoom >= trackCapabilities.zoom.min && this.zoom <= trackCapabilities.zoom.max | ||
: false; | ||
if ('zoom' in trackSettings && typeof this.zoom === 'number' && zoomInAllowedRange) { | ||
this.#applyPTZ('zoom', this.zoom); | ||
if (typeof this.zoom === 'number' && zoomInAllowedRange) { | ||
this.#applyConstraint('zoom', this.zoom); | ||
} | ||
} | ||
if (name === 'torch' && oldValue !== newValue && 'torch' in this.#supportedConstraints) { | ||
this.#applyConstraint('torch', this.torch); | ||
} | ||
} | ||
@@ -287,2 +296,3 @@ | ||
this.#upgradeProperty('zoom'); | ||
this.#upgradeProperty('torch'); | ||
this.#upgradeProperty('calculateFileSize'); | ||
@@ -305,12 +315,14 @@ | ||
if (!CapturePhoto.isSupported()) { | ||
return this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { | ||
error: { | ||
name: 'NotSupportedError', | ||
message: 'Not supported' | ||
return this.dispatchEvent( | ||
new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { | ||
error: { | ||
name: 'NotSupportedError', | ||
message: 'Not supported' | ||
} | ||
} | ||
} | ||
})); | ||
}) | ||
); | ||
} | ||
@@ -420,2 +432,14 @@ | ||
/** | ||
* @type {boolean} torch - Whether or not the fill light is connected. | ||
* @attribute torch - Reflects the torch attribute. | ||
*/ | ||
get torch() { | ||
return this.hasAttribute('torch'); | ||
} | ||
set torch(value) { | ||
this.toggleAttribute('torch', !!value); | ||
} | ||
/** | ||
* @type {boolean} loading - Whether or not the video stream is loading. | ||
@@ -473,17 +497,27 @@ * @attribute loading - Reflects the loading attribute. | ||
video.play().then(() => { | ||
this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}:video-play`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { video } | ||
})); | ||
}).catch(/** @param {Error} error */error => { | ||
this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { error } | ||
})); | ||
}).finally(() => { | ||
this.removeAttribute('loading'); | ||
}); | ||
video | ||
.play() | ||
.then(() => { | ||
this.dispatchEvent( | ||
new CustomEvent(`${COMPONENT_NAME}:video-play`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { video } | ||
}) | ||
); | ||
}) | ||
.catch( | ||
/** @param {Error} error */ error => { | ||
this.dispatchEvent( | ||
new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { error } | ||
}) | ||
); | ||
} | ||
) | ||
.finally(() => { | ||
this.removeAttribute('loading'); | ||
}); | ||
}; | ||
@@ -503,9 +537,9 @@ | ||
/** | ||
* Applies the pan, tilt or zoom constraint. | ||
* Applies a constraint to the video track. | ||
* | ||
* @param {'pan' | 'tilt' | 'zoom'} constraintName - The name of the constraint. | ||
* @param {number} constraintValue - The value of the constraint. | ||
* @param {string} constraint - The name of the constraint. | ||
* @param {any} value - The value of the constraint. | ||
*/ | ||
#applyPTZ(constraintName, constraintValue) { | ||
if (!this.#stream || !constraintName || !constraintValue) { | ||
#applyConstraint(constraint, value) { | ||
if (!this.#stream) { | ||
return; | ||
@@ -519,8 +553,15 @@ } | ||
if (constraintName in trackSettings) { | ||
track.applyConstraints({ | ||
advanced: [{ | ||
[constraintName]: clamp(Number(constraintValue), trackCapabilities[constraintName]?.min || 1, trackCapabilities[constraintName]?.max || 1) | ||
}] | ||
}); | ||
const constraintValue = | ||
constraint === 'pan' || constraint === 'tilt' || constraint === 'zoom' | ||
? clamp(Number(value), trackCapabilities[constraint]?.min || 1, trackCapabilities[constraint]?.max || 1) | ||
: value; | ||
if (constraint in trackSettings) { | ||
track | ||
.applyConstraints({ | ||
advanced: [{ [constraint]: constraintValue }] | ||
}) | ||
.catch(() => { | ||
// Fail silently... | ||
}); | ||
} | ||
@@ -579,5 +620,7 @@ } | ||
return this.#facingModeButtonSlot.assignedElements({ flatten: true }).find(el => { | ||
return el.nodeName === 'BUTTON' || el.getAttribute('slot') === 'facing-mode-button'; | ||
}) || null; | ||
return ( | ||
this.#facingModeButtonSlot.assignedElements({ flatten: true }).find(el => { | ||
return el.nodeName === 'BUTTON' || el.getAttribute('slot') === 'facing-mode-button'; | ||
}) || null | ||
); | ||
} | ||
@@ -595,8 +638,18 @@ | ||
return this.#captureButtonSlot.assignedElements({ flatten: true }).find(el => { | ||
return el.nodeName === 'BUTTON' || el.getAttribute('slot') === 'capture-button'; | ||
}) || null; | ||
return ( | ||
this.#captureButtonSlot.assignedElements({ flatten: true }).find(el => { | ||
return el.nodeName === 'BUTTON' || el.getAttribute('slot') === 'capture-button'; | ||
}) || null | ||
); | ||
} | ||
/** | ||
* Restarts the video stream. | ||
*/ | ||
#restartVideoStream() { | ||
this.stopVideoStream(); | ||
this.startVideoStream(); | ||
} | ||
/** | ||
* This is to safe guard against cases where, for instance, a framework may have added the element to the page and | ||
@@ -608,3 +661,3 @@ * set a value on one of its properties, but lazy loaded its definition. Without this guard, the upgraded element would | ||
* | ||
* @param {'autpoPlay' | 'noImage' | 'facingMode' | 'cameraResolution' | 'pan' | 'tilt' | 'zoom' | 'calculateFileSize'} prop | ||
* @param {'autpoPlay' | 'noImage' | 'facingMode' | 'cameraResolution' | 'pan' | 'tilt' | 'zoom' | 'calculateFileSize' | 'torch'} prop | ||
*/ | ||
@@ -642,3 +695,4 @@ #upgradeProperty(prop) { | ||
tilt: true, | ||
zoom: true | ||
zoom: true, | ||
torch: this.torch | ||
}, | ||
@@ -664,5 +718,5 @@ audio: false | ||
this.#applyPTZ('pan', this.pan); | ||
this.#applyPTZ('tilt', this.tilt); | ||
this.#applyPTZ('zoom', this.zoom); | ||
this.#applyConstraint('pan', this.pan); | ||
this.#applyConstraint('tilt', this.tilt); | ||
this.#applyConstraint('zoom', this.zoom); | ||
@@ -675,7 +729,9 @@ const trackSettings = this.getTrackSettings(); | ||
} catch (error) { | ||
this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { error } | ||
})); | ||
this.dispatchEvent( | ||
new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { error } | ||
}) | ||
); | ||
} finally { | ||
@@ -726,2 +782,3 @@ this.removeAttribute('loading'); | ||
image.height = height; | ||
image.alt = 'Captured photo'; | ||
image.setAttribute('part', 'output-image'); | ||
@@ -744,3 +801,3 @@ this.#emptyOutputElement(); | ||
} | ||
} catch (err) { | ||
} catch { | ||
// Fail silently... | ||
@@ -750,14 +807,18 @@ } | ||
this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}:success`, { | ||
this.dispatchEvent( | ||
new CustomEvent(`${COMPONENT_NAME}:success`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: eventDetail | ||
}) | ||
); | ||
} | ||
} catch (error) { | ||
this.dispatchEvent( | ||
new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: eventDetail | ||
})); | ||
} | ||
} catch (error) { | ||
this.dispatchEvent(new CustomEvent(`${COMPONENT_NAME}:error`, { | ||
bubbles: true, | ||
composed: true, | ||
detail: { error } | ||
})); | ||
detail: { error } | ||
}) | ||
); | ||
} | ||
@@ -764,0 +825,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1198
218
153352
15