mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-10 17:24:06 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d949d1eadb | ||
|
|
11970675c6 | ||
|
|
d6c223b0f6 | ||
|
|
5b703a1c78 | ||
|
|
3dd7a9cb58 | ||
|
|
7b3f5dad2a | ||
|
|
dceb67a065 | ||
|
|
dd39b8b2ca | ||
|
|
71963cbd92 | ||
|
|
816bb020fb |
@@ -5,6 +5,7 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -14,7 +15,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -27,12 +28,15 @@ jobs:
|
|||||||
git push origin release
|
git push origin release
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
registry-url: https://registry.npmjs.org
|
registry-url: https://registry.npmjs.org
|
||||||
|
|
||||||
|
- name: Update npm
|
||||||
|
run: npm install -g npm@latest
|
||||||
|
|
||||||
- name: Get package.json version
|
- name: Get package.json version
|
||||||
id: package-json-version
|
id: package-json-version
|
||||||
run: echo "version=v$(cat package.json | jq '.version' --raw-output)" >> $GITHUB_OUTPUT
|
run: echo "version=v$(cat package.json | jq '.version' --raw-output)" >> $GITHUB_OUTPUT
|
||||||
@@ -58,11 +62,7 @@ jobs:
|
|||||||
run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
|
run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
|
||||||
|
|
||||||
- name: Publish Mediabunny to npm
|
- name: Publish Mediabunny to npm
|
||||||
run: npm publish --provenance --access public
|
run: npm publish --access public
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Publish workspace packages to npm
|
- name: Publish workspace packages to npm
|
||||||
run: npm publish --provenance --access public --workspaces
|
run: npm publish --access public --workspaces
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|||||||
+7
-3
@@ -14,9 +14,13 @@
|
|||||||
source: new Mediabunny.BlobSource(file),
|
source: new Mediabunny.BlobSource(file),
|
||||||
});
|
});
|
||||||
|
|
||||||
const audioTrack = await input.getPrimaryAudioTrack();
|
const videoTrack = await input.getPrimaryVideoTrack();
|
||||||
const decoderConfig = await audioTrack.getDecoderConfig();
|
const sink = new Mediabunny.EncodedPacketSink(videoTrack);
|
||||||
console.log(decoderConfig);
|
|
||||||
|
for await (const packet of sink.packets()) {
|
||||||
|
console.log(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const videoTrack = await input.getPrimaryVideoTrack();
|
const videoTrack = await input.getPrimaryVideoTrack();
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,7 @@
|
|||||||
<button>Go</button>
|
<button>Go</button>
|
||||||
|
|
||||||
<script src="../dist/bundles/mediabunny.cjs"></script>
|
<script src="../dist/bundles/mediabunny.cjs"></script>
|
||||||
|
<script src="../packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js"></script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
function download(blob, filename) {
|
function download(blob, filename) {
|
||||||
@@ -12,6 +13,8 @@
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediabunnyMp3Encoder.registerMp3Encoder();
|
||||||
|
|
||||||
const button = document.querySelector('button');
|
const button = document.querySelector('button');
|
||||||
button.addEventListener('click', async () => {
|
button.addEventListener('click', async () => {
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
||||||
@@ -34,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
if (audioTrack) {
|
if (audioTrack) {
|
||||||
const source = new Mediabunny.MediaStreamAudioTrackSource(audioTrack, {
|
const source = new Mediabunny.MediaStreamAudioTrackSource(audioTrack, {
|
||||||
codec: 'aac',
|
codec: 'mp3',
|
||||||
bitrate: Mediabunny.QUALITY_MEDIUM
|
bitrate: Mediabunny.QUALITY_MEDIUM
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.25.4",
|
"version": "1.25.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.25.4",
|
"version": "1.25.7",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -7739,9 +7739,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mediabunny": {
|
"node_modules/mediabunny": {
|
||||||
"version": "1.25.3",
|
"version": "1.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.3.tgz",
|
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.6.tgz",
|
||||||
"integrity": "sha512-+LDXv/kybsElRQCmMlrdbKmPNvegeIyouGeOUzW5DJ8+M56H4G6wyEL2AJ1A45UcOB+U62qJMV7tuFO9S7yA7g==",
|
"integrity": "sha512-0CUgi9oHbyzPHZttn6aCQrWZLUpSAb7m1QKSSjr780LU0bgF2rgsYXAzPwt0V9ScK0t5CPuVhlmxF8MVe5BFHw==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -12065,7 +12065,7 @@
|
|||||||
},
|
},
|
||||||
"packages/mp3-encoder": {
|
"packages/mp3-encoder": {
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"version": "1.25.4",
|
"version": "1.25.7",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.25.4",
|
"version": "1.25.7",
|
||||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.25.4",
|
"version": "1.25.7",
|
||||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Mp3Encoder extends CustomAudioEncoder {
|
|||||||
|
|
||||||
private buffer = new Uint8Array(2 ** 16);
|
private buffer = new Uint8Array(2 ** 16);
|
||||||
private currentBufferOffset = 0;
|
private currentBufferOffset = 0;
|
||||||
private currentTimestamp = 0;
|
private currentTimestamp: number | null = null;
|
||||||
private chunkMetadata: EncodedAudioChunkMetadata = {};
|
private chunkMetadata: EncodedAudioChunkMetadata = {};
|
||||||
|
|
||||||
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
|
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
|
||||||
@@ -79,6 +79,11 @@ class Mp3Encoder extends CustomAudioEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async encode(audioSample: AudioSample) {
|
async encode(audioSample: AudioSample) {
|
||||||
|
if (this.currentTimestamp === null) {
|
||||||
|
// The first sample's timestamp determines where we start
|
||||||
|
this.currentTimestamp = audioSample.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
const sizePerChannel = audioSample.allocationSize({
|
const sizePerChannel = audioSample.allocationSize({
|
||||||
format: 's16-planar',
|
format: 's16-planar',
|
||||||
planeIndex: 0,
|
planeIndex: 0,
|
||||||
@@ -123,6 +128,8 @@ class Mp3Encoder extends CustomAudioEncoder {
|
|||||||
* these chunks and extract the MP3 frames only when they're complete.
|
* these chunks and extract the MP3 frames only when they're complete.
|
||||||
*/
|
*/
|
||||||
private digestOutput(bytes: Uint8Array) {
|
private digestOutput(bytes: Uint8Array) {
|
||||||
|
assert(this.currentTimestamp !== null);
|
||||||
|
|
||||||
const requiredBufferSize = this.currentBufferOffset + bytes.length;
|
const requiredBufferSize = this.currentBufferOffset + bytes.length;
|
||||||
if (requiredBufferSize > this.buffer.length) {
|
if (requiredBufferSize > this.buffer.length) {
|
||||||
// Grow the buffer to the required size
|
// Grow the buffer to the required size
|
||||||
|
|||||||
+10
-4
@@ -82,10 +82,16 @@ export type ConversionOptions = {
|
|||||||
|
|
||||||
/** Options to trim the input file. */
|
/** Options to trim the input file. */
|
||||||
trim?: {
|
trim?: {
|
||||||
/** The time in the input file in seconds at which the output file should start. Must be less than `end`. */
|
/**
|
||||||
start: number;
|
* 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 end. Must be greater than `start`. */
|
* Defaults to 0 when omitted.
|
||||||
end: number;
|
*/
|
||||||
|
start?: number;
|
||||||
|
/**
|
||||||
|
* The time in the input file in seconds at which the output file should end. Must be greater than `start`.
|
||||||
|
* Defaults to the duration of the input when omitted.
|
||||||
|
*/
|
||||||
|
end?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2489,11 +2489,8 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
|
|||||||
const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
|
const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
|
||||||
|
|
||||||
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
||||||
const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale);
|
const sampleIndex = getKeyframeSampleIndexForTimestamp(sampleTable, timestampInTimescale);
|
||||||
const keyFrameSampleIndex = sampleIndex === -1
|
const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options);
|
||||||
? -1
|
|
||||||
: getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
|
|
||||||
const regularPacket = await this.fetchPacketForSampleIndex(keyFrameSampleIndex, options);
|
|
||||||
|
|
||||||
if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
|
if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
|
||||||
// Prefer the non-fragmented packet
|
// Prefer the non-fragmented packet
|
||||||
@@ -2913,7 +2910,45 @@ const getSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: nu
|
|||||||
|
|
||||||
const entry = sampleTable.sampleTimingEntries[index]!;
|
const entry = sampleTable.sampleTimingEntries[index]!;
|
||||||
return entry.startIndex
|
return entry.startIndex
|
||||||
+ Math.min(Math.floor((timescaleUnits - entry.startDecodeTimestamp) / entry.delta), entry.count - 1);
|
+ Math.min(
|
||||||
|
Math.floor((timescaleUnits - entry.startDecodeTimestamp) / entry.delta),
|
||||||
|
entry.count - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKeyframeSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: number) => {
|
||||||
|
if (!sampleTable.keySampleIndices) {
|
||||||
|
// Every sample is a keyframe
|
||||||
|
return getSampleIndexForTimestamp(sampleTable, timescaleUnits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleTable.presentationTimestamps) {
|
||||||
|
const index = binarySearchLessOrEqual(
|
||||||
|
sampleTable.presentationTimestamps,
|
||||||
|
timescaleUnits,
|
||||||
|
x => x.presentationTimestamp,
|
||||||
|
);
|
||||||
|
if (index === -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the samples in presentation order until we find one that's a keyframe
|
||||||
|
for (let i = index; i >= 0; i--) {
|
||||||
|
const sampleIndex = sampleTable.presentationTimestamps[i]!.sampleIndex;
|
||||||
|
const isKeyFrame = binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1;
|
||||||
|
|
||||||
|
if (isKeyFrame) {
|
||||||
|
return sampleIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
const sampleIndex = getSampleIndexForTimestamp(sampleTable, timescaleUnits);
|
||||||
|
|
||||||
|
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
|
||||||
|
return sampleTable.keySampleIndices[index] ?? -1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3002,15 +3037,6 @@ const getSampleInfo = (sampleTable: SampleTable, sampleIndex: number): SampleInf
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRelevantKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
|
|
||||||
if (!sampleTable.keySampleIndices) {
|
|
||||||
return sampleIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
|
|
||||||
return sampleTable.keySampleIndices[index] ?? -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNextKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
|
const getNextKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
|
||||||
if (!sampleTable.keySampleIndices) {
|
if (!sampleTable.keySampleIndices) {
|
||||||
return sampleIndex + 1;
|
return sampleIndex + 1;
|
||||||
|
|||||||
+9
-7
@@ -201,9 +201,10 @@ export class EncodedPacketSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const packet = await this._track._backing.getKeyPacket(timestamp, options);
|
const packet = await this._track._backing.getKeyPacket(timestamp, options);
|
||||||
if (!packet || packet.type === 'delta') {
|
if (!packet) {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
assert(packet.type === 'key');
|
||||||
|
|
||||||
const determinedType = await this._track.determinePacketType(packet);
|
const determinedType = await this._track.determinePacketType(packet);
|
||||||
if (determinedType === 'delta') {
|
if (determinedType === 'delta') {
|
||||||
@@ -235,9 +236,10 @@ export class EncodedPacketSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nextPacket = await this._track._backing.getNextKeyPacket(packet, options);
|
const nextPacket = await this._track._backing.getNextKeyPacket(packet, options);
|
||||||
if (!nextPacket || nextPacket.type === 'delta') {
|
if (!nextPacket) {
|
||||||
return nextPacket;
|
return nextPacket;
|
||||||
}
|
}
|
||||||
|
assert(nextPacket.type === 'key');
|
||||||
|
|
||||||
const determinedType = await this._track.determinePacketType(nextPacket);
|
const determinedType = await this._track.determinePacketType(nextPacket);
|
||||||
if (determinedType === 'delta') {
|
if (determinedType === 'delta') {
|
||||||
@@ -474,9 +476,6 @@ export abstract class BaseMediaSampleSink<
|
|||||||
const packetSink = this._createPacketSink();
|
const packetSink = this._createPacketSink();
|
||||||
const keyPacket = await packetSink.getKeyPacket(startTimestamp, { verifyKeyPackets: true })
|
const keyPacket = await packetSink.getKeyPacket(startTimestamp, { verifyKeyPackets: true })
|
||||||
?? await packetSink.getFirstPacket();
|
?? await packetSink.getFirstPacket();
|
||||||
if (!keyPacket) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentPacket: EncodedPacket | null = keyPacket;
|
let currentPacket: EncodedPacket | null = keyPacket;
|
||||||
|
|
||||||
@@ -498,7 +497,7 @@ export abstract class BaseMediaSampleSink<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const packets = packetSink.packets(keyPacket, endPacket);
|
const packets = packetSink.packets(keyPacket ?? undefined, endPacket);
|
||||||
await packets.next(); // Skip the start packet as we already have it
|
await packets.next(); // Skip the start packet as we already have it
|
||||||
|
|
||||||
while (currentPacket && !ended && !this._track.input._disposed) {
|
while (currentPacket && !ended && !this._track.input._disposed) {
|
||||||
@@ -2127,11 +2126,14 @@ export class AudioBufferSink {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_audioSampleToWrappedArrayBuffer(sample: AudioSample): WrappedAudioBuffer {
|
_audioSampleToWrappedArrayBuffer(sample: AudioSample): WrappedAudioBuffer {
|
||||||
return {
|
const result: WrappedAudioBuffer = {
|
||||||
buffer: sample.toAudioBuffer(),
|
buffer: sample.toAudioBuffer(),
|
||||||
timestamp: sample.timestamp,
|
timestamp: sample.timestamp,
|
||||||
duration: sample.duration,
|
duration: sample.duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sample.close();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+19
-13
@@ -1356,21 +1356,27 @@ class AudioEncoderWrapper {
|
|||||||
(audioSample.timestamp + audioSample.duration) * audioSample.sampleRate,
|
(audioSample.timestamp + audioSample.duration) * audioSample.sampleRate,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.lastEndSampleIndex !== null && startSampleIndex > this.lastEndSampleIndex) {
|
if (this.lastEndSampleIndex === null) {
|
||||||
const sampleCount = startSampleIndex - this.lastEndSampleIndex;
|
this.lastEndSampleIndex = endSampleIndex;
|
||||||
const fillSample = new AudioSample({
|
} else {
|
||||||
data: new Float32Array(sampleCount * audioSample.numberOfChannels),
|
const sampleDiff = startSampleIndex - this.lastEndSampleIndex;
|
||||||
format: 'f32-planar',
|
|
||||||
sampleRate: audioSample.sampleRate,
|
|
||||||
numberOfChannels: audioSample.numberOfChannels,
|
|
||||||
numberOfFrames: sampleCount,
|
|
||||||
timestamp: this.lastEndSampleIndex / audioSample.sampleRate,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.add(fillSample, true); // Recursive call
|
if (sampleDiff >= 64) {
|
||||||
|
// The gap is big enough, let's add a correction sample
|
||||||
|
const fillSample = new AudioSample({
|
||||||
|
data: new Float32Array(sampleDiff * audioSample.numberOfChannels),
|
||||||
|
format: 'f32-planar',
|
||||||
|
sampleRate: audioSample.sampleRate,
|
||||||
|
numberOfChannels: audioSample.numberOfChannels,
|
||||||
|
numberOfFrames: sampleDiff,
|
||||||
|
timestamp: this.lastEndSampleIndex / audioSample.sampleRate,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.add(fillSample, true); // Recursive call
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastEndSampleIndex += audioSample.numberOfFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastEndSampleIndex = endSampleIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ test('can decode samples from a FLAC file', async () => {
|
|||||||
|
|
||||||
const sink = new AudioSampleSink(track);
|
const sink = new AudioSampleSink(track);
|
||||||
|
|
||||||
const sample = await sink.getSample(1);
|
using sample = await sink.getSample(1);
|
||||||
assert(sample);
|
assert(sample);
|
||||||
expect(sample.timestamp).toBe(0.9287981859410431);
|
expect(sample.timestamp).toBe(0.9287981859410431);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ test('Can decode transparent video', async () => {
|
|||||||
expect(await videoTrack.canBeTransparent()).toBe(true);
|
expect(await videoTrack.canBeTransparent()).toBe(true);
|
||||||
|
|
||||||
const sink = new VideoSampleSink(videoTrack);
|
const sink = new VideoSampleSink(videoTrack);
|
||||||
const sample = (await sink.getSample(0.5))!;
|
using sample = (await sink.getSample(0.5))!;
|
||||||
|
|
||||||
expect(sample.format).toContain('A'); // Probably RGBA
|
expect(sample.format).toContain('A'); // Probably RGBA
|
||||||
expect(sample.hasAlpha).toBe(true);
|
expect(sample.hasAlpha).toBe(true);
|
||||||
@@ -47,10 +47,10 @@ test('Can decode faulty transparent video and behaves gracefully', async () => {
|
|||||||
|
|
||||||
const sink = new VideoSampleSink(videoTrack);
|
const sink = new VideoSampleSink(videoTrack);
|
||||||
|
|
||||||
const startSample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
using startSample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
||||||
expect(startSample.format).toContain('A');
|
expect(startSample.format).toContain('A');
|
||||||
|
|
||||||
const secondSample = (await sink.getSample(secondKeyPacket.timestamp))!;
|
using secondSample = (await sink.getSample(secondKeyPacket.timestamp))!;
|
||||||
expect(secondSample.format).not.toContain('A'); // There was no alpha key frame for this one
|
expect(secondSample.format).not.toContain('A'); // There was no alpha key frame for this one
|
||||||
expect(secondSample.hasAlpha).toBe(false);
|
expect(secondSample.hasAlpha).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -164,7 +164,7 @@ test('Can encode transparent video', async () => {
|
|||||||
|
|
||||||
const sink = new VideoSampleSink(videoTrack);
|
const sink = new VideoSampleSink(videoTrack);
|
||||||
|
|
||||||
const firstSample = (await sink.getSample(0))!;
|
using firstSample = (await sink.getSample(0))!;
|
||||||
expect(firstSample.format).toContain('A');
|
expect(firstSample.format).toContain('A');
|
||||||
|
|
||||||
probeContext.clearRect(0, 0, probeCanvas.width, probeCanvas.height);
|
probeContext.clearRect(0, 0, probeCanvas.width, probeCanvas.height);
|
||||||
@@ -201,7 +201,7 @@ test('Can encode video with alternating transparency', async () => {
|
|||||||
await output.start();
|
await output.start();
|
||||||
|
|
||||||
for (let i = 0; i < 64; i++) {
|
for (let i = 0; i < 64; i++) {
|
||||||
const sample = new VideoSample(new Uint8Array(640 * 480 * 4), {
|
using sample = new VideoSample(new Uint8Array(640 * 480 * 4), {
|
||||||
format: i % 2 ? 'RGBX' : 'RGBA',
|
format: i % 2 ? 'RGBX' : 'RGBA',
|
||||||
codedWidth: 640,
|
codedWidth: 640,
|
||||||
codedHeight: 480,
|
codedHeight: 480,
|
||||||
@@ -235,7 +235,7 @@ test('Can encode video with alternating transparency', async () => {
|
|||||||
const sampleSink = new VideoSampleSink(videoTrack);
|
const sampleSink = new VideoSampleSink(videoTrack);
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
for await (const sample of sampleSink.samples()) {
|
for await (using sample of sampleSink.samples()) {
|
||||||
if (i % 2) {
|
if (i % 2) {
|
||||||
expect(sample.format).not.toContain('A');
|
expect(sample.format).not.toContain('A');
|
||||||
} else {
|
} else {
|
||||||
@@ -299,7 +299,7 @@ test('Can transmux transparent video, discards alpha by default', async () => {
|
|||||||
expect(await videoTrack.canBeTransparent()).toBe(false);
|
expect(await videoTrack.canBeTransparent()).toBe(false);
|
||||||
|
|
||||||
const sink = new VideoSampleSink(videoTrack);
|
const sink = new VideoSampleSink(videoTrack);
|
||||||
const sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
using sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
||||||
expect(sample.hasAlpha).toBe(false);
|
expect(sample.hasAlpha).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ test('Can transmux transparent video, can keep alpha', async () => {
|
|||||||
expect(await videoTrack.canBeTransparent()).toBe(true);
|
expect(await videoTrack.canBeTransparent()).toBe(true);
|
||||||
|
|
||||||
const sink = new VideoSampleSink(videoTrack);
|
const sink = new VideoSampleSink(videoTrack);
|
||||||
const sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
using sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
||||||
expect(sample.format).toContain('A');
|
expect(sample.format).toContain('A');
|
||||||
expect(sample.hasAlpha).toBe(true);
|
expect(sample.hasAlpha).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -370,7 +370,7 @@ test('Can reencode transparent video, keeping alpha', async () => {
|
|||||||
expect(videoTrack.displayWidth).toBe(320);
|
expect(videoTrack.displayWidth).toBe(320);
|
||||||
|
|
||||||
const sink = new VideoSampleSink(videoTrack);
|
const sink = new VideoSampleSink(videoTrack);
|
||||||
const sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
using sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!;
|
||||||
expect(sample.format).toContain('A');
|
expect(sample.format).toContain('A');
|
||||||
expect(sample.hasAlpha).toBe(true);
|
expect(sample.hasAlpha).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user