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.

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