web/webcodecs: use polyfill as fallback
This commit is contained in:
parent
43b3db1317
commit
9f21a6eb46
4 changed files with 73 additions and 45 deletions
|
@ -97,9 +97,12 @@ importers:
|
||||||
'@imput/libav.js-remux-cli':
|
'@imput/libav.js-remux-cli':
|
||||||
specifier: ^5.7.6
|
specifier: ^5.7.6
|
||||||
version: 5.7.6
|
version: 5.7.6
|
||||||
|
'@imput/libavjs-webcodecs-bridge':
|
||||||
|
specifier: ^0.1.2
|
||||||
|
version: 0.1.2
|
||||||
'@imput/libavjs-webcodecs-polyfill':
|
'@imput/libavjs-webcodecs-polyfill':
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.2
|
||||||
version: 0.5.1
|
version: 0.5.2
|
||||||
'@imput/version-info':
|
'@imput/version-info':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../packages/version-info
|
version: link:../packages/version-info
|
||||||
|
@ -109,9 +112,6 @@ importers:
|
||||||
'@vitejs/plugin-basic-ssl':
|
'@vitejs/plugin-basic-ssl':
|
||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0(vite@5.3.5(@types/node@20.14.14))
|
version: 1.1.0(vite@5.3.5(@types/node@20.14.14))
|
||||||
libavjs-webcodecs-bridge:
|
|
||||||
specifier: ^0.1.0
|
|
||||||
version: 0.1.0
|
|
||||||
mime:
|
mime:
|
||||||
specifier: ^4.0.4
|
specifier: ^4.0.4
|
||||||
version: 4.0.4
|
version: 4.0.4
|
||||||
|
@ -544,8 +544,11 @@ packages:
|
||||||
'@imput/libav.js-remux-cli@5.7.6':
|
'@imput/libav.js-remux-cli@5.7.6':
|
||||||
resolution: {integrity: sha512-ofSSLjRF9RfZ3QMBlb7fhxso8p8xlDqU4qX8eCJCukCB15g7iBShthCyGVnYz+3lLoFu9klbvVal9bEncBj/FQ==}
|
resolution: {integrity: sha512-ofSSLjRF9RfZ3QMBlb7fhxso8p8xlDqU4qX8eCJCukCB15g7iBShthCyGVnYz+3lLoFu9klbvVal9bEncBj/FQ==}
|
||||||
|
|
||||||
'@imput/libavjs-webcodecs-polyfill@0.5.1':
|
'@imput/libavjs-webcodecs-bridge@0.1.2':
|
||||||
resolution: {integrity: sha512-uQ5vawG/4ppLKDumkg8BjnvqKWzoKXxt7cj0lvTpB9ph1hiuzjNx8wmwMcXbeTVAzRcts+GtCTPpSF9rFosYBg==}
|
resolution: {integrity: sha512-8QVvCe9CRe3oVGpMrx/RtHmPunrK+I52nUImM/FDbOt13nEq0rd+zu5ADIMOw7rq/8OgLqqf/o1yQ+Sa4/mhSw==}
|
||||||
|
|
||||||
|
'@imput/libavjs-webcodecs-polyfill@0.5.2':
|
||||||
|
resolution: {integrity: sha512-3wyRFUTV7yJyYNJvSKzoZWNnGbSN/5RVj+Xx9vYH8/xt73TLpXBBqotGc7UiHNeSy6eFO0EjUzAmieBa+YjLrQ==}
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
'@isaacs/cliui@8.0.2':
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
|
@ -1541,9 +1544,6 @@ packages:
|
||||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
libavjs-webcodecs-bridge@0.1.0:
|
|
||||||
resolution: {integrity: sha512-K9pGvW+k2sGnbaCEuhbLG6ylzKoQyVAA1WxO+qIzaYEeVumosaFeKtMzVAThcIy6VHo11rGVF6IqLfSEEQL7sg==}
|
|
||||||
|
|
||||||
lilconfig@3.1.2:
|
lilconfig@3.1.2:
|
||||||
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
|
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
@ -2522,7 +2522,9 @@ snapshots:
|
||||||
|
|
||||||
'@imput/libav.js-remux-cli@5.7.6': {}
|
'@imput/libav.js-remux-cli@5.7.6': {}
|
||||||
|
|
||||||
'@imput/libavjs-webcodecs-polyfill@0.5.1':
|
'@imput/libavjs-webcodecs-bridge@0.1.2': {}
|
||||||
|
|
||||||
|
'@imput/libavjs-webcodecs-polyfill@0.5.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ungap/global-this': 0.4.4
|
'@ungap/global-this': 0.4.4
|
||||||
|
|
||||||
|
@ -3572,8 +3574,6 @@ snapshots:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
type-check: 0.4.0
|
type-check: 0.4.0
|
||||||
|
|
||||||
libavjs-webcodecs-bridge@0.1.0: {}
|
|
||||||
|
|
||||||
lilconfig@3.1.2: {}
|
lilconfig@3.1.2: {}
|
||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
|
|
|
@ -51,11 +51,11 @@
|
||||||
"@fontsource/ibm-plex-mono": "^5.0.13",
|
"@fontsource/ibm-plex-mono": "^5.0.13",
|
||||||
"@imput/libav.js-encode-cli": "^5.7.6",
|
"@imput/libav.js-encode-cli": "^5.7.6",
|
||||||
"@imput/libav.js-remux-cli": "^5.7.6",
|
"@imput/libav.js-remux-cli": "^5.7.6",
|
||||||
"@imput/libavjs-webcodecs-polyfill": "^0.5.1",
|
"@imput/libavjs-webcodecs-bridge": "^0.1.2",
|
||||||
|
"@imput/libavjs-webcodecs-polyfill": "^0.5.2",
|
||||||
"@imput/version-info": "workspace:^",
|
"@imput/version-info": "workspace:^",
|
||||||
"@tabler/icons-svelte": "3.6.0",
|
"@tabler/icons-svelte": "3.6.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^1.1.0",
|
"@vitejs/plugin-basic-ssl": "^1.1.0",
|
||||||
"libavjs-webcodecs-bridge": "^0.1.0",
|
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"sveltekit-i18n": "^2.4.2",
|
"sveltekit-i18n": "^2.4.2",
|
||||||
"ts-deepmerge": "^7.0.0",
|
"ts-deepmerge": "^7.0.0",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import LibAV, { type LibAV as LibAVInstance, type Packet, type Stream } from "@imput/libav.js-encode-cli";
|
import LibAV, { type Packet, type Stream } from "@imput/libav.js-encode-cli";
|
||||||
import type { Chunk, ChunkMetadata, Decoder, FFmpegProgressCallback, OutputStream, Pipeline, RenderingPipeline } from "../types/libav";
|
import type { Chunk, ChunkMetadata, Decoder, OutputStream, Pipeline, RenderingPipeline } from "../types/libav";
|
||||||
import * as LibAVWebCodecs from "libavjs-webcodecs-bridge";
|
import * as LibAVWebCodecs from "@imput/libavjs-webcodecs-bridge";
|
||||||
import { BufferStream } from "./buffer-stream";
|
|
||||||
import { BufferStream } from "../buffer-stream";
|
import { BufferStream } from "../buffer-stream";
|
||||||
import WebCodecsWrapper from "./webcodecs";
|
import WebCodecsWrapper from "./webcodecs";
|
||||||
import LibAVWrapper from "./instance";
|
import LibAVWrapper from "./instance";
|
||||||
|
@ -44,7 +43,8 @@ export default class EncodeLibAV extends LibAVWrapper {
|
||||||
const pipes: RenderingPipeline[] = [];
|
const pipes: RenderingPipeline[] = [];
|
||||||
const output_streams: OutputStream[] = [];
|
const output_streams: OutputStream[] = [];
|
||||||
for (const stream of streams) {
|
for (const stream of streams) {
|
||||||
if (stream.codec_id === 61) {
|
// FIXME: support images.
|
||||||
|
if (stream.codec_id === 61 || stream.codec_id === 62) {
|
||||||
pipes.push(null);
|
pipes.push(null);
|
||||||
output_streams.push(null);
|
output_streams.push(null);
|
||||||
continue;
|
continue;
|
||||||
|
@ -53,7 +53,7 @@ export default class EncodeLibAV extends LibAVWrapper {
|
||||||
const {
|
const {
|
||||||
pipe,
|
pipe,
|
||||||
stream: ostream
|
stream: ostream
|
||||||
} = await this.#createEncoder(stream, 'avc1.64083e');
|
} = await this.#createEncoder(stream, 'mp3');
|
||||||
|
|
||||||
pipes.push({
|
pipes.push({
|
||||||
decoder: await this.#createDecoder(stream),
|
decoder: await this.#createDecoder(stream),
|
||||||
|
@ -312,12 +312,11 @@ export default class EncodeLibAV extends LibAVWrapper {
|
||||||
if (stream.codec_type === libav.AVMEDIA_TYPE_VIDEO) {
|
if (stream.codec_type === libav.AVMEDIA_TYPE_VIDEO) {
|
||||||
streamToConfig = LibAVWebCodecs.videoStreamToConfig;
|
streamToConfig = LibAVWebCodecs.videoStreamToConfig;
|
||||||
configToStream = LibAVWebCodecs.configToVideoStream;
|
configToStream = LibAVWebCodecs.configToVideoStream;
|
||||||
initEncoder = webcodecs!.initVideoEncoder.bind(webcodecs);
|
initEncoder = webcodecs.initVideoEncoder.bind(webcodecs);
|
||||||
} else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {
|
} else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {
|
||||||
streamToConfig = LibAVWebCodecs.audioStreamToConfig;
|
streamToConfig = LibAVWebCodecs.audioStreamToConfig;
|
||||||
configToStream = LibAVWebCodecs.configToAudioStream;
|
configToStream = LibAVWebCodecs.configToAudioStream;
|
||||||
initEncoder = webcodecs.initAudioEncoder.bind(webcodecs);
|
initEncoder = webcodecs.initAudioEncoder.bind(webcodecs);
|
||||||
codec = 'mp4a.40.29';
|
|
||||||
} else throw "Unknown type: " + stream.codec_type;
|
} else throw "Unknown type: " + stream.codec_type;
|
||||||
|
|
||||||
const config = await streamToConfig(libav, stream, true);
|
const config = await streamToConfig(libav, stream, true);
|
||||||
|
|
|
@ -16,31 +16,46 @@ export default class WebCodecsWrapper {
|
||||||
async #load() {
|
async #load() {
|
||||||
if (typeof this.#ready === 'undefined') {
|
if (typeof this.#ready === 'undefined') {
|
||||||
this.#ready = LibAVPolyfill.load({
|
this.#ready = LibAVPolyfill.load({
|
||||||
polyfill: false,
|
polyfill: true,
|
||||||
LibAV: { LibAV: () => this.#libav }
|
LibAV: { LibAV: () => this.#libav }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.#ready;
|
await this.#ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: save me generics. generics save me
|
||||||
async #getDecoder(config: VideoDecoderConfig | AudioDecoderConfig) {
|
async #getDecoder(config: VideoDecoderConfig | AudioDecoderConfig) {
|
||||||
if (has(config, 'numberOfChannels') && has(config, 'sampleRate')) {
|
if (has(config, 'numberOfChannels') && has(config, 'sampleRate')) {
|
||||||
const audioConfig = config as AudioDecoderConfig;
|
const audioConfig = config as AudioDecoderConfig;
|
||||||
|
for (const source of [ window, LibAVPolyfill ]) {
|
||||||
|
if (source === LibAVPolyfill) {
|
||||||
|
await this.#load();
|
||||||
|
}
|
||||||
|
|
||||||
if ('AudioDecoder' in window && await window.AudioDecoder.isConfigSupported(audioConfig))
|
try {
|
||||||
return window.AudioDecoder;
|
const { supported } = await source.AudioDecoder.isConfigSupported(audioConfig);
|
||||||
|
if (supported) return source.AudioDecoder;
|
||||||
await this.#load();
|
} catch(e) {
|
||||||
if (await LibAVPolyfill.AudioDecoder.isConfigSupported(audioConfig))
|
console.error('AudioDecoder missing or does not support', config);
|
||||||
return LibAVPolyfill.AudioDecoder;
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const videoConfig = config as VideoDecoderConfig;
|
const videoConfig = config as VideoDecoderConfig;
|
||||||
if ('VideoDecoder' in window && await window.VideoDecoder.isConfigSupported(videoConfig))
|
for (const source of [ window, LibAVPolyfill ]) {
|
||||||
return window.VideoDecoder;
|
if (source === LibAVPolyfill) {
|
||||||
|
await this.#load();
|
||||||
|
}
|
||||||
|
|
||||||
await this.#load();
|
try {
|
||||||
if (await LibAVPolyfill.VideoDecoder.isConfigSupported(videoConfig))
|
const { supported } = await source.VideoDecoder.isConfigSupported(videoConfig);
|
||||||
return LibAVPolyfill.VideoDecoder;
|
if (supported) return source.VideoDecoder;
|
||||||
|
} catch(e) {
|
||||||
|
console.error('VideoDecoder missing or does not support', config);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -49,20 +64,34 @@ export default class WebCodecsWrapper {
|
||||||
async #getEncoder(config: VideoEncoderConfig | AudioEncoderConfig) {
|
async #getEncoder(config: VideoEncoderConfig | AudioEncoderConfig) {
|
||||||
if (has(config, 'numberOfChannels') && has(config, 'sampleRate')) {
|
if (has(config, 'numberOfChannels') && has(config, 'sampleRate')) {
|
||||||
const audioConfig = config as AudioEncoderConfig;
|
const audioConfig = config as AudioEncoderConfig;
|
||||||
if ('AudioEncoder' in window && await window.AudioEncoder.isConfigSupported(audioConfig))
|
for (const source of [ window, LibAVPolyfill ]) {
|
||||||
return window.AudioEncoder;
|
if (source === LibAVPolyfill) {
|
||||||
|
await this.#load();
|
||||||
|
}
|
||||||
|
|
||||||
await this.#load();
|
try {
|
||||||
if (await LibAVPolyfill.AudioEncoder.isConfigSupported(audioConfig))
|
const { supported } = await source.AudioEncoder.isConfigSupported(audioConfig);
|
||||||
return LibAVPolyfill.AudioEncoder;
|
if (supported) return source.AudioEncoder;
|
||||||
|
} catch(e) {
|
||||||
|
console.error('AudioEncoder missing or does not support', config);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (has(config, 'width') && has(config, 'height')) {
|
} else if (has(config, 'width') && has(config, 'height')) {
|
||||||
const videoConfig = config as VideoEncoderConfig;
|
const videoConfig = config as VideoEncoderConfig;
|
||||||
if ('VideoEncoder' in window && await window.VideoEncoder.isConfigSupported(videoConfig))
|
for (const source of [ window, LibAVPolyfill ]) {
|
||||||
return window.VideoEncoder;
|
if (source === LibAVPolyfill) {
|
||||||
|
await this.#load();
|
||||||
|
}
|
||||||
|
|
||||||
await this.#load();
|
try {
|
||||||
if (await LibAVPolyfill.VideoEncoder.isConfigSupported(videoConfig))
|
const { supported } = await source.VideoEncoder.isConfigSupported(videoConfig);
|
||||||
return LibAVPolyfill.VideoEncoder;
|
if (supported) return source.VideoEncoder;
|
||||||
|
} catch(e) {
|
||||||
|
console.error('VideoEncoder missing or does not support', config);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else throw new Error("unreachable");
|
} else throw new Error("unreachable");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
Loading…
Reference in a new issue