Read only headers, no payload, when possible

This commit is contained in:
Vanilagy
2026-01-14 17:10:30 +01:00
parent 301a6adb1d
commit 4a85aad012
+195 -106
View File
@@ -76,16 +76,19 @@ type ElementaryStream = {
type ElementaryVideoStream = ElementaryStream & { info: { type: 'video' } }; type ElementaryVideoStream = ElementaryStream & { info: { type: 'video' } };
type ElementaryAudioStream = ElementaryStream & { info: { type: 'audio' } }; type ElementaryAudioStream = ElementaryStream & { info: { type: 'audio' } };
type PacketHeader = { type TsPacketHeader = {
payloadUnitStartIndicator: number; payloadUnitStartIndicator: number;
pid: number; pid: number;
adaptationFieldControl: number; adaptationFieldControl: number;
};
type TsPacket = TsPacketHeader & {
body: Uint8Array<ArrayBufferLike>; body: Uint8Array<ArrayBufferLike>;
}; };
type Section = { type Section = {
startPos: number; startPos: number;
endPos: number; endPos: number | null; // null if the section was not read fully
pid: number; pid: number;
payload: Uint8Array<ArrayBufferLike>; payload: Uint8Array<ArrayBufferLike>;
}; };
@@ -136,7 +139,7 @@ export class MpegTsDemuxer extends Demuxer {
let hasProgramMap = false; let hasProgramMap = false;
while (true) { while (true) {
const section = await this.readSection(currentPos); const section = await this.readSection(currentPos, true);
if (!section) { if (!section) {
break; break;
} }
@@ -316,6 +319,7 @@ export class MpegTsDemuxer extends Demuxer {
break; break;
} }
assert(section.endPos !== null);
currentPos = section.endPos; currentPos = section.endPos;
} }
@@ -366,12 +370,13 @@ export class MpegTsDemuxer extends Demuxer {
return string; return string;
} }
async readSection(startPos: number): Promise<Section | null> { async readSection(startPos: number, full: boolean): Promise<Section | null> {
let endPos = startPos; let endPos = startPos;
let currentPos = startPos; let currentPos = startPos;
const chunks: Uint8Array[] = []; const chunks: Uint8Array[] = [];
let chunksByteLength = 0;
let firstPacket: PacketHeader | null = null; let firstPacket: TsPacket | null = null;
while (true) { while (true) {
const packet = await this.readPacket(currentPos); const packet = await this.readPacket(currentPos);
@@ -408,12 +413,19 @@ export class MpegTsDemuxer extends Demuxer {
if (hasPayload) { if (hasPayload) {
if (adaptationFieldLength === 0) { if (adaptationFieldLength === 0) {
chunks.push(packet.body); chunks.push(packet.body);
chunksByteLength += packet.body.byteLength;
} else { } else {
chunks.push(packet.body.subarray(adaptationFieldLength)); chunks.push(packet.body.subarray(adaptationFieldLength));
chunksByteLength += packet.body.byteLength - adaptationFieldLength;
} }
} }
endPos = currentPos; endPos = currentPos;
// 64 is just "a bit of data", enough for the PES packet header
if (!full && chunksByteLength >= 64) {
break;
}
} }
if (!firstPacket) { if (!firstPacket) {
@@ -435,13 +447,49 @@ export class MpegTsDemuxer extends Demuxer {
return { return {
startPos, startPos,
endPos, endPos: full ? endPos : null,
pid: firstPacket.pid, pid: firstPacket.pid,
payload: merged, payload: merged,
}; };
} }
async readPacket(pos: number): Promise<PacketHeader | null> { async readPacketHeader(pos: number): Promise<TsPacketHeader | null> {
let slice = this.reader.requestSlice(pos, 4);
if (slice instanceof Promise) slice = await slice;
if (!slice) {
return null;
}
const syncByte = readU8(slice);
if (syncByte !== 0x47) {
throw new Error('Invalid TS packet sync byte. Likely an internal bug, please report this file.');
}
const nextTwoBytes = readU16Be(slice);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const transportErrorIndicator = nextTwoBytes >> 15;
const payloadUnitStartIndicator = (nextTwoBytes >> 14) & 0x1;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const transportPriority = (nextTwoBytes >> 13) & 0x1;
const pid = nextTwoBytes & 0x1FFF;
const nextByte = readU8(slice);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const transportScramblingControl = nextByte >> 6;
const adaptationFieldControl = (nextByte >> 4) & 0x3;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const continuityCounter = nextByte & 0xF;
return {
payloadUnitStartIndicator,
pid,
adaptationFieldControl,
};
}
async readPacket(pos: number): Promise<TsPacket | null> {
// Code in here is duplicated from readPacketHeader for performance reasons
let slice = this.reader.requestSlice(pos, TS_PACKET_SIZE); let slice = this.reader.requestSlice(pos, TS_PACKET_SIZE);
if (slice instanceof Promise) slice = await slice; if (slice instanceof Promise) slice = await slice;
@@ -473,19 +521,22 @@ export class MpegTsDemuxer extends Demuxer {
payloadUnitStartIndicator, payloadUnitStartIndicator,
pid, pid,
adaptationFieldControl, adaptationFieldControl,
body: readBytes(slice, 184), body: readBytes(slice, TS_PACKET_SIZE - 4),
}; };
} }
} }
type PesPacket = { type PesPacketHeader = {
sectionStartPos: number; sectionStartPos: number;
sectionEndPos: number; sectionEndPos: number | null; // null if the section wasn't read fully
pts: number; pts: number;
};
type PesPacket = PesPacketHeader & {
data: Uint8Array<ArrayBufferLike>; data: Uint8Array<ArrayBufferLike>;
}; };
const readPesPacket = (section: Section): PesPacket | null => { const readPesPacketHeader = (section: Section): PesPacketHeader | null => {
const bitstream = new Bitstream(section.payload); const bitstream = new Bitstream(section.payload);
const startCodePrefix = bitstream.readBits(24); const startCodePrefix = bitstream.readBits(24);
@@ -494,8 +545,7 @@ const readPesPacket = (section: Section): PesPacket | null => {
} }
const streamId = bitstream.readBits(8); const streamId = bitstream.readBits(8);
const pesPacketLength = bitstream.readBits(16); bitstream.skipBits(16);
const BYTES_UNTIL_END_OF_PES_PACKET_LENGTH = 6;
if ( if (
streamId === 0b10111100 // program_stream_map streamId === 0b10111100 // program_stream_map
@@ -514,9 +564,7 @@ const readPesPacket = (section: Section): PesPacket | null => {
const ptsDtsFlags = bitstream.readBits(2); const ptsDtsFlags = bitstream.readBits(2);
bitstream.skipBits(6); bitstream.skipBits(14);
const pesHeaderDataLength = bitstream.readBits(8);
const pesHeaderEndPos = bitstream.pos + 8 * pesHeaderDataLength;
if (ptsDtsFlags !== 0b10 && ptsDtsFlags !== 0b11) { if (ptsDtsFlags !== 0b10 && ptsDtsFlags !== 0b11) {
return null; // Support only timestamped packets return null; // Support only timestamped packets
@@ -534,6 +582,30 @@ const readPesPacket = (section: Section): PesPacket | null => {
return null; // Support only timestamped packets return null; // Support only timestamped packets
} }
return {
sectionStartPos: section.startPos,
sectionEndPos: section.endPos,
pts,
};
};
const readPesPacket = (section: Section): PesPacket | null => {
assert(section.endPos !== null); // Can only read full PES packets from fully read sections
const header = readPesPacketHeader(section);
if (!header) {
return null;
}
const bitstream = new Bitstream(section.payload);
bitstream.skipBits(32);
const pesPacketLength = bitstream.readBits(16);
const BYTES_UNTIL_END_OF_PES_PACKET_LENGTH = 6;
bitstream.skipBits(16);
const pesHeaderDataLength = bitstream.readBits(8);
const pesHeaderEndPos = bitstream.pos + 8 * pesHeaderDataLength;
bitstream.pos = pesHeaderEndPos; bitstream.pos = pesHeaderEndPos;
const bytePos = pesHeaderEndPos / 8; const bytePos = pesHeaderEndPos / 8;
@@ -550,9 +622,7 @@ const readPesPacket = (section: Section): PesPacket | null => {
); );
return { return {
sectionStartPos: section.startPos, ...header,
sectionEndPos: section.endPos,
pts,
data, data,
}; };
}; };
@@ -562,7 +632,7 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
* Reference PES packets, spread throughout the file, to be used to speed up random access and perform * Reference PES packets, spread throughout the file, to be used to speed up random access and perform
* binary search for packets. * binary search for packets.
*/ */
referencePesPackets: PesPacket[] = []; referencePesPackets: PesPacketHeader[] = [];
endReferencePesPacketAdded = false; endReferencePesPacketAdded = false;
readingContexts = new WeakMap<EncodedPacket, PacketReadingContext>(); readingContexts = new WeakMap<EncodedPacket, PacketReadingContext>();
@@ -609,35 +679,35 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
abstract getPacketType(packetData: Uint8Array): PacketType; abstract getPacketType(packetData: Uint8Array): PacketType;
abstract markNextPacket(context: PacketReadingContext): Promise<void>; abstract markNextPacket(context: PacketReadingContext): Promise<void>;
maybeInsertReferencePacket(pesPacket: PesPacket, force: boolean) { maybeInsertReferencePacket(pesPacketHeader: PesPacketHeader, force: boolean) {
const index = binarySearchLessOrEqual(this.referencePesPackets, pesPacket.pts, x => x.pts); const index = binarySearchLessOrEqual(this.referencePesPackets, pesPacketHeader.pts, x => x.pts);
if (index >= 0) { if (index >= 0) {
// Since pts and file position don't necessarily have a monotonic relationship (since pts can go crazy), // Since pts and file position don't necessarily have a monotonic relationship (since pts can go crazy),
// let's see if inserting at the given index would violate the file position order. If so, return. // let's see if inserting at the given index would violate the file position order. If so, return.
const entry = this.referencePesPackets[index]!; const entry = this.referencePesPackets[index]!;
if (pesPacket.sectionStartPos <= entry.sectionStartPos) { if (pesPacketHeader.sectionStartPos <= entry.sectionStartPos) {
return false; return false;
} }
// Too close temporally // Too close temporally
if (!force && pesPacket.pts - entry.pts < TIMESCALE / 2) { if (!force && pesPacketHeader.pts - entry.pts < TIMESCALE / 2) {
return false; return false;
} }
if (index < this.referencePesPackets.length - 1) { if (index < this.referencePesPackets.length - 1) {
const nextEntry = this.referencePesPackets[index + 1]!; const nextEntry = this.referencePesPackets[index + 1]!;
if (nextEntry.sectionStartPos < pesPacket.sectionStartPos) { if (nextEntry.sectionStartPos < pesPacketHeader.sectionStartPos) {
return false; return false;
} }
// Too close temporally // Too close temporally
if (!force && nextEntry.pts - pesPacket.pts < TIMESCALE / 2) { if (!force && nextEntry.pts - pesPacketHeader.pts < TIMESCALE / 2) {
return false; return false;
} }
} }
} }
this.referencePesPackets.splice(index + 1, 0, pesPacket); this.referencePesPackets.splice(index + 1, 0, pesPacketHeader);
return true; return true;
} }
@@ -732,30 +802,30 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
&& !this.endReferencePesPacketAdded; && !this.endReferencePesPacketAdded;
if (needsToLookForLastPacket) { if (needsToLookForLastPacket) {
let currentPos = reader.fileSize! - demuxer.packetStride + demuxer.packetOffset; let currentPos = reader.fileSize! - demuxer.packetStride + demuxer.packetOffset;
let packet = await demuxer.readPacket(currentPos); let packetHeader = await demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
return null; return null;
} }
while (packet.pid !== this.elementaryStream.pid || packet.payloadUnitStartIndicator === 0) { while (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator === 0) {
currentPos -= demuxer.packetStride; currentPos -= demuxer.packetStride;
const previousPacket = await demuxer.readPacket(currentPos); const previousPacketHeader = await demuxer.readPacketHeader(currentPos);
if (!previousPacket) { if (!previousPacketHeader) {
return null; return null;
} }
packet = previousPacket; packetHeader = previousPacketHeader;
} }
const section = await demuxer.readSection(currentPos); const section = await demuxer.readSection(currentPos, false);
assert(section); assert(section);
const pesPacket = readPesPacket(section); const pesPacketHeader = readPesPacketHeader(section);
if (!pesPacket) { if (!pesPacketHeader) {
throw new Error(MISSING_PES_PACKET_ERROR); throw new Error(MISSING_PES_PACKET_ERROR);
} }
this.maybeInsertReferencePacket(pesPacket, true); this.maybeInsertReferencePacket(pesPacketHeader, true);
this.endReferencePesPacketAdded = true; this.endReferencePesPacketAdded = true;
} }
@@ -779,20 +849,20 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
demuxer.packetStride, demuxer.packetStride,
) + demuxer.packetOffset; ) + demuxer.packetOffset;
let currentPos = midpoint; let currentPos = midpoint;
let packet = await demuxer.readPacket(currentPos); let packetHeader = await demuxer.readPacketHeader(currentPos);
assert(packet); assert(packetHeader);
while ( while (
currentPos < nextEntry.sectionStartPos currentPos < nextEntry.sectionStartPos
&& (packet.pid !== this.elementaryStream.pid || packet.payloadUnitStartIndicator === 0) && (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator === 0)
) { ) {
currentPos += demuxer.packetStride; currentPos += demuxer.packetStride;
const previousPacket = await demuxer.readPacket(currentPos); const previousPacketHeader = await demuxer.readPacketHeader(currentPos);
if (!previousPacket) { if (!previousPacketHeader) {
return null; return null;
} }
packet = previousPacket; packetHeader = previousPacketHeader;
} }
if (currentPos >= nextEntry.sectionStartPos) { if (currentPos >= nextEntry.sectionStartPos) {
@@ -800,33 +870,40 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
break; break;
} }
const section = await demuxer.readSection(currentPos); const section = await demuxer.readSection(currentPos, false);
assert(section); assert(section);
const pesPacket = readPesPacket(section); const pesPacketHeader = readPesPacketHeader(section);
if (!pesPacket) { if (!pesPacketHeader) {
throw new Error(MISSING_PES_PACKET_ERROR); throw new Error(MISSING_PES_PACKET_ERROR);
} }
const addedPoint = this.maybeInsertReferencePacket(pesPacket, false); const addedPoint = this.maybeInsertReferencePacket(pesPacketHeader, false);
if (!addedPoint) { if (!addedPoint) {
break; // Should rarely kick break; // Should rarely kick
} }
if (pesPacket.pts <= searchPts) { if (pesPacketHeader.pts <= searchPts) {
// The midpoint packet is to the left of our search timestamp, so continue with the right half now // The midpoint packet is to the left of our search timestamp, so continue with the right half now
currentIndex++; currentIndex++;
} }
} }
let currentPesPacket = this.referencePesPackets[currentIndex]!; let currentPesPacketHeader = this.referencePesPackets[currentIndex]!;
assert(currentPesPacket.pts <= searchPts); assert(currentPesPacketHeader.pts <= searchPts);
/** Stores the best PES packet we've found so far (that meets all required criteria). */ /** Stores the best PES packet we've found so far (that meets all required criteria). */
let bestPesPacket: PesPacket | null = null; let bestPesPacketHeader: PesPacketHeader | null = null;
const pesPacketHasKeyframe = async (pesPacket: PesPacket) => { const pesPacketHasKeyframe = async (sectionStartPos: number) => {
const context = new PacketReadingContext(this, pesPacket, false); const section = await demuxer.readSection(sectionStartPos, true);
assert(section);
assert(section.pid === this.elementaryStream.pid);
const fullPesPacket = readPesPacket(section);
assert(fullPesPacket);
const context = new PacketReadingContext(this, fullPesPacket, false);
await this.markNextPacket(context); await this.markNextPacket(context);
if (!context.suppliedPacket) { if (!context.suppliedPacket) {
@@ -836,62 +913,63 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
return this.getPacketType(context.suppliedPacket.data); return this.getPacketType(context.suppliedPacket.data);
}; };
if (!keyframesOnly || await pesPacketHasKeyframe(currentPesPacket)) { if (!keyframesOnly || await pesPacketHasKeyframe(currentPesPacketHeader.sectionStartPos)) {
bestPesPacket = currentPesPacket; bestPesPacketHeader = currentPesPacketHeader;
} }
const advancedPesPackets = [bestPesPacket]; // "advanced" as in "moved past"
const advancedPesPacketHeaders = [bestPesPacketHeader];
// Starting from the binary search guess, let's now find the moment where the packet timestamps cross the // Starting from the binary search guess, let's now find the moment where the packet timestamps cross the
// search timestamp. This point will then be used as the center around which we search. // search timestamp. This point will then be used as the center around which we search.
outer: outer:
while (true) { while (true) {
let currentPos = currentPesPacket.sectionEndPos; let currentPos = currentPesPacketHeader.sectionStartPos + demuxer.packetStride;
while (true) { while (true) {
const packet = await demuxer.readPacket(currentPos); const packetHeader = await demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
break outer; // End of file break outer; // End of file
} }
if (packet.pid === this.elementaryStream.pid) { if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
break; break;
} }
currentPos += demuxer.packetStride; currentPos += demuxer.packetStride;
} }
const nextSection = await demuxer.readSection(currentPos); const nextSection = await demuxer.readSection(currentPos, false);
if (!nextSection) { if (!nextSection) {
break; break;
} }
const nextPesPacket = readPesPacket(nextSection); const nextPesPacketHeader = readPesPacketHeader(nextSection);
if (!nextPesPacket) { if (!nextPesPacketHeader) {
throw new Error(MISSING_PES_PACKET_ERROR); throw new Error(MISSING_PES_PACKET_ERROR);
} }
if (nextPesPacket.pts > searchPts) { if (nextPesPacketHeader.pts > searchPts) {
// The timestamps cross the search timestamp, stop // The timestamps cross the search timestamp, stop
break; break;
} }
// Collect matching packets we find along the way // Collect matching packets we find along the way
if ( if (
(bestPesPacket === null || bestPesPacket.pts < nextPesPacket.pts) (bestPesPacketHeader === null || bestPesPacketHeader.pts < nextPesPacketHeader.pts)
&& nextPesPacket.pts <= searchPts && nextPesPacketHeader.pts <= searchPts
&& (!keyframesOnly || await pesPacketHasKeyframe(nextPesPacket)) && (!keyframesOnly || await pesPacketHasKeyframe(currentPos))
) { ) {
bestPesPacket = nextPesPacket; bestPesPacketHeader = nextPesPacketHeader;
} }
currentPesPacket = nextPesPacket; currentPesPacketHeader = nextPesPacketHeader;
advancedPesPackets.push(nextPesPacket); advancedPesPacketHeaders.push(nextPesPacketHeader);
if (reader.fileSize === null) { if (reader.fileSize === null) {
// If the file size is undefined, that means that the binary search step is skipped, meaning no // If the file size is undefined, that means that the binary search step is skipped, meaning no
// reference packets are inserted. So, let's instead insert reference packets in the linear search step. // reference packets are inserted. So, let's instead insert reference packets in the linear search step.
this.maybeInsertReferencePacket(nextPesPacket, false); this.maybeInsertReferencePacket(nextPesPacketHeader, false);
} }
} }
@@ -902,8 +980,8 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
// Depending on how long the previous scan went, we might not need to do the full lookbehind, or even none at // Depending on how long the previous scan went, we might not need to do the full lookbehind, or even none at
// all if we're lucky // all if we're lucky
const lookbehindNeeded = Math.max(lookaround - advancedPesPackets.length + 1, 0); const lookbehindNeeded = Math.max(lookaround - advancedPesPacketHeaders.length + 1, 0);
let minPos = advancedPesPackets[0]!.sectionStartPos; let minPos = advancedPesPacketHeaders[0]!.sectionStartPos;
/** Scans `n` contiguous PES packets in succession. */ /** Scans `n` contiguous PES packets in succession. */
const doLinearScan = async (startPos: number, n: number) => { const doLinearScan = async (startPos: number, n: number) => {
@@ -912,36 +990,39 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
outer: outer:
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
while (true) { while (true) {
const packet = await demuxer.readPacket(currentPos); const packetHeader = await demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
break outer; // End of file break outer; // End of file
} }
if (packet.pid === this.elementaryStream.pid) { if (
packetHeader.pid === this.elementaryStream.pid
&& packetHeader.payloadUnitStartIndicator === 1
) {
break; break;
} }
currentPos += demuxer.packetStride; currentPos += demuxer.packetStride;
} }
const section = await demuxer.readSection(currentPos); const section = await demuxer.readSection(currentPos, false);
assert(section); assert(section);
assert(section.pid === this.elementaryStream.pid); assert(section.pid === this.elementaryStream.pid);
const pesPacket = readPesPacket(section); const pesPacketHeader = readPesPacketHeader(section);
if (!pesPacket) { if (!pesPacketHeader) {
throw new Error(MISSING_PES_PACKET_ERROR); throw new Error(MISSING_PES_PACKET_ERROR);
} }
if ( if (
(bestPesPacket === null || bestPesPacket.pts < pesPacket.pts) (bestPesPacketHeader === null || bestPesPacketHeader.pts < pesPacketHeader.pts)
&& pesPacket.pts <= searchPts && pesPacketHeader.pts <= searchPts
&& (!keyframesOnly || await pesPacketHasKeyframe(pesPacket)) && (!keyframesOnly || await pesPacketHasKeyframe(currentPos))
) { ) {
bestPesPacket = pesPacket; bestPesPacketHeader = pesPacketHeader;
} }
currentPos = section.endPos; currentPos += demuxer.packetStride;
} }
}; };
@@ -954,12 +1035,15 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
while (true) { while (true) {
currentPos -= demuxer.packetStride; currentPos -= demuxer.packetStride;
const packet = await demuxer.readPacket(currentPos); const packetHeader = await demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
break outer; break outer;
} }
if (packet.pid === this.elementaryStream.pid && packet.payloadUnitStartIndicator === 1) { if (
packetHeader.pid === this.elementaryStream.pid
&& packetHeader.payloadUnitStartIndicator === 1
) {
break; break;
} }
} }
@@ -971,43 +1055,47 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
} }
// Lookahead // Lookahead
await doLinearScan(currentPesPacket.sectionEndPos, lookaround); await doLinearScan(currentPesPacketHeader.sectionStartPos + demuxer.packetStride, lookaround);
// If we're looking specifically for a keyframe but haven't found one yet, that means we'll need to go left // If we're looking specifically for a keyframe but haven't found one yet, that means we'll need to go left
// until we find one. // until we find one.
if (!bestPesPacket && keyframesOnly) { if (!bestPesPacketHeader && keyframesOnly) {
let currentPos = minPos; let currentPos = minPos;
while (true) { while (true) {
currentPos -= demuxer.packetStride; currentPos -= demuxer.packetStride;
const packet = await demuxer.readPacket(currentPos); const packetHeader = await demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
break; break;
} }
if (packet.pid === this.elementaryStream.pid && packet.payloadUnitStartIndicator === 1) { if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
const section = await demuxer.readSection(currentPos); const section = await demuxer.readSection(currentPos, false);
assert(section); assert(section);
const pesPacket = readPesPacket(section); const pesPacketHeader = readPesPacketHeader(section);
if (!pesPacket) { if (!pesPacketHeader) {
throw new Error(MISSING_PES_PACKET_ERROR); throw new Error(MISSING_PES_PACKET_ERROR);
} }
if (pesPacket.pts <= searchPts && (await pesPacketHasKeyframe(pesPacket))) { if (pesPacketHeader.pts <= searchPts && (await pesPacketHasKeyframe(currentPos))) {
bestPesPacket = pesPacket; bestPesPacketHeader = pesPacketHeader;
break; break;
} }
} }
} }
} }
if (!bestPesPacket) { if (!bestPesPacketHeader) {
// Nothing was found // Nothing was found
return null; return null;
} }
const bestSection = await demuxer.readSection(bestPesPacketHeader.sectionStartPos, true); // Read it in full
let bestPesPacket = readPesPacket(bestSection!);
assert(bestPesPacket);
// Final stage: we found the best PES packet, but that PES packet might contain multiple individual encoded // Final stage: we found the best PES packet, but that PES packet might contain multiple individual encoded
// packets. Or, it might not be the start of an encoded packet, and simply a continuation of a previous one. // packets. Or, it might not be the start of an encoded packet, and simply a continuation of a previous one.
// So, we have one last search to do. // So, we have one last search to do.
@@ -1046,14 +1134,14 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
while (true) { while (true) {
currentPos -= demuxer.packetStride; currentPos -= demuxer.packetStride;
const packet = await demuxer.readPacket(currentPos); const packetHeader = await demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
// Past start of file // Past start of file
return null; return null;
} }
if (packet.pid === this.elementaryStream.pid && packet.payloadUnitStartIndicator === 1) { if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
const section = await demuxer.readSection(currentPos); const section = await demuxer.readSection(currentPos, true);
assert(section); assert(section);
const pesPacket = readPesPacket(section); const pesPacket = readPesPacket(section);
@@ -1392,21 +1480,22 @@ class PacketReadingContext {
} else { } else {
// Find the next PES packet // Find the next PES packet
let currentPos = last(this.pesPackets)!.sectionEndPos; let currentPos = last(this.pesPackets)!.sectionEndPos;
assert(currentPos !== null);
while (true) { while (true) {
const packet = await this.demuxer.readPacket(currentPos); const packetHeader = await this.demuxer.readPacketHeader(currentPos);
if (!packet) { if (!packetHeader) {
return; return;
} }
if (packet.pid === this.pid) { if (packetHeader.pid === this.pid) {
break; break;
} }
currentPos += this.demuxer.packetStride; currentPos += this.demuxer.packetStride;
} }
const nextSection = await this.demuxer.readSection(currentPos); const nextSection = await this.demuxer.readSection(currentPos, true);
if (!nextSection) { if (!nextSection) {
return; return;
} }