4208 lines
194 KiB
JavaScript
4208 lines
194 KiB
JavaScript
(function (global, factory) {
|
||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.LibAVWebCodecs = {}));
|
||
})(this, (function (exports) { 'use strict';
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
let EncodedAudioChunk$1 = class EncodedAudioChunk {
|
||
constructor(init) {
|
||
/* 1. If init.transfer contains more than one reference to the same
|
||
* ArrayBuffer, then throw a DataCloneError DOMException. */
|
||
// 2. For each transferable in init.transfer:
|
||
/* 1. If [[Detached]] internal slot is true, then throw a
|
||
* DataCloneError DOMException. */
|
||
// (not worth checking in a polyfill)
|
||
/* 3. Let chunk be a new EncodedAudioChunk object, initialized as
|
||
* follows */
|
||
{
|
||
// 1. Assign init.type to [[type]].
|
||
this.type = init.type;
|
||
// 2. Assign init.timestamp to [[timestamp]].
|
||
this.timestamp = init.timestamp;
|
||
/* 3. If init.duration exists, assign it to [[duration]], or assign
|
||
* null otherwise. */
|
||
if (typeof init.duration === "number")
|
||
this.duration = init.duration;
|
||
else
|
||
this.duration = null;
|
||
// 4. Assign init.data.byteLength to [[byte length]];
|
||
this.byteLength = init.data.byteLength;
|
||
/* 5. If init.transfer contains an ArrayBuffer referenced by
|
||
* init.data the User Agent MAY choose to: */
|
||
let transfer = false;
|
||
if (init.transfer) {
|
||
/* 1. Let resource be a new media resource referencing sample
|
||
* data in init.data. */
|
||
let inBuffer;
|
||
if (init.data.buffer)
|
||
inBuffer = init.data.buffer;
|
||
else
|
||
inBuffer = init.data;
|
||
let t;
|
||
if (init.transfer instanceof Array)
|
||
t = init.transfer;
|
||
else
|
||
t = Array.from(init.transfer);
|
||
for (const b of t) {
|
||
if (b === inBuffer) {
|
||
transfer = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// 6. Otherwise:
|
||
// 1. Assign a copy of init.data to [[internal data]].
|
||
const data = new Uint8Array(init.data.buffer || init.data, init.data.byteOffset || 0, init.data.BYTES_PER_ELEMENT
|
||
? (init.data.BYTES_PER_ELEMENT * init.data.length)
|
||
: init.data.byteLength);
|
||
if (transfer)
|
||
this._data = data;
|
||
else
|
||
this._data = data.slice(0);
|
||
}
|
||
// 4. For each transferable in init.transfer:
|
||
// 1. Perform DetachArrayBuffer on transferable
|
||
// (already done by transferring)
|
||
// 5. Return chunk.
|
||
}
|
||
// Internal
|
||
_libavGetData() { return this._data; }
|
||
copyTo(destination) {
|
||
(new Uint8Array(destination.buffer || destination, destination.byteOffset || 0)).set(this._data);
|
||
}
|
||
};
|
||
|
||
(function (Object) {
|
||
typeof globalThis !== 'object' && (
|
||
this ?
|
||
get() :
|
||
(Object.defineProperty(Object.prototype, '_T_', {
|
||
configurable: true,
|
||
get: get
|
||
}), _T_)
|
||
);
|
||
function get() {
|
||
var global = this || self;
|
||
global.globalThis = global;
|
||
delete Object.prototype._T_;
|
||
}
|
||
}(Object));
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
let AudioData$1 = class AudioData {
|
||
constructor(init) {
|
||
// 1. If init is not a valid AudioDataInit, throw a TypeError.
|
||
AudioData._checkValidAudioDataInit(init);
|
||
/* 2. If init.transfer contains more than one reference to the same
|
||
* ArrayBuffer, then throw a DataCloneError DOMException. */
|
||
// 3. For each transferable in init.transfer:
|
||
// 1. If [[Detached]] internal slot is true, then throw a DataCloneError DOMException.
|
||
// (Not worth doing in polyfill)
|
||
// 4. Let frame be a new AudioData object, initialized as follows:
|
||
{
|
||
// 1. Assign false to [[Detached]].
|
||
// (not doable in polyfill)
|
||
// 2. Assign init.format to [[format]].
|
||
this.format = init.format;
|
||
// 3. Assign init.sampleRate to [[sample rate]].
|
||
this.sampleRate = init.sampleRate;
|
||
// 4. Assign init.numberOfFrames to [[number of frames]].
|
||
this.numberOfFrames = init.numberOfFrames;
|
||
// 5. Assign init.numberOfChannels to [[number of channels]].
|
||
this.numberOfChannels = init.numberOfChannels;
|
||
// 6. Assign init.timestamp to [[timestamp]].
|
||
this.timestamp = init.timestamp;
|
||
/* 7. If init.transfer contains an ArrayBuffer referenced by
|
||
* init.data the User Agent MAY choose to: */
|
||
let transfer = false;
|
||
if (init.transfer) {
|
||
// 1. Let resource be a new media resource referencing sample data in data.
|
||
let inBuffer;
|
||
if (init.data.buffer)
|
||
inBuffer = init.data.buffer;
|
||
else
|
||
inBuffer = init.data;
|
||
let t;
|
||
if (init.transfer instanceof Array)
|
||
t = init.transfer;
|
||
else
|
||
t = Array.from(init.transfer);
|
||
for (const b of t) {
|
||
if (b === inBuffer) {
|
||
transfer = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// 8. Otherwise:
|
||
// 1. Let resource be a media resource containing a copy of init.data.
|
||
// 9. Let resourceReference be a reference to resource.
|
||
let inData, byteOffset = 0;
|
||
if (transfer) {
|
||
inData = init.data;
|
||
byteOffset = init.data.byteOffset || 0;
|
||
}
|
||
else {
|
||
inData = init.data.slice(0);
|
||
}
|
||
const resourceReference = audioView(init.format, inData.buffer || inData, byteOffset);
|
||
// 10. Assign resourceReference to [[resource reference]].
|
||
this._data = resourceReference;
|
||
}
|
||
// 5. For each transferable in init.transfer:
|
||
// 1. Perform DetachArrayBuffer on transferable
|
||
// (Already done by transferring)
|
||
// 6. Return frame.
|
||
// Duration not calculated in spec?
|
||
this.duration = init.numberOfFrames / init.sampleRate * 1000000;
|
||
}
|
||
/**
|
||
* Convert a polyfill AudioData to a native AudioData.
|
||
* @param opts Conversion options
|
||
*/
|
||
toNative(opts = {}) {
|
||
const ret = new globalThis.AudioData({
|
||
data: this._data,
|
||
format: this.format,
|
||
sampleRate: this.sampleRate,
|
||
numberOfFrames: this.numberOfFrames,
|
||
numberOfChannels: this.numberOfChannels,
|
||
timestamp: this.timestamp,
|
||
transfer: opts.transfer ? [this._data.buffer] : []
|
||
});
|
||
if (opts.transfer)
|
||
this.close();
|
||
return ret;
|
||
}
|
||
/**
|
||
* Convert a native AudioData to a polyfill AudioData. WARNING: Inefficient,
|
||
* as the data cannot be transferred out.
|
||
* @param from AudioData to copy in
|
||
*/
|
||
static fromNative(from /* native AudioData */) {
|
||
const ad = from;
|
||
const isInterleaved_ = isInterleaved(ad.format);
|
||
const planes = isInterleaved_ ? 1 : ad.numberOfChannels;
|
||
const sizePerPlane = ad.allocationSize({
|
||
format: ad.format,
|
||
planeIndex: 0
|
||
});
|
||
const data = new Uint8Array(sizePerPlane);
|
||
for (let p = 0; p < planes; p++) {
|
||
ad.copyTo(data.subarray(p * sizePerPlane), {
|
||
format: ad.format,
|
||
planeIndex: p
|
||
});
|
||
}
|
||
return new AudioData({
|
||
data,
|
||
format: ad.format,
|
||
sampleRate: ad.sampleRate,
|
||
numberOfFrames: ad.numberOfFrames,
|
||
numberOfChannels: ad.numberOfChannels,
|
||
timestamp: ad.timestamp,
|
||
transfer: [data.buffer]
|
||
});
|
||
}
|
||
// Internal
|
||
_libavGetData() { return this._data; }
|
||
static _checkValidAudioDataInit(init) {
|
||
// 1. If sampleRate less than or equal to 0, return false.
|
||
if (init.sampleRate <= 0)
|
||
throw new TypeError(`Invalid sample rate ${init.sampleRate}`);
|
||
// 2. If numberOfFrames = 0, return false.
|
||
if (init.numberOfFrames <= 0)
|
||
throw new TypeError(`Invalid number of frames ${init.numberOfFrames}`);
|
||
// 3. If numberOfChannels = 0, return false.
|
||
if (init.numberOfChannels <= 0)
|
||
throw new TypeError(`Invalid number of channels ${init.numberOfChannels}`);
|
||
// 4. Verify data has enough data by running the following steps:
|
||
{
|
||
// 1. Let totalSamples be the product of multiplying numberOfFrames by numberOfChannels.
|
||
const totalSamples = init.numberOfFrames * init.numberOfChannels;
|
||
// 2. Let bytesPerSample be the number of bytes per sample, as defined by the format.
|
||
const bytesPerSample_ = bytesPerSample(init.format);
|
||
// 3. Let totalSize be the product of multiplying bytesPerSample with totalSamples.
|
||
const totalSize = bytesPerSample_ * totalSamples;
|
||
// 4. Let dataSize be the size in bytes of data.
|
||
const dataSize = init.data.byteLength;
|
||
// 5. If dataSize is less than totalSize, return false.
|
||
if (dataSize < totalSize)
|
||
throw new TypeError(`This audio data must be at least ${totalSize} bytes`);
|
||
}
|
||
// 5. Return true.
|
||
}
|
||
allocationSize(options) {
|
||
// 1. If [[Detached]] is true, throw an InvalidStateError DOMException.
|
||
if (this._data === null)
|
||
throw new DOMException("Detached", "InvalidStateError");
|
||
/* 2. Let copyElementCount be the result of running the Compute Copy
|
||
* Element Count algorithm with options. */
|
||
const copyElementCount = this._computeCopyElementCount(options);
|
||
// 3. Let destFormat be the value of [[format]].
|
||
let destFormat = this.format;
|
||
// 4. If options.format exists, assign options.format to destFormat.
|
||
if (options.format)
|
||
destFormat = options.format;
|
||
/* 5. Let bytesPerSample be the number of bytes per sample, as defined
|
||
* by the destFormat. */
|
||
const bytesPerSample_ = bytesPerSample(destFormat);
|
||
/* 6. Return the product of multiplying bytesPerSample by
|
||
* copyElementCount. */
|
||
return bytesPerSample_ * copyElementCount;
|
||
}
|
||
_computeCopyElementCount(options) {
|
||
// 1. Let destFormat be the value of [[format]].
|
||
let destFormat = this.format;
|
||
// 2. If options.format exists, assign options.format to destFormat.
|
||
if (options.format)
|
||
destFormat = options.format;
|
||
/* 3. If destFormat describes an interleaved AudioSampleFormat and
|
||
* options.planeIndex is greater than 0, throw a RangeError. */
|
||
const isInterleaved_ = isInterleaved(destFormat);
|
||
if (isInterleaved_) {
|
||
if (options.planeIndex > 0)
|
||
throw new RangeError("Invalid plane");
|
||
}
|
||
/* 4. Otherwise, if destFormat describes a planar AudioSampleFormat and
|
||
* if options.planeIndex is greater or equal to [[number of channels]],
|
||
* throw a RangeError. */
|
||
else if (options.planeIndex >= this.numberOfChannels)
|
||
throw new RangeError("Invalid plane");
|
||
/* 5. If [[format]] does not equal destFormat and the User Agent does
|
||
* not support the requested AudioSampleFormat conversion, throw a
|
||
* NotSupportedError DOMException. Conversion to f32-planar MUST always
|
||
* be supported. */
|
||
if (this.format !== destFormat &&
|
||
destFormat !== "f32-planar")
|
||
throw new DOMException("Only conversion to f32-planar is supported", "NotSupportedError");
|
||
/* 6. Let frameCount be the number of frames in the plane identified by
|
||
* options.planeIndex. */
|
||
const frameCount = this.numberOfFrames; // All planes have the same number of frames
|
||
/* 7. If options.frameOffset is greater than or equal to frameCount,
|
||
* throw a RangeError. */
|
||
const frameOffset = options.frameOffset || 0;
|
||
if (frameOffset >= frameCount)
|
||
throw new RangeError("Frame offset out of range");
|
||
/* 8. Let copyFrameCount be the difference of subtracting
|
||
* options.frameOffset from frameCount. */
|
||
let copyFrameCount = frameCount - frameOffset;
|
||
// 9. If options.frameCount exists:
|
||
if (typeof options.frameCount === "number") {
|
||
/* 1. If options.frameCount is greater than copyFrameCount, throw a
|
||
* RangeError. */
|
||
if (options.frameCount >= copyFrameCount)
|
||
throw new RangeError("Frame count out of range");
|
||
// 2. Otherwise, assign options.frameCount to copyFrameCount.
|
||
copyFrameCount = options.frameCount;
|
||
}
|
||
// 10. Let elementCount be copyFrameCount.
|
||
let elementCount = copyFrameCount;
|
||
/* 11. If destFormat describes an interleaved AudioSampleFormat,
|
||
* mutliply elementCount by [[number of channels]] */
|
||
if (isInterleaved_)
|
||
elementCount *= this.numberOfChannels;
|
||
// 12. return elementCount.
|
||
return elementCount;
|
||
}
|
||
copyTo(destination, options) {
|
||
// 1. If [[Detached]] is true, throw an InvalidStateError DOMException.
|
||
if (this._data === null)
|
||
throw new DOMException("Detached", "InvalidStateError");
|
||
/* 2. Let copyElementCount be the result of running the Compute Copy
|
||
* Element Count algorithm with options. */
|
||
const copyElementCount = this._computeCopyElementCount(options);
|
||
// 3. Let destFormat be the value of [[format]].
|
||
let destFormat = this.format;
|
||
// 4. If options.format exists, assign options.format to destFormat.
|
||
if (options.format)
|
||
destFormat = options.format;
|
||
/* 5. Let bytesPerSample be the number of bytes per sample, as defined
|
||
* by the destFormat. */
|
||
const bytesPerSample_ = bytesPerSample(destFormat);
|
||
/* 6. If the product of multiplying bytesPerSample by copyElementCount
|
||
* is greater than destination.byteLength, throw a RangeError. */
|
||
if (bytesPerSample_ * copyElementCount > destination.byteLength)
|
||
throw new RangeError("Buffer too small");
|
||
/* 7. Let resource be the media resource referenced by [[resource
|
||
* reference]]. */
|
||
const resource = this._data;
|
||
/* 8. Let planeFrames be the region of resource corresponding to
|
||
* options.planeIndex. */
|
||
const planeFrames = resource.subarray(options.planeIndex * this.numberOfFrames);
|
||
const frameOffset = options.frameOffset || 0;
|
||
const numberOfChannels = this.numberOfChannels;
|
||
/* 9. Copy elements of planeFrames into destination, starting with the
|
||
* frame positioned at options.frameOffset and stopping after
|
||
* copyElementCount samples have been copied. If destFormat does not
|
||
* equal [[format]], convert elements to the destFormat
|
||
* AudioSampleFormat while making the copy. */
|
||
if (this.format === destFormat) {
|
||
const dest = audioView(destFormat, destination.buffer || destination, destination.byteOffset || 0);
|
||
if (isInterleaved(destFormat)) {
|
||
dest.set(planeFrames.subarray(frameOffset * numberOfChannels, frameOffset * numberOfChannels + copyElementCount));
|
||
}
|
||
else {
|
||
dest.set(planeFrames.subarray(frameOffset, frameOffset + copyElementCount));
|
||
}
|
||
}
|
||
else {
|
||
// Actual conversion necessary. Always to f32-planar.
|
||
const out = audioView(destFormat, destination.buffer || destination, destination.byteOffset || 0);
|
||
// First work out the conversion
|
||
let sub = 0;
|
||
let div = 1;
|
||
switch (this.format) {
|
||
case "u8":
|
||
case "u8-planar":
|
||
sub = 0x80;
|
||
div = 0x80;
|
||
break;
|
||
case "s16":
|
||
case "s16-planar":
|
||
div = 0x8000;
|
||
break;
|
||
case "s32":
|
||
case "s32-planar":
|
||
div = 0x80000000;
|
||
break;
|
||
}
|
||
// Then do it
|
||
if (isInterleaved(this.format)) {
|
||
for (let i = options.planeIndex + frameOffset * numberOfChannels, o = 0; o < copyElementCount; i += numberOfChannels, o++)
|
||
out[o] = (planeFrames[i] - sub) / div;
|
||
}
|
||
else {
|
||
for (let i = frameOffset, o = 0; o < copyElementCount; i++, o++)
|
||
out[o] = (planeFrames[i] - sub) / div;
|
||
}
|
||
}
|
||
}
|
||
clone() {
|
||
// 1. If [[Detached]] is true, throw an InvalidStateError DOMException.
|
||
if (this._data === null)
|
||
throw new DOMException("Detached", "InvalidStateError");
|
||
/* 2. Return the result of running the Clone AudioData algorithm with
|
||
* this. */
|
||
return new AudioData({
|
||
format: this.format,
|
||
sampleRate: this.sampleRate,
|
||
numberOfFrames: this.numberOfFrames,
|
||
numberOfChannels: this.numberOfChannels,
|
||
timestamp: this.timestamp,
|
||
data: this._data
|
||
});
|
||
}
|
||
close() {
|
||
this._data = null;
|
||
}
|
||
};
|
||
/**
|
||
* Construct the appropriate type of ArrayBufferView for the given sample
|
||
* format and buffer.
|
||
* @param format Sample format
|
||
* @param buffer ArrayBuffer (NOT view)
|
||
* @param byteOffset Offset into the buffer
|
||
*/
|
||
function audioView(format, buffer, byteOffset) {
|
||
switch (format) {
|
||
case "u8":
|
||
case "u8-planar":
|
||
return new Uint8Array(buffer, byteOffset);
|
||
case "s16":
|
||
case "s16-planar":
|
||
return new Int16Array(buffer, byteOffset);
|
||
case "s32":
|
||
case "s32-planar":
|
||
return new Int32Array(buffer, byteOffset);
|
||
case "f32":
|
||
case "f32-planar":
|
||
return new Float32Array(buffer, byteOffset);
|
||
default:
|
||
throw new TypeError("Invalid AudioSampleFormat");
|
||
}
|
||
}
|
||
/**
|
||
* Number of bytes per sample of this format.
|
||
* @param format Sample format
|
||
*/
|
||
function bytesPerSample(format) {
|
||
switch (format) {
|
||
case "u8":
|
||
case "u8-planar":
|
||
return 1;
|
||
case "s16":
|
||
case "s16-planar":
|
||
return 2;
|
||
case "s32":
|
||
case "s32-planar":
|
||
case "f32":
|
||
case "f32-planar":
|
||
return 4;
|
||
default:
|
||
throw new TypeError("Invalid AudioSampleFormat");
|
||
}
|
||
}
|
||
/**
|
||
* Is this format interleaved?
|
||
* @param format Sample format
|
||
*/
|
||
function isInterleaved(format) {
|
||
switch (format) {
|
||
case "u8":
|
||
case "s16":
|
||
case "s32":
|
||
case "f32":
|
||
return true;
|
||
case "u8-planar":
|
||
case "s16-planar":
|
||
case "s32-planar":
|
||
case "f32-planar":
|
||
return false;
|
||
default:
|
||
throw new TypeError("Invalid AudioSampleFormat");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
/* Unfortunately, browsers don't let us extend EventTarget. So, we implement an
|
||
* EventTarget interface with a “has-a” relationship instead of an “is-a”
|
||
* relationship. We have an event target, and expose its event functions as our
|
||
* own. */
|
||
class HasAEventTarget {
|
||
constructor() {
|
||
const ev = this._eventer = new EventTarget();
|
||
this.addEventListener = ev.addEventListener.bind(ev);
|
||
this.removeEventListener = ev.removeEventListener.bind(ev);
|
||
this.dispatchEvent = ev.dispatchEvent.bind(ev);
|
||
}
|
||
}
|
||
class DequeueEventTarget extends HasAEventTarget {
|
||
constructor() {
|
||
super();
|
||
this.addEventListener("dequeue", ev => {
|
||
if (this.ondequeue)
|
||
this.ondequeue(ev);
|
||
});
|
||
}
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$8 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
// Wrapper function to use
|
||
let LibAVWrapper = null;
|
||
// Currently available libav instances
|
||
const libavs = [];
|
||
// Options required to create a LibAV instance
|
||
let libavOptions = {};
|
||
/**
|
||
* Supported decoders.
|
||
*/
|
||
let decoders = null;
|
||
/**
|
||
* Supported encoders.
|
||
*/
|
||
let encoders = null;
|
||
/**
|
||
* Set the libav wrapper to use.
|
||
*/
|
||
function setLibAV(to) {
|
||
LibAVWrapper = to;
|
||
}
|
||
/**
|
||
* Set the libav loading options.
|
||
*/
|
||
function setLibAVOptions(to) {
|
||
libavOptions = to;
|
||
}
|
||
/**
|
||
* Get a libav instance.
|
||
*/
|
||
function get() {
|
||
return __awaiter$8(this, void 0, void 0, function* () {
|
||
if (libavs.length)
|
||
return libavs.shift();
|
||
return yield LibAVWrapper.LibAV(libavOptions);
|
||
});
|
||
}
|
||
/**
|
||
* Free a libav instance for later reuse.
|
||
*/
|
||
function free(libav) {
|
||
libavs.push(libav);
|
||
}
|
||
/**
|
||
* Get the list of encoders/decoders supported by libav (which are also
|
||
* supported by this polyfill)
|
||
* @param encoders Check for encoders instead of decoders
|
||
*/
|
||
function codecs(encoders) {
|
||
return __awaiter$8(this, void 0, void 0, function* () {
|
||
const libav = yield get();
|
||
const ret = [];
|
||
for (const [avname, codec] of [
|
||
["flac", "flac"],
|
||
["libopus", "opus"],
|
||
["libvorbis", "vorbis"],
|
||
["libaom-av1", "av01"],
|
||
["libvpx-vp9", "vp09"],
|
||
["libvpx", "vp8"]
|
||
]) {
|
||
if (encoders) {
|
||
if (yield libav.avcodec_find_encoder_by_name(avname))
|
||
ret.push(codec);
|
||
}
|
||
else {
|
||
if (yield libav.avcodec_find_decoder_by_name(avname))
|
||
ret.push(codec);
|
||
}
|
||
}
|
||
free(libav);
|
||
return ret;
|
||
});
|
||
}
|
||
/**
|
||
* Load the lists of supported decoders and encoders.
|
||
*/
|
||
function load$2() {
|
||
return __awaiter$8(this, void 0, void 0, function* () {
|
||
LibAVWrapper = LibAVWrapper || LibAV;
|
||
decoders = yield codecs(false);
|
||
encoders = yield codecs(true);
|
||
});
|
||
}
|
||
/**
|
||
* Convert a decoder from the codec registry (or libav.js-specific parameters)
|
||
* to libav.js. Returns null if unsupported.
|
||
*/
|
||
function decoder(codec, config) {
|
||
if (typeof codec === "string") {
|
||
codec = codec.replace(/\..*/, "");
|
||
let outCodec = codec;
|
||
switch (codec) {
|
||
// Audio
|
||
case "flac":
|
||
if (typeof config.description === "undefined") {
|
||
// description is required per spec, but one can argue, if this limitation makes sense
|
||
return null;
|
||
}
|
||
break;
|
||
case "opus":
|
||
if (typeof config.description !== "undefined") {
|
||
// ogg bitstream is not supported by the current implementation
|
||
return null;
|
||
}
|
||
outCodec = "libopus";
|
||
break;
|
||
case "vorbis":
|
||
if (typeof config.description === "undefined") {
|
||
// description is required per spec, but one can argue, if this limitation makes sense
|
||
return null;
|
||
}
|
||
outCodec = "libvorbis";
|
||
break;
|
||
// Video
|
||
case "av01":
|
||
outCodec = "libaom-av1";
|
||
break;
|
||
case "vp09":
|
||
outCodec = "libvpx-vp9";
|
||
break;
|
||
case "vp8":
|
||
outCodec = "libvpx";
|
||
break;
|
||
// Unsupported
|
||
case "mp3":
|
||
case "mp4a":
|
||
case "ulaw":
|
||
case "alaw":
|
||
case "avc1":
|
||
case "avc3":
|
||
case "hev1":
|
||
case "hvc1":
|
||
return null;
|
||
// Unrecognized
|
||
default:
|
||
throw new TypeError("Unrecognized codec");
|
||
}
|
||
// Check whether we actually support this codec
|
||
if (!(decoders.indexOf(codec) >= 0))
|
||
return null;
|
||
return { codec: outCodec };
|
||
}
|
||
else {
|
||
return codec.libavjs;
|
||
}
|
||
}
|
||
/**
|
||
* Convert an encoder from the codec registry (or libav.js-specific parameters)
|
||
* to libav.js. Returns null if unsupported.
|
||
*/
|
||
function encoder(codec, config) {
|
||
if (typeof codec === "string") {
|
||
const codecParts = codec.split(".");
|
||
codec = codecParts[0];
|
||
let outCodec = codec;
|
||
const ctx = {};
|
||
const options = {};
|
||
let video = false;
|
||
switch (codec) {
|
||
// Audio
|
||
case "flac":
|
||
ctx.sample_fmt = 2 /* S32 */;
|
||
ctx.bit_rate = 0;
|
||
if (typeof config.flac === "object" &&
|
||
config.flac !== null) {
|
||
const flac = config.flac;
|
||
// FIXME: Check block size
|
||
if (typeof flac.blockSize === "number")
|
||
ctx.frame_size = flac.blockSize;
|
||
if (typeof flac.compressLevel === "number") {
|
||
// Not supported
|
||
return null;
|
||
}
|
||
}
|
||
break;
|
||
case "opus":
|
||
outCodec = "libopus";
|
||
ctx.sample_fmt = 3 /* FLT */;
|
||
ctx.sample_rate = 48000;
|
||
if (typeof config.opus === "object" &&
|
||
config.opus !== null) {
|
||
const opus = config.opus;
|
||
// FIXME: Check frame duration
|
||
if (typeof opus.frameDuration === "number")
|
||
options.frame_duration = "" + (opus.frameDuration / 1000);
|
||
if (typeof opus.complexity !== "undefined") {
|
||
// We don't support the complexity option
|
||
return null;
|
||
}
|
||
if (typeof opus.packetlossperc === "number") {
|
||
if (opus.packetlossperc < 0 || opus.packetlossperc > 100)
|
||
return null;
|
||
options.packet_loss = "" + opus.packetlossperc;
|
||
}
|
||
if (typeof opus.useinbandfec === "boolean")
|
||
options.fec = opus.useinbandfec ? "1" : "0";
|
||
if (typeof opus.usedtx === "boolean") {
|
||
// We don't support the usedtx option
|
||
return null;
|
||
}
|
||
if (typeof opus.format === "string") {
|
||
// ogg bitstream is not supported
|
||
if (opus.format !== "opus")
|
||
return null;
|
||
}
|
||
}
|
||
break;
|
||
case "vorbis":
|
||
outCodec = "libvorbis";
|
||
ctx.sample_fmt = 8 /* FLTP */;
|
||
break;
|
||
// Video
|
||
case "av01":
|
||
video = true;
|
||
outCodec = "libaom-av1";
|
||
if (config.latencyMode === "realtime") {
|
||
options.usage = "realtime";
|
||
options["cpu-used"] = "8";
|
||
}
|
||
// Check for advanced options
|
||
if (!av1Advanced(codecParts, ctx))
|
||
return null;
|
||
break;
|
||
case "vp09":
|
||
video = true;
|
||
outCodec = "libvpx-vp9";
|
||
if (config.latencyMode === "realtime") {
|
||
options.quality = "realtime";
|
||
options["cpu-used"] = "8";
|
||
}
|
||
// Check for advanced options
|
||
if (!vp9Advanced(codecParts, ctx))
|
||
return null;
|
||
break;
|
||
case "vp8":
|
||
video = true;
|
||
outCodec = "libvpx";
|
||
if (config.latencyMode === "realtime") {
|
||
options.quality = "realtime";
|
||
options["cpu-used"] = "8";
|
||
}
|
||
break;
|
||
// Unsupported
|
||
case "mp3":
|
||
case "mp4a":
|
||
case "ulaw":
|
||
case "alaw":
|
||
case "avc1":
|
||
return null;
|
||
// Unrecognized
|
||
default:
|
||
throw new TypeError("Unrecognized codec");
|
||
}
|
||
// Check whether we actually support this codec
|
||
if (!(encoders.indexOf(codec) >= 0))
|
||
return null;
|
||
if (video) {
|
||
if (typeof ctx.pix_fmt !== "number")
|
||
ctx.pix_fmt = 0 /* YUV420P */;
|
||
const width = ctx.width = config.width;
|
||
const height = ctx.height = config.height;
|
||
if (config.framerate) {
|
||
/* FIXME: We need this as a rational, not a floating point, and
|
||
* this is obviously not the right way to do it */
|
||
ctx.framerate_num = Math.round(config.framerate);
|
||
ctx.framerate_den = 1;
|
||
}
|
||
// Check for non-square pixels
|
||
const dWidth = config.displayWidth || config.width;
|
||
const dHeight = config.displayHeight || config.height;
|
||
if (dWidth !== width || dHeight !== height) {
|
||
ctx.sample_aspect_ratio_num = dWidth * height;
|
||
ctx.sample_aspect_ratio_den = dHeight * width;
|
||
}
|
||
}
|
||
else {
|
||
if (!ctx.sample_rate)
|
||
ctx.sample_rate = config.sampleRate || 48000;
|
||
if (config.numberOfChannels) {
|
||
const n = config.numberOfChannels;
|
||
ctx.channel_layout = (n === 1) ? 4 : ((1 << n) - 1);
|
||
}
|
||
}
|
||
if (typeof ctx.bit_rate !== "number" && config.bitrate) {
|
||
// NOTE: CBR requests are, quite rightly, ignored
|
||
ctx.bit_rate = config.bitrate;
|
||
}
|
||
return {
|
||
codec: outCodec,
|
||
ctx, options
|
||
};
|
||
}
|
||
else {
|
||
return codec.libavjs;
|
||
}
|
||
}
|
||
/**
|
||
* Handler for advanced options for AV1.
|
||
* @param codecParts .-separated parts of the codec string.
|
||
* @param ctx Context to populate with advanced options.
|
||
*/
|
||
function av1Advanced(codecParts, ctx) {
|
||
if (codecParts[1]) {
|
||
const profile = +codecParts[1];
|
||
if (profile >= 0 && profile <= 2)
|
||
ctx.profile = profile;
|
||
else
|
||
throw new TypeError("Invalid AV1 profile");
|
||
}
|
||
if (codecParts[2]) {
|
||
const level = +codecParts[2];
|
||
if (level >= 0 && level <= 23)
|
||
ctx.level = level;
|
||
else
|
||
throw new TypeError("Invalid AV1 level");
|
||
}
|
||
if (codecParts[3]) {
|
||
switch (codecParts[3]) {
|
||
case "M":
|
||
// Default
|
||
break;
|
||
case "H":
|
||
if (ctx.level && ctx.level >= 8) {
|
||
// Valid but unsupported
|
||
return false;
|
||
}
|
||
else {
|
||
throw new TypeError("The AV1 high tier is only available for level 4.0 and up");
|
||
}
|
||
default:
|
||
throw new TypeError("Invalid AV1 tier");
|
||
}
|
||
}
|
||
if (codecParts[4]) {
|
||
const depth = +codecParts[3];
|
||
if (depth === 10 || depth === 12) {
|
||
// Valid but unsupported
|
||
return false;
|
||
}
|
||
else if (depth !== 8) {
|
||
throw new TypeError("Invalid AV1 bit depth");
|
||
}
|
||
}
|
||
if (codecParts[5]) {
|
||
// Monochrome
|
||
switch (codecParts[5]) {
|
||
case "0":
|
||
// Default
|
||
break;
|
||
case "1":
|
||
// Valid but unsupported
|
||
return false;
|
||
default:
|
||
throw new TypeError("Invalid AV1 monochrome flag");
|
||
}
|
||
}
|
||
if (codecParts[6]) {
|
||
// Subsampling mode
|
||
switch (codecParts[6]) {
|
||
case "000": // YUV444
|
||
ctx.pix_fmt = 5 /* YUV444P */;
|
||
break;
|
||
case "100": // YUV422
|
||
ctx.pix_fmt = 4 /* YUV422P */;
|
||
break;
|
||
case "110": // YUV420P (default)
|
||
ctx.pix_fmt = 0 /* YUV420P */;
|
||
break;
|
||
case "111": // Monochrome
|
||
return false;
|
||
default:
|
||
throw new TypeError("Invalid AV1 subsampling mode");
|
||
}
|
||
}
|
||
/* The remaining values have to do with color formats, which we don't
|
||
* support correctly anyway */
|
||
return true;
|
||
}
|
||
/**
|
||
* Handler for advanced options for VP9.
|
||
* @param codecParts .-separated parts of the codec string.
|
||
* @param ctx Context to populate with advanced options.
|
||
*/
|
||
function vp9Advanced(codecParts, ctx) {
|
||
if (codecParts[1]) {
|
||
const profile = +codecParts[1];
|
||
if (profile >= 0 && profile <= 3)
|
||
ctx.profile = profile;
|
||
else
|
||
throw new TypeError("Invalid VP9 profile");
|
||
}
|
||
if (codecParts[2]) {
|
||
const level = [+codecParts[2][0], +codecParts[2][1]];
|
||
if (level[0] >= 1 && level[0] <= 4) {
|
||
if (level[1] >= 0 && level[1] <= 1) ;
|
||
else {
|
||
throw new TypeError("Invalid VP9 level");
|
||
}
|
||
}
|
||
else if (level[0] >= 5 && level[0] <= 6) {
|
||
if (level[1] >= 0 && level[1] <= 2) ;
|
||
else {
|
||
throw new TypeError("Invalid VP9 level");
|
||
}
|
||
}
|
||
else {
|
||
throw new TypeError("Invalid VP9 level");
|
||
}
|
||
ctx.level = +codecParts[2];
|
||
}
|
||
if (codecParts[3]) {
|
||
const depth = +codecParts[3];
|
||
if (depth === 10 || depth === 12) {
|
||
// Valid but unsupported
|
||
return false;
|
||
}
|
||
else if (depth !== 8) {
|
||
throw new TypeError("Invalid VP9 bit depth");
|
||
}
|
||
}
|
||
if (codecParts[4]) {
|
||
const chromaMode = +codecParts[4];
|
||
switch (chromaMode) {
|
||
case 0:
|
||
case 1:
|
||
// FIXME: These are subtly different YUV420P modes, but we treat them the same
|
||
ctx.pix_fmt = 0 /* YUV420P */;
|
||
break;
|
||
case 2: // YUV422
|
||
ctx.pix_fmt = 4 /* YUV422P */;
|
||
break;
|
||
case 3: // YUV444
|
||
ctx.pix_fmt = 5 /* YUV444P */;
|
||
break;
|
||
default:
|
||
throw new TypeError("Invalid VP9 chroma subsampling format");
|
||
}
|
||
}
|
||
/* The remaining values have to do with color formats, which we don't
|
||
* support correctly anyway */
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
/**
|
||
* Clone this configuration. Just copies over the supported/recognized fields.
|
||
*/
|
||
function cloneConfig(config, fields) {
|
||
const ret = {};
|
||
for (const field of fields) {
|
||
if (field in config)
|
||
ret[field] = config[field];
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$7 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
let AudioDecoder$1 = class AudioDecoder extends DequeueEventTarget {
|
||
constructor(init) {
|
||
super();
|
||
// 1. Let d be a new AudioDecoder object.
|
||
// 2. Assign a new queue to [[control message queue]].
|
||
this._p = Promise.all([]);
|
||
// 3. Assign false to [[message queue blocked]].
|
||
// (unused in polyfill)
|
||
// 4. Assign null to [[codec implementation]].
|
||
this._libav = null;
|
||
this._codec = this._c = this._pkt = this._frame = 0;
|
||
// 5. Assign the result of starting a new parallel queue to [[codec work queue]].
|
||
// (shared with control message queue)
|
||
// 6. Assign false to [[codec saturated]].
|
||
// (codec is never saturated)
|
||
// 7. Assign init.output to [[output callback]].
|
||
this._output = init.output;
|
||
// 8. Assign init.error to [[error callback]].
|
||
this._error = init.error;
|
||
// 9. Assign true to [[key chunk required]].
|
||
// (implicit part of the underlying codec)
|
||
// 10. Assign "unconfigured" to [[state]]
|
||
this.state = "unconfigured";
|
||
// 11. Assign 0 to [[decodeQueueSize]].
|
||
this.decodeQueueSize = 0;
|
||
// 12. Assign a new list to [[pending flush promises]].
|
||
// (shared with control message queue)
|
||
// 13. Assign false to [[dequeue event scheduled]].
|
||
// (shared with control message queue)
|
||
// 14. Return d.
|
||
}
|
||
configure(config) {
|
||
// 1. If config is not a valid AudioDecoderConfig, throw a TypeError.
|
||
// NOTE: We don't support sophisticated codec string parsing (yet)
|
||
// 2. If [[state]] is “closed”, throw an InvalidStateError DOMException.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Decoder is closed", "InvalidStateError");
|
||
// Free any internal state
|
||
if (this._libav)
|
||
this._p = this._p.then(() => this._free());
|
||
// 3. Set [[state]] to "configured".
|
||
this.state = "configured";
|
||
// 4. Set [[key chunk required]] to true.
|
||
// (implicit part of underlying codecs)
|
||
// 5. Queue a control message to configure the decoder with config.
|
||
this._p = this._p.then(() => __awaiter$7(this, void 0, void 0, function* () {
|
||
/* 1. Let supported be the result of running the Check
|
||
* Configuration Support algorithm with config. */
|
||
let udesc = void 0;
|
||
if (config.description) {
|
||
if (ArrayBuffer.isView(config.description)) {
|
||
const descView = config.description;
|
||
udesc = new Uint8Array(descView.buffer, descView.byteOffset, descView.byteLength);
|
||
}
|
||
else {
|
||
const descBuf = config.description;
|
||
udesc = new Uint8Array(descBuf);
|
||
}
|
||
}
|
||
const supported = decoder(config.codec, config);
|
||
/* 2. If supported is false, queue a task to run the Close
|
||
* AudioDecoder algorithm with NotSupportedError and abort these
|
||
* steps. */
|
||
if (!supported) {
|
||
this._closeAudioDecoder(new DOMException("Unsupported codec", "NotSupportedError"));
|
||
return;
|
||
}
|
||
/* 3. If needed, assign [[codec implementation]] with an
|
||
* implementation supporting config. */
|
||
const libav = this._libav = yield get();
|
||
const codecpara = yield libav.avcodec_parameters_alloc();
|
||
const ps = [
|
||
libav.AVCodecParameters_channels_s(codecpara, config.numberOfChannels),
|
||
libav.AVCodecParameters_sample_rate_s(codecpara, config.sampleRate),
|
||
libav.AVCodecParameters_codec_type_s(codecpara, 1 /* AVMEDIA_TYPE_AUDIO */)
|
||
];
|
||
let extraDataPtr = 0;
|
||
if (!udesc) {
|
||
ps.push(libav.AVCodecParameters_extradata_s(codecpara, 0));
|
||
ps.push(libav.AVCodecParameters_extradata_size_s(codecpara, 0));
|
||
}
|
||
else {
|
||
ps.push(libav.AVCodecParameters_extradata_size_s(codecpara, udesc.byteLength));
|
||
extraDataPtr = yield libav.calloc(udesc.byteLength + 64 /* AV_INPUT_BUFFER_PADDING_SIZE */, 1);
|
||
ps.push(libav.copyin_u8(extraDataPtr, udesc));
|
||
ps.push(libav.AVCodecParameters_extradata_s(codecpara, extraDataPtr));
|
||
}
|
||
yield Promise.all(ps);
|
||
// 4. Configure [[codec implementation]] with config.
|
||
[this._codec, this._c, this._pkt, this._frame] =
|
||
yield libav.ff_init_decoder(supported.codec, codecpara);
|
||
const fps = [
|
||
libav.AVCodecContext_time_base_s(this._c, 1, 1000),
|
||
libav.avcodec_parameters_free_js(codecpara)
|
||
];
|
||
if (extraDataPtr)
|
||
fps.push(libav.free(extraDataPtr));
|
||
yield Promise.all(fps);
|
||
// 5. queue a task to run the following steps:
|
||
// 1. Assign false to [[message queue blocked]].
|
||
// 2. Queue a task to Process the control message queue.
|
||
// (shared queue)
|
||
})).catch(this._error);
|
||
}
|
||
// Our own algorithm, close libav
|
||
_free() {
|
||
return __awaiter$7(this, void 0, void 0, function* () {
|
||
if (this._c) {
|
||
yield this._libav.ff_free_decoder(this._c, this._pkt, this._frame);
|
||
this._codec = this._c = this._pkt = this._frame = 0;
|
||
}
|
||
if (this._libav) {
|
||
free(this._libav);
|
||
this._libav = null;
|
||
}
|
||
});
|
||
}
|
||
_closeAudioDecoder(exception) {
|
||
// 1. Run the Reset AudioDecoder algorithm with exception.
|
||
this._resetAudioDecoder(exception);
|
||
// 2. Set [[state]] to "closed".
|
||
this.state = "closed";
|
||
/* 3. Clear [[codec implementation]] and release associated system
|
||
* resources. */
|
||
this._p = this._p.then(() => this._free());
|
||
/* 4. If exception is not an AbortError DOMException, queue a task on
|
||
* the control thread event loop to invoke the [[error callback]] with
|
||
* exception. */
|
||
if (exception.name !== "AbortError")
|
||
this._p = this._p.then(() => { this._error(exception); });
|
||
}
|
||
_resetAudioDecoder(exception) {
|
||
// 1. If [[state]] is "closed", throw an InvalidStateError.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Decoder closed", "InvalidStateError");
|
||
// 2. Set [[state]] to "unconfigured".
|
||
this.state = "unconfigured";
|
||
// ... really, we're just going to free it now
|
||
this._p = this._p.then(() => this._free());
|
||
}
|
||
decode(chunk) {
|
||
// 1. If [[state]] is not "configured", throw an InvalidStateError.
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Unconfigured", "InvalidStateError");
|
||
// 2. If [[key chunk required]] is true:
|
||
// 1. If chunk.[[type]] is not key, throw a DataError.
|
||
/* 2. Implementers SHOULD inspect the chunk’s [[internal data]] to
|
||
* verify that it is truly a key chunk. If a mismatch is detected,
|
||
* throw a DataError. */
|
||
// 3. Otherwise, assign false to [[key chunk required]].
|
||
// (handled within the codec)
|
||
// 3. Increment [[decodeQueueSize]].
|
||
this.decodeQueueSize++;
|
||
// 4. Queue a control message to decode the chunk.
|
||
this._p = this._p.then(() => __awaiter$7(this, void 0, void 0, function* () {
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const pkt = this._pkt;
|
||
const frame = this._frame;
|
||
let decodedOutputs = null;
|
||
// (1. and 2. relate to saturation)
|
||
// 3. Decrement [[decodeQueueSize]] and run the Schedule Dequeue Event algorithm.
|
||
this.decodeQueueSize--;
|
||
this.dispatchEvent(new CustomEvent("dequeue"));
|
||
// 1. Attempt to use [[codec implementation]] to decode the chunk.
|
||
try {
|
||
// Convert to a libav packet
|
||
const ptsFull = Math.floor(chunk.timestamp / 1000);
|
||
const [pts, ptshi] = libav.f64toi64(ptsFull);
|
||
const packet = {
|
||
data: chunk._libavGetData(),
|
||
pts,
|
||
ptshi,
|
||
dts: pts,
|
||
dtshi: ptshi
|
||
};
|
||
if (chunk.duration) {
|
||
packet.duration = Math.floor(chunk.duration / 1000);
|
||
packet.durationhi = 0;
|
||
}
|
||
decodedOutputs = yield libav.ff_decode_multi(c, pkt, frame, [packet]);
|
||
/* 2. If decoding results in an error, queue a task to run the Close
|
||
* AudioDecoder algorithm with EncodingError and return. */
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeAudioDecoder(ex);
|
||
});
|
||
return;
|
||
}
|
||
/* 3. If [[codec saturated]] equals true and
|
||
* [[codec implementation]] is no longer saturated, queue a task
|
||
* to perform the following steps: */
|
||
// 1. Assign false to [[codec saturated]].
|
||
// 2. Process the control message queue.
|
||
// (no saturation)
|
||
/* 4. Let decoded outputs be a list of decoded audio data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
/* 5. If decoded outputs is not empty, queue a task to run the
|
||
* Output AudioData algorithm with decoded outputs. */
|
||
if (decodedOutputs)
|
||
this._outputAudioData(decodedOutputs);
|
||
})).catch(this._error);
|
||
}
|
||
_outputAudioData(outputs) {
|
||
const libav = this._libav;
|
||
for (const frame of outputs) {
|
||
// 1. format
|
||
let format;
|
||
let planar = false;
|
||
switch (frame.format) {
|
||
case libav.AV_SAMPLE_FMT_U8:
|
||
format = "u8";
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_S16:
|
||
format = "s16";
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_S32:
|
||
format = "s32";
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_FLT:
|
||
format = "f32";
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_U8P:
|
||
format = "u8";
|
||
planar = true;
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_S16P:
|
||
format = "s16";
|
||
planar = true;
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_S32P:
|
||
format = "s32";
|
||
planar = true;
|
||
break;
|
||
case libav.AV_SAMPLE_FMT_FLTP:
|
||
format = "f32";
|
||
planar = true;
|
||
break;
|
||
default:
|
||
throw new DOMException("Unsupported libav format!", "EncodingError");
|
||
}
|
||
// 2. sampleRate
|
||
const sampleRate = frame.sample_rate;
|
||
// 3. numberOfFrames
|
||
const numberOfFrames = frame.nb_samples;
|
||
// 4. numberOfChannels
|
||
const numberOfChannels = frame.channels;
|
||
// 5. timestamp
|
||
const timestamp = libav.i64tof64(frame.pts, frame.ptshi) * 1000;
|
||
// 6. data
|
||
let raw;
|
||
if (planar) {
|
||
let ct = 0;
|
||
for (let i = 0; i < frame.data.length; i++)
|
||
ct += frame.data[i].length;
|
||
raw = new (frame.data[0].constructor)(ct);
|
||
ct = 0;
|
||
for (let i = 0; i < frame.data.length; i++) {
|
||
const part = frame.data[i];
|
||
raw.set(part, ct);
|
||
ct += part.length;
|
||
}
|
||
}
|
||
else {
|
||
raw = frame.data;
|
||
}
|
||
const data = new AudioData$1({
|
||
format, sampleRate, numberOfFrames, numberOfChannels,
|
||
timestamp, data: raw
|
||
});
|
||
this._output(data);
|
||
}
|
||
}
|
||
flush() {
|
||
/* 1. If [[state]] is not "configured", return a promise rejected with
|
||
* InvalidStateError DOMException. */
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Invalid state", "InvalidStateError");
|
||
// 2. Set [[key chunk required]] to true.
|
||
// (part of the codec)
|
||
// 3. Let promise be a new Promise.
|
||
// 4. Append promise to [[pending flush promises]].
|
||
// 5. Queue a control message to flush the codec with promise.
|
||
// 6. Process the control message queue.
|
||
// 7. Return promise.
|
||
const ret = this._p.then(() => __awaiter$7(this, void 0, void 0, function* () {
|
||
// 1. Signal [[codec implementation]] to emit all internal pending outputs.
|
||
if (!this._c)
|
||
return;
|
||
// Make sure any last data is flushed
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const pkt = this._pkt;
|
||
const frame = this._frame;
|
||
let decodedOutputs = null;
|
||
try {
|
||
decodedOutputs = yield libav.ff_decode_multi(c, pkt, frame, [], true);
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeAudioDecoder(ex);
|
||
});
|
||
}
|
||
/* 2. Let decoded outputs be a list of decoded audio data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
// 3. Queue a task to perform these steps:
|
||
{
|
||
/* 1. If decoded outputs is not empty, run the Output AudioData
|
||
* algorithm with decoded outputs. */
|
||
if (decodedOutputs)
|
||
this._outputAudioData(decodedOutputs);
|
||
// 2. Remove promise from [[pending flush promises]].
|
||
// 3. Resolve promise.
|
||
}
|
||
}));
|
||
this._p = ret;
|
||
return ret;
|
||
}
|
||
reset() {
|
||
this._resetAudioDecoder(new DOMException("Reset", "AbortError"));
|
||
}
|
||
close() {
|
||
this._closeAudioDecoder(new DOMException("Close", "AbortError"));
|
||
}
|
||
static isConfigSupported(config) {
|
||
return __awaiter$7(this, void 0, void 0, function* () {
|
||
const dec = decoder(config.codec, config);
|
||
let supported = false;
|
||
if (dec) {
|
||
const libav = yield get();
|
||
try {
|
||
const [, c, pkt, frame] = yield libav.ff_init_decoder(dec.codec);
|
||
yield libav.ff_free_decoder(c, pkt, frame);
|
||
supported = true;
|
||
}
|
||
catch (ex) { }
|
||
yield free(libav);
|
||
}
|
||
return {
|
||
supported,
|
||
config: cloneConfig(config, ["codec", "sampleRate", "numberOfChannels"])
|
||
};
|
||
});
|
||
}
|
||
};
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$6 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
let AudioEncoder$1 = class AudioEncoder extends DequeueEventTarget {
|
||
constructor(init) {
|
||
super();
|
||
// Metadata argument for output
|
||
this._outputMetadata = null;
|
||
this._outputMetadataFilled = false;
|
||
this._pts = null;
|
||
// 1. Let e be a new AudioEncoder object.
|
||
// 2. Assign a new queue to [[control message queue]].
|
||
this._p = Promise.all([]);
|
||
// 3. Assign false to [[message queue blocked]].
|
||
// (unused in polyfill)
|
||
// 4. Assign null to [[codec implementation]].
|
||
this._libav = null;
|
||
this._codec = this._c = this._frame = this._pkt = 0;
|
||
this._filter_in_ctx = this._filter_out_ctx = null;
|
||
this._filter_graph = this._buffersrc_ctx = this._buffersink_ctx = 0;
|
||
/* 5. Assign the result of starting a new parallel queue to
|
||
* [[codec work queue]]. */
|
||
// (shared queue)
|
||
// 6. Assign false to [[codec saturated]].
|
||
// (saturation unneeded in the polyfill)
|
||
// 7. Assign init.output to [[output callback]].
|
||
this._output = init.output;
|
||
// 8. Assign init.error to [[error callback]].
|
||
this._error = init.error;
|
||
// 9. Assign null to [[active encoder config]].
|
||
// 10. Assign null to [[active output config]].
|
||
// (both part of the codec)
|
||
// 11. Assign "unconfigured" to [[state]]
|
||
this.state = "unconfigured";
|
||
// 12. Assign 0 to [[encodeQueueSize]].
|
||
this.encodeQueueSize = 0;
|
||
// 13. Assign a new list to [[pending flush promises]].
|
||
// 14. Assign false to [[dequeue event scheduled]].
|
||
// (shared queue)
|
||
// 15. Return e.
|
||
}
|
||
configure(config) {
|
||
const self = this;
|
||
// 1. If config is not a valid AudioEncoderConfig, throw a TypeError.
|
||
// NOTE: We don't support sophisticated codec string parsing (yet)
|
||
// 2. If [[state]] is "closed", throw an InvalidStateError.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Encoder is closed", "InvalidStateError");
|
||
// Free any internal state
|
||
if (this._libav)
|
||
this._p = this._p.then(() => this._free());
|
||
// 3. Set [[state]] to "configured".
|
||
this.state = "configured";
|
||
// 4. Queue a control message to configure the encoder using config.
|
||
this._p = this._p.then(function () {
|
||
return __awaiter$6(this, void 0, void 0, function* () {
|
||
/* 1. Let supported be the result of running the Check
|
||
* Configuration Support algorithm with config. */
|
||
const supported = encoder(config.codec, config);
|
||
// Get the output metadata now
|
||
self._outputMetadata = { decoderConfig: {
|
||
codec: config.codec,
|
||
// Rest will be filled in when we get data
|
||
sampleRate: 0,
|
||
numberOfChannels: 0
|
||
} };
|
||
self._outputMetadataFilled = false;
|
||
/* 2. If supported is false, queue a task to run the Close
|
||
* AudioEncoder algorithm with NotSupportedError and abort these
|
||
* steps. */
|
||
if (!supported) {
|
||
self._closeAudioEncoder(new DOMException("Unsupported codec", "NotSupportedError"));
|
||
return;
|
||
}
|
||
/* 3. If needed, assign [[codec implementation]] with an
|
||
* implementation supporting config. */
|
||
// 4. Configure [[codec implementation]] with config.
|
||
const libav = self._libav = yield get();
|
||
// And initialize
|
||
let frame_size;
|
||
[self._codec, self._c, self._frame, self._pkt, frame_size] =
|
||
yield libav.ff_init_encoder(supported.codec, supported);
|
||
self._pts = null;
|
||
yield libav.AVCodecContext_time_base_s(self._c, 1, supported.ctx.sample_rate);
|
||
// Be ready to set up the filter
|
||
self._filter_out_ctx = {
|
||
sample_rate: supported.ctx.sample_rate,
|
||
sample_fmt: supported.ctx.sample_fmt,
|
||
channel_layout: supported.ctx.channel_layout,
|
||
frame_size
|
||
};
|
||
// 5. queue a task to run the following steps:
|
||
// 1. Assign false to [[message queue blocked]].
|
||
// 2. Queue a task to Process the control message queue.
|
||
// (shared queue)
|
||
});
|
||
}).catch(this._error);
|
||
}
|
||
// Our own algorithm, close libav
|
||
_free() {
|
||
return __awaiter$6(this, void 0, void 0, function* () {
|
||
if (this._filter_graph) {
|
||
yield this._libav.avfilter_graph_free_js(this._filter_graph);
|
||
this._filter_in_ctx = this._filter_out_ctx = null;
|
||
this._filter_graph = this._buffersrc_ctx = this._buffersink_ctx =
|
||
0;
|
||
}
|
||
if (this._c) {
|
||
yield this._libav.ff_free_encoder(this._c, this._frame, this._pkt);
|
||
this._codec = this._c = this._frame = this._pkt = 0;
|
||
}
|
||
if (this._libav) {
|
||
free(this._libav);
|
||
this._libav = null;
|
||
}
|
||
});
|
||
}
|
||
_closeAudioEncoder(exception) {
|
||
// 1. Run the Reset AudioEncoder algorithm with exception.
|
||
this._resetAudioEncoder(exception);
|
||
// 2. Set [[state]] to "closed".
|
||
this.state = "closed";
|
||
/* 3. Clear [[codec implementation]] and release associated system
|
||
* resources. */
|
||
this._p = this._p.then(() => this._free());
|
||
/* 4. If exception is not an AbortError DOMException, invoke the
|
||
* [[error callback]] with exception. */
|
||
if (exception.name !== "AbortError")
|
||
this._p = this._p.then(() => { this._error(exception); });
|
||
}
|
||
_resetAudioEncoder(exception) {
|
||
// 1. If [[state]] is "closed", throw an InvalidStateError.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Encoder closed", "InvalidStateError");
|
||
// 2. Set [[state]] to "unconfigured".
|
||
this.state = "unconfigured";
|
||
// ... really, we're just going to free it now
|
||
this._p = this._p.then(() => this._free());
|
||
}
|
||
encode(data) {
|
||
/* 1. If the value of data’s [[Detached]] internal slot is true, throw
|
||
* a TypeError. */
|
||
if (data._libavGetData() === null)
|
||
throw new TypeError("Detached");
|
||
// 2. If [[state]] is not "configured", throw an InvalidStateError.
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Unconfigured", "InvalidStateError");
|
||
/* 3. Let dataClone hold the result of running the Clone AudioData
|
||
* algorithm with data. */
|
||
const dataClone = data.clone();
|
||
// 4. Increment [[encodeQueueSize]].
|
||
this.encodeQueueSize++;
|
||
// 5. Queue a control message to encode dataClone.
|
||
this._p = this._p.then(() => __awaiter$6(this, void 0, void 0, function* () {
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const pkt = this._pkt;
|
||
const framePtr = this._frame;
|
||
let encodedOutputs = null;
|
||
/* 3. Decrement [[encodeQueueSize]] and run the Schedule Dequeue
|
||
* Event algorithm. */
|
||
this.encodeQueueSize--;
|
||
this.dispatchEvent(new CustomEvent("dequeue"));
|
||
/* 1. Attempt to use [[codec implementation]] to encode the media
|
||
* resource described by dataClone. */
|
||
try {
|
||
// Arrange the data
|
||
let raw = dataClone._libavGetData();
|
||
const nb_samples = dataClone.numberOfFrames;
|
||
if (!isInterleaved(dataClone.format)) {
|
||
let split = [];
|
||
for (let i = 0; i < dataClone.numberOfChannels; i++)
|
||
split.push(raw.subarray(i * nb_samples, (i + 1) * nb_samples));
|
||
raw = split;
|
||
}
|
||
// Convert the format
|
||
let format;
|
||
switch (dataClone.format) {
|
||
case "u8":
|
||
format = libav.AV_SAMPLE_FMT_U8;
|
||
break;
|
||
case "s16":
|
||
format = libav.AV_SAMPLE_FMT_S16;
|
||
break;
|
||
case "s32":
|
||
format = libav.AV_SAMPLE_FMT_S32;
|
||
break;
|
||
case "f32":
|
||
format = libav.AV_SAMPLE_FMT_FLT;
|
||
break;
|
||
case "u8-planar":
|
||
format = libav.AV_SAMPLE_FMT_U8P;
|
||
break;
|
||
case "s16-planar":
|
||
format = libav.AV_SAMPLE_FMT_S16P;
|
||
break;
|
||
case "s32-planar":
|
||
format = libav.AV_SAMPLE_FMT_S32P;
|
||
break;
|
||
case "f32-planar":
|
||
format = libav.AV_SAMPLE_FMT_FLTP;
|
||
break;
|
||
default:
|
||
throw new TypeError("Invalid AudioSampleFormat");
|
||
}
|
||
// Convert the timestamp
|
||
const ptsFull = Math.floor(dataClone.timestamp / 1000);
|
||
const [pts, ptshi] = libav.f64toi64(ptsFull);
|
||
// Convert the channel layout
|
||
const cc = dataClone.numberOfChannels;
|
||
const channel_layout = (cc === 1) ? 4 : ((1 << cc) - 1);
|
||
// Make the frame
|
||
const sample_rate = dataClone.sampleRate;
|
||
const frame = {
|
||
data: raw,
|
||
format, pts, ptshi, channel_layout, sample_rate
|
||
};
|
||
// Check if the filter needs to be reconfigured
|
||
let preOutputs = null;
|
||
if (this._filter_in_ctx) {
|
||
const filter_ctx = this._filter_in_ctx;
|
||
if (filter_ctx.sample_fmt !== frame.format ||
|
||
filter_ctx.channel_layout !== frame.channel_layout ||
|
||
filter_ctx.sample_rate !== frame.sample_rate) {
|
||
// Need a new filter! First, get anything left in the filter
|
||
let fframes = yield this._filter([], true);
|
||
// Can't send partial frames through the encoder
|
||
fframes = fframes.filter(x => {
|
||
let frame_size;
|
||
if (x.data[0].length) {
|
||
// Planar
|
||
frame_size = x.data[0].length;
|
||
}
|
||
else {
|
||
frame_size = x.data.length / x.channels;
|
||
}
|
||
return frame_size === this._filter_out_ctx.frame_size;
|
||
});
|
||
if (fframes.length) {
|
||
preOutputs =
|
||
yield libav.ff_encode_multi(c, framePtr, pkt, fframes);
|
||
}
|
||
yield libav.avfilter_graph_free_js(this._filter_graph);
|
||
this._filter_in_ctx = null;
|
||
this._filter_graph = this._buffersrc_ctx =
|
||
this._buffersink_ctx = 0;
|
||
}
|
||
}
|
||
// Set up the filter
|
||
if (!this._filter_graph) {
|
||
const filter_ctx = this._filter_in_ctx = {
|
||
sample_rate: frame.sample_rate,
|
||
sample_fmt: frame.format,
|
||
channel_layout: frame.channel_layout
|
||
};
|
||
[this._filter_graph, this._buffersrc_ctx, this._buffersink_ctx] =
|
||
yield libav.ff_init_filter_graph("aresample", filter_ctx, this._filter_out_ctx);
|
||
}
|
||
// Filter
|
||
const fframes = yield this._filter([frame]);
|
||
// And encode
|
||
encodedOutputs =
|
||
yield libav.ff_encode_multi(c, framePtr, pkt, fframes);
|
||
if (preOutputs)
|
||
encodedOutputs = preOutputs.concat(encodedOutputs);
|
||
if (encodedOutputs.length && !this._outputMetadataFilled &&
|
||
fframes && fframes.length)
|
||
yield this._getOutputMetadata(fframes[0]);
|
||
/* 2. If encoding results in an error, queue a task on the control
|
||
* thread event loop to run the Close AudioEncoder algorithm with
|
||
* EncodingError. */
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeAudioEncoder(ex);
|
||
});
|
||
}
|
||
/* 3. If [[codec saturated]] equals true and
|
||
* [[codec implementation]] is no longer saturated, queue a task
|
||
* to perform the following steps: */
|
||
// 1. Assign false to [[codec saturated]].
|
||
// 2. Process the control message queue.
|
||
// (no saturation)
|
||
/* 4. Let encoded outputs be a list of encoded audio data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
/* 5. If encoded outputs is not empty, queue a task to run the
|
||
* Output EncodedAudioChunks algorithm with encoded outputs. */
|
||
if (encodedOutputs)
|
||
this._outputEncodedAudioChunks(encodedOutputs);
|
||
})).catch(this._error);
|
||
}
|
||
// Internal: Filter the given audio
|
||
_filter(frames, fin = false) {
|
||
return __awaiter$6(this, void 0, void 0, function* () {
|
||
/* The specification does not state how timestamps should be related
|
||
* between input and output. It's obvious that the timestamps should
|
||
* increase at the appropriate rate based on the number of samples seen,
|
||
* but where they should start is not stated. Google Chrome starts with
|
||
* the timestamp of the first input frame, and ignores all other input
|
||
* frame timestamps. We follow that convention as well. */
|
||
if (frames.length && this._pts === null)
|
||
this._pts = (frames[0].pts || 0);
|
||
const fframes = yield this._libav.ff_filter_multi(this._buffersrc_ctx, this._buffersink_ctx, this._frame, frames, fin);
|
||
for (const frame of fframes) {
|
||
frame.pts = this._pts;
|
||
frame.ptshi = 0;
|
||
this._pts += frame.nb_samples;
|
||
}
|
||
return fframes;
|
||
});
|
||
}
|
||
// Internal: Get output metadata
|
||
_getOutputMetadata(frame) {
|
||
return __awaiter$6(this, void 0, void 0, function* () {
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const extradataPtr = yield libav.AVCodecContext_extradata(c);
|
||
const extradata_size = yield libav.AVCodecContext_extradata_size(c);
|
||
let extradata = null;
|
||
if (extradataPtr && extradata_size)
|
||
extradata = yield libav.copyout_u8(extradataPtr, extradata_size);
|
||
this._outputMetadata.decoderConfig.sampleRate = frame.sample_rate;
|
||
this._outputMetadata.decoderConfig.numberOfChannels = frame.channels;
|
||
if (extradata)
|
||
this._outputMetadata.decoderConfig.description = extradata;
|
||
this._outputMetadataFilled = true;
|
||
});
|
||
}
|
||
_outputEncodedAudioChunks(packets) {
|
||
const libav = this._libav;
|
||
const sampleRate = this._filter_out_ctx.sample_rate;
|
||
for (const packet of packets) {
|
||
// 1. type
|
||
const type = (packet.flags & 1) ? "key" : "delta";
|
||
// 2. timestamp
|
||
let timestamp = libav.i64tof64(packet.pts, packet.ptshi);
|
||
timestamp = Math.floor(timestamp / sampleRate * 1000000);
|
||
const chunk = new EncodedAudioChunk$1({
|
||
type, timestamp,
|
||
data: packet.data
|
||
});
|
||
if (this._outputMetadataFilled)
|
||
this._output(chunk, this._outputMetadata || void 0);
|
||
else
|
||
this._output(chunk);
|
||
}
|
||
}
|
||
flush() {
|
||
/* 1. If [[state]] is not "configured", return a promise rejected with
|
||
* InvalidStateError DOMException. */
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Invalid state", "InvalidStateError");
|
||
// 2. Let promise be a new Promise.
|
||
// 3. Append promise to [[pending flush promises]].
|
||
// 4. Queue a control message to flush the codec with promise.
|
||
// 5. Process the control message queue.
|
||
// 6. Return promise.
|
||
const ret = this._p.then(() => __awaiter$6(this, void 0, void 0, function* () {
|
||
if (!this._c)
|
||
return;
|
||
/* 1. Signal [[codec implementation]] to emit all internal pending
|
||
* outputs. */
|
||
// Make sure any last data is flushed
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const frame = this._frame;
|
||
const pkt = this._pkt;
|
||
const buffersrc_ctx = this._buffersrc_ctx;
|
||
this._buffersink_ctx;
|
||
let encodedOutputs = null;
|
||
try {
|
||
let fframes = null;
|
||
if (buffersrc_ctx)
|
||
fframes = yield this._filter([], true);
|
||
encodedOutputs =
|
||
yield libav.ff_encode_multi(c, frame, pkt, fframes || [], true);
|
||
if (!this._outputMetadataFilled && fframes && fframes.length)
|
||
yield this._getOutputMetadata(fframes[0]);
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeAudioEncoder(ex);
|
||
});
|
||
}
|
||
/* 2. Let encoded outputs be a list of encoded audio data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
// 3. Queue a task to perform these steps:
|
||
{
|
||
/* 1. If encoded outputs is not empty, run the Output
|
||
* EncodedAudioChunks algorithm with encoded outputs. */
|
||
if (encodedOutputs)
|
||
this._outputEncodedAudioChunks(encodedOutputs);
|
||
// 2. Remove promise from [[pending flush promises]].
|
||
// 3. Resolve promise.
|
||
// (shared queue)
|
||
}
|
||
}));
|
||
this._p = ret;
|
||
return ret;
|
||
}
|
||
reset() {
|
||
this._resetAudioEncoder(new DOMException("Reset", "AbortError"));
|
||
}
|
||
close() {
|
||
this._closeAudioEncoder(new DOMException("Close", "AbortError"));
|
||
}
|
||
static isConfigSupported(config) {
|
||
return __awaiter$6(this, void 0, void 0, function* () {
|
||
const enc = encoder(config.codec, config);
|
||
let supported = false;
|
||
if (enc) {
|
||
const libav = yield get();
|
||
try {
|
||
const [, c, frame, pkt] = yield libav.ff_init_encoder(enc.codec, enc);
|
||
yield libav.ff_free_encoder(c, frame, pkt);
|
||
supported = true;
|
||
}
|
||
catch (ex) { }
|
||
yield free(libav);
|
||
}
|
||
return {
|
||
supported,
|
||
config: cloneConfig(config, ["codec", "sampleRate", "numberOfChannels", "bitrate"])
|
||
};
|
||
});
|
||
}
|
||
};
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
const EncodedVideoChunk$1 = EncodedAudioChunk$1;
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$5 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
// A canvas element used to convert CanvasImageSources to buffers
|
||
let offscreenCanvas = null;
|
||
let VideoFrame$1 = class VideoFrame {
|
||
constructor(data, init) {
|
||
/* NOTE: These should all be readonly, but the constructor style above
|
||
* doesn't work with that */
|
||
this.format = "I420";
|
||
this.codedWidth = 0;
|
||
this.codedHeight = 0;
|
||
this.codedRect = null;
|
||
this.visibleRect = null;
|
||
this.displayWidth = 0;
|
||
this.displayHeight = 0;
|
||
this.timestamp = 0; // microseconds
|
||
this._layout = null;
|
||
this._data = null;
|
||
/**
|
||
* (Internal) Does this use non-square pixels?
|
||
*/
|
||
this._nonSquarePixels = false;
|
||
/**
|
||
* (Internal) If non-square pixels, the SAR (sample/pixel aspect ratio)
|
||
*/
|
||
this._sar_num = 1;
|
||
this._sar_den = 1;
|
||
if (data instanceof ArrayBuffer ||
|
||
data.buffer instanceof ArrayBuffer) {
|
||
this._constructBuffer(data, init);
|
||
}
|
||
else if (data instanceof VideoFrame ||
|
||
(globalThis.VideoFrame && data instanceof globalThis.VideoFrame)) {
|
||
const array = new Uint8Array(data.allocationSize());
|
||
data.copyTo(array);
|
||
this._constructBuffer(array, {
|
||
transfer: [array.buffer],
|
||
// 1. Let format be otherFrame.format.
|
||
/* 2. FIXME: If init.alpha is discard, assign
|
||
* otherFrame.format's equivalent opaque format format. */
|
||
format: data.format,
|
||
/* 3. Let validInit be the result of running the Validate
|
||
* VideoFrameInit algorithm with format and otherFrame’s
|
||
* [[coded width]] and [[coded height]]. */
|
||
// 4. If validInit is false, throw a TypeError.
|
||
/* 7. Assign the following attributes from otherFrame to frame:
|
||
* codedWidth, codedHeight, colorSpace. */
|
||
codedHeight: data.codedHeight,
|
||
codedWidth: data.codedWidth,
|
||
colorSpace: data.colorSpace,
|
||
/* 8. Let defaultVisibleRect be the result of performing the
|
||
* getter steps for visibleRect on otherFrame. */
|
||
/* 9. Let defaultDisplayWidth, and defaultDisplayHeight be
|
||
* otherFrame’s [[display width]], and [[display height]]
|
||
* respectively. */
|
||
/* 10. Run the Initialize Visible Rect and Display Size
|
||
* algorithm with init, frame, defaultVisibleRect,
|
||
* defaultDisplayWidth, and defaultDisplayHeight. */
|
||
visibleRect: (init === null || init === void 0 ? void 0 : init.visibleRect) || data.visibleRect,
|
||
displayHeight: (init === null || init === void 0 ? void 0 : init.displayHeight) || data.displayHeight,
|
||
displayWidth: (init === null || init === void 0 ? void 0 : init.displayWidth) || data.displayWidth,
|
||
/* 11. If duration exists in init, assign it to frame’s
|
||
* [[duration]]. Otherwise, assign otherFrame.duration to
|
||
* frame’s [[duration]]. */
|
||
duration: (init === null || init === void 0 ? void 0 : init.duration) || data.duration,
|
||
/* 12. If timestamp exists in init, assign it to frame’s
|
||
* [[timestamp]]. Otherwise, assign otherFrame’s timestamp to
|
||
* frame’s [[timestamp]]. */
|
||
timestamp: (init === null || init === void 0 ? void 0 : init.timestamp) || data.timestamp,
|
||
/* Assign the result of calling Copy VideoFrame metadata with
|
||
* init’s metadata to frame.[[metadata]]. */
|
||
metadata: JSON.parse(JSON.stringify(init === null || init === void 0 ? void 0 : init.metadata))
|
||
});
|
||
}
|
||
else if (data instanceof HTMLVideoElement) {
|
||
/* Check the usability of the image argument. If this throws an
|
||
* exception or returns bad, then throw an InvalidStateError
|
||
* DOMException. */
|
||
if (data.readyState === HTMLVideoElement.prototype.HAVE_NOTHING
|
||
|| data.readyState === HTMLVideoElement.prototype.HAVE_METADATA) {
|
||
throw new DOMException("Video is not ready for reading frames", "InvalidStateError");
|
||
}
|
||
// If image’s networkState attribute is NETWORK_EMPTY, then throw an InvalidStateError DOMException.
|
||
if (data.networkState === data.NETWORK_EMPTY) {
|
||
throw new DOMException("Video network state is empty", "InvalidStateError");
|
||
}
|
||
this._constructCanvas(data, Object.assign(Object.assign({}, init), { timestamp: (init === null || init === void 0 ? void 0 : init.timestamp) || data.currentTime * 1e6 }));
|
||
}
|
||
else {
|
||
this._constructCanvas(data, init);
|
||
}
|
||
}
|
||
_constructCanvas(image, init) {
|
||
/* The spec essentially re-specifies “draw it”, and has specific
|
||
* instructions for each sort of thing it might be. So, we don't
|
||
* document all the steps here, we just... draw it. */
|
||
// Get the width and height
|
||
let width = 0, height = 0;
|
||
if (image.naturalWidth) {
|
||
width = image.naturalWidth;
|
||
height = image.naturalHeight;
|
||
}
|
||
else if (image.videoWidth) {
|
||
width = image.videoWidth;
|
||
height = image.videoHeight;
|
||
}
|
||
else if (image.width) {
|
||
width = image.width;
|
||
height = image.height;
|
||
}
|
||
if (!width || !height)
|
||
throw new DOMException("Could not determine dimensions", "InvalidStateError");
|
||
if (offscreenCanvas === null) {
|
||
if (typeof OffscreenCanvas !== "undefined") {
|
||
offscreenCanvas = new OffscreenCanvas(width, height);
|
||
}
|
||
else {
|
||
offscreenCanvas = document.createElement("canvas");
|
||
offscreenCanvas.style.display = "none";
|
||
document.body.appendChild(offscreenCanvas);
|
||
}
|
||
}
|
||
offscreenCanvas.width = width;
|
||
offscreenCanvas.height = height;
|
||
const options = { desynchronized: true, willReadFrequently: true };
|
||
const ctx = offscreenCanvas.getContext("2d", options);
|
||
ctx.clearRect(0, 0, width, height);
|
||
ctx.drawImage(image, 0, 0);
|
||
this._constructBuffer(ctx.getImageData(0, 0, width, height).data, {
|
||
format: "RGBA",
|
||
codedWidth: width,
|
||
codedHeight: height,
|
||
timestamp: (init === null || init === void 0 ? void 0 : init.timestamp) || 0,
|
||
duration: (init === null || init === void 0 ? void 0 : init.duration) || 0,
|
||
layout: [{ offset: 0, stride: width * 4 }],
|
||
displayWidth: (init === null || init === void 0 ? void 0 : init.displayWidth) || width,
|
||
displayHeight: (init === null || init === void 0 ? void 0 : init.displayHeight) || height
|
||
});
|
||
}
|
||
_constructBuffer(data, init) {
|
||
// 1. If init is not a valid VideoFrameBufferInit, throw a TypeError.
|
||
VideoFrame._checkValidVideoFrameBufferInit(init);
|
||
/* 2. Let defaultRect be «[ "x:" → 0, "y" → 0, "width" →
|
||
* init.codedWidth, "height" → init.codedWidth ]». */
|
||
const defaultRect = new DOMRect(0, 0, init.codedWidth, init.codedHeight);
|
||
// 3. Let overrideRect be undefined.
|
||
let overrideRect = void 0;
|
||
// 4. If init.visibleRect exists, assign its value to overrideRect.
|
||
if (init.visibleRect)
|
||
overrideRect = DOMRect.fromRect(init.visibleRect);
|
||
/* 5. Let parsedRect be the result of running the Parse Visible Rect
|
||
* algorithm with defaultRect, overrideRect, init.codedWidth,
|
||
* init.codedHeight, and init.format. */
|
||
// 6. If parsedRect is an exception, return parsedRect.
|
||
this.codedWidth = init.codedWidth; // (for _parseVisibleRect)
|
||
this.codedHeight = init.codedHeight;
|
||
const parsedRect = this._parseVisibleRect(defaultRect, overrideRect || null);
|
||
// 7. Let optLayout be undefined.
|
||
let optLayout = void 0;
|
||
// 8. If init.layout exists, assign its value to optLayout.
|
||
if (init.layout) {
|
||
if (init.layout instanceof Array)
|
||
optLayout = init.layout;
|
||
else
|
||
optLayout = Array.from(init.layout);
|
||
}
|
||
/* 9. Let combinedLayout be the result of running the Compute Layout
|
||
* and Allocation Size algorithm with parsedRect, init.format, and
|
||
* optLayout. */
|
||
// 10. If combinedLayout is an exception, throw combinedLayout.
|
||
this.format = init.format; // (needed for _computeLayoutAndAllocationSize)
|
||
const combinedLayout = this._computeLayoutAndAllocationSize(parsedRect, optLayout || null);
|
||
/* 11. If data.byteLength is less than combinedLayout’s allocationSize,
|
||
* throw a TypeError. */
|
||
if (data.byteLength < combinedLayout.allocationSize)
|
||
throw new TypeError("data is too small for layout");
|
||
/* 12. If init.transfer contains more than one reference to the same
|
||
* ArrayBuffer, then throw a DataCloneError DOMException. */
|
||
// 13. For each transferable in init.transfer:
|
||
// 1. If [[Detached]] internal slot is true, then throw a DataCloneError DOMException.
|
||
// (not checked in polyfill)
|
||
/* 14. If init.transfer contains an ArrayBuffer referenced by data the
|
||
* User Agent MAY choose to: */
|
||
let transfer = false;
|
||
if (init.transfer) {
|
||
/* 1. Let resource be a new media resource referencing pixel data
|
||
* in data. */
|
||
let inBuffer;
|
||
if (data.buffer)
|
||
inBuffer = data.buffer;
|
||
else
|
||
inBuffer = data;
|
||
let t;
|
||
if (init.transfer instanceof Array)
|
||
t = init.transfer;
|
||
else
|
||
t = Array.from(init.transfer);
|
||
for (const b of t) {
|
||
if (b === inBuffer) {
|
||
transfer = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
// 15. Otherwise:
|
||
/* 1. Let resource be a new media resource containing a copy of
|
||
* data. Use visibleRect and layout to determine where in data
|
||
* the pixels for each plane reside. */
|
||
/* The User Agent MAY choose to allocate resource with a larger
|
||
* coded size and plane strides to improve memory alignment.
|
||
* Increases will be reflected by codedWidth and codedHeight.
|
||
* Additionally, the User Agent MAY use visibleRect to copy only
|
||
* the visible rectangle. It MAY also reposition the visible
|
||
* rectangle within resource. The final position will be
|
||
* reflected by visibleRect. */
|
||
/* NOTE: The spec seems to be missing the step where you actually use
|
||
* the resource to define the [[resource reference]]. */
|
||
const format = init.format;
|
||
if (init.layout) {
|
||
// FIXME: Make sure it's the right size
|
||
if (init.layout instanceof Array)
|
||
this._layout = init.layout;
|
||
else
|
||
this._layout = Array.from(init.layout);
|
||
}
|
||
else {
|
||
const numPlanes_ = numPlanes(format);
|
||
const layout = [];
|
||
let offset = 0;
|
||
for (let i = 0; i < numPlanes_; i++) {
|
||
const sampleWidth = horizontalSubSamplingFactor(format, i);
|
||
const sampleHeight = verticalSubSamplingFactor(format, i);
|
||
const stride = ~~(this.codedWidth / sampleWidth);
|
||
layout.push({ offset, stride });
|
||
offset += stride * (~~(this.codedHeight / sampleHeight));
|
||
}
|
||
this._layout = layout;
|
||
}
|
||
this._data = new Uint8Array(data.buffer || data, data.byteOffset || 0);
|
||
if (!transfer) {
|
||
const numPlanes_ = numPlanes(format);
|
||
// Only copy the relevant part
|
||
let layout = this._layout;
|
||
let lo = 1 / 0;
|
||
let hi = 0;
|
||
for (let i = 0; i < numPlanes_; i++) {
|
||
const plane = layout[i];
|
||
let offset = plane.offset;
|
||
if (offset < lo)
|
||
lo = offset;
|
||
const sampleHeight = verticalSubSamplingFactor(format, i);
|
||
offset += plane.stride * (~~(this.codedHeight / sampleHeight));
|
||
if (offset > hi)
|
||
hi = offset;
|
||
}
|
||
// Fix the layout to compensate
|
||
if (lo !== 0) {
|
||
layout = this._layout = layout.map(x => ({
|
||
offset: x.offset - lo,
|
||
stride: x.stride
|
||
}));
|
||
}
|
||
this._data = this._data.slice(lo, hi);
|
||
}
|
||
// 16. For each transferable in init.transfer:
|
||
// 1. Perform DetachArrayBuffer on transferable
|
||
// (not doable in polyfill)
|
||
// 17. Let resourceCodedWidth be the coded width of resource.
|
||
const resourceCodedWidth = init.codedWidth;
|
||
// 18. Let resourceCodedHeight be the coded height of resource.
|
||
const resourceCodedHeight = init.codedHeight;
|
||
/* 19. Let resourceVisibleLeft be the left offset for the visible
|
||
* rectangle of resource. */
|
||
parsedRect.left;
|
||
/* 20. Let resourceVisibleTop be the top offset for the visible
|
||
* rectangle of resource. */
|
||
parsedRect.top;
|
||
// 21. Let frame be a new VideoFrame object initialized as follows:
|
||
{
|
||
/* 1. Assign resourceCodedWidth, resourceCodedHeight,
|
||
* resourceVisibleLeft, and resourceVisibleTop to
|
||
* [[coded width]], [[coded height]], [[visible left]], and
|
||
* [[visible top]] respectively. */
|
||
// (codedWidth/codedHeight done earlier)
|
||
this.codedRect = new DOMRect(0, 0, resourceCodedWidth, resourceCodedHeight);
|
||
this.visibleRect = parsedRect;
|
||
// 2. If init.visibleRect exists:
|
||
if (init.visibleRect) {
|
||
// 1. Let truncatedVisibleWidth be the value of visibleRect.width after truncating.
|
||
// 2. Assign truncatedVisibleWidth to [[visible width]].
|
||
// 3. Let truncatedVisibleHeight be the value of visibleRect.height after truncating.
|
||
// 4. Assign truncatedVisibleHeight to [[visible height]].
|
||
this.visibleRect = DOMRect.fromRect(init.visibleRect);
|
||
// 3. Otherwise:
|
||
}
|
||
else {
|
||
// 1. Assign [[coded width]] to [[visible width]].
|
||
// 2. Assign [[coded height]] to [[visible height]].
|
||
this.visibleRect = new DOMRect(0, 0, resourceCodedWidth, resourceCodedHeight);
|
||
}
|
||
/* 4. If init.displayWidth exists, assign it to [[display width]].
|
||
* Otherwise, assign [[visible width]] to [[display width]]. */
|
||
if (typeof init.displayWidth === "number")
|
||
this.displayWidth = init.displayWidth;
|
||
else
|
||
this.displayWidth = this.visibleRect.width;
|
||
/* 5. If init.displayHeight exists, assign it to [[display height]].
|
||
* Otherwise, assign [[visible height]] to [[display height]]. */
|
||
if (typeof init.displayHeight === "number")
|
||
this.displayHeight = init.displayHeight;
|
||
else
|
||
this.displayHeight = this.visibleRect.height;
|
||
// Account for non-square pixels
|
||
if (this.displayWidth !== this.visibleRect.width ||
|
||
this.displayHeight !== this.visibleRect.height) {
|
||
// Dubious (but correct) SAR calculation
|
||
this._nonSquarePixels = true;
|
||
this._sar_num = this.displayWidth * this.visibleRect.width;
|
||
this._sar_den = this.displayHeight * this.visibleRect.height;
|
||
}
|
||
else {
|
||
this._nonSquarePixels = false;
|
||
this._sar_num = this._sar_den = 1;
|
||
}
|
||
/* 6. Assign init’s timestamp and duration to [[timestamp]] and
|
||
* [[duration]] respectively. */
|
||
this.timestamp = init.timestamp;
|
||
this.duration = init.duration;
|
||
// 7. Let colorSpace be undefined.
|
||
// 8. If init.colorSpace exists, assign its value to colorSpace.
|
||
// (color spaces not supported)
|
||
// 9. Assign init’s format to [[format]].
|
||
// (done earlier)
|
||
/* 10. Assign the result of running the Pick Color Space algorithm,
|
||
* with colorSpace and [[format]], to [[color space]]. */
|
||
// (color spaces not supported)
|
||
/* 11. Assign the result of calling Copy VideoFrame metadata with
|
||
* init’s metadata to frame.[[metadata]]. */
|
||
// (no actual metadata is yet described by the spec)
|
||
}
|
||
// 22. Return frame.
|
||
}
|
||
/**
|
||
* Convert a polyfill VideoFrame to a native VideoFrame.
|
||
* @param opts Conversion options
|
||
*/
|
||
toNative(opts = {}) {
|
||
const ret = new globalThis.VideoFrame(this._data, {
|
||
layout: this._layout,
|
||
format: this.format,
|
||
codedWidth: this.codedWidth,
|
||
codedHeight: this.codedHeight,
|
||
visibleRect: this.visibleRect,
|
||
displayWidth: this.displayWidth,
|
||
displayHeight: this.displayHeight,
|
||
duration: this.duration,
|
||
timestamp: this.timestamp,
|
||
transfer: opts.transfer ? [this._data.buffer] : []
|
||
});
|
||
if (opts.transfer)
|
||
this.close();
|
||
return ret;
|
||
}
|
||
/**
|
||
* Convert a native VideoFrame to a polyfill VideoFrame. WARNING: Inefficient,
|
||
* as the data cannot be transferred out.
|
||
* @param from VideoFrame to copy in
|
||
*/
|
||
static fromNative(from /* native VideoFrame */) {
|
||
const vf = from;
|
||
const data = new Uint8Array(vf.allocationSize());
|
||
vf.copyTo(data);
|
||
return new VideoFrame(data, {
|
||
format: vf.format,
|
||
codedWidth: vf.codedWidth,
|
||
codedHeight: vf.codedHeight,
|
||
visibleRect: vf.visibleRect,
|
||
displayWidth: vf.displayWidth,
|
||
displayHeight: vf.displayHeight,
|
||
duration: vf.duration,
|
||
timestamp: vf.timestamp
|
||
});
|
||
}
|
||
// Internal
|
||
_libavGetData() { return this._data; }
|
||
_libavGetLayout() { return this._layout; }
|
||
static _checkValidVideoFrameBufferInit(init) {
|
||
// 1. If codedWidth = 0 or codedHeight = 0,return false.
|
||
if (!init.codedWidth || !init.codedHeight)
|
||
throw new TypeError("Invalid coded dimensions");
|
||
if (init.visibleRect) {
|
||
/* 2. If any attribute of visibleRect is negative or not finite, return
|
||
* false. */
|
||
const vr = DOMRect.fromRect(init.visibleRect);
|
||
if (vr.x < 0 || !Number.isFinite(vr.x) ||
|
||
vr.y < 0 || !Number.isFinite(vr.y) ||
|
||
vr.width < 0 || !Number.isFinite(vr.width) ||
|
||
vr.height < 0 || !Number.isFinite(vr.height)) {
|
||
throw new TypeError("Invalid visible rectangle");
|
||
}
|
||
// 3. If visibleRect.y + visibleRect.height > codedHeight, return false.
|
||
if (vr.y + vr.height > init.codedHeight)
|
||
throw new TypeError("Visible rectangle outside of coded height");
|
||
// 4. If visibleRect.x + visibleRect.width > codedWidth, return false.
|
||
if (vr.x + vr.width > init.codedWidth)
|
||
throw new TypeError("Visible rectangle outside of coded width");
|
||
// 5. If only one of displayWidth or displayHeight exists, return false.
|
||
// 6. If displayWidth = 0 or displayHeight = 0, return false.
|
||
if ((init.displayWidth && !init.displayHeight) ||
|
||
(!init.displayWidth && !init.displayHeight) ||
|
||
(init.displayWidth === 0 || init.displayHeight === 0))
|
||
throw new TypeError("Invalid display dimensions");
|
||
}
|
||
// 7. Return true.
|
||
}
|
||
metadata() {
|
||
// 1. If [[Detached]] is true, throw an InvalidStateError DOMException.
|
||
if (this._data === null)
|
||
throw new DOMException("Detached", "InvalidStateError");
|
||
/* 2. Return the result of calling Copy VideoFrame metadata with
|
||
* [[metadata]]. */
|
||
// No actual metadata is yet defined in the spec
|
||
return null;
|
||
}
|
||
allocationSize(options = {}) {
|
||
// 1. If [[Detached]] is true, throw an InvalidStateError DOMException.
|
||
if (this._data === null)
|
||
throw new DOMException("Detached", "InvalidStateError");
|
||
// 2. If [[format]] is null, throw a NotSupportedError DOMException.
|
||
if (this.format === null)
|
||
throw new DOMException("Not supported", "NotSupportedError");
|
||
/* 3. Let combinedLayout be the result of running the Parse
|
||
* VideoFrameCopyToOptions algorithm with options. */
|
||
// 4. If combinedLayout is an exception, throw combinedLayout.
|
||
const combinedLayout = this._parseVideoFrameCopyToOptions(options);
|
||
// 5. Return combinedLayout’s allocationSize.
|
||
return combinedLayout.allocationSize;
|
||
}
|
||
_parseVideoFrameCopyToOptions(options) {
|
||
/* 1. Let defaultRect be the result of performing the getter steps for
|
||
* visibleRect. */
|
||
const defaultRect = this.visibleRect;
|
||
// 2. Let overrideRect be undefined.
|
||
// 3. If options.rect exists, assign its value to overrideRect.
|
||
let overrideRect = options.rect ?
|
||
new DOMRect(options.rect.x, options.rect.y, options.rect.width, options.rect.height)
|
||
: null;
|
||
/* 4. Let parsedRect be the result of running the Parse Visible Rect
|
||
* algorithm with defaultRect, overrideRect, [[coded width]], [[coded
|
||
* height]], and [[format]]. */
|
||
// 5. If parsedRect is an exception, return parsedRect.
|
||
const parsedRect = this._parseVisibleRect(defaultRect, overrideRect);
|
||
// 6. Let optLayout be undefined.
|
||
// 7. If options.layout exists, assign its value to optLayout.
|
||
let optLayout = null;
|
||
if (options.layout) {
|
||
if (options.layout instanceof Array)
|
||
optLayout = options.layout;
|
||
else
|
||
optLayout = Array.from(options.layout);
|
||
}
|
||
/* 8. Let combinedLayout be the result of running the Compute Layout
|
||
* and Allocation Size algorithm with parsedRect, [[format]], and
|
||
* optLayout. */
|
||
const combinedLayout = this._computeLayoutAndAllocationSize(parsedRect, optLayout);
|
||
// 9. Return combinedLayout.
|
||
return combinedLayout;
|
||
}
|
||
_parseVisibleRect(defaultRect, overrideRect) {
|
||
// 1. Let sourceRect be defaultRect
|
||
let sourceRect = defaultRect;
|
||
// 2. If overrideRect is not undefined:
|
||
if (overrideRect) {
|
||
/* 1. If either of overrideRect.width or height is 0, return a
|
||
* TypeError. */
|
||
if (overrideRect.width === 0 || overrideRect.height === 0)
|
||
throw new TypeError("Invalid rectangle");
|
||
/* 2. If the sum of overrideRect.x and overrideRect.width is
|
||
* greater than [[coded width]], return a TypeError. */
|
||
if (overrideRect.x + overrideRect.width > this.codedWidth)
|
||
throw new TypeError("Invalid rectangle");
|
||
/* 3. If the sum of overrideRect.y and overrideRect.height is
|
||
* greater than [[coded height]], return a TypeError. */
|
||
if (overrideRect.y + overrideRect.height > this.codedHeight)
|
||
throw new TypeError("Invalid rectangle");
|
||
// 4. Assign overrideRect to sourceRect.
|
||
sourceRect = overrideRect;
|
||
}
|
||
/* 3. Let validAlignment be the result of running the Verify Rect Offset
|
||
* Alignment algorithm with format and sourceRect. */
|
||
const validAlignment = this._verifyRectOffsetAlignment(sourceRect);
|
||
// 4. If validAlignment is false, throw a TypeError.
|
||
if (!validAlignment)
|
||
throw new TypeError("Invalid alignment");
|
||
// 5. Return sourceRect.
|
||
return sourceRect;
|
||
}
|
||
_computeLayoutAndAllocationSize(parsedRect, layout) {
|
||
// 1. Let numPlanes be the number of planes as defined by format.
|
||
let numPlanes_ = numPlanes(this.format);
|
||
/* 2. If layout is not undefined and its length does not equal
|
||
* numPlanes, throw a TypeError. */
|
||
if (layout && layout.length !== numPlanes_)
|
||
throw new TypeError("Invalid layout");
|
||
// 3. Let minAllocationSize be 0.
|
||
let minAllocationSize = 0;
|
||
// 4. Let computedLayouts be a new list.
|
||
let computedLayouts = [];
|
||
// 5. Let endOffsets be a new list.
|
||
let endOffsets = [];
|
||
// 6. Let planeIndex be 0.
|
||
let planeIndex = 0;
|
||
// 7. While planeIndex < numPlanes:
|
||
while (planeIndex < numPlanes_) {
|
||
/* 1. Let plane be the Plane identified by planeIndex as defined by
|
||
* format. */
|
||
// 2. Let sampleBytes be the number of bytes per sample for plane.
|
||
const sampleBytes_ = sampleBytes(this.format, planeIndex);
|
||
/* 3. Let sampleWidth be the horizontal sub-sampling factor of each
|
||
* subsample for plane. */
|
||
const sampleWidth = horizontalSubSamplingFactor(this.format, planeIndex);
|
||
/* 4. Let sampleHeight be the vertical sub-sampling factor of each
|
||
* subsample for plane. */
|
||
const sampleHeight = verticalSubSamplingFactor(this.format, planeIndex);
|
||
// 5. Let computedLayout be a new computed plane layout.
|
||
const computedLayout = {
|
||
destinationOffset: 0,
|
||
destinationStride: 0,
|
||
/* 6. Set computedLayout’s sourceTop to the result of the division
|
||
* of truncated parsedRect.y by sampleHeight, rounded up to the
|
||
* nearest integer. */
|
||
sourceTop: Math.ceil(~~parsedRect.y / sampleHeight),
|
||
/* 7. Set computedLayout’s sourceHeight to the result of the
|
||
* division of truncated parsedRect.height by sampleHeight,
|
||
* rounded up to the nearest integer. */
|
||
sourceHeight: Math.ceil(~~parsedRect.height / sampleHeight),
|
||
/* 8. Set computedLayout’s sourceLeftBytes to the result of the
|
||
* integer division of truncated parsedRect.x by sampleWidth,
|
||
* multiplied by sampleBytes. */
|
||
sourceLeftBytes: ~~(parsedRect.x / sampleWidth * sampleBytes_),
|
||
/* 9. Set computedLayout’s sourceWidthBytes to the result of the
|
||
* integer division of truncated parsedRect.width by
|
||
* sampleHeight, multiplied by sampleBytes. */
|
||
sourceWidthBytes: ~~(parsedRect.width / sampleWidth * sampleBytes_)
|
||
};
|
||
// 10. If layout is not undefined:
|
||
if (layout) {
|
||
/* 1. Let planeLayout be the PlaneLayout in layout at position
|
||
* planeIndex. */
|
||
const planeLayout = layout[planeIndex];
|
||
/* 2. If planeLayout.stride is less than computedLayout’s
|
||
* sourceWidthBytes, return a TypeError. */
|
||
if (planeLayout.stride < computedLayout.sourceWidthBytes)
|
||
throw new TypeError("Invalid stride");
|
||
/* 3. Assign planeLayout.offset to computedLayout’s
|
||
* destinationOffset. */
|
||
computedLayout.destinationOffset = planeLayout.offset;
|
||
/* 4. Assign planeLayout.stride to computedLayout’s
|
||
* destinationStride. */
|
||
computedLayout.destinationStride = planeLayout.stride;
|
||
// 11. Otherwise:
|
||
}
|
||
else {
|
||
/* 1. Assign minAllocationSize to computedLayout’s
|
||
* destinationOffset. */
|
||
computedLayout.destinationOffset = minAllocationSize;
|
||
/* 2. Assign computedLayout’s sourceWidthBytes to
|
||
* computedLayout’s destinationStride. */
|
||
computedLayout.destinationStride = computedLayout.sourceWidthBytes;
|
||
}
|
||
/* 12. Let planeSize be the product of multiplying computedLayout’s
|
||
* destinationStride and sourceHeight. */
|
||
const planeSize = computedLayout.destinationStride * computedLayout.sourceHeight;
|
||
/* 13. Let planeEnd be the sum of planeSize and computedLayout’s
|
||
* destinationOffset. */
|
||
const planeEnd = planeSize + computedLayout.destinationOffset;
|
||
/* 14. If planeSize or planeEnd is greater than maximum range of
|
||
* unsigned long, return a TypeError. */
|
||
if (planeSize >= 0x100000000 ||
|
||
planeEnd >= 0x100000000)
|
||
throw new TypeError("Plane too large");
|
||
// 15. Append planeEnd to endOffsets.
|
||
endOffsets.push(planeEnd);
|
||
/* 16. Assign the maximum of minAllocationSize and planeEnd to
|
||
* minAllocationSize. */
|
||
if (planeEnd > minAllocationSize)
|
||
minAllocationSize = planeEnd;
|
||
// 17. Let earlierPlaneIndex be 0.
|
||
let earlierPlaneIndex = 0;
|
||
// 18. While earlierPlaneIndex is less than planeIndex.
|
||
while (earlierPlaneIndex < planeIndex) {
|
||
// 1. Let earlierLayout be computedLayouts[earlierPlaneIndex].
|
||
const earlierLayout = computedLayouts[earlierPlaneIndex];
|
||
/* 2. If endOffsets[planeIndex] is less than or equal to
|
||
* earlierLayout’s destinationOffset or if
|
||
* endOffsets[earlierPlaneIndex] is less than or equal to
|
||
* computedLayout’s destinationOffset, continue. */
|
||
if (planeEnd <= earlierLayout.destinationOffset ||
|
||
endOffsets[earlierPlaneIndex] <= computedLayout.destinationOffset) ;
|
||
else
|
||
throw new TypeError("Invalid plane layout");
|
||
// 4. Increment earlierPlaneIndex by 1.
|
||
earlierPlaneIndex++;
|
||
}
|
||
// 19. Append computedLayout to computedLayouts.
|
||
computedLayouts.push(computedLayout);
|
||
// 20. Increment planeIndex by 1.
|
||
planeIndex++;
|
||
}
|
||
/* 8. Let combinedLayout be a new combined buffer layout, initialized
|
||
* as follows: */
|
||
const combinedLayout = {
|
||
// 1. Assign computedLayouts to computedLayouts.
|
||
computedLayouts,
|
||
// 2. Assign minAllocationSize to allocationSize.
|
||
allocationSize: minAllocationSize
|
||
};
|
||
// 9. Return combinedLayout.
|
||
return combinedLayout;
|
||
}
|
||
_verifyRectOffsetAlignment(rect) {
|
||
// 1. If format is null, return true.
|
||
if (!this.format)
|
||
return true;
|
||
// 2. Let planeIndex be 0.
|
||
let planeIndex = 0;
|
||
// 3. Let numPlanes be the number of planes as defined by format.
|
||
const numPlanes_ = numPlanes(this.format);
|
||
// 4. While planeIndex is less than numPlanes:
|
||
while (planeIndex < numPlanes_) {
|
||
/* 1. Let plane be the Plane identified by planeIndex as defined by
|
||
* format. */
|
||
/* 2. Let sampleWidth be the horizontal sub-sampling factor of each
|
||
* subsample for plane. */
|
||
const sampleWidth = horizontalSubSamplingFactor(this.format, planeIndex);
|
||
/* 3. Let sampleHeight be the vertical sub-sampling factor of each
|
||
* subsample for plane. */
|
||
const sampleHeight = verticalSubSamplingFactor(this.format, planeIndex);
|
||
// 4. If rect.x is not a multiple of sampleWidth, return false.
|
||
const xw = rect.x / sampleWidth;
|
||
if (xw !== ~~xw)
|
||
return false;
|
||
// 5. If rect.y is not a multiple of sampleHeight, return false.
|
||
const yh = rect.y / sampleHeight;
|
||
if (yh !== ~~yh)
|
||
return false;
|
||
// 6. Increment planeIndex by 1.
|
||
planeIndex++;
|
||
}
|
||
// 5. Return true.
|
||
return true;
|
||
}
|
||
copyTo(destination, options = {}) {
|
||
return __awaiter$5(this, void 0, void 0, function* () {
|
||
const destBuf = new Uint8Array(destination.buffer || destination, destination.byteOffset || 0);
|
||
// 1. If [[Detached]] is true, throw an InvalidStateError DOMException.
|
||
if (this._data === null)
|
||
throw new DOMException("Detached", "InvalidStateError");
|
||
// 2. If [[format]] is null, throw a NotSupportedError DOMException.
|
||
if (!this.format)
|
||
throw new DOMException("No format", "NotSupportedError");
|
||
/* 3. Let combinedLayout be the result of running the Parse
|
||
* VideoFrameCopyToOptions algorithm with options. */
|
||
/* 4. If combinedLayout is an exception, return a promise rejected with
|
||
* combinedLayout. */
|
||
const combinedLayout = this._parseVideoFrameCopyToOptions(options);
|
||
/* 5. If destination.byteLength is less than combinedLayout’s
|
||
* allocationSize, return a promise rejected with a TypeError. */
|
||
if (destination.byteLength < combinedLayout.allocationSize)
|
||
throw new TypeError("Insufficient space");
|
||
// 6. Let p be a new Promise.
|
||
/* 7. Let copyStepsQueue be the result of starting a new parallel
|
||
* queue. */
|
||
// 8. Let planeLayouts be a new list.
|
||
let planeLayouts = [];
|
||
// 9. Enqueue the following steps to copyStepsQueue:
|
||
{
|
||
/* 1. Let resource be the media resource referenced by [[resource
|
||
* reference]]. */
|
||
/* 2. Let numPlanes be the number of planes as defined by
|
||
* [[format]]. */
|
||
numPlanes(this.format);
|
||
// 3. Let planeIndex be 0.
|
||
let planeIndex = 0;
|
||
// 4. While planeIndex is less than combinedLayout’s numPlanes:
|
||
while (planeIndex < combinedLayout.computedLayouts.length) {
|
||
/* 1. Let sourceStride be the stride of the plane in resource as
|
||
* identified by planeIndex. */
|
||
const sourceStride = this._layout[planeIndex].stride;
|
||
/* 2. Let computedLayout be the computed plane layout in
|
||
* combinedLayout’s computedLayouts at the position of planeIndex */
|
||
const computedLayout = combinedLayout.computedLayouts[planeIndex];
|
||
/* 3. Let sourceOffset be the product of multiplying
|
||
* computedLayout’s sourceTop by sourceStride */
|
||
let sourceOffset = computedLayout.sourceTop * sourceStride;
|
||
// 4. Add computedLayout’s sourceLeftBytes to sourceOffset.
|
||
sourceOffset += computedLayout.sourceLeftBytes;
|
||
// 5. Let destinationOffset be computedLayout’s destinationOffset.
|
||
let destinationOffset = computedLayout.destinationOffset;
|
||
// 6. Let rowBytes be computedLayout’s sourceWidthBytes.
|
||
const rowBytes = computedLayout.sourceWidthBytes;
|
||
/* 7. Let layout be a new PlaneLayout, with offset set to
|
||
* destinationOffset and stride set to rowBytes. */
|
||
const layout = {
|
||
offset: computedLayout.destinationOffset,
|
||
stride: computedLayout.destinationStride
|
||
};
|
||
// 8. Let row be 0.
|
||
let row = 0;
|
||
// 9. While row is less than computedLayout’s sourceHeight:
|
||
while (row < computedLayout.sourceHeight) {
|
||
/* 1. Copy rowBytes bytes from resource starting at
|
||
* sourceOffset to destination starting at destinationOffset. */
|
||
destBuf.set(this._data.subarray(sourceOffset, sourceOffset + rowBytes), destinationOffset);
|
||
// 2. Increment sourceOffset by sourceStride.
|
||
sourceOffset += sourceStride;
|
||
/* 3. Increment destinationOffset by computedLayout’s
|
||
* destinationStride. */
|
||
destinationOffset += computedLayout.destinationStride;
|
||
// 4. Increment row by 1.
|
||
row++;
|
||
}
|
||
// 10. Increment planeIndex by 1.
|
||
planeIndex++;
|
||
// 11. Append layout to planeLayouts.
|
||
planeLayouts.push(layout);
|
||
}
|
||
// 5. Queue a task to resolve p with planeLayouts.
|
||
}
|
||
// 10. Return p.
|
||
return planeLayouts;
|
||
});
|
||
}
|
||
clone() {
|
||
return new VideoFrame(this._data, {
|
||
format: this.format,
|
||
codedWidth: this.codedWidth,
|
||
codedHeight: this.codedHeight,
|
||
timestamp: this.timestamp,
|
||
duration: this.duration,
|
||
layout: this._layout,
|
||
transfer: [this._data.buffer]
|
||
});
|
||
}
|
||
close() {
|
||
this._data = null;
|
||
}
|
||
};
|
||
/**
|
||
* Convert a WebCodecs pixel format to a libav pixel format.
|
||
* @param libav LibAV instance for constants
|
||
* @param wcFormat WebCodecs format
|
||
*/
|
||
function wcFormatToLibAVFormat(libav, wcFormat) {
|
||
let format = libav.AV_PIX_FMT_RGBA;
|
||
switch (wcFormat) {
|
||
case "I420":
|
||
format = libav.AV_PIX_FMT_YUV420P;
|
||
break;
|
||
case "I420P10":
|
||
format = 0x3E; /* AV_PIX_FMT_YUV420P10 */
|
||
break;
|
||
case "I420P12":
|
||
format = 0x7B; /* AV_PIX_FMT_YUV420P12 */
|
||
break;
|
||
case "I420A":
|
||
format = libav.AV_PIX_FMT_YUVA420P;
|
||
break;
|
||
case "I420AP10":
|
||
format = 0x57; /* AV_PIX_FMT_YUVA420P10 */
|
||
break;
|
||
case "I420AP12":
|
||
throw new TypeError("YUV420P12 is not supported by libav");
|
||
case "I422":
|
||
format = libav.AV_PIX_FMT_YUV422P;
|
||
break;
|
||
case "I422P10":
|
||
format = 0x40; /* AV_PIX_FMT_YUV422P10 */
|
||
break;
|
||
case "I422P12":
|
||
format = 0x7F; /* AV_PIX_FMT_YUV422P12 */
|
||
break;
|
||
case "I422A":
|
||
format = 0x4E; /* AV_PIX_FMT_YUVA422P */
|
||
break;
|
||
case "I422AP10":
|
||
format = 0x59; /* AV_PIX_FMT_YUVA422P10 */
|
||
break;
|
||
case "I422AP10":
|
||
format = 0xBA; /* AV_PIX_FMT_YUVA422P12 */
|
||
break;
|
||
case "I444":
|
||
format = libav.AV_PIX_FMT_YUV444P;
|
||
break;
|
||
case "I444P10":
|
||
format = 0x44; /* AV_PIX_FMT_YUV444P10 */
|
||
break;
|
||
case "I444P12":
|
||
format = 0x83; /* AV_PIX_FMT_YUV444P12 */
|
||
break;
|
||
case "I444A":
|
||
format = 0x4F; /* AV_PIX_FMT_YUVA444P */
|
||
break;
|
||
case "I444AP10":
|
||
format = 0x5B; /* AV_PIX_FMT_YUVA444P10 */
|
||
break;
|
||
case "I444AP12":
|
||
format = 0xBC; /* AV_PIX_FMT_YUVA444P10 */
|
||
break;
|
||
case "NV12":
|
||
format = libav.AV_PIX_FMT_NV12;
|
||
break;
|
||
case "RGBA":
|
||
format = libav.AV_PIX_FMT_RGBA;
|
||
break;
|
||
case "RGBX":
|
||
format = 0x77; /* AV_PIX_FMT_RGB0 */
|
||
break;
|
||
case "BGRA":
|
||
format = libav.AV_PIX_FMT_BGRA;
|
||
break;
|
||
case "BGRX":
|
||
format = 0x79; /* AV_PIX_FMT_BGR0 */
|
||
break;
|
||
default:
|
||
throw new TypeError("Invalid VideoPixelFormat");
|
||
}
|
||
return format;
|
||
}
|
||
/**
|
||
* Number of planes in the given format.
|
||
* @param format The format
|
||
*/
|
||
function numPlanes(format) {
|
||
switch (format) {
|
||
case "I420":
|
||
case "I420P10":
|
||
case "I420P12":
|
||
case "I422":
|
||
case "I422P10":
|
||
case "I422P12":
|
||
case "I444":
|
||
case "I444P10":
|
||
case "I444P12":
|
||
return 3;
|
||
case "I420A":
|
||
case "I420AP10":
|
||
case "I420AP12":
|
||
case "I422A":
|
||
case "I422AP10":
|
||
case "I422AP12":
|
||
case "I444A":
|
||
case "I444AP10":
|
||
case "I444AP12":
|
||
return 4;
|
||
case "NV12":
|
||
return 2;
|
||
case "RGBA":
|
||
case "RGBX":
|
||
case "BGRA":
|
||
case "BGRX":
|
||
return 1;
|
||
default:
|
||
throw new DOMException("Unsupported video pixel format", "NotSupportedError");
|
||
}
|
||
}
|
||
/**
|
||
* Number of bytes per sample in the given format and plane.
|
||
* @param format The format
|
||
* @param planeIndex The plane index
|
||
*/
|
||
function sampleBytes(format, planeIndex) {
|
||
switch (format) {
|
||
case "I420":
|
||
case "I420A":
|
||
case "I422":
|
||
case "I422A":
|
||
case "I444":
|
||
case "I444A":
|
||
return 1;
|
||
case "I420P10":
|
||
case "I420AP10":
|
||
case "I422P10":
|
||
case "I422AP10":
|
||
case "I444P10":
|
||
case "I444AP10":
|
||
case "I420P12":
|
||
case "I420AP12":
|
||
case "I422P12":
|
||
case "I422AP12":
|
||
case "I444P12":
|
||
case "I444AP12":
|
||
return 2;
|
||
case "NV12":
|
||
if (planeIndex === 1)
|
||
return 2;
|
||
else
|
||
return 1;
|
||
case "RGBA":
|
||
case "RGBX":
|
||
case "BGRA":
|
||
case "BGRX":
|
||
return 4;
|
||
default:
|
||
throw new DOMException("Unsupported video pixel format", "NotSupportedError");
|
||
}
|
||
}
|
||
/**
|
||
* Horizontal sub-sampling factor for the given format and plane.
|
||
* @param format The format
|
||
* @param planeIndex The plane index
|
||
*/
|
||
function horizontalSubSamplingFactor(format, planeIndex) {
|
||
// First plane (often luma) is always full
|
||
if (planeIndex === 0)
|
||
return 1;
|
||
// Plane 3 (alpha if present) is always full
|
||
if (planeIndex === 3)
|
||
return 1;
|
||
switch (format) {
|
||
case "I420":
|
||
case "I420P10":
|
||
case "I420P12":
|
||
case "I420A":
|
||
case "I420AP10":
|
||
case "I420AP12":
|
||
case "I422":
|
||
case "I422P10":
|
||
case "I422P12":
|
||
case "I422A":
|
||
case "I422AP10":
|
||
case "I422AP12":
|
||
return 2;
|
||
case "I444":
|
||
case "I444P10":
|
||
case "I444P12":
|
||
case "I444A":
|
||
case "I444AP10":
|
||
case "I444AP12":
|
||
return 1;
|
||
case "NV12":
|
||
return 2;
|
||
case "RGBA":
|
||
case "RGBX":
|
||
case "BGRA":
|
||
case "BGRX":
|
||
return 1;
|
||
default:
|
||
throw new DOMException("Unsupported video pixel format", "NotSupportedError");
|
||
}
|
||
}
|
||
/**
|
||
* Vertical sub-sampling factor for the given format and plane.
|
||
* @param format The format
|
||
* @param planeIndex The plane index
|
||
*/
|
||
function verticalSubSamplingFactor(format, planeIndex) {
|
||
// First plane (often luma) is always full
|
||
if (planeIndex === 0)
|
||
return 1;
|
||
// Plane 3 (alpha if present) is always full
|
||
if (planeIndex === 3)
|
||
return 1;
|
||
switch (format) {
|
||
case "I420":
|
||
case "I420P10":
|
||
case "I420P12":
|
||
case "I420A":
|
||
case "I420AP10":
|
||
case "I420AP12":
|
||
return 2;
|
||
case "I422":
|
||
case "I422P10":
|
||
case "I422P12":
|
||
case "I422A":
|
||
case "I422AP10":
|
||
case "I422AP12":
|
||
case "I444":
|
||
case "I444P10":
|
||
case "I444P12":
|
||
case "I444A":
|
||
case "I444AP10":
|
||
case "I444AP12":
|
||
return 1;
|
||
case "NV12":
|
||
return 2;
|
||
case "RGBA":
|
||
case "RGBX":
|
||
case "BGRA":
|
||
case "BGRX":
|
||
return 1;
|
||
default:
|
||
throw new DOMException("Unsupported video pixel format", "NotSupportedError");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$4 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
let VideoDecoder$1 = class VideoDecoder extends DequeueEventTarget {
|
||
constructor(init) {
|
||
super();
|
||
// 1. Let d be a new VideoDecoder object.
|
||
// 2. Assign a new queue to [[control message queue]].
|
||
this._p = Promise.all([]);
|
||
// 3. Assign false to [[message queue blocked]].
|
||
// (unneeded in polyfill)
|
||
// 4. Assign null to [[codec implementation]].
|
||
this._libav = null;
|
||
this._codec = this._c = this._pkt = this._frame = 0;
|
||
/* 5. Assign the result of starting a new parallel queue to
|
||
* [[codec work queue]]. */
|
||
// (shared queue)
|
||
// 6. Assign false to [[codec saturated]].
|
||
// (saturation not needed)
|
||
// 7. Assign init.output to [[output callback]].
|
||
this._output = init.output;
|
||
// 8. Assign init.error to [[error callback]].
|
||
this._error = init.error;
|
||
// 9. Assign null to [[active decoder config]].
|
||
// (part of codec)
|
||
// 10. Assign true to [[key chunk required]].
|
||
// (part of codec)
|
||
// 11. Assign "unconfigured" to [[state]]
|
||
this.state = "unconfigured";
|
||
// 12. Assign 0 to [[decodeQueueSize]].
|
||
this.decodeQueueSize = 0;
|
||
// 13. Assign a new list to [[pending flush promises]].
|
||
// (shared queue)
|
||
// 14. Assign false to [[dequeue event scheduled]].
|
||
// (not needed in polyfill)
|
||
// 15. Return d.
|
||
}
|
||
configure(config) {
|
||
// 1. If config is not a valid VideoDecoderConfig, throw a TypeError.
|
||
// NOTE: We don't support sophisticated codec string parsing (yet)
|
||
// 2. If [[state]] is “closed”, throw an InvalidStateError DOMException.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Decoder is closed", "InvalidStateError");
|
||
// Free any internal state
|
||
if (this._libav)
|
||
this._p = this._p.then(() => this._free());
|
||
// 3. Set [[state]] to "configured".
|
||
this.state = "configured";
|
||
// 4. Set [[key chunk required]] to true.
|
||
// (part of the codec)
|
||
// 5. Queue a control message to configure the decoder with config.
|
||
this._p = this._p.then(() => __awaiter$4(this, void 0, void 0, function* () {
|
||
/* 1. Let supported be the result of running the Check
|
||
* Configuration Support algorithm with config. */
|
||
const supported = decoder(config.codec, config);
|
||
/* 2. If supported is false, queue a task to run the Close
|
||
* VideoDecoder algorithm with NotSupportedError and abort these
|
||
* steps. */
|
||
if (!supported) {
|
||
this._closeVideoDecoder(new DOMException("Unsupported codec", "NotSupportedError"));
|
||
return;
|
||
}
|
||
/* 3. If needed, assign [[codec implementation]] with an
|
||
* implementation supporting config. */
|
||
// 4. Configure [[codec implementation]] with config.
|
||
const libav = this._libav = yield get();
|
||
// Initialize
|
||
[this._codec, this._c, this._pkt, this._frame] =
|
||
yield libav.ff_init_decoder(supported.codec);
|
||
yield libav.AVCodecContext_time_base_s(this._c, 1, 1000);
|
||
// 5. queue a task to run the following steps:
|
||
// 1. Assign false to [[message queue blocked]].
|
||
// 2. Queue a task to Process the control message queue.
|
||
})).catch(this._error);
|
||
}
|
||
// Our own algorithm, close libav
|
||
_free() {
|
||
return __awaiter$4(this, void 0, void 0, function* () {
|
||
if (this._c) {
|
||
yield this._libav.ff_free_decoder(this._c, this._pkt, this._frame);
|
||
this._codec = this._c = this._pkt = this._frame = 0;
|
||
}
|
||
if (this._libav) {
|
||
free(this._libav);
|
||
this._libav = null;
|
||
}
|
||
});
|
||
}
|
||
_closeVideoDecoder(exception) {
|
||
// 1. Run the Reset VideoDecoder algorithm with exception.
|
||
this._resetVideoDecoder(exception);
|
||
// 2. Set [[state]] to "closed".
|
||
this.state = "closed";
|
||
/* 3. Clear [[codec implementation]] and release associated system
|
||
* resources. */
|
||
this._p = this._p.then(() => this._free());
|
||
/* 4. If exception is not an AbortError DOMException, invoke the
|
||
* [[error callback]] with exception. */
|
||
if (exception.name !== "AbortError")
|
||
this._p = this._p.then(() => { this._error(exception); });
|
||
}
|
||
_resetVideoDecoder(exception) {
|
||
// 1. If [[state]] is "closed", throw an InvalidStateError.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Decoder closed", "InvalidStateError");
|
||
// 2. Set [[state]] to "unconfigured".
|
||
this.state = "unconfigured";
|
||
// ... really, we're just going to free it now
|
||
this._p = this._p.then(() => this._free());
|
||
}
|
||
decode(chunk) {
|
||
const self = this;
|
||
// 1. If [[state]] is not "configured", throw an InvalidStateError.
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Unconfigured", "InvalidStateError");
|
||
// 2. If [[key chunk required]] is true:
|
||
// 1. If chunk.[[type]] is not key, throw a DataError.
|
||
/* 2. Implementers SHOULD inspect the chunk’s [[internal data]] to
|
||
* verify that it is truly a key chunk. If a mismatch is detected,
|
||
* throw a DataError. */
|
||
// 3. Otherwise, assign false to [[key chunk required]].
|
||
// 3. Increment [[decodeQueueSize]].
|
||
this.decodeQueueSize++;
|
||
// 4. Queue a control message to decode the chunk.
|
||
this._p = this._p.then(function () {
|
||
return __awaiter$4(this, void 0, void 0, function* () {
|
||
const libav = self._libav;
|
||
const c = self._c;
|
||
const pkt = self._pkt;
|
||
const frame = self._frame;
|
||
let decodedOutputs = null;
|
||
/* 3. Decrement [[decodeQueueSize]] and run the Schedule Dequeue
|
||
* Event algorithm. */
|
||
self.decodeQueueSize--;
|
||
self.dispatchEvent(new CustomEvent("dequeue"));
|
||
// 1. Attempt to use [[codec implementation]] to decode the chunk.
|
||
try {
|
||
// Convert to a libav packet
|
||
const ptsFull = Math.floor(chunk.timestamp / 1000);
|
||
const [pts, ptshi] = libav.f64toi64(ptsFull);
|
||
const packet = {
|
||
data: chunk._libavGetData(),
|
||
pts,
|
||
ptshi,
|
||
dts: pts,
|
||
dtshi: ptshi
|
||
};
|
||
if (chunk.duration) {
|
||
packet.duration = Math.floor(chunk.duration / 1000);
|
||
packet.durationhi = 0;
|
||
}
|
||
decodedOutputs = yield libav.ff_decode_multi(c, pkt, frame, [packet]);
|
||
/* 2. If decoding results in an error, queue a task on the control
|
||
* thread event loop to run the Close VideoDecoder algorithm with
|
||
* EncodingError. */
|
||
}
|
||
catch (ex) {
|
||
self._p = self._p.then(() => {
|
||
self._closeVideoDecoder(ex);
|
||
});
|
||
}
|
||
/* 3. If [[codec saturated]] equals true and
|
||
* [[codec implementation]] is no longer saturated, queue a task
|
||
* to perform the following steps: */
|
||
// 1. Assign false to [[codec saturated]].
|
||
// 2. Process the control message queue.
|
||
// (unneeded)
|
||
/* 4. Let decoded outputs be a list of decoded video data outputs
|
||
* emitted by [[codec implementation]] in presentation order. */
|
||
/* 5. If decoded outputs is not empty, queue a task to run the
|
||
* Output VideoFrame algorithm with decoded outputs. */
|
||
if (decodedOutputs)
|
||
self._outputVideoFrames(decodedOutputs);
|
||
});
|
||
}).catch(this._error);
|
||
}
|
||
_outputVideoFrames(frames) {
|
||
const libav = this._libav;
|
||
for (const frame of frames) {
|
||
// 1. format
|
||
let format;
|
||
switch (frame.format) {
|
||
case libav.AV_PIX_FMT_YUV420P:
|
||
format = "I420";
|
||
break;
|
||
case 0x3E: /* AV_PIX_FMT_YUV420P10 */
|
||
format = "I420P10";
|
||
break;
|
||
case 0x7B: /* AV_PIX_FMT_YUV420P12 */
|
||
format = "I420P12";
|
||
break;
|
||
case libav.AV_PIX_FMT_YUVA420P:
|
||
format = "I420A";
|
||
break;
|
||
case 0x57: /* AV_PIX_FMT_YUVA420P10 */
|
||
format = "I420AP10";
|
||
break;
|
||
case libav.AV_PIX_FMT_YUV422P:
|
||
format = "I422";
|
||
break;
|
||
case 0x40: /* AV_PIX_FMT_YUV422P10 */
|
||
format = "I422P10";
|
||
break;
|
||
case 0x7F: /* AV_PIX_FMT_YUV422P12 */
|
||
format = "I422P12";
|
||
break;
|
||
case 0x4E: /* AV_PIX_FMT_YUVA422P */
|
||
format = "I422A";
|
||
break;
|
||
case 0x59: /* AV_PIX_FMT_YUVA422P10 */
|
||
format = "I422AP10";
|
||
break;
|
||
case 0xBA: /* AV_PIX_FMT_YUVA422P12 */
|
||
format = "I422AP12";
|
||
break;
|
||
case libav.AV_PIX_FMT_YUV444P:
|
||
format = "I444";
|
||
break;
|
||
case 0x44: /* AV_PIX_FMT_YUV444P10 */
|
||
format = "I444P10";
|
||
break;
|
||
case 0x83: /* AV_PIX_FMT_YUV444P12 */
|
||
format = "I444P12";
|
||
break;
|
||
case 0x4F: /* AV_PIX_FMT_YUVA444P */
|
||
format = "I444A";
|
||
break;
|
||
case 0x5B: /* AV_PIX_FMT_YUVA444P10 */
|
||
format = "I444AP10";
|
||
break;
|
||
case 0xBC: /* AV_PIX_FMT_YUVA444P12 */
|
||
format = "I444AP12";
|
||
break;
|
||
case libav.AV_PIX_FMT_NV12:
|
||
format = "NV12";
|
||
break;
|
||
case libav.AV_PIX_FMT_RGBA:
|
||
format = "RGBA";
|
||
break;
|
||
case 0x77: /* AV_PIX_FMT_RGB0 */
|
||
format = "RGBX";
|
||
break;
|
||
case libav.AV_PIX_FMT_BGRA:
|
||
format = "BGRA";
|
||
break;
|
||
case 0x79: /* AV_PIX_FMT_BGR0 */
|
||
format = "BGRX";
|
||
break;
|
||
default:
|
||
throw new DOMException("Unsupported libav format!", "EncodingError");
|
||
}
|
||
// 2. width and height
|
||
const codedWidth = frame.width;
|
||
const codedHeight = frame.height;
|
||
// 3. cropping
|
||
let visibleRect;
|
||
if (frame.crop) {
|
||
visibleRect = new DOMRect(frame.crop.left, frame.crop.top, codedWidth - frame.crop.left - frame.crop.right, codedHeight - frame.crop.top - frame.crop.bottom);
|
||
}
|
||
else {
|
||
visibleRect = new DOMRect(0, 0, codedWidth, codedHeight);
|
||
}
|
||
// Check for non-square pixels
|
||
let displayWidth = codedWidth;
|
||
let displayHeight = codedHeight;
|
||
if (frame.sample_aspect_ratio && frame.sample_aspect_ratio[0]) {
|
||
const sar = frame.sample_aspect_ratio;
|
||
if (sar[0] > sar[1])
|
||
displayWidth = ~~(codedWidth * sar[0] / sar[1]);
|
||
else
|
||
displayHeight = ~~(codedHeight * sar[1] / sar[0]);
|
||
}
|
||
// 3. timestamp
|
||
const timestamp = libav.i64tof64(frame.pts, frame.ptshi) * 1000;
|
||
const data = new VideoFrame$1(frame.data, {
|
||
layout: frame.layout,
|
||
format, codedWidth, codedHeight, visibleRect, displayWidth, displayHeight,
|
||
timestamp
|
||
});
|
||
this._output(data);
|
||
}
|
||
}
|
||
flush() {
|
||
/* 1. If [[state]] is not "configured", return a promise rejected with
|
||
* InvalidStateError DOMException. */
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Invalid state", "InvalidStateError");
|
||
// 2. Set [[key chunk required]] to true.
|
||
// (handled by codec)
|
||
// 3. Let promise be a new Promise.
|
||
// 4. Append promise to [[pending flush promises]].
|
||
// 5. Queue a control message to flush the codec with promise.
|
||
// 6. Process the control message queue.
|
||
const ret = this._p.then(() => __awaiter$4(this, void 0, void 0, function* () {
|
||
/* 1. Signal [[codec implementation]] to emit all internal pending
|
||
* outputs. */
|
||
if (!this._c)
|
||
return;
|
||
// Make sure any last data is flushed
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const pkt = this._pkt;
|
||
const frame = this._frame;
|
||
let decodedOutputs = null;
|
||
try {
|
||
decodedOutputs = yield libav.ff_decode_multi(c, pkt, frame, [], true);
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeVideoDecoder(ex);
|
||
});
|
||
}
|
||
/* 2. Let decoded outputs be a list of decoded video data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
// 3. Queue a task to perform these steps:
|
||
{
|
||
/* 1. If decoded outputs is not empty, run the Output VideoFrame
|
||
* algorithm with decoded outputs. */
|
||
if (decodedOutputs)
|
||
this._outputVideoFrames(decodedOutputs);
|
||
// 2. Remove promise from [[pending flush promises]].
|
||
// 3. Resolve promise.
|
||
}
|
||
}));
|
||
this._p = ret;
|
||
// 7. Return promise.
|
||
return ret;
|
||
}
|
||
reset() {
|
||
this._resetVideoDecoder(new DOMException("Reset", "AbortError"));
|
||
}
|
||
close() {
|
||
this._closeVideoDecoder(new DOMException("Close", "AbortError"));
|
||
}
|
||
static isConfigSupported(config) {
|
||
return __awaiter$4(this, void 0, void 0, function* () {
|
||
const dec = decoder(config.codec, config);
|
||
let supported = false;
|
||
if (dec) {
|
||
const libav = yield get();
|
||
try {
|
||
const [, c, pkt, frame] = yield libav.ff_init_decoder(dec.codec);
|
||
yield libav.ff_free_decoder(c, pkt, frame);
|
||
supported = true;
|
||
}
|
||
catch (ex) { }
|
||
yield free(libav);
|
||
}
|
||
return {
|
||
supported,
|
||
config: cloneConfig(config, ["codec", "codedWidth", "codedHeight"])
|
||
};
|
||
});
|
||
}
|
||
};
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$3 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
let VideoEncoder$1 = class VideoEncoder extends DequeueEventTarget {
|
||
constructor(init) {
|
||
super();
|
||
this._extradataSet = false;
|
||
this._extradata = null;
|
||
// If our output uses non-square pixels, that information
|
||
this._nonSquarePixels = false;
|
||
this._sar_num = 1;
|
||
this._sar_den = 1;
|
||
// 1. Let e be a new VideoEncoder object.
|
||
// 2. Assign a new queue to [[control message queue]].
|
||
this._p = Promise.all([]);
|
||
// 3. Assign false to [[message queue blocked]].
|
||
// (unneeded in polyfill)
|
||
// 4. Assign null to [[codec implementation]].
|
||
this._libav = null;
|
||
this._codec = this._c = this._frame = this._pkt = 0;
|
||
/* 5. Assign the result of starting a new parallel queue to
|
||
* [[codec work queue]]. */
|
||
// (shared queue)
|
||
// 6. Assign false to [[codec saturated]].
|
||
// (saturation unneeded)
|
||
// 7. Assign init.output to [[output callback]].
|
||
this._output = init.output;
|
||
// 8. Assign init.error to [[error callback]].
|
||
this._error = init.error;
|
||
// 9. Assign null to [[active encoder config]].
|
||
// (part of codec)
|
||
// 10. Assign null to [[active output config]].
|
||
this._metadata = null;
|
||
// 11. Assign "unconfigured" to [[state]]
|
||
this.state = "unconfigured";
|
||
// 12. Assign 0 to [[encodeQueueSize]].
|
||
this.encodeQueueSize = 0;
|
||
// 13. Assign a new list to [[pending flush promises]].
|
||
// (shared queue)
|
||
// 14. Assign false to [[dequeue event scheduled]].
|
||
// (shared queue)
|
||
// 15. Return e.
|
||
}
|
||
configure(config) {
|
||
// 1. If config is not a valid VideoEncoderConfig, throw a TypeError.
|
||
// NOTE: We don't support sophisticated codec string parsing (yet)
|
||
// 2. If [[state]] is "closed", throw an InvalidStateError.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Encoder is closed", "InvalidStateError");
|
||
// Free any internal state
|
||
if (this._libav)
|
||
this._p = this._p.then(() => this._free());
|
||
// 3. Set [[state]] to "configured".
|
||
this.state = "configured";
|
||
// 4. Queue a control message to configure the encoder using config.
|
||
this._p = this._p.then(() => __awaiter$3(this, void 0, void 0, function* () {
|
||
/* 1. Let supported be the result of running the Check
|
||
* Configuration Support algorithm with config. */
|
||
const supported = encoder(config.codec, config);
|
||
/* 2. If supported is false, queue a task to run the Close
|
||
* VideoEncoder algorithm with NotSupportedError and abort these
|
||
* steps. */
|
||
if (!supported) {
|
||
this._closeVideoEncoder(new DOMException("Unsupported codec", "NotSupportedError"));
|
||
return;
|
||
}
|
||
/* 3. If needed, assign [[codec implementation]] with an
|
||
* implementation supporting config. */
|
||
// 4. Configure [[codec implementation]] with config.
|
||
const libav = this._libav = yield get();
|
||
this._metadata = {
|
||
decoderConfig: {
|
||
codec: supported.codec
|
||
}
|
||
};
|
||
// And initialize
|
||
[this._codec, this._c, this._frame, this._pkt] =
|
||
yield libav.ff_init_encoder(supported.codec, supported);
|
||
this._extradataSet = false;
|
||
this._extradata = null;
|
||
yield libav.AVCodecContext_time_base_s(this._c, 1, 1000);
|
||
const width = config.width;
|
||
const height = config.height;
|
||
this._sws = 0;
|
||
this._swsFrame = 0;
|
||
this._swsOut = {
|
||
width, height,
|
||
format: supported.ctx.pix_fmt
|
||
};
|
||
// Check for non-square pixels
|
||
const dWidth = config.displayWidth || width;
|
||
const dHeight = config.displayHeight || height;
|
||
if (dWidth !== width || dHeight !== height) {
|
||
this._nonSquarePixels = true;
|
||
this._sar_num = dWidth * height;
|
||
this._sar_den = dHeight * width;
|
||
}
|
||
else {
|
||
this._nonSquarePixels = false;
|
||
}
|
||
// 5. queue a task to run the following steps:
|
||
// 1. Assign false to [[message queue blocked]].
|
||
// 2. Queue a task to Process the control message queue.
|
||
})).catch(this._error);
|
||
}
|
||
// Our own algorithm, close libav
|
||
_free() {
|
||
return __awaiter$3(this, void 0, void 0, function* () {
|
||
if (this._sws) {
|
||
yield this._libav.av_frame_free_js(this._swsFrame);
|
||
yield this._libav.sws_freeContext(this._sws);
|
||
this._sws = this._swsFrame = 0;
|
||
this._swsIn = this._swsOut = void 0;
|
||
}
|
||
if (this._c) {
|
||
yield this._libav.ff_free_encoder(this._c, this._frame, this._pkt);
|
||
this._codec = this._c = this._frame = this._pkt = 0;
|
||
}
|
||
if (this._libav) {
|
||
free(this._libav);
|
||
this._libav = null;
|
||
}
|
||
});
|
||
}
|
||
_closeVideoEncoder(exception) {
|
||
// 1. Run the Reset VideoEncoder algorithm with exception.
|
||
this._resetVideoEncoder(exception);
|
||
// 2. Set [[state]] to "closed".
|
||
this.state = "closed";
|
||
/* 3. Clear [[codec implementation]] and release associated system
|
||
* resources. */
|
||
this._p = this._p.then(() => this._free());
|
||
/* 4. If exception is not an AbortError DOMException, invoke the
|
||
* [[error callback]] with exception. */
|
||
if (exception.name !== "AbortError")
|
||
this._p = this._p.then(() => { this._error(exception); });
|
||
}
|
||
_resetVideoEncoder(exception) {
|
||
// 1. If [[state]] is "closed", throw an InvalidStateError.
|
||
if (this.state === "closed")
|
||
throw new DOMException("Encoder closed", "InvalidStateError");
|
||
// 2. Set [[state]] to "unconfigured".
|
||
this.state = "unconfigured";
|
||
// ... really, we're just going to free it now
|
||
this._p = this._p.then(() => this._free());
|
||
}
|
||
encode(frame, options = {}) {
|
||
/* 1. If the value of frame’s [[Detached]] internal slot is true, throw
|
||
* a TypeError. */
|
||
if (frame._libavGetData() === null)
|
||
throw new TypeError("Detached");
|
||
// 2. If [[state]] is not "configured", throw an InvalidStateError.
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Unconfigured", "InvalidStateError");
|
||
/* 3. Let frameClone hold the result of running the Clone VideoFrame
|
||
* algorithm with frame. */
|
||
const frameClone = frame.clone();
|
||
// 4. Increment [[encodeQueueSize]].
|
||
this.encodeQueueSize++;
|
||
// 5. Queue a control message to encode frameClone.
|
||
this._p = this._p.then(() => __awaiter$3(this, void 0, void 0, function* () {
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const pkt = this._pkt;
|
||
const framePtr = this._frame;
|
||
const swsOut = this._swsOut;
|
||
let encodedOutputs = null;
|
||
/* 3. Decrement [[encodeQueueSize]] and run the Schedule Dequeue
|
||
* Event algorithm. */
|
||
this.encodeQueueSize--;
|
||
this.dispatchEvent(new CustomEvent("dequeue"));
|
||
/* 1. Attempt to use [[codec implementation]] to encode frameClone
|
||
* according to options. */
|
||
try {
|
||
// Convert the format
|
||
const format = wcFormatToLibAVFormat(libav, frameClone.format);
|
||
// Convert the data
|
||
const rawU8 = frameClone._libavGetData();
|
||
const layout = frameClone._libavGetLayout();
|
||
// Convert the timestamp
|
||
const ptsFull = Math.floor(frameClone.timestamp / 1000);
|
||
const [pts, ptshi] = libav.f64toi64(ptsFull);
|
||
// Make the frame
|
||
const frame = {
|
||
data: rawU8, layout,
|
||
format, pts, ptshi,
|
||
width: frameClone.codedWidth,
|
||
height: frameClone.codedHeight,
|
||
crop: {
|
||
left: frameClone.visibleRect.left,
|
||
right: frameClone.visibleRect.right,
|
||
top: frameClone.visibleRect.top,
|
||
bottom: frameClone.visibleRect.bottom
|
||
},
|
||
key_frame: options.keyFrame ? 1 : 0,
|
||
pict_type: options.keyFrame ? 1 : 0
|
||
};
|
||
// Possibly scale
|
||
if (frame.width !== swsOut.width ||
|
||
frame.height !== swsOut.height ||
|
||
frame.format !== swsOut.format) {
|
||
if (frameClone._nonSquarePixels) {
|
||
frame.sample_aspect_ratio = [
|
||
frameClone._sar_num,
|
||
frameClone._sar_den
|
||
];
|
||
}
|
||
// Need a scaler
|
||
let sws = this._sws, swsIn = this._swsIn, swsFrame = this._swsFrame;
|
||
if (!sws ||
|
||
frame.width !== swsIn.width ||
|
||
frame.height !== swsIn.height ||
|
||
frame.format !== swsIn.format) {
|
||
// Need to allocate the scaler
|
||
if (sws)
|
||
yield libav.sws_freeContext(sws);
|
||
swsIn = {
|
||
width: frame.width,
|
||
height: frame.height,
|
||
format: frame.format
|
||
};
|
||
sws = yield libav.sws_getContext(swsIn.width, swsIn.height, swsIn.format, swsOut.width, swsOut.height, swsOut.format, 2, 0, 0, 0);
|
||
this._sws = sws;
|
||
this._swsIn = swsIn;
|
||
// Maybe need a frame
|
||
if (!swsFrame)
|
||
this._swsFrame = swsFrame = yield libav.av_frame_alloc();
|
||
}
|
||
// Scale and encode the frame
|
||
const [, swsRes, , , , , , encRes] = yield Promise.all([
|
||
libav.ff_copyin_frame(framePtr, frame),
|
||
libav.sws_scale_frame(sws, swsFrame, framePtr),
|
||
this._nonSquarePixels ?
|
||
libav.AVFrame_sample_aspect_ratio_s(swsFrame, this._sar_num, this._sar_den) :
|
||
null,
|
||
libav.AVFrame_pts_s(swsFrame, pts),
|
||
libav.AVFrame_ptshi_s(swsFrame, ptshi),
|
||
libav.AVFrame_key_frame_s(swsFrame, options.keyFrame ? 1 : 0),
|
||
libav.AVFrame_pict_type_s(swsFrame, options.keyFrame ? 1 : 0),
|
||
libav.avcodec_send_frame(c, swsFrame)
|
||
]);
|
||
if (swsRes < 0 || encRes < 0)
|
||
throw new Error("Encoding failed!");
|
||
encodedOutputs = [];
|
||
while (true) {
|
||
const recv = yield libav.avcodec_receive_packet(c, pkt);
|
||
if (recv === -libav.EAGAIN)
|
||
break;
|
||
else if (recv < 0)
|
||
throw new Error("Encoding failed!");
|
||
encodedOutputs.push(yield libav.ff_copyout_packet(pkt));
|
||
}
|
||
}
|
||
else {
|
||
if (this._nonSquarePixels) {
|
||
frame.sample_aspect_ratio = [
|
||
this._sar_num,
|
||
this._sar_den
|
||
];
|
||
}
|
||
// Encode directly
|
||
encodedOutputs =
|
||
yield libav.ff_encode_multi(c, framePtr, pkt, [frame]);
|
||
}
|
||
if (encodedOutputs.length && !this._extradataSet)
|
||
yield this._getExtradata();
|
||
/* 2. If encoding results in an error, queue a task to run the
|
||
* Close VideoEncoder algorithm with EncodingError and return. */
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeVideoEncoder(ex);
|
||
});
|
||
return;
|
||
}
|
||
/* 3. If [[codec saturated]] equals true and
|
||
* [[codec implementation]] is no longer saturated, queue a task
|
||
* to perform the following steps: */
|
||
// 1. Assign false to [[codec saturated]].
|
||
// 2. Process the control message queue.
|
||
// (unneeded in polyfill)
|
||
/* 4. Let encoded outputs be a list of encoded video data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
/* 5. If encoded outputs is not empty, queue a task to run the
|
||
* Output EncodedVideoChunks algorithm with encoded outputs. */
|
||
if (encodedOutputs)
|
||
this._outputEncodedVideoChunks(encodedOutputs);
|
||
})).catch(this._error);
|
||
}
|
||
// Internal: Get extradata
|
||
_getExtradata() {
|
||
return __awaiter$3(this, void 0, void 0, function* () {
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const extradata = yield libav.AVCodecContext_extradata(c);
|
||
const extradata_size = yield libav.AVCodecContext_extradata_size(c);
|
||
if (extradata && extradata_size) {
|
||
this._metadata.decoderConfig.description = this._extradata =
|
||
yield libav.copyout_u8(extradata, extradata_size);
|
||
}
|
||
this._extradataSet = true;
|
||
});
|
||
}
|
||
_outputEncodedVideoChunks(packets) {
|
||
const libav = this._libav;
|
||
for (const packet of packets) {
|
||
// 1. type
|
||
const type = (packet.flags & 1) ? "key" : "delta";
|
||
// 2. timestamp
|
||
const timestamp = libav.i64tof64(packet.pts, packet.ptshi) * 1000;
|
||
const chunk = new EncodedVideoChunk$1({
|
||
type: type, timestamp,
|
||
data: packet.data
|
||
});
|
||
if (this._extradataSet)
|
||
this._output(chunk, this._metadata || void 0);
|
||
else
|
||
this._output(chunk);
|
||
}
|
||
}
|
||
flush() {
|
||
/* 1. If [[state]] is not "configured", return a promise rejected with
|
||
* InvalidStateError DOMException. */
|
||
if (this.state !== "configured")
|
||
throw new DOMException("Invalid state", "InvalidStateError");
|
||
// 2. Let promise be a new Promise.
|
||
// 3. Append promise to [[pending flush promises]].
|
||
// 4. Queue a control message to flush the codec with promise.
|
||
// 5. Process the control message queue.
|
||
const ret = this._p.then(() => __awaiter$3(this, void 0, void 0, function* () {
|
||
/* 1. Signal [[codec implementation]] to emit all internal pending
|
||
* outputs. */
|
||
if (!this._c)
|
||
return;
|
||
// Make sure any last data is flushed
|
||
const libav = this._libav;
|
||
const c = this._c;
|
||
const frame = this._frame;
|
||
const pkt = this._pkt;
|
||
let encodedOutputs = null;
|
||
try {
|
||
encodedOutputs =
|
||
yield libav.ff_encode_multi(c, frame, pkt, [], true);
|
||
if (!this._extradataSet)
|
||
yield this._getExtradata();
|
||
}
|
||
catch (ex) {
|
||
this._p = this._p.then(() => {
|
||
this._closeVideoEncoder(ex);
|
||
});
|
||
}
|
||
/* 2. Let encoded outputs be a list of encoded video data outputs
|
||
* emitted by [[codec implementation]]. */
|
||
// 3. Queue a task to perform these steps:
|
||
{
|
||
/* 1. If encoded outputs is not empty, run the Output
|
||
* EncodedVideoChunks algorithm with encoded outputs. */
|
||
if (encodedOutputs)
|
||
this._outputEncodedVideoChunks(encodedOutputs);
|
||
// 2. Remove promise from [[pending flush promises]].
|
||
// 3. Resolve promise.
|
||
}
|
||
}));
|
||
this._p = ret;
|
||
// 6. Return promise.
|
||
return ret;
|
||
}
|
||
reset() {
|
||
this._resetVideoEncoder(new DOMException("Reset", "AbortError"));
|
||
}
|
||
close() {
|
||
this._closeVideoEncoder(new DOMException("Close", "AbortError"));
|
||
}
|
||
static isConfigSupported(config) {
|
||
return __awaiter$3(this, void 0, void 0, function* () {
|
||
const enc = encoder(config.codec, config);
|
||
let supported = false;
|
||
if (enc) {
|
||
const libav = yield get();
|
||
try {
|
||
const [, c, frame, pkt] = yield libav.ff_init_encoder(enc.codec, enc);
|
||
yield libav.ff_free_encoder(c, frame, pkt);
|
||
supported = true;
|
||
}
|
||
catch (ex) { }
|
||
yield free(libav);
|
||
}
|
||
return {
|
||
supported,
|
||
config: cloneConfig(config, ["codec", "width", "height", "bitrate", "framerate", "latencyMode"])
|
||
};
|
||
});
|
||
}
|
||
};
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$2 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
// A non-threaded libav.js instance for scaling.
|
||
let scalerSync = null;
|
||
// A synchronous libav.js instance for scaling.
|
||
let scalerAsync = null;
|
||
// The original drawImage
|
||
let origDrawImage = null;
|
||
// The original drawImage Offscreen
|
||
let origDrawImageOffscreen = null;
|
||
// The original createImageBitmap
|
||
let origCreateImageBitmap = null;
|
||
/**
|
||
* Load rendering capability.
|
||
* @param libavOptions Options to use while loading libav, only asynchronous
|
||
* @param polyfill Set to polyfill CanvasRenderingContext2D.drawImage
|
||
*/
|
||
function load$1(libavOptions, polyfill) {
|
||
return __awaiter$2(this, void 0, void 0, function* () {
|
||
// Get our scalers
|
||
if ("importScripts" in globalThis) {
|
||
// Make sure the worker code doesn't run
|
||
LibAVWrapper.nolibavworker = true;
|
||
}
|
||
scalerSync = yield LibAVWrapper.LibAV({ noworker: true });
|
||
scalerAsync = yield LibAVWrapper.LibAV(libavOptions);
|
||
// Polyfill drawImage
|
||
if ('CanvasRenderingContext2D' in globalThis) {
|
||
origDrawImage = CanvasRenderingContext2D.prototype.drawImage;
|
||
if (polyfill)
|
||
CanvasRenderingContext2D.prototype.drawImage = drawImagePolyfill;
|
||
}
|
||
if ('OffscreenCanvasRenderingContext2D' in globalThis) {
|
||
origDrawImageOffscreen = OffscreenCanvasRenderingContext2D.prototype.drawImage;
|
||
if (polyfill)
|
||
OffscreenCanvasRenderingContext2D.prototype.drawImage = drawImagePolyfillOffscreen;
|
||
}
|
||
// Polyfill createImageBitmap
|
||
origCreateImageBitmap = globalThis.createImageBitmap;
|
||
if (polyfill)
|
||
globalThis.createImageBitmap = createImageBitmap$1;
|
||
});
|
||
}
|
||
/**
|
||
* Draw this video frame on this canvas, synchronously.
|
||
* @param ctx CanvasRenderingContext2D to draw on
|
||
* @param image VideoFrame (or anything else) to draw
|
||
* @param sx Source X position OR destination X position
|
||
* @param sy Source Y position OR destination Y position
|
||
* @param sWidth Source width OR destination width
|
||
* @param sHeight Source height OR destination height
|
||
* @param dx Destination X position
|
||
* @param dy Destination Y position
|
||
* @param dWidth Destination width
|
||
* @param dHeight Destination height
|
||
*/
|
||
function canvasDrawImage$1(ctx, image, ax, ay, sWidth, sHeight, dx, dy, dWidth, dHeight) {
|
||
if (!(image._data)) {
|
||
// Just use the original
|
||
return origDrawImage.apply(ctx, Array.prototype.slice.call(arguments, 1));
|
||
}
|
||
// Normalize the arguments
|
||
if (typeof sWidth === "undefined") {
|
||
// dx, dy
|
||
dx = ax;
|
||
dy = ay;
|
||
}
|
||
else if (typeof dx === "undefined") {
|
||
// dx, dy, dWidth, dHeight
|
||
dx = ax;
|
||
dy = ay;
|
||
dWidth = sWidth;
|
||
dHeight = sHeight;
|
||
sWidth = void 0;
|
||
sHeight = void 0;
|
||
}
|
||
else ;
|
||
if (typeof dWidth === "undefined") {
|
||
dWidth = image.displayWidth;
|
||
dHeight = image.displayHeight;
|
||
}
|
||
// Convert the format to libav.js
|
||
const format = wcFormatToLibAVFormat(scalerSync, image.format);
|
||
// Convert the frame synchronously
|
||
const sctx = scalerSync.sws_getContext_sync(image.visibleRect.width, image.visibleRect.height, format, dWidth, dHeight, scalerSync.AV_PIX_FMT_RGBA, 2, 0, 0, 0);
|
||
const inFrame = scalerSync.av_frame_alloc_sync();
|
||
const outFrame = scalerSync.av_frame_alloc_sync();
|
||
let rawU8;
|
||
let layout;
|
||
if (image._libavGetData) {
|
||
rawU8 = image._libavGetData();
|
||
layout = image._libavGetLayout();
|
||
}
|
||
else {
|
||
// Just have to hope this is a polyfill VideoFrame copied weirdly!
|
||
rawU8 = image._data;
|
||
layout = image._layout;
|
||
}
|
||
// Copy it in
|
||
scalerSync.ff_copyin_frame_sync(inFrame, {
|
||
data: rawU8,
|
||
layout,
|
||
format,
|
||
width: image.codedWidth,
|
||
height: image.codedHeight,
|
||
crop: {
|
||
left: image.visibleRect.left,
|
||
right: image.visibleRect.right,
|
||
top: image.visibleRect.top,
|
||
bottom: image.visibleRect.bottom
|
||
}
|
||
});
|
||
// Rescale
|
||
scalerSync.sws_scale_frame_sync(sctx, outFrame, inFrame);
|
||
// Get the data back out again
|
||
const frameData = scalerSync.ff_copyout_frame_video_imagedata_sync(outFrame);
|
||
// Finally, draw it
|
||
ctx.putImageData(frameData, dx, dy);
|
||
// And clean up
|
||
scalerSync.av_frame_free_js_sync(outFrame);
|
||
scalerSync.av_frame_free_js_sync(inFrame);
|
||
scalerSync.sws_freeContext_sync(sctx);
|
||
}
|
||
/**
|
||
* Polyfill version of canvasDrawImage.
|
||
*/
|
||
function drawImagePolyfill(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
|
||
if (image instanceof VideoFrame$1) {
|
||
return canvasDrawImage$1(this, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
|
||
}
|
||
return origDrawImage.apply(this, arguments);
|
||
}
|
||
/**
|
||
* Polyfill version of offscreenCanvasDrawImage.
|
||
*/
|
||
function drawImagePolyfillOffscreen(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
|
||
if (image instanceof VideoFrame$1) {
|
||
return canvasDrawImage$1(this, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
|
||
}
|
||
return origDrawImageOffscreen.apply(this, arguments);
|
||
}
|
||
/**
|
||
* Create an ImageBitmap from this drawable, asynchronously. NOTE:
|
||
* Sub-rectangles are not implemented for VideoFrames, so only options is
|
||
* available, and there, only scaling is available.
|
||
* @param image VideoFrame (or anything else) to draw
|
||
* @param options Other options
|
||
*/
|
||
function createImageBitmap$1(image, opts = {}) {
|
||
if (!(image._data)) {
|
||
// Just use the original
|
||
return origCreateImageBitmap.apply(globalThis, arguments);
|
||
}
|
||
// Convert the format to libav.js
|
||
const format = wcFormatToLibAVFormat(scalerAsync, image.format);
|
||
// Normalize arguments
|
||
const dWidth = (typeof opts.resizeWidth === "number")
|
||
? opts.resizeWidth : image.displayWidth;
|
||
const dHeight = (typeof opts.resizeHeight === "number")
|
||
? opts.resizeHeight : image.displayHeight;
|
||
// Convert the frame
|
||
return (() => __awaiter$2(this, void 0, void 0, function* () {
|
||
const [sctx, inFrame, outFrame] = yield Promise.all([
|
||
scalerAsync.sws_getContext(image.visibleRect.width, image.visibleRect.height, format, dWidth, dHeight, scalerAsync.AV_PIX_FMT_RGBA, 2, 0, 0, 0),
|
||
scalerAsync.av_frame_alloc(),
|
||
scalerAsync.av_frame_alloc()
|
||
]);
|
||
// Convert the data
|
||
let rawU8;
|
||
let layout = void 0;
|
||
if (image._libavGetData) {
|
||
rawU8 = image._libavGetData();
|
||
layout = image._libavGetLayout();
|
||
}
|
||
else if (image._data) {
|
||
// Assume a VideoFrame weirdly serialized
|
||
rawU8 = image._data;
|
||
layout = image._layout;
|
||
}
|
||
else {
|
||
rawU8 = new Uint8Array(image.allocationSize());
|
||
yield image.copyTo(rawU8);
|
||
}
|
||
// Copy it in
|
||
yield scalerAsync.ff_copyin_frame(inFrame, {
|
||
data: rawU8,
|
||
layout,
|
||
format,
|
||
width: image.codedWidth,
|
||
height: image.codedHeight,
|
||
crop: {
|
||
left: image.visibleRect.left,
|
||
right: image.visibleRect.right,
|
||
top: image.visibleRect.top,
|
||
bottom: image.visibleRect.bottom
|
||
}
|
||
}),
|
||
// Rescale
|
||
yield scalerAsync.sws_scale_frame(sctx, outFrame, inFrame);
|
||
// Get the data back out again
|
||
const frameData = yield scalerAsync.ff_copyout_frame_video_imagedata(outFrame);
|
||
// And clean up
|
||
yield Promise.all([
|
||
scalerAsync.av_frame_free_js(outFrame),
|
||
scalerAsync.av_frame_free_js(inFrame),
|
||
scalerAsync.sws_freeContext(sctx)
|
||
]);
|
||
// Make the ImageBitmap
|
||
return yield origCreateImageBitmap(frameData);
|
||
}))();
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter$1 = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
/**
|
||
* Error thrown to indicate a configuration is unsupported.
|
||
*/
|
||
let UnsupportedException$1 = class UnsupportedException extends Error {
|
||
constructor() {
|
||
super("The requested configuration is not supported");
|
||
}
|
||
};
|
||
/**
|
||
* Get an AudioDecoder environment that supports this configuration. Throws an
|
||
* UnsupportedException if no environment supports the configuration.
|
||
* @param config Audio decoder configuration
|
||
*/
|
||
function getAudioDecoder$1(config) {
|
||
return __awaiter$1(this, void 0, void 0, function* () {
|
||
try {
|
||
if (typeof globalThis.AudioDecoder !== "undefined" &&
|
||
(yield globalThis.AudioDecoder.isConfigSupported(config)).supported) {
|
||
return {
|
||
AudioDecoder: globalThis.AudioDecoder,
|
||
EncodedAudioChunk: globalThis.EncodedAudioChunk,
|
||
AudioData: globalThis.AudioData
|
||
};
|
||
}
|
||
}
|
||
catch (ex) { }
|
||
if ((yield AudioDecoder$1.isConfigSupported(config)).supported) {
|
||
return {
|
||
AudioDecoder: AudioDecoder$1,
|
||
EncodedAudioChunk: EncodedAudioChunk$1,
|
||
AudioData: AudioData$1
|
||
};
|
||
}
|
||
throw new UnsupportedException$1();
|
||
});
|
||
}
|
||
/**
|
||
* Get an VideoDecoder environment that supports this configuration. Throws an
|
||
* UnsupportedException if no environment supports the configuration.
|
||
* @param config Video decoder configuration
|
||
*/
|
||
function getVideoDecoder$1(config) {
|
||
return __awaiter$1(this, void 0, void 0, function* () {
|
||
try {
|
||
if (typeof globalThis.VideoDecoder !== "undefined" &&
|
||
(yield globalThis.VideoDecoder.isConfigSupported(config)).supported) {
|
||
return {
|
||
VideoDecoder: globalThis.VideoDecoder,
|
||
EncodedVideoChunk: globalThis.EncodedVideoChunk,
|
||
VideoFrame: globalThis.VideoFrame
|
||
};
|
||
}
|
||
}
|
||
catch (ex) { }
|
||
if ((yield VideoDecoder$1.isConfigSupported(config)).supported) {
|
||
return {
|
||
VideoDecoder: VideoDecoder$1,
|
||
EncodedVideoChunk: EncodedVideoChunk$1,
|
||
VideoFrame: VideoFrame$1
|
||
};
|
||
}
|
||
throw new UnsupportedException$1();
|
||
});
|
||
}
|
||
/**
|
||
* Get an AudioEncoder environment that supports this configuration. Throws an
|
||
* UnsupportedException if no environment supports the configuration.
|
||
* @param config Audio encoder configuration
|
||
*/
|
||
function getAudioEncoder$1(config) {
|
||
return __awaiter$1(this, void 0, void 0, function* () {
|
||
try {
|
||
if (typeof globalThis.AudioEncoder !== "undefined" &&
|
||
(yield globalThis.AudioEncoder.isConfigSupported(config)).supported) {
|
||
return {
|
||
AudioEncoder: globalThis.AudioEncoder,
|
||
EncodedAudioChunk: globalThis.EncodedAudioChunk,
|
||
AudioData: globalThis.AudioData
|
||
};
|
||
}
|
||
}
|
||
catch (ex) { }
|
||
if ((yield AudioEncoder$1.isConfigSupported(config)).supported) {
|
||
return {
|
||
AudioEncoder: AudioEncoder$1,
|
||
EncodedAudioChunk: EncodedAudioChunk$1,
|
||
AudioData: AudioData$1
|
||
};
|
||
}
|
||
throw new UnsupportedException$1();
|
||
});
|
||
}
|
||
/**
|
||
* Get an VideoEncoder environment that supports this configuration. Throws an
|
||
* UnsupportedException if no environment supports the configuration.
|
||
* @param config Video encoder configuration
|
||
*/
|
||
function getVideoEncoder$1(config) {
|
||
return __awaiter$1(this, void 0, void 0, function* () {
|
||
try {
|
||
if (typeof globalThis.VideoEncoder !== "undefined" &&
|
||
(yield globalThis.VideoEncoder.isConfigSupported(config)).supported) {
|
||
return {
|
||
VideoEncoder: globalThis.VideoEncoder,
|
||
EncodedVideoChunk: globalThis.EncodedVideoChunk,
|
||
VideoFrame: globalThis.VideoFrame
|
||
};
|
||
}
|
||
}
|
||
catch (ex) { }
|
||
if ((yield VideoEncoder$1.isConfigSupported(config)).supported) {
|
||
return {
|
||
VideoEncoder: VideoEncoder$1,
|
||
EncodedVideoChunk: EncodedVideoChunk$1,
|
||
VideoFrame: VideoFrame$1
|
||
};
|
||
}
|
||
throw new UnsupportedException$1();
|
||
});
|
||
}
|
||
|
||
/*
|
||
* This file is part of the libav.js WebCodecs Polyfill implementation. The
|
||
* interface implemented is derived from the W3C standard. No attribution is
|
||
* required when using this library.
|
||
*
|
||
* Copyright (c) 2021-2024 Yahweasel
|
||
*
|
||
* Permission to use, copy, modify, and/or distribute this software for any
|
||
* purpose with or without fee is hereby granted.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*/
|
||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
};
|
||
/**
|
||
* Load LibAV-WebCodecs-Polyfill.
|
||
*/
|
||
function load(options = {}) {
|
||
return __awaiter(this, void 0, void 0, function* () {
|
||
// Set up libavOptions
|
||
let libavOptions = {};
|
||
if (options.libavOptions)
|
||
Object.assign(libavOptions, options.libavOptions);
|
||
// Maybe load libav
|
||
if (!options.LibAV && typeof globalThis.LibAV === "undefined") {
|
||
yield new Promise((res, rej) => {
|
||
// Can't load workers from another origin
|
||
libavOptions.noworker = true;
|
||
// Load libav
|
||
const libavBase = "https://cdn.jsdelivr.net/npm/@libav.js/variant-webm-vp9@6.5.7/dist";
|
||
globalThis.LibAV = { base: libavBase };
|
||
const libavVar = "libav-6.0.7.0.2-webm-vp9.js";
|
||
if (typeof importScripts !== "undefined") {
|
||
importScripts(`${libavBase}/${libavVar}`);
|
||
res(void 0);
|
||
}
|
||
else {
|
||
const scr = document.createElement("script");
|
||
scr.src = `${libavBase}/${libavVar}`;
|
||
scr.onload = res;
|
||
scr.onerror = rej;
|
||
document.body.appendChild(scr);
|
||
}
|
||
});
|
||
}
|
||
// And load the libav handler
|
||
if (options.LibAV)
|
||
setLibAV(options.LibAV);
|
||
setLibAVOptions(libavOptions);
|
||
yield load$2();
|
||
if (options.polyfill) {
|
||
for (const exp of [
|
||
["EncodedAudioChunk", EncodedAudioChunk$1],
|
||
["AudioData", AudioData$1],
|
||
["AudioDecoder", AudioDecoder$1],
|
||
["AudioEncoder", AudioEncoder$1],
|
||
["EncodedVideoChunk", EncodedVideoChunk$1],
|
||
["VideoFrame", VideoFrame$1],
|
||
["VideoDecoder", VideoDecoder$1],
|
||
["VideoEncoder", VideoEncoder$1]
|
||
]) {
|
||
if (!globalThis[exp[0]])
|
||
globalThis[exp[0]] = exp[1];
|
||
}
|
||
}
|
||
yield load$1(libavOptions, !!options.polyfill);
|
||
});
|
||
}
|
||
const EncodedAudioChunk = EncodedAudioChunk$1;
|
||
const AudioData = AudioData$1;
|
||
const AudioDecoder = AudioDecoder$1;
|
||
const AudioEncoder = AudioEncoder$1;
|
||
const EncodedVideoChunk = EncodedVideoChunk$1;
|
||
const VideoFrame = VideoFrame$1;
|
||
const VideoDecoder = VideoDecoder$1;
|
||
const VideoEncoder = VideoEncoder$1;
|
||
// Rendering
|
||
const canvasDrawImage = canvasDrawImage$1;
|
||
const createImageBitmap = createImageBitmap$1;
|
||
const UnsupportedException = UnsupportedException$1;
|
||
const getAudioDecoder = getAudioDecoder$1;
|
||
const getVideoDecoder = getVideoDecoder$1;
|
||
const getAudioEncoder = getAudioEncoder$1;
|
||
const getVideoEncoder = getVideoEncoder$1;
|
||
|
||
exports.AudioData = AudioData;
|
||
exports.AudioDecoder = AudioDecoder;
|
||
exports.AudioEncoder = AudioEncoder;
|
||
exports.EncodedAudioChunk = EncodedAudioChunk;
|
||
exports.EncodedVideoChunk = EncodedVideoChunk;
|
||
exports.UnsupportedException = UnsupportedException;
|
||
exports.VideoDecoder = VideoDecoder;
|
||
exports.VideoEncoder = VideoEncoder;
|
||
exports.VideoFrame = VideoFrame;
|
||
exports.canvasDrawImage = canvasDrawImage;
|
||
exports.createImageBitmap = createImageBitmap;
|
||
exports.getAudioDecoder = getAudioDecoder;
|
||
exports.getAudioEncoder = getAudioEncoder;
|
||
exports.getVideoDecoder = getVideoDecoder;
|
||
exports.getVideoEncoder = getVideoEncoder;
|
||
exports.load = load;
|
||
|
||
}));
|