p)!==-1:!0}},Ii=(r,e)=>{if(!r.keySampleIndices)return e;let t=I(r.keySampleIndices,e,s=>s);return r.keySampleIndices[t]??-1},_i=(r,e)=>{if(!r.keySampleIndices)return e+1;let t=I(r.keySampleIndices,e,s=>s);return r.keySampleIndices[t+1]??-1},nr=(r,e)=>{r.startTimestamp+=e,r.endTimestamp+=e;for(let t of r.samples)t.presentationTimestamp+=e;for(let t of r.presentationTimestamps)t.presentationTimestamp+=e};var st=class extends me{};var at=class{},yt=class extends at{async _canReadInput(e){if(await e._mainReader.source._getSize()<8)return!1;let s=new re(e._mainReader);return s.pos=4,s.readAscii(4)==="ftyp"}_createDemuxer(e){return new rt(e)}},vt=class extends at{async _canReadInput(){return!1}_createDemuxer(e){return new st(e)}},ye=new yt,or=ye,cr=ye,nt=new vt,It=nt,ur=nt,mr=[ye,It];var ot=class{_source;_formats;_mainReader;_demuxerPromise=null;_format=null;constructor(e){this._formats=e.formats,this._source=e.source,this._mainReader=new de(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(),m(this._format),this._format}async getDuration(){return(await this._getDemuxer()).getDuration()}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 ve=class{constructor(e){this.videoTrack=e}getFirstChunk(){return this.videoTrack._backing.getFirstChunk()}getChunk(e){return this.videoTrack._backing.getChunk(e)}getNextChunk(e){return this.videoTrack._backing.getNextChunk(e)}getKeyChunk(e){return this.videoTrack._backing.getKeyChunk(e)}getNextKeyChunk(e){return this.videoTrack._backing.getNextKeyChunk(e)}async*chunks(e,t=1/0){let s=[],{promise:i,resolve:a}=X(),{promise:n,resolve:c}=X(),o=!1,u=[],d=()=>Math.max(2,u.length);(async()=>{let l=e??await this.getFirstChunk();for(;l&&!o&&!(l.timestamp/1e6>=t);){if(s.length>d()){({promise:n,resolve:c}=X()),await n;continue}s.push(l),a(),{promise:i,resolve:a}=X(),l=await this.getNextChunk(l)}o=!0,a()})();try{for(;;)if(s.length>0){yield s.shift();let l=performance.now();for(u.push(l);u.length>0&&l-u[0]>=1e3;)u.shift();c()}else if(!o)await i;else break}finally{o=!0,c()}}},ct=class{constructor(e){this.videoTrack=e}decoderConfig=null;async createDecoder(e){this.decoderConfig||(this.decoderConfig=await this.videoTrack.getDecoderConfig());let t=new VideoDecoder({output:e,error:s=>console.error(s)});return t.configure(this.decoderConfig),t}async getKeyFrame(e){let t=null,s=await this.createDecoder(a=>t=a),i=await this.videoTrack._backing.getKeyChunk(e);return i?(s.decode(i),await s.flush(),s.close(),t):null}async getFrame(e){let t=null,s=await this.createDecoder(c=>{c.timestamp/1e6<=e?(t?.close(),t=c):c.close()}),i=await this.videoTrack._backing.getKeyChunk(e);if(!i)return null;let a=await this.videoTrack._backing.getChunk(e);m(a),s.decode(i);let n=i;for(;n!==a;){let c=await this.videoTrack._backing.getNextChunk(n);m(c),n=c,s.decode(c),s.decodeQueueSize>=10&&await new Promise(o=>s.addEventListener("dequeue",o,{once:!0}))}return await s.flush(),s.close(),t}async*frames(e=0,t=1/0){let s=[],i=!1,a=null,{promise:n,resolve:c}=X(),o=!1,u=await this.createDecoder(g=>{let h=g.timestamp/1e6;if(h>=t&&(o=!0),o){g.close();return}a&&(h>e?(s.push(a),i=!0):a.close()),h>=e&&(s.push(g),i=!0),a=i?null:g,s.length>0&&(c(),{promise:n,resolve:c}=X())}),d=await this.videoTrack._backing.getKeyChunk(e)??await this.videoTrack._backing.getFirstChunk();if(!d)return;let l=!1;(async()=>{let g=d,h=1/0;if(t<1/0){let x=await this.videoTrack._backing.getChunk(t),w=x?x.type==="key"&&x.timestamp/1e6===t?x:await this.videoTrack._backing.getNextKeyChunk(x):null;w&&(h=w.timestamp/1e6)}let p=new ve(this.videoTrack).chunks(d,h);for(await p.next();g&&!o;){u.decode(g),u.decodeQueueSize>=10&&await new Promise(w=>u.addEventListener("dequeue",w,{once:!0}));let x=await p.next();if(x.done)break;g=x.value}await p.return(),await u.flush(),u.close(),!i&&a&&s.push(a),l=!0,c()})();try{for(;;)if(s.length>0)yield s.shift();else if(!l)await n;else break}finally{o=!0}}};return gr(Ri);})();
if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Metamuxer)
diff --git a/dist/metamuxer.min.mjs b/dist/metamuxer.min.mjs
index 25982c9..082dad7 100644
--- a/dist/metamuxer.min.mjs
+++ b/dist/metamuxer.min.mjs
@@ -1,9 +1,9 @@
-function d(r){if(!r)throw new Error("Assertion failed.")}var v=r=>r&&r[r.length-1],z=r=>r>=0&&r<2**32,B=(r,e,t)=>{let i=0;for(let s=e;s p)!==-1:!0}},hi=(r,e)=>{if(!r.keySampleIndices)return e;let t=I(r.keySampleIndices,e,s=>s);return r.keySampleIndices[t]??-1},bi=(r,e)=>{if(!r.keySampleIndices)return e+1;let t=I(r.keySampleIndices,e,s=>s);return r.keySampleIndices[t+1]??-1},sr=(r,e)=>{r.startTimestamp+=e,r.endTimestamp+=e;for(let t of r.samples)t.presentationTimestamp+=e;for(let t of r.presentationTimestamps)t.presentationTimestamp+=e};var He=class extends ce{};var Le=class{},xt=class extends Le{async _canReadInput(e){if(await e._mainReader.source._getSize()<8)return!1;let s=new Y(e._mainReader);return s.pos=4,s.readAscii(4)==="ftyp"}_createDemuxer(e){return new Ne(e)}},St=class extends Le{async _canReadInput(){return!1}_createDemuxer(e){return new He(e)}},$e=new xt,gi=$e,ki=$e,Ct=new St,ar=Ct,Ti=Ct,xi=[$e,ar];var wt=class{_source;_formats;_mainReader;_demuxerPromise=null;_format=null;constructor(e){this._formats=e.formats,this._source=e.source,this._mainReader=new ue(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(),m(this._format),this._format}async getDuration(){return(await this._getDemuxer()).getDuration()}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{constructor(e){this.videoTrack=e}getFirstChunk(){return this.videoTrack._backing.getFirstChunk()}getChunk(e){return this.videoTrack._backing.getChunk(e)}getNextChunk(e){return this.videoTrack._backing.getNextChunk(e)}getKeyChunk(e){return this.videoTrack._backing.getKeyChunk(e)}getNextKeyChunk(e){return this.videoTrack._backing.getNextKeyChunk(e)}async*chunks(e,t=1/0){let s=[],{promise:i,resolve:a}=j(),{promise:n,resolve:c}=j(),o=!1,u=[],d=()=>Math.max(2,u.length);(async()=>{let l=e??await this.getFirstChunk();for(;l&&!o&&!(l.timestamp/1e6>=t);){if(s.length>d()){({promise:n,resolve:c}=j()),await n;continue}s.push(l),a(),{promise:i,resolve:a}=j(),l=await this.getNextChunk(l)}o=!0,a()})();try{for(;;)if(s.length>0){yield s.shift();let l=performance.now();for(u.push(l);u.length>0&&l-u[0]>=1e3;)u.shift();c()}else if(!o)await i;else break}finally{o=!0,c()}}},yt=class{constructor(e){this.videoTrack=e}decoderConfig=null;async createDecoder(e){this.decoderConfig||(this.decoderConfig=await this.videoTrack.getDecoderConfig());let t=new VideoDecoder({output:e,error:s=>console.error(s)});return t.configure(this.decoderConfig),t}async getKeyFrame(e){let t=null,s=await this.createDecoder(a=>t=a),i=await this.videoTrack._backing.getKeyChunk(e);return i?(s.decode(i),await s.flush(),s.close(),t):null}async getFrame(e){let t=null,s=await this.createDecoder(c=>{c.timestamp/1e6<=e?(t?.close(),t=c):c.close()}),i=await this.videoTrack._backing.getKeyChunk(e);if(!i)return null;let a=await this.videoTrack._backing.getChunk(e);m(a),s.decode(i);let n=i;for(;n!==a;){let c=await this.videoTrack._backing.getNextChunk(n);m(c),n=c,s.decode(c),s.decodeQueueSize>=10&&await new Promise(o=>s.addEventListener("dequeue",o,{once:!0}))}return await s.flush(),s.close(),t}async*frames(e=0,t=1/0){let s=[],i=!1,a=null,{promise:n,resolve:c}=j(),o=!1,u=await this.createDecoder(g=>{let h=g.timestamp/1e6;if(h>=t&&(o=!0),o){g.close();return}a&&(h>e?(s.push(a),i=!0):a.close()),h>=e&&(s.push(g),i=!0),a=i?null:g,s.length>0&&(c(),{promise:n,resolve:c}=j())}),d=await this.videoTrack._backing.getKeyChunk(e)??await this.videoTrack._backing.getFirstChunk();if(!d)return;let l=!1;(async()=>{let g=d,h=1/0;if(t<1/0){let x=await this.videoTrack._backing.getChunk(t),w=x?x.type==="key"&&x.timestamp/1e6===t?x:await this.videoTrack._backing.getNextKeyChunk(x):null;w&&(h=w.timestamp/1e6)}let p=new Qe(this.videoTrack).chunks(d,h);for(await p.next();g&&!o;){u.decode(g),u.decodeQueueSize>=10&&await new Promise(w=>u.addEventListener("dequeue",w,{once:!0}));let x=await p.next();if(x.done)break;g=x.value}await p.return(),await u.flush(),u.close(),!i&&a&&s.push(a),l=!0,c()})();try{for(;;)if(s.length>0)yield s.shift();else if(!l)await n;else break}finally{o=!0}}};export{xi as ALL_FORMATS,ie as AUDIO_CODECS,bt as ArrayBufferSource,pe as ArrayBufferTarget,lt as AudioBufferSource,dt as AudioDataSource,D as AudioSource,gt as BlobSource,ct as CanvasSource,mt as EncodedAudioChunkSource,Qe as EncodedVideoChunkDrain,nt as EncodedVideoChunkSource,$e as ISOBMFF,wt as Input,Ct as MATROSKA,ar as MKV,ki as MOV,gi as MP4,ne as MediaSource,ft as MediaStreamAudioTrackSource,ut as MediaStreamVideoTrackSource,Ue as MkvOutputFormat,at as Mp4OutputFormat,ht as Output,G as OutputFormat,Ve as SUBTITLE_CODECS,Te as Source,tt as StreamTarget,oe as SubtitleSource,X as Target,pt as TextSubtitleSource,re as VIDEO_CODECS,yt as VideoFrameDrain,ot as VideoFrameSource,P as VideoSource,Ti as WEBM,ae as WebMOutputFormat};
diff --git a/dist/metamuxer.mjs b/dist/metamuxer.mjs
index 3c08662..200e194 100644
--- a/dist/metamuxer.mjs
+++ b/dist/metamuxer.mjs
@@ -1075,8 +1075,8 @@ var tfra = (trackData, trackIndex) => {
u32(trackData.finalizedChunks.length),
// Number of entries
trackData.finalizedChunks.map((chunk) => [
- u64(intoTimescale(chunk.startTimestamp, trackData.timescale)),
- // Time
+ u64(intoTimescale(chunk.samples[0].timestamp, trackData.timescale)),
+ // Time (in presentation time)
u64(chunk.moofOffset),
// moof offset
u32(trackIndex + 1),
@@ -2405,14 +2405,14 @@ var IsobmffMuxer = class extends Muxer {
const movieBox = moov(this.trackDatas, this.creationTime, true);
this.boxWriter.writeBox(movieBox);
}
+ const tracksInFragment = this.trackDatas.filter((x) => x.currentChunk);
const moofOffset = this.writer.getPos();
- const moofBox = moof(fragmentNumber, this.trackDatas);
+ const moofBox = moof(fragmentNumber, tracksInFragment);
this.boxWriter.writeBox(moofBox);
{
const mdatBox = mdat(false);
let totalTrackSampleSize = 0;
- for (const trackData of this.trackDatas) {
- assert(trackData.currentChunk);
+ for (const trackData of tracksInFragment) {
for (const sample of trackData.currentChunk.samples) {
totalTrackSampleSize += sample.size;
}
@@ -2425,7 +2425,7 @@ var IsobmffMuxer = class extends Muxer {
mdatBox.size = mdatSize;
this.boxWriter.writeBox(mdatBox);
}
- for (const trackData of this.trackDatas) {
+ for (const trackData of tracksInFragment) {
trackData.currentChunk.offset = this.writer.getPos();
trackData.currentChunk.moofOffset = moofOffset;
for (const sample of trackData.currentChunk.samples) {
@@ -2435,10 +2435,10 @@ var IsobmffMuxer = class extends Muxer {
}
const endPos = this.writer.getPos();
this.writer.seek(this.boxWriter.offsets.get(moofBox));
- const newMoofBox = moof(fragmentNumber, this.trackDatas);
+ const newMoofBox = moof(fragmentNumber, tracksInFragment);
this.boxWriter.writeBox(newMoofBox);
this.writer.seek(endPos);
- for (const trackData of this.trackDatas) {
+ for (const trackData of tracksInFragment) {
trackData.finalizedChunks.push(trackData.currentChunk);
this.finalizedChunks.push(trackData.currentChunk);
trackData.currentChunk = null;
@@ -4084,7 +4084,6 @@ var InputAudioTrack = class extends InputTrack {
};
// src/reader.ts
-var PAGE_SIZE = 4096;
var Reader = class {
constructor(source, maxStorableBytes = Infinity) {
this.source = source;
@@ -4096,24 +4095,22 @@ var Reader = class {
nextAge = 0;
totalStoredBytes = 0;
async loadRange(start, end) {
- const alignedStart = Math.floor(start / PAGE_SIZE) * PAGE_SIZE;
- let alignedEnd = Math.ceil(end / PAGE_SIZE) * PAGE_SIZE;
- alignedEnd = Math.min(alignedEnd, await this.source._getSize());
- const matchingLoadingSegment = this.loadingSegments.find((x) => x.start <= alignedStart && x.end >= alignedEnd);
+ end = Math.min(end, await this.source._getSize());
+ const matchingLoadingSegment = this.loadingSegments.find((x) => x.start <= start && x.end >= end);
if (matchingLoadingSegment) {
await matchingLoadingSegment.promise;
return;
}
- const encasingSegmentExists = this.loadedSegments.some((x) => x.start <= alignedStart && x.end >= alignedEnd);
+ const encasingSegmentExists = this.loadedSegments.some((x) => x.start <= start && x.end >= end);
if (encasingSegmentExists) {
return;
}
- const bytesPromise = this.source._read(alignedStart, alignedEnd);
- const loadingSegment = { start: alignedStart, end: alignedEnd, promise: bytesPromise };
+ const bytesPromise = this.source._read(start, end);
+ const loadingSegment = { start, end, promise: bytesPromise };
this.loadingSegments.push(loadingSegment);
const bytes2 = await bytesPromise;
removeItem(this.loadingSegments, loadingSegment);
- this.insertIntoLoadedSegments(alignedStart, bytes2);
+ this.insertIntoLoadedSegments(start, bytes2);
}
insertIntoLoadedSegments(start, bytes2) {
const segment = {
@@ -4178,6 +4175,21 @@ var Reader = class {
offset: segment.bytes.byteOffset + start - segment.start
};
}
+ forgetRange(start, end) {
+ if (end <= start) {
+ return;
+ }
+ const startIndex = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start);
+ if (startIndex === -1) {
+ return;
+ }
+ const segment = this.loadedSegments[startIndex];
+ if (segment.start !== start || segment.end !== end) {
+ return;
+ }
+ this.loadedSegments.splice(startIndex, 1);
+ this.totalStoredBytes -= segment.bytes.byteLength;
+ }
};
// src/isobmff/isobmff-reader.ts
@@ -4281,6 +4293,11 @@ var IsobmffDemuxer = class extends Demuxer {
metadataPromise = null;
movieTimescale = -1;
movieDurationInTimescale = -1;
+ isFragmented = false;
+ fragmentTrackDefaults = [];
+ fragments = [];
+ currentFragment = null;
+ fragmentLookupMutex = new AsyncMutex();
chunkReader;
constructor(input) {
super(input);
@@ -4321,10 +4338,24 @@ var IsobmffDemuxer = class extends Demuxer {
this.isobmffReader.pos + boxInfo.contentSize
);
this.readContiguousBoxes(boxInfo.contentSize);
- return;
+ break;
}
this.isobmffReader.pos = startPos + boxInfo.totalSize;
}
+ if (this.isFragmented) {
+ await this.isobmffReader.reader.loadRange(sourceSize - 4, sourceSize);
+ this.isobmffReader.pos = sourceSize - 4;
+ const lastWord = this.isobmffReader.readU32();
+ const potentialMfraPos = sourceSize - lastWord;
+ if (potentialMfraPos >= 0 && potentialMfraPos < sourceSize) {
+ await this.isobmffReader.reader.loadRange(potentialMfraPos, sourceSize);
+ this.isobmffReader.pos = potentialMfraPos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ if (boxInfo.name === "mfra") {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ }
+ }
})();
}
getSampleTableForTrack(internalTrack) {
@@ -4366,6 +4397,73 @@ var IsobmffDemuxer = class extends Demuxer {
sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
return internalTrack.sampleTable;
}
+ async readFragment() {
+ const startPos = this.isobmffReader.pos;
+ await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16);
+ const moofBoxInfo = this.isobmffReader.readBoxHeader();
+ assert(moofBoxInfo.name === "moof");
+ await this.isobmffReader.reader.loadRange(startPos, startPos + moofBoxInfo.totalSize);
+ this.isobmffReader.pos = startPos;
+ this.traverseBox();
+ const index = binarySearchExact(this.fragments, startPos, (x) => x.moofOffset);
+ assert(index !== -1);
+ const fragment = this.fragments[index];
+ assert(fragment.moofOffset === startPos);
+ this.isobmffReader.reader.forgetRange(startPos, startPos + moofBoxInfo.totalSize);
+ for (const [trackId, trackData] of fragment.trackData) {
+ if (trackData.startTimestampIsFinal) {
+ continue;
+ }
+ const internalTrack = this.tracks.find((x) => x.id === trackId);
+ this.isobmffReader.pos = 0;
+ let currentFragment = null;
+ let lastFragment = null;
+ const index2 = binarySearchLessOrEqual(
+ internalTrack.fragments,
+ startPos - 1,
+ (x) => x.moofOffset
+ );
+ if (index2 !== -1) {
+ currentFragment = internalTrack.fragments[index2];
+ lastFragment = currentFragment;
+ this.isobmffReader.pos = currentFragment.moofOffset + currentFragment.moofSize;
+ }
+ while (this.isobmffReader.pos < startPos) {
+ if (currentFragment?.nextFragment) {
+ currentFragment = currentFragment.nextFragment;
+ this.isobmffReader.pos = currentFragment.moofOffset + currentFragment.moofSize;
+ } else {
+ await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16);
+ const startPos2 = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ if (boxInfo.name === "moof") {
+ const index3 = binarySearchExact(this.fragments, startPos2, (x) => x.moofOffset);
+ if (index3 === -1) {
+ this.isobmffReader.pos = startPos2;
+ const fragment2 = await this.readFragment();
+ if (currentFragment) currentFragment.nextFragment = fragment2;
+ currentFragment = fragment2;
+ } else {
+ const fragment2 = this.fragments[index3];
+ if (currentFragment) currentFragment.nextFragment = fragment2;
+ currentFragment = fragment2;
+ }
+ }
+ this.isobmffReader.pos = startPos2 + boxInfo.totalSize;
+ }
+ if (currentFragment && currentFragment.trackData.has(trackId)) {
+ lastFragment = currentFragment;
+ }
+ }
+ if (lastFragment) {
+ const otherTrackData = lastFragment.trackData.get(trackId);
+ assert(otherTrackData.startTimestampIsFinal);
+ offsetFragmentTrackDataByTimestamp(trackData, otherTrackData.endTimestamp);
+ }
+ trackData.startTimestampIsFinal = true;
+ }
+ return fragment;
+ }
readContiguousBoxes(totalSize) {
const startIndex = this.isobmffReader.pos;
while (this.isobmffReader.pos - startIndex < totalSize) {
@@ -4380,6 +4478,7 @@ var IsobmffDemuxer = class extends Demuxer {
case "mdia":
case "minf":
case "dinf":
+ case "mfra":
{
this.readContiguousBoxes(boxInfo.contentSize);
}
@@ -4412,7 +4511,10 @@ var IsobmffDemuxer = class extends Demuxer {
durationInTimescale: -1,
rotation: 0,
sampleTableOffset: -1,
- sampleTable: null
+ sampleTable: null,
+ fragmentLookupTable: null,
+ currentFragmentState: null,
+ fragments: []
};
this.currentTrack = track;
this.readContiguousBoxes(boxInfo.contentSize);
@@ -4829,6 +4931,293 @@ var IsobmffDemuxer = class extends Demuxer {
}
;
break;
+ case "mvex":
+ {
+ this.isFragmented = true;
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ ;
+ break;
+ case "mehd":
+ {
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ const fragmentDuration = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32();
+ this.movieDurationInTimescale = fragmentDuration;
+ }
+ ;
+ break;
+ case "trex":
+ {
+ this.isobmffReader.pos += 4;
+ const trackId = this.isobmffReader.readU32();
+ const defaultSampleDescriptionIndex = this.isobmffReader.readU32();
+ const defaultSampleDuration = this.isobmffReader.readU32();
+ const defaultSampleSize = this.isobmffReader.readU32();
+ const defaultSampleFlags = this.isobmffReader.readU32();
+ this.fragmentTrackDefaults.push({
+ trackId,
+ defaultSampleDescriptionIndex,
+ defaultSampleDuration,
+ defaultSampleSize,
+ defaultSampleFlags
+ });
+ }
+ ;
+ break;
+ case "tfra":
+ {
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ const trackId = this.isobmffReader.readU32();
+ const track = this.tracks.find((x2) => x2.id === trackId);
+ if (!track) {
+ break;
+ }
+ track.fragmentLookupTable = [];
+ const word = this.isobmffReader.readU32();
+ const lengthSizeOfTrafNum = (word & 48) >> 4;
+ const lengthSizeOfTrunNum = (word & 12) >> 2;
+ const lengthSizeOfSampleNum = word & 3;
+ const x = this.isobmffReader;
+ const functions = [x.readU8.bind(x), x.readU16.bind(x), x.readU24.bind(x), x.readU32.bind(x)];
+ const readTrafNum = functions[lengthSizeOfTrafNum];
+ const readTrunNum = functions[lengthSizeOfTrunNum];
+ const readSampleNum = functions[lengthSizeOfSampleNum];
+ const numberOfEntries = this.isobmffReader.readU32();
+ for (let i = 0; i < numberOfEntries; i++) {
+ const time = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32();
+ const moofOffset = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32();
+ const trafNumber = readTrafNum();
+ const trunNumber = readTrunNum();
+ const sampleNumber = readSampleNum();
+ track.fragmentLookupTable.push({
+ timestamp: time,
+ moofOffset
+ });
+ }
+ }
+ ;
+ break;
+ case "moof":
+ {
+ this.currentFragment = {
+ moofOffset: startPos,
+ moofSize: boxInfo.totalSize,
+ implicitBaseDataOffset: startPos,
+ trackData: /* @__PURE__ */ new Map(),
+ dataStart: Infinity,
+ dataEnd: 0,
+ nextFragment: null
+ };
+ this.readContiguousBoxes(boxInfo.contentSize);
+ const insertionIndex = binarySearchLessOrEqual(
+ this.fragments,
+ this.currentFragment.moofOffset,
+ (x) => x.moofOffset
+ );
+ this.fragments.splice(insertionIndex + 1, 0, this.currentFragment);
+ for (const [, trackData] of this.currentFragment.trackData) {
+ const firstSample = trackData.samples[0];
+ const lastSample = last(trackData.samples);
+ this.currentFragment.dataStart = Math.min(
+ this.currentFragment.dataStart,
+ firstSample.byteOffset
+ );
+ this.currentFragment.dataEnd = Math.max(
+ this.currentFragment.dataEnd,
+ lastSample.byteOffset + lastSample.byteSize
+ );
+ }
+ this.currentFragment = null;
+ }
+ ;
+ break;
+ case "traf":
+ {
+ assert(this.currentFragment);
+ this.readContiguousBoxes(boxInfo.contentSize);
+ if (this.currentTrack) {
+ const trackData = this.currentFragment.trackData.get(this.currentTrack.id);
+ if (trackData) {
+ const insertionIndex = binarySearchLessOrEqual(
+ this.currentTrack.fragments,
+ this.currentFragment.moofOffset,
+ (x) => x.moofOffset
+ );
+ this.currentTrack.fragments.splice(insertionIndex + 1, 0, this.currentFragment);
+ const { currentFragmentState } = this.currentTrack;
+ assert(currentFragmentState);
+ if (currentFragmentState.startTimestamp !== null) {
+ offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp);
+ trackData.startTimestampIsFinal = true;
+ }
+ }
+ this.currentTrack.currentFragmentState = null;
+ this.currentTrack = null;
+ }
+ }
+ ;
+ break;
+ case "tfhd":
+ {
+ assert(this.currentFragment);
+ this.isobmffReader.pos += 1;
+ const flags = this.isobmffReader.readU24();
+ const baseDataOffsetPresent = Boolean(flags & 1);
+ const sampleDescriptionIndexPresent = Boolean(flags & 2);
+ const defaultSampleDurationPresent = Boolean(flags & 8);
+ const defaultSampleSizePresent = Boolean(flags & 16);
+ const defaultSampleFlagsPresent = Boolean(flags & 32);
+ const durationIsEmpty = Boolean(flags & 65536);
+ const defaultBaseIsMoof = Boolean(flags & 131072);
+ const trackId = this.isobmffReader.readU32();
+ const track = this.tracks.find((x) => x.id === trackId);
+ if (!track) {
+ break;
+ }
+ const defaults = this.fragmentTrackDefaults.find((x) => x.trackId === trackId);
+ this.currentTrack = track;
+ track.currentFragmentState = {
+ baseDataOffset: this.currentFragment.implicitBaseDataOffset,
+ sampleDescriptionIndex: defaults?.defaultSampleDescriptionIndex ?? null,
+ defaultSampleDuration: defaults?.defaultSampleDuration ?? null,
+ defaultSampleSize: defaults?.defaultSampleSize ?? null,
+ defaultSampleFlags: defaults?.defaultSampleFlags ?? null,
+ startTimestamp: null
+ };
+ if (baseDataOffsetPresent) {
+ track.currentFragmentState.baseDataOffset = this.isobmffReader.readU64();
+ } else if (defaultBaseIsMoof) {
+ track.currentFragmentState.baseDataOffset = this.currentFragment.moofOffset;
+ }
+ if (sampleDescriptionIndexPresent) {
+ track.currentFragmentState.sampleDescriptionIndex = this.isobmffReader.readU32();
+ }
+ if (defaultSampleDurationPresent) {
+ track.currentFragmentState.defaultSampleDuration = this.isobmffReader.readU32();
+ }
+ if (defaultSampleSizePresent) {
+ track.currentFragmentState.defaultSampleSize = this.isobmffReader.readU32();
+ }
+ if (defaultSampleFlagsPresent) {
+ track.currentFragmentState.defaultSampleFlags = this.isobmffReader.readU32();
+ }
+ if (durationIsEmpty) {
+ track.currentFragmentState.defaultSampleDuration = 0;
+ }
+ }
+ ;
+ break;
+ case "tfdt":
+ {
+ const track = this.currentTrack;
+ if (!track) {
+ break;
+ }
+ assert(track.currentFragmentState);
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ const baseMediaDecodeTime = version === 0 ? this.isobmffReader.readU32() : this.isobmffReader.readU64();
+ track.currentFragmentState.startTimestamp = baseMediaDecodeTime;
+ }
+ ;
+ break;
+ case "trun":
+ {
+ const track = this.currentTrack;
+ if (!track) {
+ break;
+ }
+ assert(this.currentFragment);
+ assert(track.currentFragmentState);
+ if (this.currentFragment.trackData.has(track.id)) {
+ throw new Error("Can't have two trun boxes for the same track in one fragment.");
+ }
+ const version = this.isobmffReader.readU8();
+ const flags = this.isobmffReader.readU24();
+ const dataOffsetPresent = Boolean(flags & 1);
+ const firstSampleFlagsPresent = Boolean(flags & 4);
+ const sampleDurationPresent = Boolean(flags & 256);
+ const sampleSizePresent = Boolean(flags & 512);
+ const sampleFlagsPresent = Boolean(flags & 1024);
+ const sampleCompositionTimeOffsetsPresent = Boolean(flags & 2048);
+ const sampleCount = this.isobmffReader.readU32();
+ let dataOffset = track.currentFragmentState.baseDataOffset;
+ if (dataOffsetPresent) {
+ dataOffset += this.isobmffReader.readI32();
+ }
+ let firstSampleFlags = null;
+ if (firstSampleFlagsPresent) {
+ firstSampleFlags = this.isobmffReader.readU32();
+ }
+ let currentOffset = dataOffset;
+ if (sampleCount === 0) {
+ this.currentFragment.implicitBaseDataOffset = currentOffset;
+ break;
+ }
+ let currentTimestamp = 0;
+ const trackData = {
+ startTimestamp: 0,
+ endTimestamp: 0,
+ samples: [],
+ presentationTimestamps: [],
+ startTimestampIsFinal: false
+ };
+ this.currentFragment.trackData.set(track.id, trackData);
+ for (let i = 0; i < sampleCount; i++) {
+ let sampleDuration;
+ if (sampleDurationPresent) {
+ sampleDuration = this.isobmffReader.readU32();
+ } else {
+ assert(track.currentFragmentState.defaultSampleDuration !== null);
+ sampleDuration = track.currentFragmentState.defaultSampleDuration;
+ }
+ let sampleSize;
+ if (sampleSizePresent) {
+ sampleSize = this.isobmffReader.readU32();
+ } else {
+ assert(track.currentFragmentState.defaultSampleSize !== null);
+ sampleSize = track.currentFragmentState.defaultSampleSize;
+ }
+ let sampleFlags;
+ if (sampleFlagsPresent) {
+ sampleFlags = this.isobmffReader.readU32();
+ } else {
+ assert(track.currentFragmentState.defaultSampleFlags !== null);
+ sampleFlags = track.currentFragmentState.defaultSampleFlags;
+ }
+ if (i === 0 && firstSampleFlags !== null) {
+ sampleFlags = firstSampleFlags;
+ }
+ let sampleCompositionTimeOffset = 0;
+ if (sampleCompositionTimeOffsetsPresent) {
+ if (version === 0) {
+ sampleCompositionTimeOffset = this.isobmffReader.readU32();
+ } else {
+ sampleCompositionTimeOffset = this.isobmffReader.readI32();
+ }
+ }
+ const isKeyFrame = !(sampleFlags & 65536);
+ trackData.samples.push({
+ presentationTimestamp: currentTimestamp + sampleCompositionTimeOffset,
+ duration: sampleDuration,
+ byteOffset: currentOffset,
+ byteSize: sampleSize,
+ isKeyFrame
+ });
+ currentOffset += sampleSize;
+ currentTimestamp += sampleDuration;
+ }
+ trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
+ const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex];
+ const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex];
+ trackData.startTimestamp = firstSample.presentationTimestamp;
+ trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration;
+ this.currentFragment.implicitBaseDataOffset = currentOffset;
+ }
+ ;
+ break;
}
this.isobmffReader.pos = boxEndPos;
}
@@ -4839,6 +5228,8 @@ var IsobmffTrackBacking = class {
}
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.");
}
@@ -4873,6 +5264,176 @@ var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking {
colorSpace: this.internalTrack.info.colorSpace ?? void 0
};
}
+ async getFirstChunk() {
+ if (this.internalTrack.demuxer.isFragmented) {
+ return this.performFragmentedLookup(
+ () => {
+ const fragment = this.internalTrack.fragments[0];
+ return {
+ fragmentIndex: fragment ? 0 : -1,
+ sampleIndex: fragment ? 0 : -1,
+ correctSampleFound: !!fragment
+ };
+ },
+ 0,
+ Infinity
+ );
+ }
+ return this.fetchChunkForSampleIndex(0);
+ }
+ async getChunk(timestamp) {
+ const timestampInTimescale = timestamp * this.internalTrack.timescale;
+ if (this.internalTrack.demuxer.isFragmented) {
+ return this.performFragmentedLookup(
+ () => this.findSampleInFragmentsForTimestamp(timestampInTimescale),
+ timestampInTimescale,
+ timestampInTimescale
+ );
+ } else {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale);
+ return this.fetchChunkForSampleIndex(sampleIndex);
+ }
+ }
+ async getNextChunk(chunk) {
+ if (this.internalTrack.demuxer.isFragmented) {
+ const locationInFragment = this.chunkToFragmentLocation.get(chunk);
+ if (locationInFragment === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ const trackData = locationInFragment.fragment.trackData.get(this.internalTrack.id);
+ const sample = trackData.samples[locationInFragment.sampleIndex];
+ const fragmentIndex = binarySearchExact(
+ this.internalTrack.fragments,
+ locationInFragment.fragment.moofOffset,
+ (x) => x.moofOffset
+ );
+ assert(fragmentIndex !== -1);
+ return this.performFragmentedLookup(
+ () => {
+ if (locationInFragment.sampleIndex + 1 < trackData.samples.length) {
+ return {
+ fragmentIndex,
+ sampleIndex: locationInFragment.sampleIndex + 1,
+ correctSampleFound: true
+ };
+ } else {
+ let currentFragment = locationInFragment.fragment;
+ while (currentFragment.nextFragment) {
+ currentFragment = currentFragment.nextFragment;
+ const trackData2 = currentFragment.trackData.get(this.internalTrack.id);
+ if (trackData2) {
+ const fragmentIndex2 = binarySearchExact(
+ this.internalTrack.fragments,
+ currentFragment.moofOffset,
+ (x) => x.moofOffset
+ );
+ assert(fragmentIndex2 !== -1);
+ return {
+ fragmentIndex: fragmentIndex2,
+ sampleIndex: 0,
+ correctSampleFound: true
+ };
+ }
+ }
+ return {
+ fragmentIndex,
+ sampleIndex: -1,
+ correctSampleFound: false
+ };
+ }
+ },
+ sample.presentationTimestamp,
+ Infinity
+ );
+ }
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ return this.fetchChunkForSampleIndex(sampleIndex + 1);
+ }
+ async getKeyChunk(timestamp) {
+ const timestampInTimescale = timestamp * this.internalTrack.timescale;
+ if (this.internalTrack.demuxer.isFragmented) {
+ return this.performFragmentedLookup(
+ () => this.findKeySampleInFragmentsForTimestamp(timestampInTimescale),
+ timestampInTimescale,
+ timestampInTimescale
+ );
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale);
+ const keyFrameSampleIndex = sampleIndex === -1 ? -1 : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(keyFrameSampleIndex);
+ }
+ async getNextKeyChunk(chunk) {
+ if (this.internalTrack.demuxer.isFragmented) {
+ const locationInFragment = this.chunkToFragmentLocation.get(chunk);
+ if (locationInFragment === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ const trackData = locationInFragment.fragment.trackData.get(this.internalTrack.id);
+ const sample = trackData.samples[locationInFragment.sampleIndex];
+ const fragmentIndex = binarySearchExact(
+ this.internalTrack.fragments,
+ locationInFragment.fragment.moofOffset,
+ (x) => x.moofOffset
+ );
+ assert(fragmentIndex !== -1);
+ return this.performFragmentedLookup(
+ () => {
+ const nextKeyFrameIndex = trackData.samples.findIndex(
+ (x, i) => x.isKeyFrame && i > locationInFragment.sampleIndex
+ );
+ if (nextKeyFrameIndex !== -1) {
+ return {
+ fragmentIndex,
+ sampleIndex: nextKeyFrameIndex,
+ correctSampleFound: true
+ };
+ } else {
+ let currentFragment = locationInFragment.fragment;
+ while (currentFragment.nextFragment) {
+ currentFragment = currentFragment.nextFragment;
+ const trackData2 = currentFragment.trackData.get(this.internalTrack.id);
+ if (trackData2) {
+ const fragmentIndex2 = binarySearchExact(
+ this.internalTrack.fragments,
+ currentFragment.moofOffset,
+ (x) => x.moofOffset
+ );
+ assert(fragmentIndex2 !== -1);
+ const keyFrameIndex = trackData2.samples.findIndex((x) => x.isKeyFrame);
+ if (keyFrameIndex === -1) {
+ throw new Error("Not supported: Fragment does not contain key sample.");
+ }
+ return {
+ fragmentIndex: fragmentIndex2,
+ sampleIndex: keyFrameIndex,
+ correctSampleFound: true
+ };
+ }
+ }
+ return {
+ fragmentIndex,
+ sampleIndex: -1,
+ correctSampleFound: false
+ };
+ }
+ },
+ sample.presentationTimestamp,
+ Infinity
+ );
+ }
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex);
+ }
async fetchChunkForSampleIndex(sampleIndex) {
if (sampleIndex === -1) {
return null;
@@ -4904,35 +5465,163 @@ var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking {
this.sampleIndexToChunk.set(sampleIndex, new WeakRef(chunk));
return chunk;
}
- async getFirstChunk() {
- return this.fetchChunkForSampleIndex(0);
- }
- async getChunk(timestamp) {
- const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
- const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
- return this.fetchChunkForSampleIndex(sampleIndex);
- }
- async getNextChunk(chunk) {
- const sampleIndex = this.chunkToSampleIndex.get(chunk);
- if (sampleIndex === void 0) {
- throw new Error("Chunk was not created from this track.");
+ async fetchChunkInFragment(fragment, sampleIndex) {
+ if (sampleIndex === -1) {
+ return null;
}
- return this.fetchChunkForSampleIndex(sampleIndex + 1);
+ 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);
+ await this.internalTrack.demuxer.chunkReader.reader.loadRange(fragment.dataStart, fragment.dataEnd);
+ const data = this.internalTrack.demuxer.chunkReader.readRange(
+ sample.byteOffset,
+ sample.byteOffset + sample.byteSize
+ );
+ const chunk = new EncodedVideoChunk({
+ data,
+ timestamp: 1e6 * sample.presentationTimestamp / this.internalTrack.timescale,
+ duration: 1e6 * sample.duration / this.internalTrack.timescale,
+ type: sample.isKeyFrame ? "key" : "delta"
+ });
+ this.chunkToFragmentLocation.set(chunk, { fragment, sampleIndex });
+ this.fragmentLocationToChunk.set(compositeKey, new WeakRef(chunk));
+ return chunk;
}
- async getKeyChunk(timestamp) {
- const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
- const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
- const keyFrameSampleIndex = sampleIndex === -1 ? -1 : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
- return this.fetchChunkForSampleIndex(keyFrameSampleIndex);
+ findSampleInFragmentsForTimestamp(timestampInTimescale) {
+ const fragmentIndex = binarySearchLessOrEqual(
+ this.internalTrack.fragments,
+ timestampInTimescale,
+ (x) => x.trackData.get(this.internalTrack.id).startTimestamp
+ );
+ let sampleIndex = -1;
+ let correctSampleFound = false;
+ if (fragmentIndex !== -1) {
+ const fragment = this.internalTrack.fragments[fragmentIndex];
+ const trackData = fragment.trackData.get(this.internalTrack.id);
+ const index = binarySearchLessOrEqual(
+ trackData.presentationTimestamps,
+ timestampInTimescale,
+ (x) => x.presentationTimestamp
+ );
+ assert(index !== -1);
+ sampleIndex = trackData.presentationTimestamps[index].sampleIndex;
+ correctSampleFound = timestampInTimescale < trackData.endTimestamp;
+ }
+ return { fragmentIndex, sampleIndex, correctSampleFound };
}
- async getNextKeyChunk(chunk) {
- const sampleIndex = this.chunkToSampleIndex.get(chunk);
- if (sampleIndex === void 0) {
- throw new Error("Chunk was not created from this track.");
+ findKeySampleInFragmentsForTimestamp(timestampInTimescale) {
+ const fragmentIndex = binarySearchLessOrEqual(
+ this.internalTrack.fragments,
+ timestampInTimescale,
+ (x) => x.trackData.get(this.internalTrack.id).startTimestamp
+ );
+ let sampleIndex = -1;
+ let correctSampleFound = false;
+ if (fragmentIndex !== -1) {
+ const fragment = this.internalTrack.fragments[fragmentIndex];
+ const trackData = fragment.trackData.get(this.internalTrack.id);
+ const index = trackData.presentationTimestamps.findLastIndex((x) => {
+ const sample = trackData.samples[x.sampleIndex];
+ return sample.isKeyFrame;
+ });
+ if (index === -1) {
+ throw new Error("Not supported: Fragment does not contain key sample.");
+ }
+ const entry = trackData.presentationTimestamps[index];
+ sampleIndex = entry.sampleIndex;
+ correctSampleFound = timestampInTimescale < trackData.endTimestamp;
+ }
+ return { fragmentIndex, sampleIndex, correctSampleFound };
+ }
+ /** Looks for a sample in the fragments while trying to load as few fragments as possible to retrieve it. */
+ async performFragmentedLookup(getBestMatch, earliestTimestamp, latestTimestamp) {
+ const { fragmentIndex, sampleIndex, correctSampleFound } = getBestMatch();
+ if (correctSampleFound) {
+ const fragment = this.internalTrack.fragments[fragmentIndex];
+ return this.fetchChunkInFragment(fragment, sampleIndex);
+ }
+ const demuxer = this.internalTrack.demuxer;
+ const release = await demuxer.fragmentLookupMutex.acquire();
+ try {
+ const isobmffReader = demuxer.isobmffReader;
+ const sourceSize = await isobmffReader.reader.source._getSize();
+ let prevFragment = null;
+ let bestFragmentIndex = fragmentIndex;
+ let bestSampleIndex = sampleIndex;
+ let lookupEntry = null;
+ if (this.internalTrack.fragmentLookupTable) {
+ const index = binarySearchLessOrEqual(
+ this.internalTrack.fragmentLookupTable,
+ earliestTimestamp,
+ (x) => x.timestamp
+ );
+ if (index !== -1) {
+ lookupEntry = this.internalTrack.fragmentLookupTable[index];
+ }
+ }
+ if (fragmentIndex === -1) {
+ isobmffReader.pos = lookupEntry?.moofOffset ?? 0;
+ } else {
+ const fragment = this.internalTrack.fragments[fragmentIndex];
+ if (!lookupEntry || fragment.moofOffset >= fragment.moofOffset) {
+ isobmffReader.pos = fragment.moofOffset + fragment.moofSize;
+ prevFragment = fragment;
+ } else {
+ isobmffReader.pos = lookupEntry.moofOffset;
+ }
+ }
+ while (isobmffReader.pos < sourceSize) {
+ if (prevFragment) {
+ const trackData = prevFragment.trackData.get(this.internalTrack.id);
+ if (trackData && trackData.startTimestamp > latestTimestamp) {
+ break;
+ }
+ if (prevFragment.nextFragment) {
+ isobmffReader.pos = prevFragment.nextFragment.moofOffset + prevFragment.nextFragment.moofSize;
+ prevFragment = prevFragment.nextFragment;
+ continue;
+ }
+ }
+ await isobmffReader.reader.loadRange(isobmffReader.pos, isobmffReader.pos + 16);
+ const startPos = isobmffReader.pos;
+ const boxInfo = isobmffReader.readBoxHeader();
+ if (boxInfo.name === "moof") {
+ const index = binarySearchExact(demuxer.fragments, startPos, (x) => x.moofOffset);
+ if (index === -1) {
+ isobmffReader.pos = startPos;
+ const fragment = await demuxer.readFragment();
+ if (prevFragment) prevFragment.nextFragment = fragment;
+ prevFragment = fragment;
+ const { fragmentIndex: fragmentIndex2, sampleIndex: sampleIndex2, correctSampleFound: correctSampleFound2 } = getBestMatch();
+ if (correctSampleFound2) {
+ const fragment2 = this.internalTrack.fragments[fragmentIndex2];
+ return this.fetchChunkInFragment(fragment2, sampleIndex2);
+ }
+ if (fragmentIndex2 !== -1) {
+ bestFragmentIndex = fragmentIndex2;
+ bestSampleIndex = sampleIndex2;
+ }
+ } else {
+ const fragment = demuxer.fragments[index];
+ if (prevFragment) prevFragment.nextFragment = fragment;
+ prevFragment = fragment;
+ }
+ }
+ isobmffReader.pos = startPos + boxInfo.totalSize;
+ }
+ if (bestFragmentIndex !== -1) {
+ const fragment = this.internalTrack.fragments[bestFragmentIndex];
+ return this.fetchChunkInFragment(fragment, bestSampleIndex);
+ }
+ return null;
+ } finally {
+ release();
}
- const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
- const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex);
- return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex);
}
};
var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking {
@@ -5032,6 +5721,16 @@ var getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => {
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x);
return sampleTable.keySampleIndices[index + 1] ?? -1;
};
+var offsetFragmentTrackDataByTimestamp = (trackData, timestamp) => {
+ trackData.startTimestamp += timestamp;
+ trackData.endTimestamp += timestamp;
+ for (const sample of trackData.samples) {
+ sample.presentationTimestamp += timestamp;
+ }
+ for (const entry of trackData.presentationTimestamps) {
+ entry.presentationTimestamp += timestamp;
+ }
+};
// src/matroska/matroska-demuxer.ts
var MatroskaDemuxer = class extends Demuxer {
@@ -5047,7 +5746,6 @@ var IsobmffInputFormat = class extends InputFormat {
if (sourceSize < 8) {
return false;
}
- await input._mainReader.loadRange(4, 8);
const isobmffReader = new IsobmffReader(input._mainReader);
isobmffReader.pos = 4;
const fourCc = isobmffReader.readAscii(4);
@@ -5094,6 +5792,7 @@ var Input = class {
/** @internal */
_getDemuxer() {
return this._demuxerPromise ??= (async () => {
+ await this._mainReader.loadRange(0, 4096);
for (const format of this._formats) {
const canRead = await format._canReadInput(this);
if (canRead) {
diff --git a/src/index.ts b/src/index.ts
index 694b19f..5d3e7b5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -39,3 +39,5 @@ export { Source, ArrayBufferSource, BlobSource } from './source';
export { ALL_FORMATS, ISOBMFF, MP4, MOV, MATROSKA, MKV, WEBM } from './input-format';
export { Input, InputOptions } from './input';
export { EncodedVideoChunkDrain, VideoFrameDrain } from './media-drain';
+
+// 🐡🦔
diff --git a/src/input-format.ts b/src/input-format.ts
index ef32456..cd7f15f 100644
--- a/src/input-format.ts
+++ b/src/input-format.ts
@@ -20,8 +20,6 @@ class IsobmffInputFormat extends InputFormat {
return false;
}
- await input._mainReader.loadRange(4, 8);
-
const isobmffReader = new IsobmffReader(input._mainReader);
isobmffReader.pos = 4;
const fourCc = isobmffReader.readAscii(4);
diff --git a/src/input-track.ts b/src/input-track.ts
index b28aa58..aa1ad0a 100644
--- a/src/input-track.ts
+++ b/src/input-track.ts
@@ -1,4 +1,5 @@
import { AudioCodec, MediaCodec, VideoCodec } from './codec';
+import { Rotation } from './misc';
export interface InputTrackBacking {
getCodec(): Promise