n.moofOffset);this.fragments.splice(s+1,0,this.currentFragment);for(let[,n]of this.currentFragment.trackData){let a=n.samples[0],c=A(n.samples);this.currentFragment.dataStart=Math.min(this.currentFragment.dataStart,a.byteOffset),this.currentFragment.dataEnd=Math.max(this.currentFragment.dataEnd,c.byteOffset+c.byteSize)}this.currentFragment=null}break;case"traf":if(d(this.currentFragment),this.readContiguousBoxes(t.contentSize),this.currentTrack){let s=this.currentFragment.trackData.get(this.currentTrack.id);if(s){let n=I(this.currentTrack.fragments,this.currentFragment.moofOffset,c=>c.moofOffset);this.currentTrack.fragments.splice(n+1,0,this.currentFragment);let{currentFragmentState:a}=this.currentTrack;d(a),a.startTimestamp!==null&&(dr(s,a.startTimestamp),s.startTimestampIsFinal=!0)}this.currentTrack.currentFragmentState=null,this.currentTrack=null}break;case"tfhd":{d(this.currentFragment),this.isobmffReader.pos+=1;let s=this.isobmffReader.readU24(),n=!!(s&1),a=!!(s&2),c=!!(s&8),o=!!(s&16),u=!!(s&32),m=!!(s&65536),l=!!(s&131072),h=this.isobmffReader.readU32(),p=this.tracks.find(g=>g.id===h);if(!p)break;let b=this.fragmentTrackDefaults.find(g=>g.trackId===h);this.currentTrack=p,p.currentFragmentState={baseDataOffset:this.currentFragment.implicitBaseDataOffset,sampleDescriptionIndex:b?.defaultSampleDescriptionIndex??null,defaultSampleDuration:b?.defaultSampleDuration??null,defaultSampleSize:b?.defaultSampleSize??null,defaultSampleFlags:b?.defaultSampleFlags??null,startTimestamp:null},n?p.currentFragmentState.baseDataOffset=this.isobmffReader.readU64():l&&(p.currentFragmentState.baseDataOffset=this.currentFragment.moofOffset),a&&(p.currentFragmentState.sampleDescriptionIndex=this.isobmffReader.readU32()),c&&(p.currentFragmentState.defaultSampleDuration=this.isobmffReader.readU32()),o&&(p.currentFragmentState.defaultSampleSize=this.isobmffReader.readU32()),u&&(p.currentFragmentState.defaultSampleFlags=this.isobmffReader.readU32()),m&&(p.currentFragmentState.defaultSampleDuration=0)}break;case"tfdt":{let s=this.currentTrack;if(!s)break;d(s.currentFragmentState);let n=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let a=n===0?this.isobmffReader.readU32():this.isobmffReader.readU64();s.currentFragmentState.startTimestamp=a}break;case"trun":{let s=this.currentTrack;if(!s)break;if(d(this.currentFragment),d(s.currentFragmentState),this.currentFragment.trackData.has(s.id))throw new Error("Can't have two trun boxes for the same track in one fragment.");let n=this.isobmffReader.readU8(),a=this.isobmffReader.readU24(),c=!!(a&1),o=!!(a&4),u=!!(a&256),m=!!(a&512),l=!!(a&1024),h=!!(a&2048),p=this.isobmffReader.readU32(),b=s.currentFragmentState.baseDataOffset;c&&(b+=this.isobmffReader.readI32());let g=null;o&&(g=this.isobmffReader.readU32());let k=b;if(p===0){this.currentFragment.implicitBaseDataOffset=k;break}let w=0,y={startTimestamp:0,endTimestamp:0,samples:[],presentationTimestamps:[],startTimestampIsFinal:!1};this.currentFragment.trackData.set(s.id,y);for(let E=0;E({presentationTimestamp:E.presentationTimestamp,sampleIndex:F})).sort((E,F)=>E.presentationTimestamp-F.presentationTimestamp);let v=y.samples[y.presentationTimestamps[0].sampleIndex],D=y.samples[A(y.presentationTimestamps).sampleIndex];y.startTimestamp=v.presentationTimestamp,y.endTimestamp=D.presentationTimestamp+D.duration,this.currentFragment.implicitBaseDataOffset=k}break}this.isobmffReader.pos=r}},Ne=class{constructor(e){this.internalTrack=e}chunkToSampleIndex=new WeakMap;sampleIndexToChunk=new Map;chunkToFragmentLocation=new WeakMap;fragmentLocationToChunk=new Map;getCodec(){throw new Error("Not implemented on base class.")}async computeDuration(){let t=(await this.getChunk(1/0,{metadataOnly:!0}))?.timestamp;return t?t/1e6:0}async getFirstChunk(e){return this.internalTrack.demuxer.isFragmented?this.performFragmentedLookup(()=>{let t=this.internalTrack.fragments[0];return{fragmentIndex:t?0:-1,sampleIndex:t?0:-1,correctSampleFound:!!t}},0,1/0,e):this.fetchChunkForSampleIndex(0,e)}async getChunk(e,t){let r=e*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findSampleInFragmentsForTimestamp(r),r,r,t);{let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=mr(s,r);return this.fetchChunkForSampleIndex(n,t)}}async getNextChunk(e,t){if(this.internalTrack.demuxer.isFragmented){let s=this.chunkToFragmentLocation.get(e);if(s===void 0)throw new Error("Chunk was not created from this track.");let n=s.fragment.trackData.get(this.internalTrack.id),a=n.samples[s.sampleIndex],c=U(this.internalTrack.fragments,s.fragment.moofOffset,o=>o.moofOffset);return d(c!==-1),this.performFragmentedLookup(()=>{if(s.sampleIndex+1l.moofOffset);return d(m!==-1),{fragmentIndex:m,sampleIndex:0,correctSampleFound:!0}}return{fragmentIndex:c,sampleIndex:-1,correctSampleFound:!1}}},a.presentationTimestamp,1/0,t)}let r=this.chunkToSampleIndex.get(e);if(r===void 0)throw new Error("Chunk was not created from this track.");return this.fetchChunkForSampleIndex(r+1,t)}async getKeyChunk(e,t){let r=e*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findKeySampleInFragmentsForTimestamp(r),r,r,t);let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=mr(s,r),a=n===-1?-1:Si(s,n);return this.fetchChunkForSampleIndex(a,t)}async getNextKeyChunk(e,t){if(this.internalTrack.demuxer.isFragmented){let a=this.chunkToFragmentLocation.get(e);if(a===void 0)throw new Error("Chunk was not created from this track.");let c=a.fragment.trackData.get(this.internalTrack.id),o=c.samples[a.sampleIndex],u=U(this.internalTrack.fragments,a.fragment.moofOffset,m=>m.moofOffset);return d(u!==-1),this.performFragmentedLookup(()=>{let m=c.samples.findIndex((l,h)=>l.isKeyFrame&&h>a.sampleIndex);if(m!==-1)return{fragmentIndex:u,sampleIndex:m,correctSampleFound:!0};{let l=a.fragment;for(;l.nextFragment;){l=l.nextFragment;let h=l.trackData.get(this.internalTrack.id);if(h){let p=U(this.internalTrack.fragments,l.moofOffset,g=>g.moofOffset);d(p!==-1);let b=h.samples.findIndex(g=>g.isKeyFrame);if(b===-1)throw new Error("Not supported: Fragment does not contain key sample.");return{fragmentIndex:p,sampleIndex:b,correctSampleFound:!0}}}return{fragmentIndex:u,sampleIndex:-1,correctSampleFound:!1}}},o.presentationTimestamp,1/0,t)}let r=this.chunkToSampleIndex.get(e);if(r===void 0)throw new Error("Chunk was not created from this track.");let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=wi(s,r);return this.fetchChunkForSampleIndex(n,t)}async fetchChunkForSampleIndex(e,t){if(e===-1)return null;let r=this.sampleIndexToChunk.get(e)?.deref();if(r)return r;let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=Ci(s,e);if(!n)return null;let a;t.metadataOnly?a=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(n.chunkOffset,n.chunkOffset+n.chunkSize),a=this.internalTrack.demuxer.chunkReader.readRange(n.sampleOffset,n.sampleOffset+n.sampleSize));let c=1e6*n.presentationTimestamp/this.internalTrack.timescale,o=1e6*n.duration/this.internalTrack.timescale,u=this.createChunk(a,c,o,n.isKeyFrame);return this.chunkToSampleIndex.set(u,e),this.sampleIndexToChunk.set(e,new WeakRef(u)),u}async fetchChunkInFragment(e,t,r){if(t===-1)return null;let s=`${e.moofOffset}:${t}`,n=this.fragmentLocationToChunk.get(s)?.deref();if(n)return n;let c=e.trackData.get(this.internalTrack.id).samples[t];d(c);let o;r.metadataOnly?o=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(e.dataStart,e.dataEnd),o=this.internalTrack.demuxer.chunkReader.readRange(c.byteOffset,c.byteOffset+c.byteSize));let u=1e6*c.presentationTimestamp/this.internalTrack.timescale,m=1e6*c.duration/this.internalTrack.timescale,l=this.createChunk(o,u,m,c.isKeyFrame);return this.chunkToFragmentLocation.set(l,{fragment:e,sampleIndex:t}),this.fragmentLocationToChunk.set(s,new WeakRef(l)),l}findSampleInFragmentsForTimestamp(e){let t=I(this.internalTrack.fragments,e,n=>n.trackData.get(this.internalTrack.id).startTimestamp),r=-1,s=!1;if(t!==-1){let a=this.internalTrack.fragments[t].trackData.get(this.internalTrack.id),c=I(a.presentationTimestamps,e,o=>o.presentationTimestamp);d(c!==-1),r=a.presentationTimestamps[c].sampleIndex,s=en.trackData.get(this.internalTrack.id).startTimestamp),r=-1,s=!1;if(t!==-1){let a=this.internalTrack.fragments[t].trackData.get(this.internalTrack.id),c=a.presentationTimestamps.findLastIndex(u=>a.samples[u.sampleIndex].isKeyFrame&&u.presentationTimestamp<=e);if(c===-1)throw new Error("Not supported: Fragment does not begin with a key sample.");r=a.presentationTimestamps[c].sampleIndex,s=ew.timestamp);k!==-1&&(g=this.internalTrack.fragmentLookupTable[k])}if(c===-1)m.pos=g?.moofOffset??0;else{let k=this.internalTrack.fragments[c];!g||k.moofOffset>=k.moofOffset?(m.pos=k.moofOffset+k.moofSize,h=k):m.pos=g.moofOffset}for(;m.posr)break;if(h.nextFragment){m.pos=h.nextFragment.moofOffset+h.nextFragment.moofSize,h=h.nextFragment;continue}}await m.reader.loadRange(m.pos,m.pos+16);let k=m.pos,w=m.readBoxHeader();if(w.name==="moof"){let y=U(n.fragments,k,v=>v.moofOffset);if(y===-1){m.pos=k;let v=await n.readFragment();h&&(h.nextFragment=v),h=v;let{fragmentIndex:D,sampleIndex:E,correctSampleFound:F}=e();if(F){let J=this.internalTrack.fragments[D];return this.fetchChunkInFragment(J,E,s)}D!==-1&&(p=D,b=E)}else{let v=n.fragments[y];h&&(h.nextFragment=v),h=v}}m.pos=k+w.totalSize}if(p!==-1){let k=this.internalTrack.fragments[p];return this.fetchChunkInFragment(k,b,s)}return null}finally{a()}}},yt=class extends Ne{internalTrack;constructor(e){super(e),this.internalTrack=e}async getCodec(){return this.internalTrack.info.codec}async getWidth(){return this.internalTrack.info.width}async getHeight(){return this.internalTrack.info.height}async getRotation(){return this.internalTrack.rotation}async getDecoderConfig(){return{codec:tr(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),codedWidth:this.internalTrack.info.width,codedHeight:this.internalTrack.info.height,description:this.internalTrack.info.codecDescription??void 0,colorSpace:this.internalTrack.info.colorSpace??void 0}}createChunk(e,t,r,s){return new EncodedVideoChunk({data:e,timestamp:t,duration:r,type:s?"key":"delta"})}},vt=class extends Ne{internalTrack;constructor(e){super(e),this.internalTrack=e}async getCodec(){return this.internalTrack.info.codec}async getNumberOfChannels(){return this.internalTrack.info.numberOfChannels}async getSampleRate(){return this.internalTrack.info.sampleRate}async getDecoderConfig(){return{codec:ir(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),numberOfChannels:this.internalTrack.info.numberOfChannels,sampleRate:this.internalTrack.info.sampleRate,description:this.internalTrack.info.codecDescription??void 0}}createChunk(e,t,r,s){return new EncodedAudioChunk({data:e,timestamp:t,duration:r,type:s?"key":"delta"})}},mr=(i,e)=>{let t=I(i.presentationTimestamps,e,r=>r.presentationTimestamp);return t===-1?-1:i.presentationTimestamps[t].sampleIndex},Ci=(i,e)=>{let t=I(i.sampleTimingEntries,e,g=>g.startIndex),r=i.sampleTimingEntries[t];if(!r||r.startIndex+r.count<=e)return null;let n=r.startDecodeTimestamp+(e-r.startIndex)*r.delta,a=I(i.sampleCompositionTimeOffsets,e,g=>g.startIndex),c=i.sampleCompositionTimeOffsets[a];c&&(n+=c.offset);let o=i.sampleSizes[Math.min(e,i.sampleSizes.length-1)],u=I(i.sampleToChunk,e,g=>g.startSampleIndex),m=i.sampleToChunk[u];d(m);let l=m.startChunkIndex+Math.floor((e-m.startSampleIndex)/m.samplesPerChunk),h=i.chunkOffsets[l],p=0,b=h;if(i.sampleSizes.length===1)b+=o*(e-m.startSampleIndex),p+=o*m.samplesPerChunk;else{let g=m.startSampleIndex+(l-m.startChunkIndex)*m.samplesPerChunk;for(let k=g;kg)!==-1:!0}},Si=(i,e)=>{if(!i.keySampleIndices)return e;let t=I(i.keySampleIndices,e,r=>r);return i.keySampleIndices[t]??-1},wi=(i,e)=>{if(!i.keySampleIndices)return e+1;let t=I(i.keySampleIndices,e,r=>r);return i.keySampleIndices[t+1]??-1},dr=(i,e)=>{i.startTimestamp+=e,i.endTimestamp+=e;for(let t of i.samples)t.presentationTimestamp+=e;for(let t of i.presentationTimestamps)t.presentationTimestamp+=e};var He=class extends ue{};var Le=class{},Rt=class extends Le{async _canReadInput(e){if(await e._mainReader.source._getSize()<8)return!1;let r=new Z(e._mainReader);return r.pos=4,r.readAscii(4)==="ftyp"}_createDemuxer(e){return new We(e)}},It=class extends Le{async _canReadInput(){return!1}_createDemuxer(e){return new He(e)}},Ke=new Rt,yi=Ke,vi=Ke,At=new It,lr=At,Ri=At,Ii=[Ke,lr];var Ot=class{_source;_formats;_mainReader;_demuxerPromise=null;_format=null;constructor(e){this._formats=e.formats,this._source=e.source,this._mainReader=new me(e.source)}_getDemuxer(){return this._demuxerPromise??=(async()=>{await this._mainReader.loadRange(0,4096);for(let e of this._formats)if(await e._canReadInput(this))return this._format=e,e._createDemuxer(this);throw new Error("Input has an unrecognizable format.")})()}async getFormat(){return await this._getDemuxer(),d(this._format),this._format}async computeDuration(){return(await this._getDemuxer()).computeDuration()}async getTracks(){return(await this._getDemuxer()).getTracks()}async getVideoTracks(){return(await this.getTracks()).filter(t=>t.isVideoTrack())}async getPrimaryVideoTrack(){return(await this.getTracks()).find(t=>t.isVideoTrack())??null}async getAudioTracks(){return(await this.getTracks()).filter(t=>t.isAudioTrack())}async getPrimaryAudioTrack(){return(await this.getTracks()).find(t=>t.isAudioTrack())??null}async getMimeType(){return(await this._getDemuxer()).getMimeType()}};var Qe=class{async*chunks(e,t=1/0){let r=[],{promise:s,resolve:n}=B(),{promise:a,resolve:c}=B(),o=!1,u=[],m=()=>Math.max(2,u.length);(async()=>{let l=e??await this.getFirstChunk();for(;l&&!o&&!(l.timestamp/1e6>=t);){if(r.length>m()){({promise:a,resolve:c}=B()),await a;continue}r.push(l),n(),{promise:s,resolve:n}=B(),l=await this.getNextChunk(l)}o=!0,n()})();try{for(;;)if(r.length>0){yield r.shift();let l=performance.now();for(u.push(l);u.length>0&&l-u[0]>=1e3;)u.shift();c()}else if(!o)await s;else break}finally{o=!0,c()}}},$e=class{async getKeyMediaFrame(e){let t=null,r=await this.createDecoder(a=>t=a),n=await this.createChunkDrain().getKeyChunk(e);return n?(r.decode(n),await r.flush(),r.close(),t):null}async getMediaFrame(e){let t=null,r=await this.createDecoder(o=>{o.timestamp/1e6<=e?(t?.close(),t=o):o.close()}),s=this.createChunkDrain(),n=await s.getKeyChunk(e);if(!n)return null;let a=await s.getChunk(e);d(a),r.decode(n);let c=n;for(;c!==a;){let o=await s.getNextChunk(c);d(o),c=o,r.decode(o),r.decodeQueueSize>=10&&await new Promise(u=>r.addEventListener("dequeue",u,{once:!0}))}return await r.flush(),r.close(),t}async*mediaFrames(e=0,t=1/0){let r=[],s=!1,n=null,{promise:a,resolve:c}=B(),{promise:o,resolve:u}=B(),m=!1,l=8,h=await this.createDecoder(k=>{u();let w=k.timestamp/1e6;if(w>=t&&(m=!0),m){k.close();return}n&&(w>e?(r.push(n),s=!0):n.close()),w>=e&&(r.push(k),s=!0),n=s?null:k,r.length>0&&(c(),{promise:a,resolve:c}=B())}),p=this.createChunkDrain(),b=await p.getKeyChunk(e)??await p.getFirstChunk();if(!b)return;let g=!1;(async()=>{let k=b,w=1/0;if(t<1/0){let v=await p.getChunk(t),D=v?v.type==="key"&&v.timestamp/1e6===t?v:await p.getNextKeyChunk(v):null;D&&(w=D.timestamp/1e6)}let y=p.chunks(b,w);for(await y.next();k&&!m;){if(r.length+h.decodeQueueSize>l){({promise:o,resolve:u}=B()),await o;continue}h.decode(k);let v=await y.next();if(v.done)break;k=v.value}await y.return(),await h.flush(),h.close(),!s&&n&&r.push(n),g=!0,c()})();try{for(;;)if(r.length>0)yield r.shift(),u();else if(!g)await a;else break}finally{m=!0,u();for(let k of r)k.close()}}},je=class extends Qe{constructor(t){super();this.videoTrack=t}getFirstChunk(t={}){return this.videoTrack._backing.getFirstChunk(t)}getChunk(t,r={}){return this.videoTrack._backing.getChunk(t,r)}getNextChunk(t,r={}){return this.videoTrack._backing.getNextChunk(t,r)}getKeyChunk(t,r={}){return this.videoTrack._backing.getKeyChunk(t,r)}getNextKeyChunk(t,r={}){return this.videoTrack._backing.getNextKeyChunk(t,r)}},qe=class extends $e{constructor(t){super();this.videoTrack=t}decoderConfig=null;async createDecoder(t){this.decoderConfig??=await this.videoTrack.getDecoderConfig();let r=new VideoDecoder({output:t,error:s=>console.error(s)});return r.configure(this.decoderConfig),r}createChunkDrain(){return new je(this.videoTrack)}getKeyFrame(t){return this.getKeyMediaFrame(t)}getFrame(t){return this.getMediaFrame(t)}frames(t=0,r=1/0){return this.mediaFrames(t,r)}},_t=class{constructor(e,t){this.videoTrack=e;this.dimensions=t;this.videoFrameDrain=new qe(e)}videoFrameDrain;async videoFrameToWrappedCanvas(e){let t=this.dimensions?.width??await this.videoTrack.getRotatedWidth(),r=this.dimensions?.height??await this.videoTrack.getRotatedHeight(),s=await this.videoTrack.getRotation(),n=document.createElement("canvas");n.width=t,n.height=r;let a=n.getContext("2d",{alpha:!1});d(a),a.translate(t/2,r/2),a.rotate(s*Math.PI/180),a.translate(-t/2,-r/2);let[c,o]=s%180===0?[t,r]:[r,t];a.drawImage(e,(t-c)/2,(r-o)/2,c,o);let u={canvas:n,timestamp:e.timestamp/1e6,duration:(e.duration??0)/1e6};return e.close(),u}async getCanvas(e){let t=await this.videoFrameDrain.getFrame(e);return t&&this.videoFrameToWrappedCanvas(t)}async*canvases(e=0,t=1/0){for await(let r of this.videoFrameDrain.frames(e,t))yield this.videoFrameToWrappedCanvas(r)}},Xe=class extends Qe{constructor(t){super();this.audioTrack=t}getFirstChunk(t={}){return this.audioTrack._backing.getFirstChunk(t)}getChunk(t,r={}){return this.audioTrack._backing.getChunk(t,r)}getNextChunk(t,r={}){return this.audioTrack._backing.getNextChunk(t,r)}getKeyChunk(t,r={}){return this.audioTrack._backing.getKeyChunk(t,r)}getNextKeyChunk(t,r={}){return this.audioTrack._backing.getNextKeyChunk(t,r)}},Ge=class extends $e{constructor(t){super();this.audioTrack=t}decoderConfig=null;async createDecoder(t){this.decoderConfig??=await this.audioTrack.getDecoderConfig();let r=new AudioDecoder({output:t,error:s=>console.error(s)});return r.configure(this.decoderConfig),r}createChunkDrain(){return new Xe(this.audioTrack)}getKeyData(t){return this.getKeyMediaFrame(t)}getData(t){return this.getMediaFrame(t)}data(t=0,r=1/0){return this.mediaFrames(t,r)}},Et=class{constructor(e){this.audioTrack=e;this.audioDataDrain=new Ge(e)}audioDataDrain;audioDataToWrappedArrayBuffer(e){let t=new AudioBuffer({numberOfChannels:e.numberOfChannels,length:e.numberOfFrames,sampleRate:e.sampleRate}),r=new Float32Array(e.allocationSize({planeIndex:0,format:"f32-planar"})/4);for(let a=0;at.track===e))return;e:for(;;){let e=null,t=1/0;for(let s of this.trackDatas){if(s.chunkQueue.length===0&&!s.track.source._closed)break e;s.chunkQueue.length>0&&s.chunkQueue[0].timestamp=2&&r++;let o={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];Ft(t.data,r+0,r+3,o)}createInternalChunk(e,t,r,s,a=null){return{data:e,type:s,timestamp:t,duration:r,additions:a}}writeBlock(e,t){this.segment||(this.createTracks(),this.createSegment());let r=Math.floor(1e3*t.timestamp),s=this.trackDatas.every(m=>{if(m.track.source._closed)return!0;if(e===m)return t.type==="key";let d=m.chunkQueue[0];return d&&d.type==="key"});(!this.currentCluster||s&&r-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(r);let a=r-this.currentClusterMsTimestamp;if(a<0)return;if(a>=mt)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${mt} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${mt} milliseconds.`);let c=new Uint8Array(4),o=new DataView(c.buffer);o.setUint8(0,128|e.track.id),o.setInt16(1,a,!1);let u=Math.floor(1e3*t.duration);if(u===0&&!t.additions){o.setUint8(3,+(t.type==="key")<<7);let m={id:163,data:[c,t.data]};this.writeEBML(m)}else{let m={id:160,data:[{id:161,data:[c,t.data]},t.type==="delta"?{id:251,data:new ge(e.lastWrittenMsTimestamp-r)}:null,t.additions?{id:30113,data:[{id:166,data:[{id:165,data:t.additions},{id:238,data:1}]}]}:null,u>0?{id:155,data:u}:null]};this.writeEBML(m)}this.duration=Math.max(this.duration,r+u),e.lastWrittenMsTimestamp=r,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:mr,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){l(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),t=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,mr),this.writer.seek(t);let r=this.offsets.get(this.currentCluster)-this.segmentDataOffset;l(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(s=>({id:183,data:[{id:247,data:s.track.id},{id:241,data:r}]}))]})}async onTrackClose(){let e=await this.mutex.acquire();await this.interleaveChunks(),e()}async finalize(){let e=await this.mutex.acquire();this.segment||(this.createTracks(),this.createSegment());for(let t of this.trackDatas)for(;t.chunkQueue.length>0;)this.writeBlock(t,t.chunkQueue.shift());if(!this.format._options.streamable&&this.currentCluster&&this.finalizeCurrentCluster(),l(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let t=this.writer.getPos(),r=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(r,ur),this.segmentDuration.data=new ae(this.duration),this.writer.seek(this.offsets.get(this.segmentDuration)),this.writeEBML(this.segmentDuration),this.seekHead.data[0].data[1].data=this.offsets.get(this.cues)-this.segmentDataOffset,this.seekHead.data[1].data[1].data=this.offsets.get(this.segmentInfo)-this.segmentDataOffset,this.seekHead.data[2].data[1].data=this.offsets.get(this.tracksElement)-this.segmentDataOffset,this.writer.seek(this.offsets.get(this.seekHead)),this.writeEBML(this.seekHead),this.writer.seek(t)}e()}};var Y=class{},lt=class extends Y{_options;constructor(e={}){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(e.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(e.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super(),this._options=e}_createMuxer(e){return new Pe(e,this)}},Ue=class extends Y{_options;constructor(e={}){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(e.streamable!==void 0&&typeof e.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super(),this._options=e}_createMuxer(e){return new ze(e,this)}},ne=class extends Ue{};var oe=class{_connectedTrack=null;_closed=!1;_offsetTimestamps=!1;_ensureValidDigest(){if(!this._connectedTrack)throw new Error("Cannot call digest without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call digest before output has been started.");if(this._connectedTrack.output._finalizing)throw new Error("Cannot call digest after output has started finalizing.");if(this._closed)throw new Error("Cannot call digest after source has been closed.")}_start(){}async _flush(){}close(){if(this._closed)throw new Error("Source already closed.");if(!this._connectedTrack)throw new Error("Cannot call close without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call close before output has been started.");this._closed=!0,!this._connectedTrack.output._finalizing&&this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack)}},W=class extends oe{_connectedTrack=null;_codec;constructor(e){if(super(),!ie.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${ie.join(", ")}.`);this._codec=e}},dt=class extends W{constructor(e){super(e)}digest(e,t){if(!(e instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,e,t)}},ki=5,Ti=i=>{if(!i||typeof i!="object")throw new TypeError("Codec config must be an object.");if(!ie.includes(i.codec))throw new TypeError(`Invalid video codec '${i.codec}'. Must be one of: ${ie.join(", ")}.`);if(!Number.isInteger(i.bitrate)||i.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(i.latencyMode!==void 0&&!["quality","realtime"].includes(i.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.")},ke=class{constructor(e,t){this.source=e;this.codecConfig=t;Ti(t)}encoder=null;muxer=null;lastMultipleOfKeyFrameInterval=-1;lastWidth=null;lastHeight=null;async digest(e){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(e.codedWidth!==this.lastWidth||e.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${e.codedWidth}x${e.codedHeight}.`)}else this.lastWidth=e.codedWidth,this.lastHeight=e.codedHeight;this.ensureEncoder(e),l(this.encoder);let t=Math.floor(e.timestamp/1e6/ki);this.encoder.encode(e,{keyFrame:t!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=t,this.encoder.encodeQueueSize>=4&&await new Promise(r=>this.encoder.addEventListener("dequeue",r,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(e){this.encoder||(this.encoder=new VideoEncoder({output:(t,r)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,t,r),error:t=>console.error("Video encode error:",t)}),this.encoder.configure({codec:tr(this.codecConfig.codec,e.codedWidth,e.codedHeight,this.codecConfig.bitrate),width:e.codedWidth,height:e.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...ar(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},ft=class extends W{_encoder;constructor(e){super(e.codec),this._encoder=new ke(this,e)}digest(e){if(!(e instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(e)}_flush(){return this._encoder.flush()}},pt=class extends W{_encoder;_canvas;constructor(e,t){if(!(e instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(t.codec),this._encoder=new ke(this,t),this._canvas=e}digest(e,t=0){if(!Number.isFinite(e)||e<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(t)||t<0)throw new TypeError("duration must be a non-negative number.");let r=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*e),duration:Math.round(1e6*t),alpha:"discard"}),s=this._encoder.digest(r);return r.close(),s}_flush(){return this._encoder.flush()}},ht=class extends W{_encoder;_abortController=null;_track;_offsetTimestamps=!0;constructor(e,t){if(!(e instanceof MediaStreamTrack)||e.kind!=="video")throw new TypeError("track must be a video MediaStreamTrack.");t={...t,latencyMode:"realtime"},super(t.codec),this._encoder=new ke(this,t),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),t=new WritableStream({write:r=>{this._encoder.digest(r),r.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(r=>{r instanceof DOMException&&r.name==="AbortError"||console.error("Pipe error:",r)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},N=class extends oe{_connectedTrack=null;_codec;constructor(e){if(super(),!se.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${se.join(", ")}.`);this._codec=e}},bt=class extends N{constructor(e){super(e)}digest(e,t){if(!(e instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,e,t)}},xi=i=>{if(!i||typeof i!="object")throw new TypeError("Codec config must be an object.");if(!se.includes(i.codec))throw new TypeError(`Invalid audio codec '${i.codec}'. Must be one of: ${se.join(", ")}.`);if(!Number.isInteger(i.bitrate)||i.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},Te=class{constructor(e,t){this.source=e;this.codecConfig=t;xi(t)}encoder=null;muxer=null;lastNumberOfChannels=null;lastSampleRate=null;async digest(e){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(e.numberOfChannels!==this.lastNumberOfChannels||e.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${e.numberOfChannels} channels at ${e.sampleRate} Hz.`)}else this.lastNumberOfChannels=e.numberOfChannels,this.lastSampleRate=e.sampleRate;this.ensureEncoder(e),l(this.encoder),this.encoder.encode(e),this.encoder.encodeQueueSize>=4&&await new Promise(t=>this.encoder.addEventListener("dequeue",t,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(e){this.encoder||(this.encoder=new AudioEncoder({output:(t,r)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,t,r),error:t=>console.error("Audio encode error:",t)}),this.encoder.configure({codec:ir(this.codecConfig.codec,e.numberOfChannels,e.sampleRate),numberOfChannels:e.numberOfChannels,sampleRate:e.sampleRate,bitrate:this.codecConfig.bitrate,...nr(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},gt=class extends N{_encoder;constructor(e){super(e.codec),this._encoder=new Te(this,e)}digest(e){if(!(e instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(e)}_flush(){return this._encoder.flush()}},kt=class extends N{_encoder;_accumulatedFrameCount=0;constructor(e){super(e.codec),this._encoder=new Te(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let t=e.numberOfChannels,r=e.sampleRate,s=e.length,a=new Float32Array(t*s);for(let o=0;o{this._encoder.digest(r),r.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(r=>{r instanceof DOMException&&r.name==="AbortError"||console.error("Pipe error:",r)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},ce=class extends oe{_connectedTrack=null;_codec;constructor(e){if(super(),!Fe.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Fe.join(", ")}.`);this._codec=e}},xt=class extends ce{_parser;constructor(e){super(e),this._parser=new ve({codec:e,output:(t,r)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,t,r),error:t=>console.error("Subtitle parse error:",t)})}digest(e){if(typeof e!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(e),this._connectedTrack.output._muxer.mutex.currentPromise}};var Ct=class{_muxer;_writer;_tracks=[];_started=!1;_finalizing=!1;_mutex=new H;constructor(e){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(!(e.format instanceof Y))throw new TypeError("options.format must be an OutputFormat.");if(!(e.target instanceof G))throw new TypeError("options.target must be a Target.");if(e.target._output)throw new Error("Target is already used for another output.");e.target._output=this,this._writer=e.target._createWriter(),this._muxer=e.format._createMuxer(this)}addVideoTrack(e,t={}){if(!(e instanceof W))throw new TypeError("source must be a VideoSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");if(typeof t.rotation=="number"&&![0,90,180,270].includes(t.rotation))throw new TypeError(`Invalid video rotation: ${t.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(t.rotation)&&(t.rotation.length!==9||t.rotation.some(r=>!Number.isFinite(r))))throw new TypeError(`Invalid video transformation matrix: ${t.rotation.join()}`);if(t.frameRate!==void 0&&(!Number.isInteger(t.frameRate)||t.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${t.frameRate}. Must be a positive integer.`);this._addTrack("video",e,t)}addAudioTrack(e,t={}){if(!(e instanceof N))throw new TypeError("source must be an AudioSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",e,t)}addSubtitleTrack(e,t={}){if(!(e instanceof ce))throw new TypeError("source must be a SubtitleSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",e,t)}_addTrack(e,t,r){if(this._started)throw new Error("Cannot add track after output has started.");if(t._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:e,source:t,metadata:r};this._muxer.beforeTrackAdd(s),this._tracks.push(s),t._connectedTrack=s}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let e=await this._mutex.acquire();await this._muxer.start();for(let t of this._tracks)t.source._start();e()}async finalize(){if(!this._started)throw new Error("Cannot finalize before starting.");if(this._finalizing)throw new Error("Cannot call finalize twice.");this._finalizing=!0;let e=await this._mutex.acquire(),t=this._tracks.map(r=>r.source._flush());await Promise.all(t),await this._muxer.finalize(),await this._writer.flush(),await this._writer.finalize(),e()}};var xe=class{_sizePromise=null;_getSize(){return this._sizePromise??=this._retrieveSize()}},St=class extends xe{constructor(t){super();this.buffer=t}async _read(t,r){return new Uint8Array(this.buffer,t,r-t)}async _retrieveSize(){return this.buffer.byteLength}},wt=class extends xe{constructor(t){super();this.blob=t}async _read(t,r){let a=await this.blob.slice(t,r).arrayBuffer();return new Uint8Array(a)}async _retrieveSize(){return this.blob.size}};var ue=class{input;constructor(e){this.input=e}};var Be=class{_backing;constructor(e){this._backing=e}isVideoTrack(){return this instanceof Ce}isAudioTrack(){return this instanceof Se}computeDuration(){return this._backing.computeDuration()}},Ce=class extends Be{_backing;constructor(e){super(e),this._backing=e}getCodec(){return this._backing.getCodec()}getWidth(){return this._backing.getWidth()}getHeight(){return this._backing.getHeight()}getRotation(){return this._backing.getRotation()}async getRotatedWidth(){return await this._backing.getRotation()%180===0?this._backing.getWidth():this._backing.getHeight()}async getRotatedHeight(){return await this._backing.getRotation()%180===0?this._backing.getHeight():this._backing.getWidth()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}},Se=class extends Be{_backing;constructor(e){super(e),this._backing=e}getCodec(){return this._backing.getCodec()}getNumberOfChannels(){return this._backing.getNumberOfChannels()}getSampleRate(){return this._backing.getSampleRate()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}};var me=class{constructor(e,t=1/0){this.source=e;this.maxStorableBytes=t}loadedSegments=[];loadingSegments=[];sourceSizePromise=null;nextAge=0;totalStoredBytes=0;async loadRange(e,t){t=Math.min(t,await this.source._getSize());let r=this.loadingSegments.find(o=>o.start<=e&&o.end>=t);if(r){await r.promise;return}if(this.loadedSegments.some(o=>o.start<=e&&o.end>=t))return;let a=this.source._read(e,t),n={start:e,end:t,promise:a};this.loadingSegments.push(n);let c=await a;Ut(this.loadingSegments,n),this.insertIntoLoadedSegments(e,c)}insertIntoLoadedSegments(e,t){let r={start:e,end:e+t.byteLength,bytes:t,view:new DataView(t.buffer),age:this.nextAge++},s=A(this.loadedSegments,e,a=>a.start);(s===-1||this.loadedSegments[s].start=r.end)break;r.start<=n.start&&n.end<=r.end&&(this.loadedSegments.splice(a,1),a--)}for(;this.totalStoredBytes>this.maxStorableBytes&&this.loadedSegments.length>1;){let a=null,n=-1;for(let c=0;ca.start),s=null;if(r!==-1)for(let a=r;ae)break;if(t<=n.end){s=n;break}}if(!s)throw new Error(`No segment loaded for range [${e}, ${t}).`);return s.age=this.nextAge++,{view:s.view,offset:s.bytes.byteOffset+e-s.start}}forgetRange(e,t){if(t<=e)return;let r=A(this.loadedSegments,e,a=>a.start);if(r===-1)return;let s=this.loadedSegments[r];s.start!==e||s.end!==t||(this.loadedSegments.splice(r,1),this.totalStoredBytes-=s.bytes.byteLength)}};var Z=class{constructor(e){this.reader=e}pos=0;readRange(e,t){let{view:r,offset:s}=this.reader.getViewAndOffset(e,t);return new Uint8Array(r.buffer,s,t-e)}readU8(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+1);return this.pos++,e.getUint8(t)}readU16(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+2);return this.pos+=2,e.getUint16(t,!1)}readU24(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+3);this.pos+=3;let r=e.getUint16(t,!1),s=e.getUint8(t+2);return r*256+s}readS32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getInt32(t,!1)}readU32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getUint32(t,!1)}readI32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getInt32(t,!1)}readU64(){let e=this.readU32(),t=this.readU32();return e*4294967296+t}readF64(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+8);return this.pos+=8,e.getFloat64(t,!1)}readFixed_16_16(){return this.readS32()/65536}readFixed_2_30(){return this.readS32()/1073741824}readAscii(e){let{view:t,offset:r}=this.reader.getViewAndOffset(this.pos,this.pos+e);this.pos+=e;let s="";for(let a=0;ar.computeDuration()));return Math.max(0,...t)}async getTracks(){return await this.readMetadata(),this.tracks.map(e=>e.inputTrack)}async getMimeType(){await this.readMetadata();let e="video/mp4";if(this.tracks.length>0){let t=await Promise.all(this.tracks.map(s=>s.inputTrack.getCodecMimeType())),r=[...new Set(t)];e+=`; codecs="${r.join(", ")}"`}return e}readMetadata(){return this.metadataPromise??=(async()=>{let e=await this.isobmffReader.reader.source._getSize();for(;this.isobmffReader.pos=0&&rr.presentationTimestamp-s.presentationTimestamp),e.sampleTable}async readFragment(){let e=this.isobmffReader.pos;await this.isobmffReader.reader.loadRange(this.isobmffReader.pos,this.isobmffReader.pos+16);let t=this.isobmffReader.readBoxHeader();l(t.name==="moof"),await this.isobmffReader.reader.loadRange(e,e+t.totalSize),this.isobmffReader.pos=e,this.traverseBox();let r=B(this.fragments,e,a=>a.moofOffset);l(r!==-1);let s=this.fragments[r];l(s.moofOffset===e),this.isobmffReader.reader.forgetRange(e,e+t.totalSize);for(let[a,n]of s.trackData){if(n.startTimestampIsFinal)continue;let c=this.tracks.find(d=>d.id===a);this.isobmffReader.pos=0;let o=null,u=null,m=A(c.fragments,e-1,d=>d.moofOffset);for(m!==-1&&(o=c.fragments[m],u=o,this.isobmffReader.pos=o.moofOffset+o.moofSize);this.isobmffReader.posb.moofOffset);if(g===-1){this.isobmffReader.pos=d;let b=await this.readFragment();o&&(o.nextFragment=b),o=b}else{let b=this.fragments[g];o&&(o.nextFragment=b),o=b}}this.isobmffReader.pos=d+h.totalSize}o&&o.trackData.has(a)&&(u=o)}if(u){let d=u.trackData.get(a);l(d.startTimestampIsFinal),dr(n,d.endTimestamp)}n.startTimestampIsFinal=!0}return s}readContiguousBoxes(e){let t=this.isobmffReader.pos;for(;this.isobmffReader.pos-tm.every((d,h)=>d===o[h]));u===-1?s.rotation=0:s.rotation=90*u}break;case"mdhd":{let s=this.currentTrack;l(s);let a=this.isobmffReader.readU8();this.isobmffReader.pos+=3,a===0?(this.isobmffReader.pos+=8,s.timescale=this.isobmffReader.readU32(),s.durationInTimescale=this.isobmffReader.readU32()):a===1&&(this.isobmffReader.pos+=16,s.timescale=this.isobmffReader.readU32(),s.durationInTimescale=this.isobmffReader.readU64())}break;case"hdlr":{let s=this.currentTrack;l(s),this.isobmffReader.pos+=8;let a=this.isobmffReader.readAscii(4);a==="vide"?s.info={type:"video",width:-1,height:-1,codec:null,codecDescription:null,colorSpace:null}:a==="soun"&&(s.info={type:"audio",numberOfChannels:-1,sampleRate:-1,codec:null,codecDescription:null})}break;case"stbl":{let s=this.currentTrack;l(s),s.sampleTableOffset=e,this.readContiguousBoxes(t.contentSize)}break;case"stsd":{let s=this.currentTrack;if(l(s),s.info===null||s.sampleTable)break;let a=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let n=this.isobmffReader.readU32();for(let c=0;c0){if(u===1)this.isobmffReader.pos+=4*4;else if(u===2){this.isobmffReader.pos+=4,d=this.isobmffReader.readF64(),m=this.isobmffReader.readU32(),this.isobmffReader.pos+=4;let h=this.isobmffReader.readU32(),g=this.isobmffReader.readU32(),b=this.isobmffReader.readU32(),f=this.isobmffReader.readU32()}}s.info.numberOfChannels=m,s.info.sampleRate=d,this.readContiguousBoxes(e+o.totalSize-this.isobmffReader.pos)}}}break;case"avcC":{let s=this.currentTrack;l(s&&s.info),s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+t.contentSize)}break;case"hvcC":{let s=this.currentTrack;l(s&&s.info),s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+t.contentSize)}break;case"colr":{let s=this.currentTrack;if(l(s&&s.info?.type==="video"),this.isobmffReader.readAscii(4)!=="nclx")break;let n=this.isobmffReader.readU16(),c=this.isobmffReader.readU16(),o=this.isobmffReader.readU16(),u=!!(this.isobmffReader.readU8()&128);s.info.colorSpace={primaries:Mt[n],transfer:Vt[c],matrix:Dt[o],fullRange:u}}break;case"wave":t.totalSize>8&&this.readContiguousBoxes(t.contentSize);break;case"esds":{let s=this.currentTrack;l(s&&s.info),this.isobmffReader.pos+=4;let a=this.isobmffReader.readU8();l(a===3),this.isobmffReader.readIsomVariableInteger(),this.isobmffReader.pos+=2;let n=this.isobmffReader.readU8(),c=(n&128)!==0,o=(n&64)!==0,u=(n&32)!==0;if(c&&(this.isobmffReader.pos+=2),o){let b=this.isobmffReader.readU8();this.isobmffReader.pos+=b}u&&(this.isobmffReader.pos+=2);let m=this.isobmffReader.readU8();l(m===4),this.isobmffReader.readIsomVariableInteger();let d=this.isobmffReader.readU8();l(d===64),this.isobmffReader.pos+=12;let h=this.isobmffReader.readU8();l(h===5);let g=this.isobmffReader.readIsomVariableInteger();s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+g)}break;case"stts":{let s=this.currentTrack;if(l(s),!s.sampleTable)break;this.isobmffReader.pos+=4;let a=this.isobmffReader.readU32(),n=0,c=0;for(let o=0;oC.id===a);if(!n)break;n.fragmentLookupTable=[];let c=this.isobmffReader.readU32(),o=(c&48)>>4,u=(c&12)>>2,m=c&3,d=this.isobmffReader,h=[d.readU8.bind(d),d.readU16.bind(d),d.readU24.bind(d),d.readU32.bind(d)],g=h[o],b=h[u],f=h[m],k=this.isobmffReader.readU32();for(let C=0;Ca.moofOffset);this.fragments.splice(s+1,0,this.currentFragment);for(let[,a]of this.currentFragment.trackData){let n=a.samples[0],c=_(a.samples);this.currentFragment.dataStart=Math.min(this.currentFragment.dataStart,n.byteOffset),this.currentFragment.dataEnd=Math.max(this.currentFragment.dataEnd,c.byteOffset+c.byteSize)}this.currentFragment=null}break;case"traf":if(l(this.currentFragment),this.readContiguousBoxes(t.contentSize),this.currentTrack){let s=this.currentFragment.trackData.get(this.currentTrack.id);if(s){let a=A(this.currentTrack.fragments,this.currentFragment.moofOffset,c=>c.moofOffset);this.currentTrack.fragments.splice(a+1,0,this.currentFragment);let{currentFragmentState:n}=this.currentTrack;l(n),n.startTimestamp!==null&&(dr(s,n.startTimestamp),s.startTimestampIsFinal=!0)}this.currentTrack.currentFragmentState=null,this.currentTrack=null}break;case"tfhd":{l(this.currentFragment),this.isobmffReader.pos+=1;let s=this.isobmffReader.readU24(),a=!!(s&1),n=!!(s&2),c=!!(s&8),o=!!(s&16),u=!!(s&32),m=!!(s&65536),d=!!(s&131072),h=this.isobmffReader.readU32(),g=this.tracks.find(f=>f.id===h);if(!g)break;let b=this.fragmentTrackDefaults.find(f=>f.trackId===h);this.currentTrack=g,g.currentFragmentState={baseDataOffset:this.currentFragment.implicitBaseDataOffset,sampleDescriptionIndex:b?.defaultSampleDescriptionIndex??null,defaultSampleDuration:b?.defaultSampleDuration??null,defaultSampleSize:b?.defaultSampleSize??null,defaultSampleFlags:b?.defaultSampleFlags??null,startTimestamp:null},a?g.currentFragmentState.baseDataOffset=this.isobmffReader.readU64():d&&(g.currentFragmentState.baseDataOffset=this.currentFragment.moofOffset),n&&(g.currentFragmentState.sampleDescriptionIndex=this.isobmffReader.readU32()),c&&(g.currentFragmentState.defaultSampleDuration=this.isobmffReader.readU32()),o&&(g.currentFragmentState.defaultSampleSize=this.isobmffReader.readU32()),u&&(g.currentFragmentState.defaultSampleFlags=this.isobmffReader.readU32()),m&&(g.currentFragmentState.defaultSampleDuration=0)}break;case"tfdt":{let s=this.currentTrack;if(!s)break;l(s.currentFragmentState);let a=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let n=a===0?this.isobmffReader.readU32():this.isobmffReader.readU64();s.currentFragmentState.startTimestamp=n}break;case"trun":{let s=this.currentTrack;if(!s)break;if(l(this.currentFragment),l(s.currentFragmentState),this.currentFragment.trackData.has(s.id))throw new Error("Can't have two trun boxes for the same track in one fragment.");let a=this.isobmffReader.readU8(),n=this.isobmffReader.readU24(),c=!!(n&1),o=!!(n&4),u=!!(n&256),m=!!(n&512),d=!!(n&1024),h=!!(n&2048),g=this.isobmffReader.readU32(),b=s.currentFragmentState.baseDataOffset;c&&(b+=this.isobmffReader.readI32());let f=null;o&&(f=this.isobmffReader.readU32());let k=b;if(g===0){this.currentFragment.implicitBaseDataOffset=k;break}let C=0,v={startTimestamp:0,endTimestamp:0,samples:[],presentationTimestamps:[],startTimestampIsFinal:!1};this.currentFragment.trackData.set(s.id,v);for(let R=0;R({presentationTimestamp:R.presentationTimestamp,sampleIndex:V})).sort((R,V)=>R.presentationTimestamp-V.presentationTimestamp);let w=v.samples[v.presentationTimestamps[0].sampleIndex],O=v.samples[_(v.presentationTimestamps).sampleIndex];v.startTimestamp=w.presentationTimestamp,v.endTimestamp=O.presentationTimestamp+O.duration,this.currentFragment.implicitBaseDataOffset=k}break}this.isobmffReader.pos=r}},Ne=class{constructor(e){this.internalTrack=e}chunkToSampleIndex=new WeakMap;chunkToFragmentLocation=new WeakMap;getCodec(){throw new Error("Not implemented on base class.")}async computeDuration(){let t=(await this.getChunk(1/0,{metadataOnly:!0}))?.timestamp;return t?t/1e6:0}async getFirstChunk(e){return this.internalTrack.demuxer.isFragmented?this.performFragmentedLookup(()=>{let t=this.internalTrack.fragments[0];return{fragmentIndex:t?0:-1,sampleIndex:t?0:-1,correctSampleFound:!!t}},0,1/0,e):this.fetchChunkForSampleIndex(0,e)}roundToMicrosecond(e){return(Math.floor(e*1e6)+.99999999)/1e6}async getChunk(e,t){e=this.roundToMicrosecond(e);let r=e*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findSampleInFragmentsForTimestamp(r),r,r,t);{let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),a=lr(s,r);return this.fetchChunkForSampleIndex(a,t)}}async getNextChunk(e,t){if(this.internalTrack.demuxer.isFragmented){let s=this.chunkToFragmentLocation.get(e);if(s===void 0)throw new Error("Chunk was not created from this track.");let a=s.fragment.trackData.get(this.internalTrack.id),n=a.samples[s.sampleIndex],c=B(this.internalTrack.fragments,s.fragment.moofOffset,o=>o.moofOffset);return l(c!==-1),this.performFragmentedLookup(()=>{if(s.sampleIndex+1d.moofOffset);return l(m!==-1),{fragmentIndex:m,sampleIndex:0,correctSampleFound:!0}}return{fragmentIndex:c,sampleIndex:-1,correctSampleFound:!1}}},n.presentationTimestamp,1/0,t)}let r=this.chunkToSampleIndex.get(e);if(r===void 0)throw new Error("Chunk was not created from this track.");return this.fetchChunkForSampleIndex(r+1,t)}async getKeyChunk(e,t){e=this.roundToMicrosecond(e);let r=e*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findKeySampleInFragmentsForTimestamp(r),r,r,t);let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),a=lr(s,r),n=a===-1?-1:wi(s,a);return this.fetchChunkForSampleIndex(n,t)}async getNextKeyChunk(e,t){if(this.internalTrack.demuxer.isFragmented){let n=this.chunkToFragmentLocation.get(e);if(n===void 0)throw new Error("Chunk was not created from this track.");let c=n.fragment.trackData.get(this.internalTrack.id),o=c.samples[n.sampleIndex],u=B(this.internalTrack.fragments,n.fragment.moofOffset,m=>m.moofOffset);return l(u!==-1),this.performFragmentedLookup(()=>{let m=c.samples.findIndex((d,h)=>d.isKeyFrame&&h>n.sampleIndex);if(m!==-1)return{fragmentIndex:u,sampleIndex:m,correctSampleFound:!0};{let d=n.fragment;for(;d.nextFragment;){d=d.nextFragment;let h=d.trackData.get(this.internalTrack.id);if(h){let g=B(this.internalTrack.fragments,d.moofOffset,f=>f.moofOffset);l(g!==-1);let b=h.samples.findIndex(f=>f.isKeyFrame);if(b===-1)throw new Error("Not supported: Fragment does not contain key sample.");return{fragmentIndex:g,sampleIndex:b,correctSampleFound:!0}}}return{fragmentIndex:u,sampleIndex:-1,correctSampleFound:!1}}},o.presentationTimestamp,1/0,t)}let r=this.chunkToSampleIndex.get(e);if(r===void 0)throw new Error("Chunk was not created from this track.");let s=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),a=yi(s,r);return this.fetchChunkForSampleIndex(a,t)}async fetchChunkForSampleIndex(e,t){if(e===-1)return null;let r=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),s=Si(r,e);if(!s)return null;let a;t.metadataOnly?a=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(s.chunkOffset,s.chunkOffset+s.chunkSize),a=this.internalTrack.demuxer.chunkReader.readRange(s.sampleOffset,s.sampleOffset+s.sampleSize));let n=1e6*s.presentationTimestamp/this.internalTrack.timescale,c=1e6*s.duration/this.internalTrack.timescale,o=this.createChunk(a,n,c,s.isKeyFrame);return this.chunkToSampleIndex.set(o,e),o}async fetchChunkInFragment(e,t,r){if(t===-1)return null;let a=e.trackData.get(this.internalTrack.id).samples[t];l(a);let n;r.metadataOnly?n=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(e.dataStart,e.dataEnd),n=this.internalTrack.demuxer.chunkReader.readRange(a.byteOffset,a.byteOffset+a.byteSize));let c=1e6*a.presentationTimestamp/this.internalTrack.timescale,o=1e6*a.duration/this.internalTrack.timescale,u=this.createChunk(n,c,o,a.isKeyFrame);return this.chunkToFragmentLocation.set(u,{fragment:e,sampleIndex:t}),u}findSampleInFragmentsForTimestamp(e){let t=A(this.internalTrack.fragments,e,a=>a.trackData.get(this.internalTrack.id).startTimestamp),r=-1,s=!1;if(t!==-1){let n=this.internalTrack.fragments[t].trackData.get(this.internalTrack.id),c=A(n.presentationTimestamps,e,o=>o.presentationTimestamp);l(c!==-1),r=n.presentationTimestamps[c].sampleIndex,s=ea.trackData.get(this.internalTrack.id).startTimestamp),r=-1,s=!1;if(t!==-1){let n=this.internalTrack.fragments[t].trackData.get(this.internalTrack.id),c=n.presentationTimestamps.findLastIndex(u=>n.samples[u.sampleIndex].isKeyFrame&&u.presentationTimestamp<=e);if(c===-1)throw new Error("Not supported: Fragment does not begin with a key sample.");r=n.presentationTimestamps[c].sampleIndex,s=eC.timestamp);k!==-1&&(f=this.internalTrack.fragmentLookupTable[k])}if(c===-1)m.pos=f?.moofOffset??0;else{let k=this.internalTrack.fragments[c];!f||k.moofOffset>=k.moofOffset?(m.pos=k.moofOffset+k.moofSize,h=k):m.pos=f.moofOffset}for(;m.posr)break;if(h.nextFragment){m.pos=h.nextFragment.moofOffset+h.nextFragment.moofSize,h=h.nextFragment;continue}}await m.reader.loadRange(m.pos,m.pos+16);let k=m.pos,C=m.readBoxHeader();if(C.name==="moof"){let v=B(a.fragments,k,w=>w.moofOffset);if(v===-1){m.pos=k;let w=await a.readFragment();h&&(h.nextFragment=w),h=w;let{fragmentIndex:O,sampleIndex:R,correctSampleFound:V}=e();if(V){let J=this.internalTrack.fragments[O];return this.fetchChunkInFragment(J,R,s)}O!==-1&&(g=O,b=R)}else{let w=a.fragments[v];h&&(h.nextFragment=w),h=w}}m.pos=k+C.totalSize}if(g!==-1){let k=this.internalTrack.fragments[g];return this.fetchChunkInFragment(k,b,s)}return null}finally{n()}}},yt=class extends Ne{internalTrack;constructor(e){super(e),this.internalTrack=e}async getCodec(){return this.internalTrack.info.codec}async getWidth(){return this.internalTrack.info.width}async getHeight(){return this.internalTrack.info.height}async getRotation(){return this.internalTrack.rotation}async getDecoderConfig(){return{codec:rr(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),codedWidth:this.internalTrack.info.width,codedHeight:this.internalTrack.info.height,description:this.internalTrack.info.codecDescription??void 0,colorSpace:this.internalTrack.info.colorSpace??void 0}}createChunk(e,t,r,s){return new EncodedVideoChunk({data:e,timestamp:t,duration:r,type:s?"key":"delta"})}},vt=class extends Ne{internalTrack;constructor(e){super(e),this.internalTrack=e}async getCodec(){return this.internalTrack.info.codec}async getNumberOfChannels(){return this.internalTrack.info.numberOfChannels}async getSampleRate(){return this.internalTrack.info.sampleRate}async getDecoderConfig(){return{codec:sr(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),numberOfChannels:this.internalTrack.info.numberOfChannels,sampleRate:this.internalTrack.info.sampleRate,description:this.internalTrack.info.codecDescription??void 0}}createChunk(e,t,r,s){return new EncodedAudioChunk({data:e,timestamp:t,duration:r,type:s?"key":"delta"})}},lr=(i,e)=>{let t=A(i.presentationTimestamps,e,r=>r.presentationTimestamp);return t===-1?-1:i.presentationTimestamps[t].sampleIndex},Si=(i,e)=>{let t=A(i.sampleTimingEntries,e,f=>f.startIndex),r=i.sampleTimingEntries[t];if(!r||r.startIndex+r.count<=e)return null;let a=r.startDecodeTimestamp+(e-r.startIndex)*r.delta,n=A(i.sampleCompositionTimeOffsets,e,f=>f.startIndex),c=i.sampleCompositionTimeOffsets[n];c&&(a+=c.offset);let o=i.sampleSizes[Math.min(e,i.sampleSizes.length-1)],u=A(i.sampleToChunk,e,f=>f.startSampleIndex),m=i.sampleToChunk[u];l(m);let d=m.startChunkIndex+Math.floor((e-m.startSampleIndex)/m.samplesPerChunk),h=i.chunkOffsets[d],g=0,b=h;if(i.sampleSizes.length===1)b+=o*(e-m.startSampleIndex),g+=o*m.samplesPerChunk;else{let f=m.startSampleIndex+(d-m.startChunkIndex)*m.samplesPerChunk;for(let k=f;kf)!==-1:!0}},wi=(i,e)=>{if(!i.keySampleIndices)return e;let t=A(i.keySampleIndices,e,r=>r);return i.keySampleIndices[t]??-1},yi=(i,e)=>{if(!i.keySampleIndices)return e+1;let t=A(i.keySampleIndices,e,r=>r);return i.keySampleIndices[t+1]??-1},dr=(i,e)=>{i.startTimestamp+=e,i.endTimestamp+=e;for(let t of i.samples)t.presentationTimestamp+=e;for(let t of i.presentationTimestamps)t.presentationTimestamp+=e};var He=class extends ue{};var Le=class{},It=class extends Le{async _canReadInput(e){if(await e._mainReader.source._getSize()<8)return!1;let r=new Z(e._mainReader);return r.pos=4,r.readAscii(4)==="ftyp"}_createDemuxer(e){return new We(e)}},At=class extends Le{async _canReadInput(){return!1}_createDemuxer(e){return new He(e)}},Qe=new It,vi=Qe,Ii=Qe,Rt=new At,fr=Rt,Ai=Rt,Ri=[Qe,fr];var Ot=class{_source;_formats;_mainReader;_demuxerPromise=null;_format=null;constructor(e){this._formats=e.formats,this._source=e.source,this._mainReader=new me(e.source)}_getDemuxer(){return this._demuxerPromise??=(async()=>{await this._mainReader.loadRange(0,4096);for(let e of this._formats)if(await e._canReadInput(this))return this._format=e,e._createDemuxer(this);throw new Error("Input has an unrecognizable format.")})()}async getFormat(){return await this._getDemuxer(),l(this._format),this._format}async computeDuration(){return(await this._getDemuxer()).computeDuration()}async getTracks(){return(await this._getDemuxer()).getTracks()}async getVideoTracks(){return(await this.getTracks()).filter(t=>t.isVideoTrack())}async getPrimaryVideoTrack(){return(await this.getTracks()).find(t=>t.isVideoTrack())??null}async getAudioTracks(){return(await this.getTracks()).filter(t=>t.isAudioTrack())}async getPrimaryAudioTrack(){return(await this.getTracks()).find(t=>t.isAudioTrack())??null}async getMimeType(){return(await this._getDemuxer()).getMimeType()}};var Ke=class{async*chunks(e,t=1/0){let r=[],{promise:s,resolve:a}=F(),{promise:n,resolve:c}=F(),o=!1,u=[],m=()=>Math.max(2,u.length);(async()=>{let d=e??await this.getFirstChunk();for(;d&&!o&&!(d.timestamp/1e6>=t);){if(r.length>m()){({promise:n,resolve:c}=F()),await n;continue}r.push(d),a(),{promise:s,resolve:a}=F(),d=await this.getNextChunk(d)}o=!0,a()})();try{for(;;)if(r.length>0){yield r.shift();let d=performance.now();for(u.push(d);u.length>0&&d-u[0]>=1e3;)u.shift();c()}else if(!o)await s;else break}finally{o=!0,c()}}},$e=class{duplicateFrame(e){return structuredClone(e)}async*mediaFramesAtTimestamps(e){let t=Bt(e),r=[],s=[],{promise:a,resolve:n}=F(),{promise:c,resolve:o}=F(),u=!1,m=!1,d=8,h=null,g=f=>{s.push(f),n(),{promise:a,resolve:n}=F()},b=await this.createDecoder(f=>{if(o(),m){f.close();return}let k=!1;for(;r.length>0&&r[0]===f.timestamp;)g(this.duplicateFrame(f)),r.shift(),k=!0;k?(h?.close(),h=f):f.close()});(async()=>{let f=this.createChunkDrain(),k=null,C=null;for await(let v of t){for(;s.length+b.decodeQueueSize>d;)({promise:c,resolve:o}=F()),await c;if(m)break;let w=await f.getChunk(v);if(!w){g(null);continue}let O=await f.getKeyChunk(v);if(!O){g(null);continue}for(r.push(w.timestamp),k&&O.timestamp===k.timestamp&&w.timestamp>=C.timestamp?(l(C),w.timestamp===C.timestamp&&r.length===1&&(h&&g(this.duplicateFrame(h)),r.shift())):(k=O,C=O,b.decode(O));C.timestamp!==w.timestamp;){let R=await f.getNextChunk(C);l(R),C=R,b.decode(R)}b.decodeQueueSize>=10&&await new Promise(R=>b.addEventListener("dequeue",R,{once:!0}))}await b.flush(),b.close(),u=!0,n()})();try{for(;;)if(s.length>0){let f=s.shift();l(f!==void 0),yield f,o()}else if(!u)await a;else break}finally{m=!0,o();for(let f of s)f?.close();h?.close()}}async*mediaFramesInRange(e=0,t=1/0){let r=[],s=!1,a=null,{promise:n,resolve:c}=F(),{promise:o,resolve:u}=F(),m=!1,d=!1,h=8,g=await this.createDecoder(k=>{u();let C=k.timestamp/1e6;if(C>=t&&(d=!0),d){k.close();return}a&&(C>e?(r.push(a),s=!0):a.close()),C>=e&&(r.push(k),s=!0),a=s?null:k,r.length>0&&(c(),{promise:n,resolve:c}=F())}),b=this.createChunkDrain(),f=await b.getKeyChunk(e)??await b.getFirstChunk();if(f){(async()=>{let k=f,C=1/0;if(t<1/0){let w=await b.getChunk(t),O=w?w.type==="key"&&w.timestamp/1e6===t?w:await b.getNextKeyChunk(w):null;O&&(C=O.timestamp/1e6)}let v=b.chunks(f,C);for(await v.next();k&&!d;){if(r.length+g.decodeQueueSize>h){({promise:o,resolve:u}=F()),await o;continue}g.decode(k);let w=await v.next();if(w.done)break;k=w.value}await v.return(),await g.flush(),g.close(),!s&&a&&r.push(a),m=!0,c()})();try{for(;;)if(r.length>0)yield r.shift(),u();else if(!m)await n;else break}finally{d=!0,u();for(let k of r)k.close()}}}},je=class extends Ke{constructor(t){super();this.videoTrack=t}getFirstChunk(t={}){return this.videoTrack._backing.getFirstChunk(t)}getChunk(t,r={}){return this.videoTrack._backing.getChunk(t,r)}getNextChunk(t,r={}){return this.videoTrack._backing.getNextChunk(t,r)}getKeyChunk(t,r={}){return this.videoTrack._backing.getKeyChunk(t,r)}getNextKeyChunk(t,r={}){return this.videoTrack._backing.getNextKeyChunk(t,r)}},qe=class extends $e{constructor(t){super();this.videoTrack=t}decoderConfig=null;async createDecoder(t){this.decoderConfig??=await this.videoTrack.getDecoderConfig();let r=new VideoDecoder({output:t,error:s=>console.error(s)});return r.configure(this.decoderConfig),r}createChunkDrain(){return new je(this.videoTrack)}async getFrame(t){for await(let r of this.mediaFramesAtTimestamps([t]))return r;throw new Error("Internal error: Iterator returned nothing.")}frames(t=0,r=1/0){return this.mediaFramesInRange(t,r)}framesAtTimestamps(t){return this.mediaFramesAtTimestamps(t)}},_t=class{constructor(e,t){this.videoTrack=e;this.dimensions=t;this.videoFrameDrain=new qe(e)}videoFrameDrain;async videoFrameToWrappedCanvas(e){let t=this.dimensions?.width??await this.videoTrack.getRotatedWidth(),r=this.dimensions?.height??await this.videoTrack.getRotatedHeight(),s=await this.videoTrack.getRotation(),a=document.createElement("canvas");a.width=t,a.height=r;let n=a.getContext("2d",{alpha:!1});l(n),n.translate(t/2,r/2),n.rotate(s*Math.PI/180),n.translate(-t/2,-r/2);let[c,o]=s%180===0?[t,r]:[r,t];n.drawImage(e,(t-c)/2,(r-o)/2,c,o);let u={canvas:a,timestamp:e.timestamp/1e6,duration:(e.duration??0)/1e6};return e.close(),u}async getCanvas(e){let t=await this.videoFrameDrain.getFrame(e);return t&&this.videoFrameToWrappedCanvas(t)}async*canvases(e=0,t=1/0){for await(let r of this.videoFrameDrain.frames(e,t))yield this.videoFrameToWrappedCanvas(r)}async*canvasesAtTimestamps(e){for await(let t of this.videoFrameDrain.framesAtTimestamps(e))yield t&&this.videoFrameToWrappedCanvas(t)}},Xe=class extends Ke{constructor(t){super();this.audioTrack=t}getFirstChunk(t={}){return this.audioTrack._backing.getFirstChunk(t)}getChunk(t,r={}){return this.audioTrack._backing.getChunk(t,r)}getNextChunk(t,r={}){return this.audioTrack._backing.getNextChunk(t,r)}getKeyChunk(t,r={}){return this.audioTrack._backing.getKeyChunk(t,r)}getNextKeyChunk(t,r={}){return this.audioTrack._backing.getNextKeyChunk(t,r)}},Ge=class extends $e{constructor(t){super();this.audioTrack=t}decoderConfig=null;async createDecoder(t){this.decoderConfig??=await this.audioTrack.getDecoderConfig();let r=new AudioDecoder({output:t,error:s=>console.error(s)});return r.configure(this.decoderConfig),r}createChunkDrain(){return new Xe(this.audioTrack)}async getData(t){for await(let r of this.mediaFramesAtTimestamps([t]))return r;throw new Error("Internal error: Iterator returned nothing.")}data(t=0,r=1/0){return this.mediaFramesInRange(t,r)}dataAtTimestamps(t){return this.mediaFramesAtTimestamps(t)}},Et=class{constructor(e){this.audioTrack=e;this.audioDataDrain=new Ge(e)}audioDataDrain;audioDataToWrappedArrayBuffer(e){let t=new AudioBuffer({numberOfChannels:e.numberOfChannels,length:e.numberOfFrames,sampleRate:e.sampleRate}),r=new Float32Array(e.allocationSize({planeIndex:0,format:"f32-planar"})/4);for(let n=0;n {
arr.splice(index, 1);
}
};
+var toAsyncIterator = async function* (source) {
+ if (Symbol.iterator in source) {
+ yield* source[Symbol.iterator]();
+ } else {
+ yield* source[Symbol.asyncIterator]();
+ }
+};
// src/subtitles.ts
var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g;
@@ -5299,9 +5306,7 @@ var IsobmffTrackBacking = class {
this.internalTrack = internalTrack;
}
chunkToSampleIndex = /* @__PURE__ */ new WeakMap();
- sampleIndexToChunk = /* @__PURE__ */ new Map();
chunkToFragmentLocation = /* @__PURE__ */ new WeakMap();
- fragmentLocationToChunk = /* @__PURE__ */ new Map();
getCodec() {
throw new Error("Not implemented on base class.");
}
@@ -5328,7 +5333,11 @@ var IsobmffTrackBacking = class {
}
return this.fetchChunkForSampleIndex(0, options);
}
+ roundToMicrosecond(timestamp) {
+ return (Math.floor(timestamp * 1e6) + 0.99999999) / 1e6;
+ }
async getChunk(timestamp, options) {
+ timestamp = this.roundToMicrosecond(timestamp);
const timestampInTimescale = timestamp * this.internalTrack.timescale;
if (this.internalTrack.demuxer.isFragmented) {
return this.performFragmentedLookup(
@@ -5403,6 +5412,7 @@ var IsobmffTrackBacking = class {
return this.fetchChunkForSampleIndex(sampleIndex + 1, options);
}
async getKeyChunk(timestamp, options) {
+ timestamp = this.roundToMicrosecond(timestamp);
const timestampInTimescale = timestamp * this.internalTrack.timescale;
if (this.internalTrack.demuxer.isFragmented) {
return this.performFragmentedLookup(
@@ -5489,10 +5499,6 @@ var IsobmffTrackBacking = class {
if (sampleIndex === -1) {
return null;
}
- const existingChunk = this.sampleIndexToChunk.get(sampleIndex)?.deref();
- if (existingChunk) {
- return existingChunk;
- }
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
const sampleInfo = getSampleInfo(sampleTable, sampleIndex);
if (!sampleInfo) {
@@ -5515,18 +5521,12 @@ var IsobmffTrackBacking = class {
const duration = 1e6 * sampleInfo.duration / this.internalTrack.timescale;
const chunk = this.createChunk(data, timestamp, duration, sampleInfo.isKeyFrame);
this.chunkToSampleIndex.set(chunk, sampleIndex);
- this.sampleIndexToChunk.set(sampleIndex, new WeakRef(chunk));
return chunk;
}
async fetchChunkInFragment(fragment, sampleIndex, options) {
if (sampleIndex === -1) {
return null;
}
- const compositeKey = `${fragment.moofOffset}:${sampleIndex}`;
- const existingChunk = this.fragmentLocationToChunk.get(compositeKey)?.deref();
- if (existingChunk) {
- return existingChunk;
- }
const trackData = fragment.trackData.get(this.internalTrack.id);
const sample = trackData.samples[sampleIndex];
assert(sample);
@@ -5544,7 +5544,6 @@ var IsobmffTrackBacking = class {
const duration = 1e6 * sample.duration / this.internalTrack.timescale;
const chunk = this.createChunk(data, timestamp, duration, sample.isKeyFrame);
this.chunkToFragmentLocation.set(chunk, { fragment, sampleIndex });
- this.fragmentLocationToChunk.set(compositeKey, new WeakRef(chunk));
return chunk;
}
findSampleInFragmentsForTimestamp(timestampInTimescale) {
@@ -5988,57 +5987,123 @@ var BaseChunkDrain = class {
}
};
var BaseMediaFrameDrain = class {
- async getKeyMediaFrame(timestamp) {
- let result = null;
- const decoder = await this.createDecoder((frame) => result = frame);
- const chunkDrain = this.createChunkDrain();
- const chunk = await chunkDrain.getKeyChunk(timestamp);
- if (!chunk) {
- return null;
- }
- decoder.decode(chunk);
- await decoder.flush();
- decoder.close();
- return result;
+ duplicateFrame(frame) {
+ return structuredClone(frame);
}
- async getMediaFrame(timestamp) {
- let result = null;
+ async *mediaFramesAtTimestamps(timestamps) {
+ const timestampIterator = toAsyncIterator(timestamps);
+ const timestampsOfInterest = [];
+ const frameQueue = [];
+ let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
+ let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
+ let decoderIsFlushed = false;
+ let ended = false;
+ const MAX_QUEUE_SIZE = 8;
+ let lastUsedFrame = null;
+ const pushToQueue = (frame) => {
+ frameQueue.push(frame);
+ onQueueNotEmpty();
+ ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
+ };
const decoder = await this.createDecoder((frame) => {
- if (frame.timestamp / 1e6 <= timestamp) {
- result?.close();
- result = frame;
+ onQueueDequeue();
+ if (ended) {
+ frame.close();
+ return;
+ }
+ let frameUsed = false;
+ while (timestampsOfInterest.length > 0 && timestampsOfInterest[0] === frame.timestamp) {
+ pushToQueue(this.duplicateFrame(frame));
+ timestampsOfInterest.shift();
+ frameUsed = true;
+ }
+ if (frameUsed) {
+ lastUsedFrame?.close();
+ lastUsedFrame = frame;
} else {
frame.close();
}
});
- const chunkDrain = this.createChunkDrain();
- const keyChunk = await chunkDrain.getKeyChunk(timestamp);
- if (!keyChunk) {
- return null;
- }
- const targetChunk = await chunkDrain.getChunk(timestamp);
- assert(targetChunk);
- decoder.decode(keyChunk);
- let currentChunk = keyChunk;
- while (currentChunk !== targetChunk) {
- const nextChunk = await chunkDrain.getNextChunk(currentChunk);
- assert(nextChunk);
- currentChunk = nextChunk;
- decoder.decode(nextChunk);
- if (decoder.decodeQueueSize >= 10) {
- await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true }));
+ void (async () => {
+ const chunkDrain = this.createChunkDrain();
+ let lastKeyChunk = null;
+ let lastChunk = null;
+ for await (const timestamp of timestampIterator) {
+ while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
+ ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
+ await queueDequeue;
+ }
+ if (ended) {
+ break;
+ }
+ const targetChunk = await chunkDrain.getChunk(timestamp);
+ if (!targetChunk) {
+ pushToQueue(null);
+ continue;
+ }
+ const keyChunk = await chunkDrain.getKeyChunk(timestamp);
+ if (!keyChunk) {
+ pushToQueue(null);
+ continue;
+ }
+ timestampsOfInterest.push(targetChunk.timestamp);
+ if (lastKeyChunk && keyChunk.timestamp === lastKeyChunk.timestamp && targetChunk.timestamp >= lastChunk.timestamp) {
+ assert(lastChunk);
+ if (targetChunk.timestamp === lastChunk.timestamp && timestampsOfInterest.length === 1) {
+ if (lastUsedFrame) {
+ pushToQueue(this.duplicateFrame(lastUsedFrame));
+ }
+ timestampsOfInterest.shift();
+ }
+ } else {
+ lastKeyChunk = keyChunk;
+ lastChunk = keyChunk;
+ decoder.decode(keyChunk);
+ }
+ while (lastChunk.timestamp !== targetChunk.timestamp) {
+ const nextChunk = await chunkDrain.getNextChunk(lastChunk);
+ assert(nextChunk);
+ lastChunk = nextChunk;
+ decoder.decode(nextChunk);
+ }
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true }));
+ }
}
+ await decoder.flush();
+ decoder.close();
+ decoderIsFlushed = true;
+ onQueueNotEmpty();
+ })();
+ try {
+ while (true) {
+ if (frameQueue.length > 0) {
+ const nextFrame = frameQueue.shift();
+ assert(nextFrame !== void 0);
+ yield nextFrame;
+ onQueueDequeue();
+ } else if (!decoderIsFlushed) {
+ await queueNotEmpty;
+ } else {
+ break;
+ }
+ }
+ } finally {
+ ended = true;
+ onQueueDequeue();
+ for (const frame of frameQueue) {
+ frame?.close();
+ }
+ lastUsedFrame?.close();
}
- await decoder.flush();
- decoder.close();
- return result;
}
- async *mediaFrames(startTimestamp = 0, endTimestamp = Infinity) {
+ async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
const frameQueue = [];
let firstFrameQueued = false;
let lastFrame = null;
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
+ let decoderIsFlushed = false;
let ended = false;
const MAX_QUEUE_SIZE = 8;
const decoder = await this.createDecoder((frame) => {
@@ -6074,7 +6139,6 @@ var BaseMediaFrameDrain = class {
if (!keyChunk) {
return;
}
- let decoderIsFlushed = false;
void (async () => {
let currentChunk = keyChunk;
let chunksEndTimestamp = Infinity;
@@ -6168,14 +6232,17 @@ var VideoFrameDrain = class extends BaseMediaFrameDrain {
createChunkDrain() {
return new EncodedVideoChunkDrain(this.videoTrack);
}
- getKeyFrame(timestamp) {
- return this.getKeyMediaFrame(timestamp);
- }
- getFrame(timestamp) {
- return this.getMediaFrame(timestamp);
+ async getFrame(timestamp) {
+ for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
+ return frame;
+ }
+ throw new Error("Internal error: Iterator returned nothing.");
}
frames(startTimestamp = 0, endTimestamp = Infinity) {
- return this.mediaFrames(startTimestamp, endTimestamp);
+ return this.mediaFramesInRange(startTimestamp, endTimestamp);
+ }
+ framesAtTimestamps(timestamps) {
+ return this.mediaFramesAtTimestamps(timestamps);
}
};
var CanvasDrain = class {
@@ -6216,6 +6283,11 @@ var CanvasDrain = class {
yield this.videoFrameToWrappedCanvas(frame);
}
}
+ async *canvasesAtTimestamps(timestamps) {
+ for await (const frame of this.videoFrameDrain.framesAtTimestamps(timestamps)) {
+ yield frame && this.videoFrameToWrappedCanvas(frame);
+ }
+ }
};
var EncodedAudioChunkDrain = class extends BaseChunkDrain {
constructor(audioTrack) {
@@ -6256,14 +6328,17 @@ var AudioDataDrain = class extends BaseMediaFrameDrain {
createChunkDrain() {
return new EncodedAudioChunkDrain(this.audioTrack);
}
- getKeyData(timestamp) {
- return this.getKeyMediaFrame(timestamp);
- }
- getData(timestamp) {
- return this.getMediaFrame(timestamp);
+ async getData(timestamp) {
+ for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
+ return data;
+ }
+ throw new Error("Internal error: Iterator returned nothing.");
}
data(startTimestamp = 0, endTimestamp = Infinity) {
- return this.mediaFrames(startTimestamp, endTimestamp);
+ return this.mediaFramesInRange(startTimestamp, endTimestamp);
+ }
+ dataAtTimestamps(timestamps) {
+ return this.mediaFramesAtTimestamps(timestamps);
}
};
var AudioBufferDrain = class {
@@ -6301,6 +6376,11 @@ var AudioBufferDrain = class {
yield this.audioDataToWrappedArrayBuffer(data);
}
}
+ async *buffersAtTimestamps(timestamps) {
+ for await (const data of this.audioDataDrain.dataAtTimestamps(timestamps)) {
+ yield data && this.audioDataToWrappedArrayBuffer(data);
+ }
+ }
};
export {
ALL_FORMATS,
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 7c23432..ca7ad30 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -26,6 +26,7 @@ export default tseslint.config(
}],
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/require-await': 'off',
+ '@stylistic/yield-star-spacing': ['error', { before: false, after: true }]
},
},
{
diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts
index f41053e..e03f415 100644
--- a/src/isobmff/isobmff-demuxer.ts
+++ b/src/isobmff/isobmff-demuxer.ts
@@ -1305,18 +1305,12 @@ export class IsobmffDemuxer extends Demuxer {
abstract class IsobmffTrackBacking implements InputTrackBacking {
chunkToSampleIndex = new WeakMap();
- sampleIndexToChunk = new Map>();
-
chunkToFragmentLocation = new WeakMap();
- fragmentLocationToChunk = new Map>();
-
- constructor(public internalTrack: InternalTrack) {
-
- }
+ constructor(public internalTrack: InternalTrack) {}
getCodec(): Promise {
throw new Error('Not implemented on base class.');
@@ -1355,7 +1349,20 @@ abstract class IsobmffTrackBacking {
abstract getFirstChunk(options?: ChunkRetrievalOptions): Promise;
@@ -78,71 +78,154 @@ abstract class BaseMediaFrameDrain<
abstract createDecoder(onMedia: (media: MediaFrame) => unknown): Promise;
abstract createChunkDrain(): BaseChunkDrain;
- protected async getKeyMediaFrame(timestamp: number): Promise {
- let result: MediaFrame | null = null;
-
- const decoder = await this.createDecoder(frame => result = frame);
- const chunkDrain = this.createChunkDrain();
- const chunk = await chunkDrain.getKeyChunk(timestamp);
- if (!chunk) {
- return null;
- }
-
- decoder.decode(chunk);
-
- await decoder.flush();
- decoder.close();
-
- return result;
+ private duplicateFrame(frame: MediaFrame) {
+ return structuredClone(frame);
}
- protected async getMediaFrame(timestamp: number): Promise {
- let result: MediaFrame | null = null;
+ protected async* mediaFramesAtTimestamps(timestamps: AnyIterable) {
+ const timestampIterator = toAsyncIterator(timestamps);
+ const timestampsOfInterest: number[] = [];
+
+ const frameQueue: (MediaFrame | null)[] = [];
+ let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
+ let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
+ let decoderIsFlushed = false;
+ let ended = false;
+
+ const MAX_QUEUE_SIZE = 8;
+
+ let lastUsedFrame: MediaFrame | null = null;
+ const pushToQueue = (frame: MediaFrame | null) => {
+ frameQueue.push(frame);
+ onQueueNotEmpty();
+ ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
+ };
const decoder = await this.createDecoder((frame) => {
- if (frame.timestamp / 1e6 <= timestamp) {
- result?.close();
- result = frame;
+ onQueueDequeue();
+
+ if (ended) {
+ frame.close();
+ return;
+ }
+
+ let frameUsed = false;
+ while (timestampsOfInterest.length > 0 && timestampsOfInterest[0] === frame.timestamp) {
+ pushToQueue(this.duplicateFrame(frame));
+ timestampsOfInterest.shift();
+ frameUsed = true;
+ }
+
+ if (frameUsed) {
+ lastUsedFrame?.close();
+ lastUsedFrame = frame;
} else {
frame.close();
}
});
- const chunkDrain = this.createChunkDrain();
- const keyChunk = await chunkDrain.getKeyChunk(timestamp);
- if (!keyChunk) {
- return null;
- }
- const targetChunk = await chunkDrain.getChunk(timestamp);
- assert(targetChunk);
+ // The following is the "pump" process that keeps pumping chunks into the decoder
+ void (async () => {
+ const chunkDrain = this.createChunkDrain();
+ let lastKeyChunk: Chunk | null = null;
+ let lastChunk: Chunk | null = null;
- decoder.decode(keyChunk);
+ for await (const timestamp of timestampIterator) {
+ while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
+ ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
+ await queueDequeue;
+ }
- let currentChunk = keyChunk;
- while (currentChunk !== targetChunk) {
- const nextChunk = await chunkDrain.getNextChunk(currentChunk);
- assert(nextChunk);
+ if (ended) {
+ break;
+ }
- currentChunk = nextChunk;
- decoder.decode(nextChunk);
+ const targetChunk = await chunkDrain.getChunk(timestamp);
+ if (!targetChunk) {
+ pushToQueue(null);
+ continue;
+ }
- if (decoder.decodeQueueSize >= 10) {
- await new Promise(resolve => decoder.addEventListener('dequeue', resolve, { once: true }));
+ const keyChunk = await chunkDrain.getKeyChunk(timestamp);
+ if (!keyChunk) {
+ pushToQueue(null);
+ continue;
+ }
+
+ timestampsOfInterest.push(targetChunk.timestamp);
+
+ if (
+ lastKeyChunk
+ && keyChunk.timestamp === lastKeyChunk.timestamp
+ && targetChunk.timestamp >= lastChunk!.timestamp
+ ) {
+ assert(lastChunk);
+
+ if (targetChunk.timestamp === lastChunk.timestamp && timestampsOfInterest.length === 1) {
+ // Special case: We have a repeat chunk, but the frame for that chunk has already been decoded.
+ // Therefore, we need to push the frame here instead of in the decoder callback.
+ if (lastUsedFrame) {
+ pushToQueue(this.duplicateFrame(lastUsedFrame));
+ }
+ timestampsOfInterest.shift();
+ }
+ } else {
+ lastKeyChunk = keyChunk;
+ lastChunk = keyChunk;
+ decoder.decode(keyChunk);
+ }
+
+ while (lastChunk.timestamp !== targetChunk.timestamp) {
+ const nextChunk = await chunkDrain.getNextChunk(lastChunk);
+ assert(nextChunk);
+
+ lastChunk = nextChunk;
+ decoder.decode(nextChunk);
+ }
+
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise(resolve => decoder.addEventListener('dequeue', resolve, { once: true }));
+ }
}
+
+ await decoder.flush();
+ decoder.close();
+
+ decoderIsFlushed = true;
+ onQueueNotEmpty(); // To unstuck the generator
+ })();
+
+ try {
+ while (true) {
+ if (frameQueue.length > 0) {
+ const nextFrame = frameQueue.shift();
+ assert(nextFrame !== undefined);
+ yield nextFrame;
+ onQueueDequeue();
+ } else if (!decoderIsFlushed) {
+ await queueNotEmpty;
+ } else {
+ break;
+ }
+ }
+ } finally {
+ ended = true;
+ onQueueDequeue();
+
+ for (const frame of frameQueue) {
+ frame?.close();
+ }
+ (lastUsedFrame as MediaFrame | null)?.close();
}
-
- await decoder.flush();
- decoder.close();
-
- return result;
}
- protected async* mediaFrames(startTimestamp = 0, endTimestamp = Infinity) {
+ protected async* mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
const frameQueue: MediaFrame[] = [];
let firstFrameQueued = false;
let lastFrame: MediaFrame | null = null;
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
+ let decoderIsFlushed = false;
let ended = false;
const MAX_QUEUE_SIZE = 8;
@@ -192,8 +275,6 @@ abstract class BaseMediaFrameDrain<
return;
}
- let decoderIsFlushed = false;
-
// The following is the "pump" process that keeps pumping chunks into the decoder
void (async () => {
let currentChunk: Chunk | null = keyChunk;
@@ -320,16 +401,19 @@ export class VideoFrameDrain extends BaseMediaFrameDrain) {
+ return this.mediaFramesAtTimestamps(timestamps);
}
}
@@ -386,6 +470,12 @@ export class CanvasDrain {
yield this.videoFrameToWrappedCanvas(frame);
}
}
+
+ async* canvasesAtTimestamps(timestamps: AnyIterable) {
+ for await (const frame of this.videoFrameDrain.framesAtTimestamps(timestamps)) {
+ yield frame && this.videoFrameToWrappedCanvas(frame);
+ }
+ }
}
export class EncodedAudioChunkDrain extends BaseChunkDrain {
@@ -437,16 +527,19 @@ export class AudioDataDrain extends BaseMediaFrameDrain) {
+ return this.mediaFramesAtTimestamps(timestamps);
}
}
@@ -499,4 +592,10 @@ export class AudioBufferDrain {
yield this.audioDataToWrappedArrayBuffer(data);
}
}
+
+ async* buffersAtTimestamps(timestamps: AnyIterable) {
+ for await (const data of this.audioDataDrain.dataAtTimestamps(timestamps)) {
+ yield data && this.audioDataToWrappedArrayBuffer(data);
+ }
+ }
}
diff --git a/src/misc.ts b/src/misc.ts
index 6833a69..d771a72 100644
--- a/src/misc.ts
+++ b/src/misc.ts
@@ -165,7 +165,7 @@ export const binarySearchExact = (arr: T[], key: number, valueGetter: (x: T)
if (midVal === key) {
res = mid;
- high = mid - 1; // continue searching left to find the lowest index
+ high = mid - 1; // Continue searching left to find the lowest index
} else if (midVal < key) {
low = mid + 1;
} else {
@@ -213,3 +213,17 @@ export const removeItem = (arr: T[], item: T) => {
arr.splice(index, 1);
}
};
+
+export type AnyIterable =
+ | Iterable
+ | AsyncIterable;
+
+export const toAsyncIterator = async function* (source: AnyIterable): AsyncGenerator {
+ if (Symbol.iterator in source) {
+ // @ts-expect-error Trust me
+ yield* source[Symbol.iterator]();
+ } else {
+ // @ts-expect-error Trust me
+ yield* source[Symbol.asyncIterator]();
+ }
+};