mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Expand packet.clone(), make conversion start timestamp default to the input's start timestamp
This commit is contained in:
@@ -176,7 +176,7 @@ Negative sequence numbers mean the packet's ordering is undefined. When creating
|
|||||||
|
|
||||||
### Cloning packets
|
### Cloning packets
|
||||||
|
|
||||||
Use the `clone` method to create a new packet from an existing packet. While doing so, you can change its timestamp and duration.
|
Use the `clone` method to create a new packet from an existing packet. While doing so, you can partially change its data.
|
||||||
```ts
|
```ts
|
||||||
// Creates a clone identical to the original:
|
// Creates a clone identical to the original:
|
||||||
packet.clone();
|
packet.clone();
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const compressFile = async (resource: File | string) => {
|
|||||||
let progress = 0;
|
let progress = 0;
|
||||||
currentConversion.onProgress = newProgress => progress = newProgress;
|
currentConversion.onProgress = newProgress => progress = newProgress;
|
||||||
|
|
||||||
const fileDuration = await input.computeDuration();
|
const fileDuration = (await input.computeDuration()) - (await input.getFirstTimestamp());
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
const updateProgress = () => {
|
const updateProgress = () => {
|
||||||
|
|||||||
+36
-23
@@ -84,7 +84,7 @@ export type ConversionOptions = {
|
|||||||
trim?: {
|
trim?: {
|
||||||
/**
|
/**
|
||||||
* The time in the input file in seconds at which the output file should start. Must be less than `end`.
|
* The time in the input file in seconds at which the output file should start. Must be less than `end`.
|
||||||
* Defaults to 0 when omitted.
|
* When omitted, defaults to the start timestamp of the input or to 0, whichever is higher.
|
||||||
*/
|
*/
|
||||||
start?: number;
|
start?: number;
|
||||||
/**
|
/**
|
||||||
@@ -462,9 +462,9 @@ export class Conversion {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_options: ConversionOptions;
|
_options: ConversionOptions;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_startTimestamp: number;
|
_startTimestamp!: number;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_endTimestamp: number;
|
_endTimestamp!: number;
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_addedCounts: Record<TrackType, number> = {
|
_addedCounts: Record<TrackType, number> = {
|
||||||
@@ -592,9 +592,6 @@ export class Conversion {
|
|||||||
this.input = options.input;
|
this.input = options.input;
|
||||||
this.output = options.output;
|
this.output = options.output;
|
||||||
|
|
||||||
this._startTimestamp = options.trim?.start ?? 0;
|
|
||||||
this._endTimestamp = options.trim?.end ?? Infinity;
|
|
||||||
|
|
||||||
const { promise: started, resolve: start } = promiseWithResolvers();
|
const { promise: started, resolve: start } = promiseWithResolvers();
|
||||||
this._started = started;
|
this._started = started;
|
||||||
this._start = start;
|
this._start = start;
|
||||||
@@ -602,6 +599,14 @@ export class Conversion {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _init() {
|
async _init() {
|
||||||
|
this._startTimestamp = this._options.trim?.start ?? Math.max(
|
||||||
|
await this.input.getFirstTimestamp(),
|
||||||
|
// Samples can also have negative timestamps, but the meaning typically is "don't present me", so let's cut
|
||||||
|
// those out by default.
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
this._endTimestamp = this._options.trim?.end ?? Infinity;
|
||||||
|
|
||||||
const inputTracks = await this.input.getTracks();
|
const inputTracks = await this.input.getTracks();
|
||||||
const outputTrackCounts = this.output.format.getSupportedTrackCounts();
|
const outputTrackCounts = this.output.format.getSupportedTrackCounts();
|
||||||
|
|
||||||
@@ -900,8 +905,7 @@ export class Conversion {
|
|||||||
|
|
||||||
const firstTimestamp = await track.getFirstTimestamp();
|
const firstTimestamp = await track.getFirstTimestamp();
|
||||||
const needsTranscode = !!trackOptions.forceTranscode
|
const needsTranscode = !!trackOptions.forceTranscode
|
||||||
|| this._startTimestamp > 0
|
|| firstTimestamp < this._startTimestamp
|
||||||
|| firstTimestamp < 0
|
|
||||||
|| !!trackOptions.frameRate
|
|| !!trackOptions.frameRate
|
||||||
|| trackOptions.keyFrameInterval !== undefined
|
|| trackOptions.keyFrameInterval !== undefined
|
||||||
|| trackOptions.process !== undefined;
|
|| trackOptions.process !== undefined;
|
||||||
@@ -943,17 +947,19 @@ export class Conversion {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alpha === 'discard') {
|
const modifiedPacket = packet.clone({
|
||||||
// Feels hacky given that the rest of the packet is readonly. But, works for now.
|
timestamp: packet.timestamp - this._startTimestamp,
|
||||||
delete packet.sideData.alpha;
|
sideData: alpha === 'discard'
|
||||||
delete packet.sideData.alphaByteLength;
|
? {} // Remove alpha side data
|
||||||
}
|
: packet.sideData,
|
||||||
|
});
|
||||||
|
assert(modifiedPacket.timestamp >= 0);
|
||||||
|
|
||||||
this._reportProgress(track.id, packet.timestamp);
|
this._reportProgress(track.id, modifiedPacket.timestamp);
|
||||||
await source.add(packet, meta);
|
await source.add(modifiedPacket, meta);
|
||||||
|
|
||||||
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
|
if (this._synchronizer.shouldWait(track.id, modifiedPacket.timestamp)) {
|
||||||
await this._synchronizer.wait(packet.timestamp);
|
await this._synchronizer.wait(modifiedPacket.timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1314,8 +1320,7 @@ export class Conversion {
|
|||||||
let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
|
let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
|
||||||
let needsResample = numberOfChannels !== originalNumberOfChannels
|
let needsResample = numberOfChannels !== originalNumberOfChannels
|
||||||
|| sampleRate !== originalSampleRate
|
|| sampleRate !== originalSampleRate
|
||||||
|| this._startTimestamp > 0
|
|| firstTimestamp < this._startTimestamp;
|
||||||
|| firstTimestamp < 0;
|
|
||||||
|
|
||||||
let audioCodecs = this.output.format.getSupportedAudioCodecs();
|
let audioCodecs = this.output.format.getSupportedAudioCodecs();
|
||||||
if (
|
if (
|
||||||
@@ -1346,11 +1351,16 @@ export class Conversion {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._reportProgress(track.id, packet.timestamp);
|
const modifiedPacket = packet.clone({
|
||||||
await source.add(packet, meta);
|
timestamp: packet.timestamp - this._startTimestamp,
|
||||||
|
});
|
||||||
|
assert(modifiedPacket.timestamp >= 0);
|
||||||
|
|
||||||
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
|
this._reportProgress(track.id, modifiedPacket.timestamp);
|
||||||
await this._synchronizer.wait(packet.timestamp);
|
await source.add(modifiedPacket, meta);
|
||||||
|
|
||||||
|
if (this._synchronizer.shouldWait(track.id, modifiedPacket.timestamp)) {
|
||||||
|
await this._synchronizer.wait(modifiedPacket.timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1449,6 +1459,9 @@ export class Conversion {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Offset the timestamp as needed
|
||||||
|
sample.setTimestamp(sample.timestamp - this._startTimestamp);
|
||||||
|
|
||||||
await this._registerAudioSample(track, trackOptions, source, sample);
|
await this._registerAudioSample(track, trackOptions, source, sample);
|
||||||
sample.close();
|
sample.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,20 +228,13 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
|
|
||||||
switch (streamType) {
|
switch (streamType) {
|
||||||
case MpegTsStreamType.MP3_MPEG1:
|
case MpegTsStreamType.MP3_MPEG1:
|
||||||
case MpegTsStreamType.MP3_MPEG2: {
|
case MpegTsStreamType.MP3_MPEG2:
|
||||||
info = {
|
|
||||||
type: 'audio',
|
|
||||||
codec: 'mp3',
|
|
||||||
aacCodecInfo: null,
|
|
||||||
numberOfChannels: -1,
|
|
||||||
sampleRate: -1,
|
|
||||||
};
|
|
||||||
}; break;
|
|
||||||
|
|
||||||
case MpegTsStreamType.AAC: {
|
case MpegTsStreamType.AAC: {
|
||||||
|
const codec = streamType === MpegTsStreamType.AAC ? 'aac' : 'mp3';
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
codec: 'aac',
|
codec,
|
||||||
aacCodecInfo: null,
|
aacCodecInfo: null,
|
||||||
numberOfChannels: -1,
|
numberOfChannels: -1,
|
||||||
sampleRate: -1,
|
sampleRate: -1,
|
||||||
@@ -268,6 +261,11 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
reorderSize: -1,
|
reorderSize: -1,
|
||||||
};
|
};
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// If we don't recognize the codec, we don't surface the track at all. This is because
|
||||||
|
// we can't determine its metadata and also have no idea how to packetize its data.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
|
|||||||
+25
-5
@@ -234,31 +234,51 @@ export class EncodedPacket {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clones this packet while optionally updating timing information. */
|
/** Clones this packet while optionally modifying the new packet's data. */
|
||||||
clone(options?: {
|
clone(options?: {
|
||||||
|
/** The data of the cloned packet. */
|
||||||
|
data?: Uint8Array;
|
||||||
|
/** The type of the cloned packet. */
|
||||||
|
type?: PacketType;
|
||||||
/** The timestamp of the cloned packet in seconds. */
|
/** The timestamp of the cloned packet in seconds. */
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
/** The duration of the cloned packet in seconds. */
|
/** The duration of the cloned packet in seconds. */
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
/** The sequence number of the cloned packet. */
|
||||||
|
sequenceNumber?: number;
|
||||||
|
/** The side data of the cloned packet. */
|
||||||
|
sideData?: EncodedPacketSideData;
|
||||||
}): EncodedPacket {
|
}): EncodedPacket {
|
||||||
if (options !== undefined && (typeof options !== 'object' || options === null)) {
|
if (options !== undefined && (typeof options !== 'object' || options === null)) {
|
||||||
throw new TypeError('options, when provided, must be an object.');
|
throw new TypeError('options, when provided, must be an object.');
|
||||||
}
|
}
|
||||||
|
if (options?.data !== undefined && !(options.data instanceof Uint8Array)) {
|
||||||
|
throw new TypeError('options.data, when provided, must be a Uint8Array.');
|
||||||
|
}
|
||||||
|
if (options?.type !== undefined && options.type !== 'key' && options.type !== 'delta') {
|
||||||
|
throw new TypeError('options.type, when provided, must be either "key" or "delta".');
|
||||||
|
}
|
||||||
if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
|
if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
|
||||||
throw new TypeError('options.timestamp, when provided, must be a number.');
|
throw new TypeError('options.timestamp, when provided, must be a number.');
|
||||||
}
|
}
|
||||||
if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
|
if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
|
||||||
throw new TypeError('options.duration, when provided, must be a number.');
|
throw new TypeError('options.duration, when provided, must be a number.');
|
||||||
}
|
}
|
||||||
|
if (options?.sequenceNumber !== undefined && !Number.isFinite(options.sequenceNumber)) {
|
||||||
|
throw new TypeError('options.sequenceNumber, when provided, must be a number.');
|
||||||
|
}
|
||||||
|
if (options?.sideData !== undefined && (typeof options.sideData !== 'object' || options.sideData === null)) {
|
||||||
|
throw new TypeError('options.sideData, when provided, must be an object.');
|
||||||
|
}
|
||||||
|
|
||||||
return new EncodedPacket(
|
return new EncodedPacket(
|
||||||
this.data,
|
options?.data ?? this.data,
|
||||||
this.type,
|
options?.type ?? this.type,
|
||||||
options?.timestamp ?? this.timestamp,
|
options?.timestamp ?? this.timestamp,
|
||||||
options?.duration ?? this.duration,
|
options?.duration ?? this.duration,
|
||||||
this.sequenceNumber,
|
options?.sequenceNumber ?? this.sequenceNumber,
|
||||||
this.byteLength,
|
this.byteLength,
|
||||||
this.sideData,
|
options?.sideData ?? this.sideData,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user