mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Adjust Matroska cue point distribution logic, ensure no duplicate timestamps in cue points, fixing infinite recursion issue
This commit is contained in:
@@ -539,14 +539,9 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
// lookup starts sequentially from the start, incrementally summing up all fragment durations. It's sort
|
// lookup starts sequentially from the start, incrementally summing up all fragment durations. It's sort
|
||||||
// of implicit, but it ends up working nicely.
|
// of implicit, but it ends up working nicely.
|
||||||
|
|
||||||
const lookupEntryIndex = binarySearchExact(
|
const lookupEntry = track.fragmentLookupTable.find(x => x.moofOffset === fragment.moofOffset);
|
||||||
track.fragmentLookupTable,
|
if (lookupEntry) {
|
||||||
fragment.moofOffset,
|
|
||||||
x => x.moofOffset,
|
|
||||||
);
|
|
||||||
if (lookupEntryIndex !== -1) {
|
|
||||||
// There's a lookup entry, let's use its timestamp
|
// There's a lookup entry, let's use its timestamp
|
||||||
const lookupEntry = track.fragmentLookupTable[lookupEntryIndex]!;
|
|
||||||
offsetFragmentTrackDataByTimestamp(trackData, lookupEntry.timestamp);
|
offsetFragmentTrackDataByTimestamp(trackData, lookupEntry.timestamp);
|
||||||
} else {
|
} else {
|
||||||
const lastCacheIndex = binarySearchLessOrEqual(
|
const lastCacheIndex = binarySearchLessOrEqual(
|
||||||
@@ -1742,6 +1737,20 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
moofOffset,
|
moofOffset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort by timestamp in case it's not naturally sorted
|
||||||
|
track.fragmentLookupTable.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
|
// Remove multiple entries for the same time
|
||||||
|
for (let i = 0; i < track.fragmentLookupTable.length - 1; i++) {
|
||||||
|
const entry1 = track.fragmentLookupTable[i]!;
|
||||||
|
const entry2 = track.fragmentLookupTable[i + 1]!;
|
||||||
|
|
||||||
|
if (entry1.timestamp === entry2.timestamp) {
|
||||||
|
track.fragmentLookupTable.splice(i + 1, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
case 'moof': {
|
case 'moof': {
|
||||||
@@ -2747,6 +2756,8 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
|
|||||||
// The lookup table entry lied to us! We found a lookup entry but no fragment there that satisfied
|
// The lookup table entry lied to us! We found a lookup entry but no fragment there that satisfied
|
||||||
// the match. In this case, let's search again but using the lookup entry before that.
|
// the match. In this case, let's search again but using the lookup entry before that.
|
||||||
const previousLookupEntry = this.internalTrack.fragmentLookupTable[lookupEntryIndex - 1];
|
const previousLookupEntry = this.internalTrack.fragmentLookupTable[lookupEntryIndex - 1];
|
||||||
|
assert(!previousLookupEntry || previousLookupEntry.timestamp < lookupEntry.timestamp);
|
||||||
|
|
||||||
const newSearchTimestamp = previousLookupEntry?.timestamp ?? -Infinity;
|
const newSearchTimestamp = previousLookupEntry?.timestamp ?? -Infinity;
|
||||||
return this.performFragmentedLookup(
|
return this.performFragmentedLookup(
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -541,53 +541,49 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
// Put default tracks first
|
// Put default tracks first
|
||||||
this.currentSegment.tracks.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
|
this.currentSegment.tracks.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
|
||||||
|
|
||||||
// Sort cue points by cluster position (required for the next algorithm)
|
// Now, let's distribute the cue points to the tracks
|
||||||
this.currentSegment.cuePoints.sort((a, b) => a.clusterPosition - b.clusterPosition);
|
const idToTrack = new Map(this.currentSegment.tracks.map(x => [x.id, x]));
|
||||||
|
|
||||||
// Now, let's distribute the cue points to each track. Ideally, each track has their own cue point, but some
|
|
||||||
// Matroska files may only specify cue points for a single track. In this case, we still wanna use those cue
|
|
||||||
// points for all tracks.
|
|
||||||
const allTrackIds = this.currentSegment.tracks.map(x => x.id);
|
|
||||||
const remainingTrackIds = new Set<number>();
|
|
||||||
let lastClusterPosition: number | null = null;
|
|
||||||
let lastCuePoint: CuePoint | null = null;
|
|
||||||
|
|
||||||
|
// Assign cue points to their respective tracks
|
||||||
for (const cuePoint of this.currentSegment.cuePoints) {
|
for (const cuePoint of this.currentSegment.cuePoints) {
|
||||||
if (cuePoint.clusterPosition !== lastClusterPosition) {
|
const track = idToTrack.get(cuePoint.trackId);
|
||||||
for (const id of remainingTrackIds) {
|
if (track) {
|
||||||
// These tracks didn't receive a cue point for the last cluster, so let's give them one
|
track.cuePoints.push(cuePoint);
|
||||||
assert(lastCuePoint);
|
|
||||||
const track = this.currentSegment.tracks.find(x => x.id === id)!;
|
|
||||||
track.cuePoints.push(lastCuePoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const id of allTrackIds) {
|
|
||||||
remainingTrackIds.add(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCuePoint = cuePoint;
|
|
||||||
|
|
||||||
if (!remainingTrackIds.has(cuePoint.trackId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const track = this.currentSegment.tracks.find(x => x.id === cuePoint.trackId)!;
|
|
||||||
track.cuePoints.push(cuePoint);
|
|
||||||
|
|
||||||
remainingTrackIds.delete(cuePoint.trackId);
|
|
||||||
lastClusterPosition = cuePoint.clusterPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const id of remainingTrackIds) {
|
|
||||||
assert(lastCuePoint);
|
|
||||||
const track = this.currentSegment.tracks.find(x => x.id === id)!;
|
|
||||||
track.cuePoints.push(lastCuePoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const track of this.currentSegment.tracks) {
|
for (const track of this.currentSegment.tracks) {
|
||||||
// Sort cue points by time
|
// Sort cue points by time
|
||||||
track.cuePoints.sort((a, b) => a.time - b.time);
|
track.cuePoints.sort((a, b) => a.time - b.time);
|
||||||
|
|
||||||
|
// Remove multiple cue points for the same time
|
||||||
|
for (let i = 0; i < track.cuePoints.length - 1; i++) {
|
||||||
|
const cuePoint1 = track.cuePoints[i]!;
|
||||||
|
const cuePoint2 = track.cuePoints[i + 1]!;
|
||||||
|
|
||||||
|
if (cuePoint1.time === cuePoint2.time) {
|
||||||
|
track.cuePoints.splice(i + 1, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let trackWithMostCuePoints: InternalTrack | null = null;
|
||||||
|
let maxCuePointCount = -Infinity;
|
||||||
|
for (const track of this.currentSegment.tracks) {
|
||||||
|
if (track.cuePoints.length > maxCuePointCount) {
|
||||||
|
maxCuePointCount = track.cuePoints.length;
|
||||||
|
trackWithMostCuePoints = track;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For every track that has received 0 cue points (can happen, often only the video track receives cue points),
|
||||||
|
// we still want to have better seeking. Therefore, let's give it the cue points of the track with the most cue
|
||||||
|
// points, which should provide us with the most fine-grained seeking.
|
||||||
|
for (const track of this.currentSegment.tracks) {
|
||||||
|
if (track.cuePoints.length === 0) {
|
||||||
|
track.cuePoints = trackWithMostCuePoints!.cuePoints;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentSegment = null;
|
this.currentSegment = null;
|
||||||
@@ -2239,6 +2235,8 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
|||||||
// The cue point lied to us! We found a cue point but no cluster there that satisfied the match. In this
|
// The cue point lied to us! We found a cue point but no cluster there that satisfied the match. In this
|
||||||
// case, let's search again but using the cue point before that.
|
// case, let's search again but using the cue point before that.
|
||||||
const previousCuePoint = this.internalTrack.cuePoints[cuePointIndex - 1];
|
const previousCuePoint = this.internalTrack.cuePoints[cuePointIndex - 1];
|
||||||
|
assert(!previousCuePoint || previousCuePoint.time < cuePoint.time);
|
||||||
|
|
||||||
const newSearchTimestamp = previousCuePoint?.time ?? -Infinity;
|
const newSearchTimestamp = previousCuePoint?.time ?? -Infinity;
|
||||||
return this.performClusterLookup(null, getMatchInCluster, newSearchTimestamp, latestTimestamp, options);
|
return this.performClusterLookup(null, getMatchInCluster, newSearchTimestamp, latestTimestamp, options);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user