From 70bea6906dc4d6c12a80f8c4211e48077af66adb Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Fri, 15 Mar 2024 20:06:32 +0100 Subject: [PATCH] chore: Add wasm audio decoding to audio example --- examples/audio/{src => }/index.html | 2 + examples/audio/package.json | 23 +- examples/audio/src/index.ts | 460 +++++++++++++++++++++------- examples/audio/vite.config.js | 10 + pnpm-lock.yaml | 178 ++++++----- 5 files changed, 476 insertions(+), 197 deletions(-) rename examples/audio/{src => }/index.html (90%) create mode 100644 examples/audio/vite.config.js diff --git a/examples/audio/src/index.html b/examples/audio/index.html similarity index 90% rename from examples/audio/src/index.html rename to examples/audio/index.html index 6bad5b1..3c7dc05 100644 --- a/examples/audio/src/index.html +++ b/examples/audio/index.html @@ -19,5 +19,7 @@ > Select an MP3 audio file to add to a WNFS and play it. + + diff --git a/examples/audio/package.json b/examples/audio/package.json index c4d3d2a..436a857 100644 --- a/examples/audio/package.json +++ b/examples/audio/package.json @@ -10,27 +10,34 @@ "main": "src/main.jsx", "scripts": { "lint": "tsc --build && eslint . && prettier --check '**/*.{js,jsx,ts,tsx,yml,json,css}' --ignore-path ../../.gitignore", - "dev": "rsbuild dev", - "build": "rsbuild build", - "preview": "rsbuild preview" + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview" }, "dependencies": { "@wnfs-wg/nest": "*", "blockstore-core": "^4.4.0", "blockstore-idb": "^1.1.8", + "codec-parser": "^2.4.3", "idb-keyval": "^6.2.1", "interface-blockstore": "^5.2.10", "mediainfo.js": "^0.2.1", - "mime": "^4.0.1", - "uint8arrays": "^5.0.2" + "mpg123-decoder": "^1.0.0", + "mse-audio-wrapper": "^1.4.14", + "uint8arrays": "^5.0.3" }, "devDependencies": { - "@rsbuild/core": "^0.4.4", + "@rsbuild/core": "^0.4.15", "@types/node": "^20.11.0", - "typescript": "5.4.2" + "typescript": "5.4.2", + "vite": "^5.1.4", + "vite-plugin-wasm": "^3.3.0" }, "eslintConfig": { "extends": ["@fission-codes"], - "ignorePatterns": ["dist"] + "ignorePatterns": ["dist"], + "rules": { + "no-console": ["off", "always"] + } } } diff --git a/examples/audio/src/index.ts b/examples/audio/src/index.ts index cdb81c1..d8a2c07 100644 --- a/examples/audio/src/index.ts +++ b/examples/audio/src/index.ts @@ -1,71 +1,68 @@ +import type { CodecHeader, MimeType } from 'codec-parser' +import type { MediaInfo, MediaInfoType } from 'mediainfo.js' +import type { MPEGDecodedAudio } from 'mpg123-decoder' + +import * as Uint8Arr from 'uint8arrays' import { IDBBlockstore } from 'blockstore-idb' import { Path } from '@wnfs-wg/nest' -import * as FS from './fs.ts' -import mime from 'mime' +import CodecParser from 'codec-parser' + +// @ts-expect-error No type defs +import MSEAudioWrapper from 'mse-audio-wrapper' -//////// -// 🗄️ // -//////// +import * as FS from './fs.js' + +// 🗄️ const blockstore = new IDBBlockstore('blockstore') await blockstore.open() const fs = await FS.load({ blockstore }) -//////// -// 📣 // -//////// +// 📣 const state = document.querySelector('#state') -if (!state) throw new Error('Expected a #state element to exist') +if (state === null) throw new Error('Expected a #state element to exist') -function note(msg: string) { - if (state) state.innerHTML = msg +function note(msg: string): void { + if (state !== null) state.innerHTML = msg } -//////// -// 💁 // -//////// +// 💁 const fi: HTMLInputElement | null = document.querySelector('#file-input') -if (fi) +if (fi !== null) fi.addEventListener('change', (event: Event) => { if (fi?.files?.length !== 1) return const file: File = fi.files[0] - const reader = new FileReader() - note('Reading file') console.log('File selected', file) - reader.onload = (event: any) => { - const data: ArrayBuffer = event.target.result - createAudio(file.name, data) - } - - reader.readAsArrayBuffer(file) + file + .arrayBuffer() + .then(async (data) => { + await createAudio(file.name, file.type, data) + }) + .catch(console.error) }) -//////// -// 🎵 // -//////// - -async function createAudio(fileName: string, fileData: ArrayBuffer) { - const mimeType = mime.getType(fileName) - if (!mimeType || !mimeType.startsWith('audio/')) - throw new Error('Not an audio file') - - console.log('Audio mimetype', mimeType) +// 🎵 +async function createAudio( + fileName: string, + mimeType: string, + fileData: ArrayBuffer +): Promise { // File note('Adding file to WNFS') const path = Path.file('private', fileName) - if ((await fs.exists(path)) === false) { + if (!(await fs.exists(path))) { const { dataRoot } = await fs.write(path, 'bytes', new Uint8Array(fileData)) - FS.savePointer(dataRoot) + await FS.savePointer(dataRoot) } const fileSize = await fs.size(path) @@ -75,52 +72,138 @@ async function createAudio(fileName: string, fileData: ArrayBuffer) { // Audio metadata note('Looking up audio metadata') - const mediainfo = await ( - await mediaInfoClient(true) - ).analyzeData( + const mediaClient = await mediaInfoClient(true) + const mediainfo = await mediaClient.analyzeData( async (): Promise => { return fileSize }, async (chunkSize: number, offset: number): Promise => { if (chunkSize === 0) return new Uint8Array() - return fs.read(path, 'bytes', { offset, length: chunkSize }) + return await fs.read(path, 'bytes', { offset, length: chunkSize }) } ) // Audio duration const audioDuration = mediainfo?.media?.track[0]?.Duration - if (!audioDuration) throw new Error('Failed to determine audio duration') + if (audioDuration === undefined) + throw new Error('Failed to determine audio duration') console.log('Audio duration', audioDuration) - console.log('Audio metadata', mediainfo.media.track) + console.log('Audio metadata', mediainfo.media?.track) // Audio frames - if (!mediainfo.media.track[1]?.FrameCount) + if (mediainfo.media?.track[1]?.FrameCount === undefined) throw new Error('Failed to determine audio frame count') - if (!mediainfo.media.track[1]?.StreamSize) + if (mediainfo.media?.track[1]?.StreamSize === undefined) throw new Error('Failed to determine audio stream size') const audioFrameCount = mediainfo.media.track[1]?.FrameCount const audioStreamSize = mediainfo.media.track[1]?.StreamSize const audioFrameSize = Math.ceil(audioStreamSize / audioFrameCount) + const metadataSize = mediainfo?.media?.track[0]?.StreamSize ?? 0 console.log('Audio frame count', audioFrameCount) console.log('Audio stream size', audioStreamSize) console.log('Audio frame size', audioFrameSize) + // Try to create media source first + let supportsMediaSource = false + + // try { + // const { supported } = createMediaSource({ + // audioDuration, + // audioFrameCount, + // audioFrameSize, + // fileSize, + // path, + // metadataSize, + // mimeType, + // }) + + // supportsMediaSource = supported + // } catch (error) { + // console.error('Failed to create media source', error) + // } + + // If that failed, decode the audio via wasm + if (supportsMediaSource) return + + const { supported } = await createWebAudio({ + audioDuration, + audioFrameCount, + audioFrameSize, + fileSize, + mediainfo, + path, + metadataSize, + mimeType, + }) + + if (!supported) + throw new Error('Did not implement a decoder for this type of audio yet') +} + +// 🛠️ + +async function mediaInfoClient(covers: boolean): Promise { + const MediaInfoFactory = await import('mediainfo.js').then((a) => a.default) + + return await MediaInfoFactory({ + coverData: covers, + full: true, + locateFile: () => { + return new URL('mediainfo.js/MediaInfoModule.wasm', import.meta.url) + .pathname + }, + }) +} + +// MEDIA STREAM +// +// Let the browser decode the audio. + +function createMediaSource({ + audioDuration, + audioFrameCount, + audioFrameSize, + fileSize, + metadataSize, + mimeType, + path, +}: { + audioDuration: number + audioFrameCount: number + audioFrameSize: number + fileSize: number + metadataSize: number + mimeType: string + path: Path.File> +}): { supported: boolean } { + if (window.MediaSource === undefined) { + return { supported: false } + } + + // Create audio wrapper which enables support for some unsupported audio containers + console.log('Audio mimetype', mimeType) + const audioWrapper = new MSEAudioWrapper(mimeType) + + // Detect support + if (!MediaSource.isTypeSupported(mimeType)) { + return { supported: false } + } + // Buffering const bufferSize = 512 * 1024 // 512 KB - const metadataSize = mediainfo?.media?.track[0]?.StreamSize || 0 let loading = false let seeking = false let sourceBuffer: SourceBuffer - let buffered: { start: number; end: number } = { + const buffered: { start: number; end: number } = { start: 0, end: 0, } - async function loadNext() { + async function loadNext(): Promise { if ( src.readyState !== 'open' || sourceBuffer.updating || @@ -131,7 +214,7 @@ async function createAudio(fileName: string, fileData: ArrayBuffer) { loading = true - let start = buffered.end + const start = buffered.end let end = start + bufferSize let reachedEnd = false @@ -150,19 +233,25 @@ async function createAudio(fileName: string, fileData: ArrayBuffer) { length: end - start, }) - sourceBuffer.appendBuffer(buffer) + sourceBuffer.appendBuffer( + Uint8Arr.concat([...audioWrapper.iterator(buffer)] as Uint8Array[]) + ) loading = false if (reachedEnd) { - sourceBuffer.addEventListener('updateend', () => src.endOfStream(), { - once: true, - }) + sourceBuffer.addEventListener( + 'updateend', + () => { + if (!sourceBuffer.updating) src.endOfStream() + }, + { + once: true, + } + ) } } - globalThis.loadNext = loadNext - // Media source note('Setting up media source') @@ -176,7 +265,7 @@ async function createAudio(fileName: string, fileData: ArrayBuffer) { sourceBuffer.mode = 'sequence' // Load initial frames - loadNext() + loadNext().catch(console.error) }) // Create audio @@ -195,10 +284,10 @@ async function createAudio(fileName: string, fileData: ArrayBuffer) { time ) - function abortAndRemove() { + function abortAndRemove(): void { if (src.readyState === 'open') sourceBuffer.abort() sourceBuffer.addEventListener('updateend', nextUp, { once: true }) - sourceBuffer.remove(0, Infinity) + sourceBuffer.remove(0, Number.POSITIVE_INFINITY) } // `loadNext` is reading from WNFS, wait until it is finished. @@ -206,108 +295,241 @@ async function createAudio(fileName: string, fileData: ArrayBuffer) { // able to cancel this so that the resulting // `sourceBuffer.appendBuffer` call never happens. if (loading) { - sourceBuffer.addEventListener('updateend', () => abortAndRemove(), { + sourceBuffer.addEventListener('updateend', abortAndRemove, { once: true, }) } else { abortAndRemove() } - async function nextUp() { + function nextUp(): void { + if (audioDuration !== undefined && !sourceBuffer.updating) + src.duration = audioDuration sourceBuffer.timestampOffset = time const frame = Math.floor((time / audio.duration) * audioFrameCount) - const buffer = await fs.read(path, 'bytes', { - offset: metadataSize + frame * audioFrameSize, - length: bufferSize, - }) - - const headerStart = getHeaderStart(buffer) - console.log('Header start', headerStart) - - buffered.start = metadataSize + frame * audioFrameSize + headerStart + buffered.start = metadataSize + frame * audioFrameSize buffered.end = buffered.start seeking = false - loadNext() + loadNext().catch(console.error) } }) audio.addEventListener('timeupdate', () => { if (audio.seeking) return - if (audio.currentTime + 60 > sourceBuffer.timestampOffset) loadNext() + if (audio.currentTime + 60 > sourceBuffer.timestampOffset) + loadNext().catch(console.error) }) audio.addEventListener('waiting', () => { console.log('Audio element is waiting for data') - loadNext() + loadNext().catch(console.error) }) - document.body.appendChild(audio) -} + document.body.append(audio) -//////// -// 🛠️ // -//////// + return { supported: true } +} -async function mediaInfoClient(covers: boolean) { - const MediaInfoFactory = await import('mediainfo.js').then((a) => a.default) +// WEB AUDIO +// +// Decode the audio via WASM. +// +// NOTES: +// https://github.com/WebAudio/web-audio-api/issues/2227 +// +// ⚠️ This code assumes the audio bytes will be loaded in before +// the current audio segment ends. + +async function createWebAudio({ + audioDuration, + audioFrameCount, + audioFrameSize, + fileSize, + mediainfo, + metadataSize, + mimeType, + path, +}: { + audioDuration: number + audioFrameCount: number + audioFrameSize: number + fileSize: number + mediainfo: MediaInfoType + metadataSize: number + mimeType: string + path: Path.File> +}): Promise<{ supported: boolean }> { + const audioContext = new AudioContext() + + // Correct mime type + let correctedMimeType: MimeType + + const codec = mediainfo.media?.track + .find((a) => a.StreamKind === 'Audio') + ?.Format?.toLowerCase() + + switch (codec) { + case 'mpeg': + case 'mpeg audio': { + correctedMimeType = 'audio/mpeg' + break + } + default: { + return { supported: false } + } + } - return await MediaInfoFactory({ - coverData: covers, - full: true, - locateFile: () => { - return new URL('mediainfo.js/MediaInfoModule.wasm', import.meta.url) - .pathname + // Create codec parser + const codecParser = new CodecParser(correctedMimeType, { + // @ts-expect-error Faulty type definitions + onCodecHeader: (codecHeaderData: CodecHeader) => { + console.log(codecHeaderData) }, }) -} -function getHeaderStart(buffer: Uint8Array) { - let headerStart = 0 - const SyncByte1 = 0xff - const SyncByte2 = 0xfb - const SyncByte3 = 0x90 // 224 - const SyncByte4 = 0x64 // 64 + const sampleRate = mediainfo.media?.track.find( + (a) => a.StreamKind === 'Audio' + )?.SamplingRate - for (let i = 0; i + 1 < buffer.length; i++) { - if ( - buffer[i] === SyncByte1 && - buffer[i + 1] === SyncByte2 && - buffer[i + 2] === SyncByte3 && - buffer[i + 3] === SyncByte4 - ) { - return i - } + if (sampleRate === undefined) + throw new Error('Failed to determine sample rate') + + // Create decoder + const { MPEGDecoderWebWorker } = await import('mpg123-decoder') + const decoder = new MPEGDecoderWebWorker() + + await decoder.ready + + // Buffering + const bufferSize = 512 * 1024 + const buffered: { + start: number + end: number + reachedEnd: boolean + samples: number + sampleOffset: number + time: number + } = { + start: 0, + end: 0, + reachedEnd: false, + samples: 0, + sampleOffset: 0, + time: 0, } - for (let i = 0; i + 1 < buffer.length; i++) { - if ( - buffer[i] === SyncByte1 && - buffer[i + 1] === SyncByte2 && - buffer[i + 2] === 224 && - buffer[i + 3] === 64 - ) { - return i + let loading = false + + async function loadNext(): Promise { + if (loading) return + loading = true + + if (sampleRate === undefined) + throw new Error('Failed to determine sample rate') + + const start = buffered.end + let end = start + bufferSize + let reachedEnd = false + + if (end > fileSize) { + end = fileSize + reachedEnd = true } + + note(`Loading bytes, offset: ${start} - length: ${end - start}`) + console.log(`Loading bytes from ${start} to ${end}`) + + const bytes = await fs.read(path, 'bytes', { + offset: start, + length: end - start, + }) + + const frames: Array<{ data: Uint8Array }> = [ + // @ts-expect-error No type defs + ...codecParser.parseChunk(bytes), + ] + + const decoded = await decoder.decodeFrames(frames.map((f) => f.data)) + + buffered.end = end + buffered.reachedEnd = reachedEnd + buffered.samples += decoded.samplesDecoded + buffered.time = (buffered.samples - buffered.sampleOffset) / sampleRate + + loading = false + + return decoded } - for (let i = 0; i + 1 < buffer.length; i++) { - if ( - buffer[i] === SyncByte1 && - buffer[i + 1] === SyncByte2 && - buffer[i + 2] === SyncByte3 - ) { - return i - } + // Audio + const audio = new Audio() + audio.controls = true + audio.volume = 0.5 + + document.body.append(audio) + + // Create media stream and attach it to the audio element + const mediaStream = audioContext.createMediaStreamDestination() + mediaStream.channelCount = audioContext.destination.maxChannelCount + audio.srcObject = mediaStream.stream + + // Create audio buffer + const initialDecoded = await loadNext() + if (initialDecoded === undefined) + throw new Error('Failed to load initial frames') + + const { channelData } = initialDecoded + const audioBuffer = audioContext.createBuffer( + channelData.length, + audioDuration * sampleRate, + sampleRate + ) + + for (const [idx, channel] of channelData.entries()) { + audioBuffer.getChannelData(idx).set(channel) } - for (let i = 0; i + 1 < buffer.length; i++) { - if (buffer[i] === SyncByte1 && buffer[i + 1] === SyncByte2) { - return i + // Create buffer source + let source = audioContext.createBufferSource() + source.buffer = audioBuffer + source.connect(mediaStream) + source.start(0, audio.currentTime, audioDuration) + + // Audio events + audio.addEventListener('timeupdate', () => { + onTimeUpdate().catch(console.error) + }) + + async function onTimeUpdate(): Promise { + if (audio.seeking || buffered.reachedEnd) return + if (audio.currentTime + 60 > buffered.time) { + const beforeSamples = buffered.samples + const decodedAudio = await loadNext() + if (decodedAudio === undefined) return + + const { channelData } = decodedAudio + + for (const [idx, channel] of channelData.entries()) { + audioBuffer.copyToChannel(channel, idx, beforeSamples) + } + + source.stop(0) + source.disconnect() + + source = audioContext.createBufferSource() + source.buffer = audioBuffer + source.connect(mediaStream) + source.start( + 0, // buffered.sampleOffset / sampleRate / scalingFactor, + audio.currentTime, + audioDuration + ) } } - return headerStart + // Fin + return { supported: true } } diff --git a/examples/audio/vite.config.js b/examples/audio/vite.config.js new file mode 100644 index 0000000..4a0dad6 --- /dev/null +++ b/examples/audio/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import wasm from 'vite-plugin-wasm' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + target: 'esnext', + }, + plugins: [wasm()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8399b3c..801f778 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: blockstore-idb: specifier: ^1.1.8 version: 1.1.8 + codec-parser: + specifier: ^2.4.3 + version: 2.4.3 idb-keyval: specifier: ^6.2.1 version: 6.2.1 @@ -59,22 +62,31 @@ importers: mediainfo.js: specifier: ^0.2.1 version: 0.2.2 - mime: - specifier: ^4.0.1 - version: 4.0.1 + mpg123-decoder: + specifier: ^1.0.0 + version: 1.0.0 + mse-audio-wrapper: + specifier: ^1.4.14 + version: 1.4.14 uint8arrays: - specifier: ^5.0.2 - version: 5.0.2 + specifier: ^5.0.3 + version: 5.0.3 devDependencies: '@rsbuild/core': - specifier: ^0.4.4 - version: 0.4.12 + specifier: ^0.4.15 + version: 0.4.15 '@types/node': specifier: ^20.11.0 version: 20.11.25 typescript: specifier: 5.4.2 version: 5.4.2 + vite: + specifier: ^5.1.4 + version: 5.1.4(@types/node@20.11.25) + vite-plugin-wasm: + specifier: ^3.3.0 + version: 3.3.0(vite@5.1.4) examples/demo: dependencies: @@ -850,6 +862,10 @@ packages: dev: true optional: true + /@eshaz/web-worker@1.2.2: + resolution: {integrity: sha512-WxXiHFmD9u/owrzempiDlBB1ZYqiLnm9s6aPc8AlFQalq2tKmqdmMr9GXOupDgzXtqnBipj8Un0gkIm7Sjf8mw==} + dev: false + /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1051,7 +1067,7 @@ packages: dns-over-http-resolver: 3.0.2 multiformats: 13.1.0 uint8-varint: 2.0.2 - uint8arrays: 5.0.2 + uint8arrays: 5.0.3 transitivePeerDependencies: - supports-color @@ -1197,119 +1213,117 @@ packages: dev: true optional: true - /@rsbuild/core@0.4.12: - resolution: {integrity: sha512-OIRHXEAugbEtnw22tdos6Hiep2i8CIa1DdHU1HVVIf+WeTbVvWplW2JcsPgjbY31S3z0tg6xn3QaVT8uTzrz5w==} + /@rsbuild/core@0.4.15: + resolution: {integrity: sha512-rQAfPt36uaOe3KYtD3/QBgvjDnY0kzGsE61/ZIEg1MdKZJ6cY5WsbS3MtzQDDM/FAXT5r/+GcFAZTKxJlgzkrQ==} engines: {node: '>=14.0.0'} hasBin: true dependencies: - '@rsbuild/shared': 0.4.12(@swc/helpers@0.5.3) - '@rspack/core': 0.5.6(@swc/helpers@0.5.3) + '@rsbuild/shared': 0.4.15(@swc/helpers@0.5.3) + '@rspack/core': 0.5.7(@swc/helpers@0.5.3) '@swc/helpers': 0.5.3 - core-js: 3.32.2 - html-webpack-plugin: /html-rspack-plugin@5.6.1(@rspack/core@0.5.6) + core-js: 3.36.0 + html-webpack-plugin: /html-rspack-plugin@5.6.2(@rspack/core@0.5.7) postcss: 8.4.35 - transitivePeerDependencies: - - webpack dev: true - /@rsbuild/shared@0.4.12(@swc/helpers@0.5.3): - resolution: {integrity: sha512-2lPbqcLNjUoHfdQCB6XdoqCqiLwpVeXcydv8cvTeiNCGseLY1WWTSn14b/L6Aj72TJbWWCqAAsm7YVL7Oibqvg==} + /@rsbuild/shared@0.4.15(@swc/helpers@0.5.3): + resolution: {integrity: sha512-INItMj+7QotcbJzm5CCTZQSy8EGkbGeND3Gu+kd7ZD/tU0F9yAh0MNyCGuqVWXivqUS9qEO/y7Ln9Td51XWrEw==} dependencies: - '@rspack/core': 0.5.6(@swc/helpers@0.5.3) + '@rspack/core': 0.5.7(@swc/helpers@0.5.3) caniuse-lite: 1.0.30001596 postcss: 8.4.35 transitivePeerDependencies: - '@swc/helpers' dev: true - /@rspack/binding-darwin-arm64@0.5.6: - resolution: {integrity: sha512-Aafs4gjzFeJW3DeTHVjom7NpSGeLNOoCtipJlCYCPp2C0J2Bz/uVxQC7xG+CPwL/RDIQOAZTa0jcLiWytBRwzA==} + /@rspack/binding-darwin-arm64@0.5.7: + resolution: {integrity: sha512-zYTMILRyrON25MW7ifEhkZ6jL33mz8bAHTOhgR8yMpYVJjrKu60+s1qPa+t+GkaH7nNnVmzkTVGECCvaA75hJQ==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rspack/binding-darwin-x64@0.5.6: - resolution: {integrity: sha512-W+WgYpdN5WboR2y36189MnJeuGJwCxAIgxYOM9LH4pXW8nhb+EANqUNPet5725AOeFX74tj1gl19lnbxOVSTcw==} + /@rspack/binding-darwin-x64@0.5.7: + resolution: {integrity: sha512-4THSPWVKPMSSD/y3/TWZ5xlSeh1B33I+YnBu/Y3lDFcFrFPtc3ojIDHw3is6l2wcACX6Rro4RgN6zcUij7eEmQ==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rspack/binding-linux-arm64-gnu@0.5.6: - resolution: {integrity: sha512-pAWIBeuO2OiIToNNi7oL7FDiBE4QxwLzeUpRAJnNwnARHuPSfMyJ6TXljJyTqUpkEi+NCRi4DBjUkQh7Sa1evw==} + /@rspack/binding-linux-arm64-gnu@0.5.7: + resolution: {integrity: sha512-JB9FAYWjYAeNCPFh0mQu3SZdFHiA+EY37z1AktLDl789SoEec2HPGkvvOs+OIET1pKWgjUGD4Z4Uq4P/r5JFNA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rspack/binding-linux-arm64-musl@0.5.6: - resolution: {integrity: sha512-fB0P2C2w9A+FV2u9X40ZXnnCMelypsFmZqllavXKAkK77eCXp0lKsNG1+h4uDoeFZSmoQPeNhgjVcdqQJsSWBA==} + /@rspack/binding-linux-arm64-musl@0.5.7: + resolution: {integrity: sha512-3fNhPvA9Kj/L7rwr2Pj1bvxWBLBgqfkqSvt91iUxPbxgfTiSBQh0Tfb9+hkHv2VCTyNQI/vytkOH+4i4DNXCBw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rspack/binding-linux-x64-gnu@0.5.6: - resolution: {integrity: sha512-HAV1ki2/IHQD5LrqVJ7Dh2QeSL3S9AZ1vvI0u8+wGMoF9vR6qyz+NFqUNlvZdsojVSs5bE0a7NOQ+0ao4l3sDg==} + /@rspack/binding-linux-x64-gnu@0.5.7: + resolution: {integrity: sha512-y/GnXt1hhbKSqzBSy+ALWwievlejQhIIF8FPXL1kKFh60zl7DE+iYHSJ128jIJiph9dQkBnHw0ABJ5D+vbSqdA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rspack/binding-linux-x64-musl@0.5.6: - resolution: {integrity: sha512-N/j4b+i+O/ZX8UlHt9OEcVcVZDXuBfm9Fj9xbPrJPPQM5xCSVH9vYEsCcsNZ9Rt05Q4IdZXlUgzju7kGND5sDg==} + /@rspack/binding-linux-x64-musl@0.5.7: + resolution: {integrity: sha512-US/FUv6cvbxbe4nymINwer/EQTvGEgCaAIrvKuAP0yAfK0eyqIHYZj/zCBM2qOS69Mpc2FWVMC/ftRyCvAz/xw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rspack/binding-win32-arm64-msvc@0.5.6: - resolution: {integrity: sha512-lWs1FZ/ZVLPBDSuk5NjY7jCUfZ6vilJwDYefEueYzVo97jvJMxP3p095doaBWW6bb8qMfrtFh2o77ESHVwmgiw==} + /@rspack/binding-win32-arm64-msvc@0.5.7: + resolution: {integrity: sha512-g7NWXa5EGvh6j1VPXGOFaWuOVxdPYYLh3wpUl46Skrd6qFZKB2r+yNhuXo6lqezwYvbtHEDrmFOHF2S6epXO5g==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rspack/binding-win32-ia32-msvc@0.5.6: - resolution: {integrity: sha512-5zz2XkM0PI13D98ErtMCkheMTzLNnuKFIlCqKemDjYOK9msC3lGU6/1Wp7/1YV/ll+wezD/4Kjzn51rq3bqiig==} + /@rspack/binding-win32-ia32-msvc@0.5.7: + resolution: {integrity: sha512-5Udt4pYpPSd1wlbVKTdWzjha8oV+FQ/EXILHhoS9G7l9rbpqhMs6oIqAgEavQS3t6fKtQU837b+MSBNprudTtw==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rspack/binding-win32-x64-msvc@0.5.6: - resolution: {integrity: sha512-CmRr2yfTZvs4koVbmElNcT31JS0tAx2nvd0nXE8RPs/CSnC9liXtDLctcF3dQekAXzQVM2NtGn9JfMJ9mXUppw==} + /@rspack/binding-win32-x64-msvc@0.5.7: + resolution: {integrity: sha512-tB/SB27BBDVV0+GpEUHkl2uanCP4Jk/hlnbvl5u6lSGcIxCFm+da4OsyiGDRE24bSEdMc91dmyWVlx5425je+A==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@rspack/binding@0.5.6: - resolution: {integrity: sha512-FXqLqovSDxbibpQtOri5QVd9sEe6YtRH1rCUKJ4n5Wly7fCublUSKDtq6pmt9UP3cG2ZG0faHva7R1eb1b7XEg==} + /@rspack/binding@0.5.7: + resolution: {integrity: sha512-47MX6wNF1lP/LdShPVhbg689FX1W96Zji7QgbxhRhXmkpOKor7gdajhxqszFHxHYJtqNTLA9BSG38rpIGxJ+fw==} optionalDependencies: - '@rspack/binding-darwin-arm64': 0.5.6 - '@rspack/binding-darwin-x64': 0.5.6 - '@rspack/binding-linux-arm64-gnu': 0.5.6 - '@rspack/binding-linux-arm64-musl': 0.5.6 - '@rspack/binding-linux-x64-gnu': 0.5.6 - '@rspack/binding-linux-x64-musl': 0.5.6 - '@rspack/binding-win32-arm64-msvc': 0.5.6 - '@rspack/binding-win32-ia32-msvc': 0.5.6 - '@rspack/binding-win32-x64-msvc': 0.5.6 - dev: true - - /@rspack/core@0.5.6(@swc/helpers@0.5.3): - resolution: {integrity: sha512-VH5IyYSdTpNLeoxTc9Vm+fgqzUJMa19sGVjuas7eTNNNZTrevxV0k9e/QGMHzUV9QzlNvLHLOVY/U4LfcZerYA==} + '@rspack/binding-darwin-arm64': 0.5.7 + '@rspack/binding-darwin-x64': 0.5.7 + '@rspack/binding-linux-arm64-gnu': 0.5.7 + '@rspack/binding-linux-arm64-musl': 0.5.7 + '@rspack/binding-linux-x64-gnu': 0.5.7 + '@rspack/binding-linux-x64-musl': 0.5.7 + '@rspack/binding-win32-arm64-msvc': 0.5.7 + '@rspack/binding-win32-ia32-msvc': 0.5.7 + '@rspack/binding-win32-x64-msvc': 0.5.7 + dev: true + + /@rspack/core@0.5.7(@swc/helpers@0.5.3): + resolution: {integrity: sha512-gUF0PcanPrC2cVfFA4e+qmG66X7FkEKlRbnaUfB4LKw9JQuwiMOXCAtrBdveDjB89KE/3cw/nuYVQwd106uqWA==} engines: {node: '>=16.0.0'} peerDependencies: '@swc/helpers': '>=0.5.1' @@ -1318,7 +1332,7 @@ packages: optional: true dependencies: '@module-federation/runtime-tools': 0.0.8 - '@rspack/binding': 0.5.6 + '@rspack/binding': 0.5.7 '@swc/helpers': 0.5.3 browserslist: 4.22.2 enhanced-resolve: 5.12.0 @@ -1672,6 +1686,13 @@ packages: resolution: {integrity: sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg==} dev: true + /@wasm-audio-decoders/common@9.0.5: + resolution: {integrity: sha512-b9JNh9sPAvn8PVIizNh9D60WkfQong/u9ea873H47u7zvVDLctxYIp2aZw9CQqXaQdk7JB3MoU5UHiseO40swg==} + dependencies: + '@eshaz/web-worker': 1.2.2 + simple-yenc: 1.0.4 + dev: false + /acorn-jsx@5.3.2(acorn@8.11.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2149,6 +2170,10 @@ packages: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + /codec-parser@2.4.3: + resolution: {integrity: sha512-3dAvFtdpxn4YLstqsB2ZiJXXNg7n1j7R5ONeDuk+2kBkb39PwrCRytOFHlSWA8q5jCjW3PumeMv9q37bFHsijg==} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2202,8 +2227,8 @@ packages: p-event: 6.0.0 dev: true - /core-js@3.32.2: - resolution: {integrity: sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==} + /core-js@3.36.0: + resolution: {integrity: sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==} requiresBuild: true dev: true @@ -3491,19 +3516,16 @@ packages: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true - /html-rspack-plugin@5.6.1(@rspack/core@0.5.6): - resolution: {integrity: sha512-HxBMh931QBxzUAA12j4iPIK1yVgnQrz3ziU/CHU7hUWuolpCy+wcUgcn9Zy4YPsq8PisE9lSg2K8+3i0a//H8A==} + /html-rspack-plugin@5.6.2(@rspack/core@0.5.7): + resolution: {integrity: sha512-cPGwV3odvKJ7DBAG/DxF5e0nMMvBl1zGfyDciT2xMETRrIwajwC7LtEB3cf7auoGMK6xJOOLjWJgaKHLu/FzkQ==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x - webpack: ^5.20.0 peerDependenciesMeta: '@rspack/core': optional: true - webpack: - optional: true dependencies: - '@rspack/core': 0.5.6(@swc/helpers@0.5.3) + '@rspack/core': 0.5.7(@swc/helpers@0.5.3) lodash: 4.17.21 tapable: 2.2.1 dev: true @@ -3585,7 +3607,7 @@ packages: resolution: {integrity: sha512-9E0iXehfp/j0UbZ2mvlYB4K9pP7uQBCppfuy8WHs1EHF6wLQrM9+zwyX+8Qt6HnH4GKZRyXX/CNXm6oD4+QYgA==} dependencies: interface-store: 5.1.5 - uint8arrays: 5.0.2 + uint8arrays: 5.0.3 /interface-store@5.1.5: resolution: {integrity: sha512-X0KnJBk3o+YL13MxZBMwa88/b3Mdrpm0yPzkSTKDDVn9BSPH7UK6W+ZtIPO2bxKOQVmq7zqOwAnYnpfqWjb6/g==} @@ -3639,7 +3661,7 @@ packages: progress-events: 1.0.0 rabin-wasm: 0.1.5 uint8arraylist: 2.4.7 - uint8arrays: 5.0.2 + uint8arrays: 5.0.3 transitivePeerDependencies: - encoding - supports-color @@ -4334,12 +4356,6 @@ packages: picomatch: 2.3.1 dev: true - /mime@4.0.1: - resolution: {integrity: sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==} - engines: {node: '>=16'} - hasBin: true - dev: false - /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -4412,6 +4428,12 @@ packages: yargs-unparser: 2.0.0 dev: true + /mpg123-decoder@1.0.0: + resolution: {integrity: sha512-WV+pyuMUhRqv7s8S6p/Ii4KQHdBD1pb3yaABxcKJRsNp+HQ/Y6z2iIBIaOZu0JMHPTOoICYt0REDZ7XfLu+n/g==} + dependencies: + '@wasm-audio-decoders/common': 9.0.5 + dev: false + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -4428,6 +4450,12 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /mse-audio-wrapper@1.4.14: + resolution: {integrity: sha512-qdy5ezIS5JwA21DUJIYypGU2WtLzqJXCK9hgFoHu3ydjL/xrEt7XjsKuKPxYAiWZVOaBXjZluxfeNF+zRz5jrw==} + dependencies: + codec-parser: 2.4.3 + dev: false + /multiformats@12.1.3: resolution: {integrity: sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==} engines: {node: '>=16.0.0', npm: '>=7.0.0'} @@ -5243,6 +5271,10 @@ packages: requiresBuild: true dev: true + /simple-yenc@1.0.4: + resolution: {integrity: sha512-5gvxpSd79e9a3V4QDYUqnqxeD4HGlhCakVpb6gMnDD7lexJggSBJRBO5h52y/iJrdXRilX9UCuDaIJhSWm5OWw==} + dev: false + /sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -5747,7 +5779,7 @@ packages: /uint8arraylist@2.4.8: resolution: {integrity: sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==} dependencies: - uint8arrays: 5.0.2 + uint8arrays: 5.0.3 /uint8arrays@4.0.9: resolution: {integrity: sha512-iHU8XJJnfeijILZWzV7RgILdPHqe0mjJvyzY4mO8aUUtHsDbPa2Gc8/02Kc4zeokp2W6Qq8z9Ap1xkQ1HfbKwg==} @@ -5758,6 +5790,12 @@ packages: resolution: {integrity: sha512-S0GaeR+orZt7LaqzTRs4ZP8QqzAauJ+0d4xvP2lJTA99jIkKsE2FgDs4tGF/K/z5O9I/2W5Yvrh7IuqNeYH+0Q==} dependencies: multiformats: 13.1.0 + dev: false + + /uint8arrays@5.0.3: + resolution: {integrity: sha512-6LBuKji28kHjgPJMkQ6GDaBb1lRwIhyOYq6pDGwYMoDPfImE9SkuYENVmR0yu9yGgs2clHUSY9fKDukR+AXfqQ==} + dependencies: + multiformats: 13.1.0 /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}