web/webcodecs: use polyfill as fallback

This commit is contained in:
dumbmoron 2024-08-23 18:40:44 +00:00
parent 43b3db1317
commit 9f21a6eb46
No known key found for this signature in database
4 changed files with 73 additions and 45 deletions

View file

@ -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: {}

View file

@ -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",

View file

@ -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);

View file

@ -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;