UNPKG

@webav/av-recorder

Version:

Record MediaStream to MP4, Use Webcodecs API encode VideoFrame and AudioData, [mp4box.js](https://github.com/gpac/mp4box.js) as muxer. 录制 MediaStream 到 MP4,使用 Webcodecs API 编码 VideoFrame、AudioData,mp4box.js 封装。

3 lines (2 loc) 3.49 kB
(function(a,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("@webav/internal-utils")):typeof define=="function"&&define.amd?define(["exports","@webav/internal-utils"],i):(a=typeof globalThis<"u"?globalThis:a||self,i(a["av-recorder"]={},a.internalUtils))})(this,function(a,i){"use strict";class g{#e="inactive";get state(){return this.#e}set state(e){throw new Error("state is readonly")}#o=new i.EventTool;on=this.#o.on;#s;#t;constructor(e,t={}){this.#s=v(e,t),this.#t=new S(this.#s.video.expectFPS)}#i=()=>{};start(e=500){if(this.#e==="stopped")throw Error("AVRecorder is stopped");i.Log.info("AVRecorder.start recoding");const{streams:t}=this.#s;if(t.audio==null&&t.video==null)throw new Error("No available tracks in MediaStream");const{stream:s,exit:n}=V({timeSlice:e,...this.#s},this.#t,()=>{this.stop()});return this.#i(),this.#i=n,s}pause(){this.#e="paused",this.#t.pause(),this.#o.emit("stateChange",this.#e)}resume(){if(this.#e==="stopped")throw Error("AVRecorder is stopped");this.#e="recording",this.#t.play(),this.#o.emit("stateChange",this.#e)}async stop(){this.#e!=="stopped"&&(this.#e="stopped",this.#i())}}function v(o,e){const t={bitrate:3e6,expectFPS:30,videoCodec:"avc1.42E032",...e},{streams:s,width:n,height:u,sampleRate:h,channelCount:c}=T(o);return{video:{width:n??1280,height:u??720,expectFPS:t.expectFPS,codec:t.videoCodec},audio:{codec:"aac",sampleRate:h??44100,channelCount:c??2},bitrate:t.bitrate,streams:s}}function T(o){const e=o.getVideoTracks()[0],t={streams:{}};e!=null&&(Object.assign(t,e.getSettings()),t.streams.video=new MediaStreamTrackProcessor({track:e}).readable);const s=o.getAudioTracks()[0];return s!=null&&(Object.assign(t,s.getSettings()),i.Log.info("AVRecorder recording audioConf:",t),t.streams.audio=new MediaStreamTrackProcessor({track:s}).readable),t}class S{constructor(e){this.expectFPS=e,this.#r=Math.floor(e*3)}#e=performance.now();#o=this.#e;#s=0;#t=!1;#i=0;#r=30;start(){this.#e=performance.now(),this.#o=this.#e}play(){this.#t&&(this.#t=!1,this.#e+=performance.now()-this.#i,this.#o+=performance.now()-this.#i)}pause(){this.#t||(this.#t=!0,this.#i=performance.now())}transfromVideo(e){const t=performance.now(),s=t-this.#e;if(this.#t||this.#s/s*1e3>this.expectFPS){e.close();return}const n=new VideoFrame(e,{timestamp:s*1e3,duration:(t-this.#o)*1e3});return this.#o=t,this.#s+=1,e.close(),{vf:n,opts:{keyFrame:this.#s%this.#r===0}}}transformAudio(e){if(this.#t){e.close();return}return e}}function V(o,e,t){let s=null,n=null;const[u,h]=[o.streams.video!=null,o.streams.audio!=null&&o.audio!=null],c=i.recodemux({video:u?{...o.video,bitrate:o.bitrate??3e6}:null,audio:h?o.audio:null});let l=!1;if(u){let r=null,f=0;const w=d=>{clearTimeout(f),r?.close(),r=d;const m=e.transfromVideo(d.clone());m!=null&&(c.encodeVideo(m.vf,m.opts),f=self.setTimeout(()=>{if(r==null)return;const k=new VideoFrame(r,{timestamp:r.timestamp+1e6,duration:1e6});w(k)},1e3))};e.start();const R=i.autoReadStream(o.streams.video,{onChunk:async d=>{if(l){d.close();return}w(d)},onDone:()=>{}});s=()=>{R(),clearTimeout(f),r?.close()}}h&&(n=i.autoReadStream(o.streams.audio,{onChunk:async r=>{if(l){r.close();return}e.transformAudio(r)!=null&&c.encodeAudio(r)},onDone:()=>{}}));const{stream:b,stop:A}=i.file2stream(c.mp4file,o.timeSlice,()=>{p(),t()});function p(){l=!0,s?.(),n?.(),c.close(),A()}return{exit:p,stream:b}}a.AVRecorder=g,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}); //# sourceMappingURL=av-recorder.umd.cjs.map