mediabunny 1.53.0 → 1.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundles/mediabunny.cjs +279 -1
- package/dist/bundles/mediabunny.min.cjs +10 -10
- package/dist/bundles/mediabunny.min.mjs +10 -10
- package/dist/bundles/mediabunny.mjs +279 -1
- package/dist/bundles/mediabunny.node.cjs +279 -1
- package/dist/mediabunny.d.ts +75 -0
- package/dist/modules/src/index.d.ts +1 -1
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/input-track.d.ts +80 -0
- package/dist/modules/src/input-track.d.ts.map +1 -1
- package/dist/modules/src/input-track.js +300 -0
- package/dist/modules/src/target.d.ts.map +1 -1
- package/dist/modules/src/target.js +11 -2
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/input-track.ts +479 -0
- package/src/target.ts +12 -2
|
@@ -24778,6 +24778,100 @@ var Mediabunny = (() => {
|
|
|
24778
24778
|
assert(decoderConfig);
|
|
24779
24779
|
return determineVideoPacketType(codec, decoderConfig, packet.data);
|
|
24780
24780
|
}
|
|
24781
|
+
/**
|
|
24782
|
+
* Computes frame rate metrics for this video track, i.e. estimates the video's frame rate. Frame rate is never
|
|
24783
|
+
* determined from file metadata (which is unreliable) but is always deduced directly from the actual frame
|
|
24784
|
+
* timestamps.
|
|
24785
|
+
*/
|
|
24786
|
+
async computeFrameRateMetrics(options = {}) {
|
|
24787
|
+
if (!options || typeof options !== "object") {
|
|
24788
|
+
throw new TypeError("options must be an object.");
|
|
24789
|
+
}
|
|
24790
|
+
if (options.targetPacketCount !== void 0 && (!Number.isFinite(options.targetPacketCount) || options.targetPacketCount < 0)) {
|
|
24791
|
+
throw new TypeError("options.targetPacketCount must be a non-negative number.");
|
|
24792
|
+
}
|
|
24793
|
+
const timeResolution = await this.getTimeResolution();
|
|
24794
|
+
const targetPacketCount = options.targetPacketCount ?? 256;
|
|
24795
|
+
const sink = new EncodedPacketSink(this);
|
|
24796
|
+
const timestamps = [];
|
|
24797
|
+
let maxTimestamp = -Infinity;
|
|
24798
|
+
let probedPacketCount = 0;
|
|
24799
|
+
for await (const packet of sink.packets(void 0, void 0, { metadataOnly: true })) {
|
|
24800
|
+
if (timestamps.length >= targetPacketCount && packet.timestamp >= maxTimestamp) {
|
|
24801
|
+
break;
|
|
24802
|
+
}
|
|
24803
|
+
timestamps.push(packet.timestamp);
|
|
24804
|
+
maxTimestamp = Math.max(maxTimestamp, packet.timestamp);
|
|
24805
|
+
probedPacketCount++;
|
|
24806
|
+
}
|
|
24807
|
+
const ticks = new Float64Array(timestamps.length);
|
|
24808
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
24809
|
+
ticks[i] = Math.round(timestamps[i] * timeResolution);
|
|
24810
|
+
}
|
|
24811
|
+
ticks.sort();
|
|
24812
|
+
let n = 1;
|
|
24813
|
+
for (let i = 1; i < ticks.length; i++) {
|
|
24814
|
+
if (ticks[i] !== ticks[n - 1]) {
|
|
24815
|
+
ticks[n++] = ticks[i];
|
|
24816
|
+
}
|
|
24817
|
+
}
|
|
24818
|
+
if (n < 2) {
|
|
24819
|
+
return {
|
|
24820
|
+
underlyingFrameRate: null,
|
|
24821
|
+
bestGuessFrameRate: timeResolution,
|
|
24822
|
+
minFrameRate: timeResolution,
|
|
24823
|
+
maxFrameRate: timeResolution,
|
|
24824
|
+
averageFrameRate: timeResolution,
|
|
24825
|
+
medianFrameRate: timeResolution,
|
|
24826
|
+
frameRateIsConstant: true,
|
|
24827
|
+
probedPacketCount
|
|
24828
|
+
};
|
|
24829
|
+
}
|
|
24830
|
+
const activeTicks = ticks.subarray(0, n);
|
|
24831
|
+
const underlyingFrameRate = findUnderlyingFrameRate(activeTicks, timeResolution);
|
|
24832
|
+
const unitRate = underlyingFrameRate ?? timeResolution;
|
|
24833
|
+
const ticksPerFrame = underlyingFrameRate !== null ? timeResolution / underlyingFrameRate : null;
|
|
24834
|
+
const histogram = /* @__PURE__ */ new Map();
|
|
24835
|
+
let minDifference = Infinity;
|
|
24836
|
+
let maxDifference = -Infinity;
|
|
24837
|
+
let totalDifference = 0;
|
|
24838
|
+
for (let i = 1; i < n; i++) {
|
|
24839
|
+
const tickDifference = activeTicks[i] - activeTicks[i - 1];
|
|
24840
|
+
const difference = ticksPerFrame !== null ? Math.max(1, Math.round(tickDifference / ticksPerFrame)) : tickDifference;
|
|
24841
|
+
histogram.set(difference, (histogram.get(difference) ?? 0) + 1);
|
|
24842
|
+
minDifference = Math.min(minDifference, difference);
|
|
24843
|
+
maxDifference = Math.max(maxDifference, difference);
|
|
24844
|
+
totalDifference += difference;
|
|
24845
|
+
}
|
|
24846
|
+
const differenceCount = n - 1;
|
|
24847
|
+
const sortedDifferences = [...histogram.keys()].sort((a, b) => a - b);
|
|
24848
|
+
const middleA = differenceCount - 1 >> 1;
|
|
24849
|
+
const middleB = differenceCount >> 1;
|
|
24850
|
+
let medianDifferenceA = 0;
|
|
24851
|
+
let medianDifferenceB = 0;
|
|
24852
|
+
let cumulativeCount = 0;
|
|
24853
|
+
for (const difference of sortedDifferences) {
|
|
24854
|
+
cumulativeCount += histogram.get(difference);
|
|
24855
|
+
if (medianDifferenceA === 0 && cumulativeCount > middleA) {
|
|
24856
|
+
medianDifferenceA = difference;
|
|
24857
|
+
}
|
|
24858
|
+
if (cumulativeCount > middleB) {
|
|
24859
|
+
medianDifferenceB = difference;
|
|
24860
|
+
break;
|
|
24861
|
+
}
|
|
24862
|
+
}
|
|
24863
|
+
const medianFrameRate = (unitRate / medianDifferenceA + unitRate / medianDifferenceB) / 2;
|
|
24864
|
+
return {
|
|
24865
|
+
underlyingFrameRate,
|
|
24866
|
+
bestGuessFrameRate: underlyingFrameRate !== null ? underlyingFrameRate : getBestGuessFrameRate(medianFrameRate),
|
|
24867
|
+
minFrameRate: unitRate / maxDifference,
|
|
24868
|
+
maxFrameRate: unitRate / minDifference,
|
|
24869
|
+
averageFrameRate: unitRate * differenceCount / totalDifference,
|
|
24870
|
+
medianFrameRate,
|
|
24871
|
+
frameRateIsConstant: underlyingFrameRate !== null && minDifference === 1 && maxDifference === 1,
|
|
24872
|
+
probedPacketCount
|
|
24873
|
+
};
|
|
24874
|
+
}
|
|
24781
24875
|
};
|
|
24782
24876
|
var InputAudioTrack = class extends InputTrack {
|
|
24783
24877
|
/** @internal */
|
|
@@ -24995,6 +25089,183 @@ var Mediabunny = (() => {
|
|
|
24995
25089
|
return 0;
|
|
24996
25090
|
}).map((x) => x.track);
|
|
24997
25091
|
};
|
|
25092
|
+
var findUnderlyingFrameRate = (ticks, resolution) => {
|
|
25093
|
+
const MAX_DENOMINATOR = 1e6;
|
|
25094
|
+
const MIN_INLIER_RATIO = 0.98;
|
|
25095
|
+
const DELTA_TOLERANCE = 1 + 1e-9;
|
|
25096
|
+
const MAX_EFFECTIVE_FRAME_SPAN = 1e3;
|
|
25097
|
+
const KNOWN_FRAME_RATES = [
|
|
25098
|
+
12,
|
|
25099
|
+
15,
|
|
25100
|
+
20,
|
|
25101
|
+
24e3 / 1001,
|
|
25102
|
+
24,
|
|
25103
|
+
25,
|
|
25104
|
+
3e4 / 1001,
|
|
25105
|
+
30,
|
|
25106
|
+
48,
|
|
25107
|
+
50,
|
|
25108
|
+
6e4 / 1001,
|
|
25109
|
+
60,
|
|
25110
|
+
100,
|
|
25111
|
+
12e4 / 1001,
|
|
25112
|
+
120,
|
|
25113
|
+
144,
|
|
25114
|
+
240
|
|
25115
|
+
];
|
|
25116
|
+
if (ticks.length < 2) {
|
|
25117
|
+
return null;
|
|
25118
|
+
}
|
|
25119
|
+
const gaps = new Float64Array(ticks.length - 1);
|
|
25120
|
+
for (let i = 1; i < ticks.length; i++) {
|
|
25121
|
+
const gap = ticks[i] - ticks[i - 1];
|
|
25122
|
+
if (!(gap > 0)) {
|
|
25123
|
+
return null;
|
|
25124
|
+
}
|
|
25125
|
+
gaps[i - 1] = gap;
|
|
25126
|
+
}
|
|
25127
|
+
const sortedGaps = gaps.slice();
|
|
25128
|
+
sortedGaps.sort();
|
|
25129
|
+
let period = sortedGaps[Math.floor(sortedGaps.length * 0.05)];
|
|
25130
|
+
for (let iteration = 0; iteration < 6; iteration++) {
|
|
25131
|
+
let totalTicks2 = 0;
|
|
25132
|
+
let totalFrames2 = 0;
|
|
25133
|
+
for (const gap of gaps) {
|
|
25134
|
+
const multiple = Math.max(1, Math.round(gap / period));
|
|
25135
|
+
if (Math.abs(gap - multiple * period) >= DELTA_TOLERANCE) {
|
|
25136
|
+
continue;
|
|
25137
|
+
}
|
|
25138
|
+
totalTicks2 += gap;
|
|
25139
|
+
totalFrames2 += multiple;
|
|
25140
|
+
}
|
|
25141
|
+
if (totalFrames2 === 0) {
|
|
25142
|
+
return null;
|
|
25143
|
+
}
|
|
25144
|
+
const refinedPeriod = totalTicks2 / totalFrames2;
|
|
25145
|
+
if (Math.abs(refinedPeriod - period) <= 1e-12 * Math.max(1, period)) {
|
|
25146
|
+
period = refinedPeriod;
|
|
25147
|
+
break;
|
|
25148
|
+
}
|
|
25149
|
+
period = refinedPeriod;
|
|
25150
|
+
}
|
|
25151
|
+
let inlierCount = 0;
|
|
25152
|
+
let totalTicks = 0;
|
|
25153
|
+
let totalFrames = 0;
|
|
25154
|
+
for (const gap of gaps) {
|
|
25155
|
+
const multiple = Math.max(1, Math.round(gap / period));
|
|
25156
|
+
if (Math.abs(gap - multiple * period) >= DELTA_TOLERANCE) {
|
|
25157
|
+
continue;
|
|
25158
|
+
}
|
|
25159
|
+
inlierCount++;
|
|
25160
|
+
totalTicks += gap;
|
|
25161
|
+
totalFrames += multiple;
|
|
25162
|
+
}
|
|
25163
|
+
if (inlierCount / gaps.length < MIN_INLIER_RATIO) {
|
|
25164
|
+
return null;
|
|
25165
|
+
}
|
|
25166
|
+
period = totalTicks / totalFrames;
|
|
25167
|
+
const uncertainty = 1 / Math.min(
|
|
25168
|
+
totalFrames,
|
|
25169
|
+
MAX_EFFECTIVE_FRAME_SPAN
|
|
25170
|
+
);
|
|
25171
|
+
const periodLo = Math.max(Number.EPSILON, period - uncertainty);
|
|
25172
|
+
const periodHi = period + uncertainty;
|
|
25173
|
+
const fpsLo = resolution / periodHi;
|
|
25174
|
+
const fpsHi = resolution / periodLo;
|
|
25175
|
+
const fittedFps = resolution / period;
|
|
25176
|
+
let fps = null;
|
|
25177
|
+
let bestKnownError = Infinity;
|
|
25178
|
+
for (const candidate of KNOWN_FRAME_RATES) {
|
|
25179
|
+
if (candidate < fpsLo || candidate > fpsHi) {
|
|
25180
|
+
continue;
|
|
25181
|
+
}
|
|
25182
|
+
const error = Math.abs(candidate / fittedFps - 1);
|
|
25183
|
+
if (error < bestKnownError) {
|
|
25184
|
+
fps = candidate;
|
|
25185
|
+
bestKnownError = error;
|
|
25186
|
+
}
|
|
25187
|
+
}
|
|
25188
|
+
if (fps === null) {
|
|
25189
|
+
const periodFraction = simplestFractionBetween(
|
|
25190
|
+
periodLo,
|
|
25191
|
+
periodHi,
|
|
25192
|
+
MAX_DENOMINATOR
|
|
25193
|
+
);
|
|
25194
|
+
const fpsFraction = simplestFractionBetween(
|
|
25195
|
+
fpsLo,
|
|
25196
|
+
fpsHi,
|
|
25197
|
+
MAX_DENOMINATOR
|
|
25198
|
+
);
|
|
25199
|
+
if (fpsFraction && (!periodFraction || fpsFraction.den < periodFraction.den || fpsFraction.den === periodFraction.den && fpsFraction.num <= periodFraction.num)) {
|
|
25200
|
+
fps = fpsFraction.num / fpsFraction.den;
|
|
25201
|
+
} else if (periodFraction) {
|
|
25202
|
+
fps = resolution * periodFraction.den / periodFraction.num;
|
|
25203
|
+
} else {
|
|
25204
|
+
return null;
|
|
25205
|
+
}
|
|
25206
|
+
}
|
|
25207
|
+
const finalPeriod = resolution / fps;
|
|
25208
|
+
let finalInlierCount = 0;
|
|
25209
|
+
for (const gap of gaps) {
|
|
25210
|
+
const multiple = Math.max(1, Math.round(gap / finalPeriod));
|
|
25211
|
+
if (Math.abs(gap - multiple * finalPeriod) < DELTA_TOLERANCE) {
|
|
25212
|
+
finalInlierCount++;
|
|
25213
|
+
}
|
|
25214
|
+
}
|
|
25215
|
+
if (finalInlierCount / gaps.length < MIN_INLIER_RATIO) {
|
|
25216
|
+
return null;
|
|
25217
|
+
}
|
|
25218
|
+
return fps;
|
|
25219
|
+
};
|
|
25220
|
+
var simplestFractionBetween = (lo, hi, maxDenominator) => {
|
|
25221
|
+
for (let den = 1; den <= maxDenominator; den++) {
|
|
25222
|
+
const num = Math.floor(lo * den) + 1;
|
|
25223
|
+
if (num / den < hi) {
|
|
25224
|
+
return simplifyRational({ num, den });
|
|
25225
|
+
}
|
|
25226
|
+
}
|
|
25227
|
+
return null;
|
|
25228
|
+
};
|
|
25229
|
+
var getBestGuessFrameRate = (frameRate) => {
|
|
25230
|
+
const SPECIAL_FRAME_RATES = [
|
|
25231
|
+
24 / 1.001,
|
|
25232
|
+
30 / 1.001,
|
|
25233
|
+
60 / 1.001,
|
|
25234
|
+
120 / 1.001
|
|
25235
|
+
];
|
|
25236
|
+
const COMMON_FRAME_RATES = [
|
|
25237
|
+
12,
|
|
25238
|
+
15,
|
|
25239
|
+
20,
|
|
25240
|
+
24,
|
|
25241
|
+
25,
|
|
25242
|
+
30,
|
|
25243
|
+
48,
|
|
25244
|
+
50,
|
|
25245
|
+
60,
|
|
25246
|
+
100,
|
|
25247
|
+
120,
|
|
25248
|
+
144,
|
|
25249
|
+
240
|
|
25250
|
+
];
|
|
25251
|
+
const SPECIAL_TOLERANCE = 5e-4;
|
|
25252
|
+
const COMMON_TOLERANCE = 0.025;
|
|
25253
|
+
for (const candidate of SPECIAL_FRAME_RATES) {
|
|
25254
|
+
if (Math.abs(candidate / frameRate - 1) <= SPECIAL_TOLERANCE) {
|
|
25255
|
+
return candidate;
|
|
25256
|
+
}
|
|
25257
|
+
}
|
|
25258
|
+
let best = frameRate;
|
|
25259
|
+
let bestError = Infinity;
|
|
25260
|
+
for (const candidate of COMMON_FRAME_RATES) {
|
|
25261
|
+
const error = Math.abs(candidate / frameRate - 1);
|
|
25262
|
+
if (error <= COMMON_TOLERANCE && error < bestError) {
|
|
25263
|
+
best = candidate;
|
|
25264
|
+
bestError = error;
|
|
25265
|
+
}
|
|
25266
|
+
}
|
|
25267
|
+
return best;
|
|
25268
|
+
};
|
|
24998
25269
|
|
|
24999
25270
|
// src/input.ts
|
|
25000
25271
|
polyfillSymbolDispose();
|
|
@@ -29209,9 +29480,16 @@ var Mediabunny = (() => {
|
|
|
29209
29480
|
if (this._monotonicity === true && position !== this._lastFlushEnd) {
|
|
29210
29481
|
throw new Error("Internal error: Monotonicity violation.");
|
|
29211
29482
|
}
|
|
29483
|
+
const isPartialView = section.start !== 0 || section.end !== chunk.data.byteLength;
|
|
29484
|
+
let data;
|
|
29485
|
+
if (isPartialView && isWebKit()) {
|
|
29486
|
+
data = chunk.data.slice(section.start, section.end);
|
|
29487
|
+
} else {
|
|
29488
|
+
data = chunk.data.subarray(section.start, section.end);
|
|
29489
|
+
}
|
|
29212
29490
|
void this._streamWriter.write({
|
|
29213
29491
|
type: "write",
|
|
29214
|
-
data
|
|
29492
|
+
data,
|
|
29215
29493
|
position
|
|
29216
29494
|
}).catch((error) => {
|
|
29217
29495
|
this._writeError ??= error;
|