UNPKG

@webav/av-canvas

Version:

Combine Text, Image, Video, Audio, UserMedia, DisplayMedia to generate MediaStream. With [AVRcorder](../av-recorder/README.md) you can output MP4 streams and save them as local files or push them to the server.

44 lines (43 loc) 15.6 kB
(function(v,w){typeof exports=="object"&&typeof module<"u"?w(exports,require("@webav/av-cliper"),require("@webav/internal-utils")):typeof define=="function"&&define.amd?define(["exports","@webav/av-cliper","@webav/internal-utils"],w):(v=typeof globalThis<"u"?globalThis:v||self,w(v["av-canvas"]={},v.avCliper,v.internalUtils))})(this,function(v,w,T){"use strict";const O=["t","b","l","r","lt","lb","rt","rb","rotate"];function A(i){return document.createElement(i)}const I=new WeakMap;function B(i,t){if(I.has(i))return I.get(i)(t);let n=10;new ResizeObserver(s=>{const a=s[0];a!=null&&(n=10/(a.contentRect.width/i.width))}).observe(i);function r(s){const{w:a,h}=s,o=n,c=o/2,l=a/2,d=h/2,p=o*1.5,f=p/2;return{...s.fixedAspectRatio?{}:{t:new w.Rect(-c,-d-c,o,o,s),b:new w.Rect(-c,d-c,o,o,s),l:new w.Rect(-l-c,-c,o,o,s),r:new w.Rect(l-c,-c,o,o,s)},lt:new w.Rect(-l-c,-d-c,o,o,s),lb:new w.Rect(-l-c,d-c,o,o,s),rt:new w.Rect(l-c,-d-c,o,o,s),rb:new w.Rect(l-c,d-c,o,o,s),rotate:new w.Rect(-f,-d-o*2-f,p,p,s)}}return I.set(i,r),r(t)}const $=new WeakMap;function L(i){if($.has(i))return $.get(i);const t={w:i.clientWidth/i.width,h:i.clientHeight/i.height};return new ResizeObserver(()=>{t.w=i.clientWidth/i.width,t.h=i.clientHeight/i.height}).observe(i),$.set(i,t),t}var z=(i=>(i.ActiveSpriteChange="activeSpriteChange",i.AddSprite="addSprite",i))(z||{});class q{#t=[];#e=null;#i=new T.EventTool;on=this.#i.on;get activeSprite(){return this.#e}set activeSprite(t){t===this.#e||t?.interactable==="disabled"||(this.#e=t,this.#i.emit("activeSpriteChange",t))}activeSpriteByCoord(t,n){this.activeSprite=this.getSprites().reverse().find(e=>e.visible&&e.interactable!=="disabled"&&e.rect.checkHit(t,n))??null}async addSprite(t){await t.ready,this.#t.push(t),this.#t=this.#t.sort((n,e)=>n.zIndex-e.zIndex),t.on("propsChange",n=>{n.zIndex!=null&&(this.#t=this.#t.sort((e,r)=>e.zIndex-r.zIndex))}),this.#i.emit("addSprite",t)}removeSprite(t){this.#e===t&&(this.activeSprite=null),this.#t=this.#t.filter(n=>n!==t),t.destroy()}getSprites(t={time:!0}){return this.#t.filter(n=>n.visible&&(t.time?this.#s>=n.time.offset&&this.#s<=n.time.offset+n.time.duration:!0))}#s=0;updateRenderTime(t){this.#s=t;const n=this.activeSprite;n!=null&&(t<n.time.offset||t>n.time.offset+n.time.duration)&&(this.activeSprite=null)}destroy(){this.#i.destroy(),this.#t.forEach(t=>t.destroy()),this.#t=[]}}const _=` <svg t="1756779136804" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1456" width="16" height="16"> <path d="M1022.793875 170.063604L852.730271 0 511.396938 341.333333 170.063604 0 0 170.063604l341.333333 341.333334L0 852.730271l170.063604 170.063604 341.333334-340.127208 341.333333 340.127208 170.063604-170.063604-340.127208-341.333333 340.127208-341.333334z" fill="#bfbfbf" p-id="1457"></path> </svg> `;function G(i,t,n){const e=L(t),r=new ResizeObserver(()=>{n.activeSprite!=null&&W(n.activeSprite,t,a,h)});r.observe(t);let s=()=>{};const{rectEl:a,ctrlsEl:h}=J(i);a.addEventListener("pointerdown",c=>{if(Object.values(h).includes(c.target))return;const l=t.getBoundingClientRect(),d=(c.clientX-l.left)/e.w,p=(c.clientY-l.top)/e.h;n.activeSpriteByCoord(d,p)});const o=n.on(z.ActiveSpriteChange,c=>{if(s(),c==null){a.style.display="none";return}W(c,t,a,h),s=c.on("propsChange",()=>{W(c,t,a,h)})});return()=>{r.disconnect(),o(),a.remove(),s()}}function J(i){const t=A("div");t.classList.add("sprite-rect"),t.style.cssText=` position: absolute; z-index: 3; pointer-events: auto; border: 1px solid #eee; box-sizing: border-box; display: none; cursor: move; `;const n=Object.fromEntries(O.map(e=>{const r=A("div");return r.classList.add(`ctrl-key-${e}`),r.style.cssText=` display: none; position: absolute; border: 1px solid #3ee; border-radius: 50%; box-sizing: border-box; background-color: #fff; pointer-events: auto; cursor: ${e==="rotate"?"crosshair":"default"}; user-select: none; `,[e,r]}));return Object.values(n).forEach(e=>t.appendChild(e)),i.appendChild(t),{rectEl:t,ctrlsEl:n}}function W(i,t,n,e){if(i.interactable==="disabled"){n.style.display="none";return}n.style.display="";const r=L(t),{x:s,y:a,w:h,h:o,angle:c}=i.rect;Object.assign(n.style,{left:`${s*r.w}px`,top:`${a*r.h}px`,width:`${h*r.w}px`,height:`${o*r.h}px`,transform:`rotate(${c}rad)`});const l=B(t,i.rect);for(const d in e){const p=d,f=e[p],u=l[p];if(u==null){f.style.display="none";continue}const m={width:`${u.w*r.w}px`,height:`${u.h*r.h}px`,transform:`translate(${u.x*r.w}px, ${u.y*r.h}px)`,left:"50%",top:"50%"};let S={display:"none"};switch(f.innerHTML="",i.interactable){case"interactive":S={display:"block",backgroundColor:"#fff",border:"1px solid #3ee"};break;case"selectable":p!=="rotate"&&(S={display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:"transparent",border:"none"},f.innerHTML=_);break}Object.assign(f.style,m,S)}}function Q(i,t){const n=e=>{if(e.button!==0||e.target!==i)return;const r=L(i),{offsetX:s,offsetY:a}=e,h=s/r.w,o=a/r.h;t.activeSpriteByCoord(h,o)};return i.addEventListener("pointerdown",n),()=>{i.removeEventListener("pointerdown",n)}}function Z(i,t,n){let e=0,r=0,s=null;const a=rt(i,n),h=n.querySelector(".sprite-rect");if(!h)throw Error("sprite-rect DOM Node not found");const o=f=>{const u=t.activeSprite;if(f.button!==0||u==null||u.interactable!=="interactive")return;const{clientX:m,clientY:S}=f;s=u.rect.clone(),a.magneticEffect(u.rect.x,u.rect.y,u.rect),e=m,r=S,window.addEventListener("pointermove",l),window.addEventListener("pointerup",d),f.stopPropagation()},c=L(i),l=f=>{const u=t.activeSprite;if(u==null||u.interactable!=="interactive"||s==null)return;const{clientX:m,clientY:S}=f;let y=s.x+(m-e)/c.w,b=s.y+(S-r)/c.h;D(u.rect,i,a.magneticEffect(y,b,u.rect))},d=()=>{a.hide(),window.removeEventListener("pointermove",l),window.removeEventListener("pointerup",d)};h.addEventListener("pointerdown",o),i.addEventListener("pointerdown",o);const p=U(i,h,t);return()=>{a.destroy(),d(),h.removeEventListener("pointerdown",o),i.removeEventListener("pointerdown",o),p()}}function U(i,t,n){const e=Array.from(t.children),r=L(i);e.forEach((c,l)=>{const d=O[l];c.addEventListener("pointerdown",p=>{const f=n.activeSprite;if(p.button!==0||f==null||f.interactable!=="interactive")return;const{clientX:u,clientY:m}=p;d==="rotate"?nt(f.rect,it(f.rect.center,r,i)):K({sprRect:f.rect,ctrlKey:d,startX:u,startY:m,cvsRatio:r,cvsEl:i}),p.stopPropagation()})}),e[O.indexOf("rotate")].style.cursor="crosshair";const s=["ns-resize","nesw-resize","ew-resize","nwse-resize","ns-resize","nesw-resize","ew-resize","nwse-resize"],a={t:0,rt:1,r:2,rb:3,b:4,lb:5,l:6,lt:7};let h=()=>{};const o=n.on(z.ActiveSpriteChange,c=>{if(h(),c==null)return;const l=T.debounce(function(){const{angle:d}=c.rect,p=d<0?d+2*Math.PI:d;e.forEach((f,u)=>{const m=O[u];if(m==="rotate")return;const S=(a[m]+Math.floor((p+Math.PI/8)/(Math.PI/4)))%8;f.style.cursor=s[S]})},300);h=c.on("propsChange",d=>{d.rect?.angle!=null&&l()}),l()});return()=>{h(),o()}}function K({sprRect:i,startX:t,startY:n,ctrlKey:e,cvsRatio:r,cvsEl:s}){const a=i.clone(),h=c=>{const{clientX:l,clientY:d}=c,p=(l-t)/r.w,f=(d-n)/r.h,u=e.length===1?tt:et,{x:m,y:S,w:y,h:b}=a,k=Math.atan2(b,y),{incW:Y,incH:F,incS:C,rotateAngle:V}=u({deltaX:p,deltaY:f,angle:i.angle,ctrlKey:e,diagonalAngle:k}),g=10;let x=y,R=b,E=a.fixedScaleCenter?Y*2:Y,P=a.fixedScaleCenter?F*2:F,M=C;const X=Math.sqrt(b**2+y**2),N=Math.sqrt((g*(b/y))**2+g**2);switch(e){case"l":x=Math.max(y+E,g),M=Math.min(C,y-g);break;case"r":x=Math.max(y+E,g),M=Math.max(C,g-y);break;case"b":R=Math.max(b+P,g),M=Math.min(C,b-g);break;case"t":R=Math.max(b+P,g),M=Math.max(C,g-b);break;case"lt":case"lb":x=Math.max(y+E,g),R=x===g?b/y*x:b+P,M=Math.min(C,X-N);break;case"rt":case"rb":x=Math.max(y+E,g),R=x===g?b/y*x:b+P,M=Math.max(C,N-X);break}let j=m,H=S;if(a.fixedScaleCenter)j=m+y/2-x/2,H=S+b/2-R/2;else{const dt=M/2*Math.cos(V)+m+y/2,lt=M/2*Math.sin(V)+S+b/2;j=dt-x/2,H=lt-R/2}D(i,s,{x:j,y:H,w:x,h:R})},o=()=>{window.removeEventListener("pointermove",h),window.removeEventListener("pointerup",o)};window.addEventListener("pointermove",h),window.addEventListener("pointerup",o)}function tt({deltaX:i,deltaY:t,angle:n,ctrlKey:e}){let r=0,s=0,a=0,h=n;return e==="l"||e==="r"?(r=i*Math.cos(n)+t*Math.sin(n),s=r*(e==="l"?-1:1)):(e==="t"||e==="b")&&(h=n-Math.PI/2,r=i*Math.cos(h)+t*Math.sin(h),a=r*(e==="b"?-1:1)),{incW:s,incH:a,incS:r,rotateAngle:h}}function et({deltaX:i,deltaY:t,angle:n,ctrlKey:e,diagonalAngle:r}){const s=(e==="lt"||e==="rb"?1:-1)*r+n,a=i*Math.cos(s)+t*Math.sin(s),h=e==="lt"||e==="lb"?-1:1,o=a*Math.cos(r)*h,c=a*Math.sin(r)*h;return{incW:o,incH:c,incS:a,rotateAngle:s}}function nt(i,t){const n=({clientX:r,clientY:s})=>{const a=r-t.x,h=s-t.y,o=Math.atan2(h,a)+Math.PI/2;i.angle=o},e=()=>{window.removeEventListener("pointermove",n),window.removeEventListener("pointerup",e)};window.addEventListener("pointermove",n),window.addEventListener("pointerup",e)}function it(i,t,n){const e=i.x*t.w,r=i.y*t.h,{left:s,top:a}=n.getBoundingClientRect();return{x:e+s,y:r+a}}function D(i,t,n){const e={x:i.x,y:i.y,w:i.w,h:i.h,...n},r=t.width*.05,s=t.height*.05;e.x<-e.w+r?e.x=-e.w+r:e.x>t.width-r&&(e.x=t.width-r),e.y<-e.h+s?e.y=-e.h+s:e.y>t.height-s&&(e.y=t.height-s),i.x=e.x,i.y=e.y,i.w=e.w,i.h=e.h}function rt(i,t){const n="display: none; position: absolute;",e={w:0,h:0,x:0,y:0},r={vertMiddle:{...e,h:100,x:50,ref:{prop:"x",val:({w:o})=>(i.width-o)/2}},horMiddle:{...e,w:100,y:50,ref:{prop:"y",val:({h:o})=>(i.height-o)/2}},top:{...e,w:100,ref:{prop:"y",val:()=>0}},bottom:{...e,w:100,y:100,ref:{prop:"y",val:({h:o})=>i.height-o}},left:{...e,h:100,ref:{prop:"x",val:()=>0}},right:{...e,h:100,x:100,ref:{prop:"x",val:({w:o})=>i.width-o}}},s=A("div");s.style.cssText=` position: absolute; z-index: 4; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; box-sizing: border-box; `;const a=Object.fromEntries(Object.entries(r).map(([o,{w:c,h:l,x:d,y:p}])=>{const f=A("div");return f.style.cssText=` ${n} border-${c>0?"top":"left"}: 1px solid #3ee; top: ${p}%; left: ${d}%; ${d===100?"margin-left: -1px":""}; ${p===100?"margin-top: -1px":""}; width: ${c}%; height: ${l}%; `,s.appendChild(f),[o,f]}));t.appendChild(s);const h=6/(900/i.width);return{magneticEffect(o,c,l){const d={x:o,y:c},p={x:h,y:h},f={x:"",y:""};Object.values(a).forEach(u=>u.style.display="none");for(const u in r){const{prop:m,val:S}=r[u].ref,y=S(l),k=Math.abs((m==="x"?o:c)-y);k<=h&&k<p[m]&&(p[m]=k,d[m]=y,f[m]=u)}return f.x&&(a[f.x].style.display="block"),f.y&&(a[f.y].style.display="block"),d},hide(){Object.values(a).forEach(o=>o.style.display="none")},destroy(){s.remove()}}}const st={sampleRate:48e3};function ot(i){const t=A("canvas");return t.style.cssText=` width: 100%; height: 100%; display: block; touch-action: none; `,t.width=i.width,t.height=i.height,t}class at{#t;#e;#i;#s=!1;#p=[];#w;#o=new T.EventTool;on=this.#o.on;#m;#d=!1;constructor(t,n){this.#m=n,this.#t=ot(n);const e=this.#t.getContext("2d",{alpha:!1});if(e==null)throw Error("canvas context is null");this.#i=e;const r=A("div");r.style.cssText="width: 100%; height: 100%; position: relative;",r.appendChild(this.#t),t.appendChild(r),ht(this.#n).connect(this.#c),B(this.#t,{x:0,y:0,w:0,h:0}),this.#e=new q,this.#p.push(Q(this.#t,this.#e),G(r,this.#t,this.#e),Z(this.#t,this.#e,r),this.#e.on(z.AddSprite,c=>{const{rect:l}=c;l.x===0&&l.y===0&&(l.x=(this.#t.width-l.w)/2,l.y=(this.#t.height-l.h)/2)}),T.EventTool.forwardEvent(this.#e,this.#o,[z.ActiveSpriteChange]));let s=this.#a,a=performance.now(),h=0;const o=1e3/30;this.#w=T.workerTimer(()=>{(performance.now()-a)/(o*h)<1||this.#d||(h+=1,this.#i.fillStyle=n.bgColor,this.#i.fillRect(0,0,this.#t.width,this.#t.height),this.#b(),s!==this.#a&&(s=this.#a,this.#o.emit("timeupdate",Math.round(s))))},o)}#a=0;#l(t){this.#a=t,this.#e.updateRenderTime(t),this.#u.updateTime(t)}#f(){if(this.#r.step!==0){this.#r.step=0,this.#o.emit("paused"),this.#n.suspend();for(const t of this.#h)t.stop(),t.disconnect();this.#h.clear(),this.#u.reset()}}#n=new AudioContext;#c=this.#n.createMediaStreamDestination();#h=new Set;#b(){const t=this.#i;let n=this.#a;const{start:e,end:r,step:s,audioPlayAt:a}=this.#r;n+=s,s!==0&&n>=e&&n<r?this.#l(n):this.#f();const h=[];for(const o of this.#e.getSprites()){t.save();const{audio:c}=o.render(t,n-o.time.offset);t.restore(),h.push(c)}if(t.resetTransform(),s!==0){const o=Math.max(this.#n.currentTime,a),c=ct(h,this.#n);let l=0;for(const d of c)d.start(o),d.connect(this.#n.destination),d.connect(this.#c),this.#h.add(d),d.onended=()=>{d.disconnect(),this.#h.delete(d)},l=Math.max(l,d.buffer?.duration??0);this.#r.audioPlayAt=o+l}}#r={start:0,end:0,step:0,audioPlayAt:0};play(t){const n=this.#e.getSprites({time:!1}).map(r=>r.time.offset+r.time.duration),e=t.end??(n.length>0?Math.max(...n):1/0);if(t.start>=e||t.start<0)throw Error(`Invalid time parameter, ${JSON.stringify({start:t.start,end:e})}`);this.#l(t.start),this.#u.reset(),this.#r.start=t.start,this.#r.end=e,this.#r.step=(t.playbackRate??1)*(1e3/30)*1e3,this.#n.resume(),this.#r.audioPlayAt=0,this.#o.emit("playing"),w.Log.info("AVCanvs play by:",this.#r)}#u=(()=>{const t=new Set;return{reset(){t.clear()},updateTime:T.throttle(n=>{const r=this.#e.getSprites({time:!1}).filter(s=>{const{offset:a}=s.time;return a>n&&a-1e6<=n});for(const s of r)t.has(s)||s.preFrame(0),t.add(s)},500)}})();pause(){this.#f()}async previewFrame(t){this.#f(),this.#l(t),this.#d=!0;try{await Promise.all(this.#e.getSprites({time:!1}).map(n=>t>=n.time.offset&&t<=n.time.offset+n.time.duration?n.preFrame(t-n.time.offset):null))}finally{this.#d=!1}}captureImage(){return this.#t.toDataURL()}get activeSprite(){return this.#e.activeSprite}set activeSprite(t){this.#e.activeSprite=t}#y=new WeakMap;addSprite=async t=>{this.#n.state==="suspended"&&this.#n.resume().catch(w.Log.error);const n=t.getClip();if(n instanceof w.MediaStreamClip&&n.audioTrack!=null){const e=this.#n.createMediaStreamSource(new MediaStream([n.audioTrack]));e.connect(this.#c),this.#y.set(t,e)}await this.#e.addSprite(t)};removeSprite=t=>{this.#y.get(t)?.disconnect(),this.#e.removeSprite(t)};destroy(){this.#s||(this.#s=!0,this.#n.close(),this.#c.disconnect(),this.#o.destroy(),this.#w(),this.#t.parentElement?.remove(),this.#p.forEach(t=>t()),this.#h.clear(),this.#e.destroy())}captureStream(){this.#n.state==="suspended"&&this.#n.resume().catch(w.Log.error);const t=new MediaStream(this.#t.captureStream().getTracks().concat(this.#c.stream.getTracks()));return w.Log.info("AVCanvas.captureStream, tracks:",t.getTracks().map(n=>n.kind)),t}async createCombinator(t={}){w.Log.info("AVCanvas.createCombinator, opts:",t);const n=new w.Combinator({...this.#m,...t}),e=this.#e.getSprites({time:!1});if(e.length===0)throw Error("No sprite added");for(const r of e){const s=new w.OffscreenSprite(r.getClip());s.time={...r.time},r.copyStateTo(s),await n.addSprite(s)}return n}}function ct(i,t){const n=[];if(i.length===0)return n;for(const[e,r]of i){if(e==null||e.length<=0)continue;const s=t.createBuffer(2,e.length,st.sampleRate);s.copyToChannel(e,0),s.copyToChannel(r??e,1);const a=t.createBufferSource();a.buffer=s,n.push(a)}return n}function ht(i){const t=i.createOscillator(),n=new Float32Array([0,0]),e=new Float32Array([0,0]),r=i.createPeriodicWave(n,e,{disableNormalization:!0});return t.setPeriodicWave(r),t.start(),t}v.AVCanvas=at,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}); //# sourceMappingURL=av-canvas.umd.cjs.map